Skip to content

Commit

Permalink
mark as seen in the gui on source selection
Browse files Browse the repository at this point in the history
  • Loading branch information
Allie Crevier committed Oct 22, 2020
1 parent c31fff4 commit a9c318e
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 65 deletions.
37 changes: 37 additions & 0 deletions securedrop_client/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ def journalist_filename(self) -> str:
[c for c in self.journalist_designation.lower().replace(" ", "_") if c in valid_chars]
)

@property
def seen(self) -> bool:
for item in self.collection:
if not item.seen:
return False

return True


class Message(Base):

Expand Down Expand Up @@ -183,6 +191,17 @@ def location(self, data_dir: str) -> str:
)
)

@property
def seen(self) -> bool:
"""
If the submission has been downloaded or seen by any journalist, then the submssion is
considered seen.
"""
if self.seen_messages.count():
return True

return False


class File(Base):

Expand Down Expand Up @@ -264,6 +283,17 @@ def location(self, data_dir: str) -> str:
)
)

@property
def seen(self) -> bool:
"""
If the submission has been downloaded or seen by any journalist, then the submssion is
considered seen.
"""
if self.seen_files.count():
return True

return False


class Reply(Base):

Expand Down Expand Up @@ -350,6 +380,13 @@ def location(self, data_dir: str) -> str:
)
)

@property
def seen(self) -> bool:
"""
A reply is always seen in a global inbox.
"""
return True


class DownloadErrorCodes(Enum):
"""
Expand Down
95 changes: 71 additions & 24 deletions securedrop_client/gui/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -641,29 +641,10 @@ def on_source_changed(self):
if not source:
return
self.controller.session.refresh(source)
source_items = source.collection
except sqlalchemy.exc.InvalidRequestError as e:
logger.debug(e)
return

# Prepare the lists of uuids to mark as seen. Continue if one of the source conversation
# items no longer exists so that the rest of the items will be marked as seen.
files = []
messages = []
replies = []
for item in source_items:
try:
if isinstance(item, File):
files.append(item.uuid)
elif isinstance(item, Message):
messages.append(item.uuid)
elif isinstance(item, Reply):
replies.append(item.uuid)
except sqlalchemy.exc.InvalidRequestError as e:
logger.debug(e)
continue
self.controller.mark_seen(files, messages, replies)

# Try to get the SourceConversationWrapper from the persistent dict,
# else we create it.
try:
Expand All @@ -677,6 +658,7 @@ def on_source_changed(self):
self.source_conversations[source.uuid] = conversation_wrapper

self.set_conversation(conversation_wrapper)
self.source_list.mark_seen.emit(source.uuid)

def delete_conversation(self, source_uuid: str) -> None:
"""
Expand Down Expand Up @@ -818,6 +800,8 @@ class SourceList(QListWidget):

NUM_SOURCES_TO_ADD_AT_A_TIME = 32

mark_seen = pyqtSignal(str)

def __init__(self):
super().__init__()

Expand Down Expand Up @@ -888,7 +872,7 @@ def update(self, sources: List[Source]) -> List[str]:

# Add widgets for new sources
for uuid in sources_to_add:
source_widget = SourceWidget(self.controller, sources_to_add[uuid])
source_widget = SourceWidget(self.controller, sources_to_add[uuid], self.mark_seen)
source_item = SourceListWidgetItem(self)
source_item.setSizeHint(source_widget.sizeHint())
self.insertItem(0, source_item)
Expand Down Expand Up @@ -923,7 +907,7 @@ def schedule_source_management(slice_size=slice_size):
for source in sources_slice:
try:
source_uuid = source.uuid
source_widget = SourceWidget(self.controller, source)
source_widget = SourceWidget(self.controller, source, self.mark_seen)
source_item = SourceListWidgetItem(self)
source_item.setSizeHint(source_widget.sizeHint())
self.insertItem(0, source_item)
Expand Down Expand Up @@ -1017,17 +1001,21 @@ class SourceWidget(QWidget):
PREVIEW_WIDTH = 380
PREVIEW_HEIGHT = 60

def __init__(self, controller: Controller, source: Source):
SOURCE_CSS = load_css("source.css")

def __init__(self, controller: Controller, source: Source, mark_seen_signal: pyqtSignal):
super().__init__()

self.controller = controller
self.controller.source_deleted.connect(self._on_source_deleted)
self.controller.source_deletion_failed.connect(self._on_source_deletion_failed)
mark_seen_signal.connect(self._on_mark_seen)

# Store source
self.source_uuid = source.uuid
self.last_updated = source.last_updated
self.source = source
self.seen = self.source.seen
self.source_uuid = self.source.uuid
self.last_updated = self.source.last_updated

# Set layout
layout = QHBoxLayout(self)
Expand Down Expand Up @@ -1106,6 +1094,9 @@ def __init__(self, controller: Controller, source: Source):
# Add widgets to main layout
layout.addWidget(self.source_widget)

# Set click handler
# self.clicked.connect(self._on_clicked)

self.update()

def update(self):
Expand All @@ -1123,9 +1114,13 @@ def update(self):
if self.source.document_count == 0:
self.paperclip.hide()
self.star.update(self.source.is_starred)

self.seen = self.source.seen
self.update_styles()
except sqlalchemy.exc.InvalidRequestError as e:
logger.debug(f"Could not update SourceWidget for source {self.source_uuid}: {e}")

@pyqtSlot(str, str, str)
def set_snippet(self, source_uuid: str, collection_uuid: str = None, content: str = None):
"""
Update the preview snippet if the source_uuid matches our own.
Expand Down Expand Up @@ -1153,6 +1148,58 @@ def delete_source(self, event):
messagebox = DeleteSourceMessageBox(self.source, self.controller)
messagebox.launch()

def update_styles(self) -> None:
if self.seen:
self.setStyleSheet("")
self.name.setObjectName("SourceWidget_name")
self.timestamp.setObjectName("SourceWidget_timestamp")
self.preview.setObjectName("SourceWidget_preview")
self.setStyleSheet(self.SOURCE_CSS)
else:
self.setStyleSheet("")
self.name.setObjectName("SourceWidget_name_unread")
self.timestamp.setObjectName("SourceWidget_timestamp_unread")
self.preview.setObjectName("SourceWidget_preview_unread")
self.setStyleSheet(self.SOURCE_CSS)

@pyqtSlot(str)
def _on_mark_seen(self, source_uuid: str):
if self.source_uuid != source_uuid:
return

# immediately update styles to mark as seen
self.seen = True
self.update_styles()

try:
if self.source.seen:
return

# Prepare the lists of uuids to mark as seen. Continue if one of the source conversation
# items no longer exists so that the rest of the items will be marked as seen.
files = []
messages = []
replies = []
source_items = self.source.collection
for item in source_items:
if item.seen:
continue

try:
if isinstance(item, File):
files.append(item.uuid)
elif isinstance(item, Message):
messages.append(item.uuid)
elif isinstance(item, Reply):
replies.append(item.uuid)
except sqlalchemy.exc.InvalidRequestError as e:
logger.debug(e)
continue

self.controller.mark_seen(files, messages, replies)
except sqlalchemy.exc.InvalidRequestError as e:
logger.debug(e)

@pyqtSlot(str)
def _on_source_deleted(self, source_uuid: str):
if self.source_uuid == source_uuid:
Expand Down
41 changes: 0 additions & 41 deletions securedrop_client/resources/css/sdclient.css
Original file line number Diff line number Diff line change
Expand Up @@ -194,47 +194,6 @@ QListView#SourceList::item:hover{
border: 500px solid #f9f9f9;
}

#SourceWidget_container {
border-bottom: 1px solid #9b9b9b;
}

#SourceWidget_gutter {
min-width: 40px;
max-width: 40px;
}

#SourceWidget_metadata {
max-width: 60px;
}

#SourceWidget_preview {
font-family: 'Source Sans Pro';
font-weight: 400;
font-size: 13px;
color: #383838;
}

#SourceWidget_source_deleted {
font-family: 'Source Sans Pro';
font-weight: 400;
font-size: 13px;
color: #ff3366;
}

#SourceWidget_name {
font-family: 'Montserrat';
font-weight: 500;
font-size: 13px;
color: #383838;
}

#SourceWidget_timestamp {
font-family: 'Montserrat';
font-weight: 500;
font-size: 13px;
color: #383838;
}

#StarToggleButton {
border: none;
}
Expand Down
61 changes: 61 additions & 0 deletions securedrop_client/resources/css/source.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#SourceWidget_container {
border-bottom: 1px solid #9b9b9b;
}

#SourceWidget_gutter {
min-width: 40px;
max-width: 40px;
}

#SourceWidget_metadata {
max-width: 60px;
}

#SourceWidget_preview {
font-family: 'Source Sans Pro';
font-weight: 400;
font-size: 13px;
color: #383838;
}

#SourceWidget_preview_unread {
font-family: 'Source Sans Pro';
font-weight: 600;
font-size: 13px;
color: #000;
}

#SourceWidget_source_deleted {
font-family: 'Source Sans Pro';
font-weight: 400;
font-size: 13px;
color: #ff3366;
}

#SourceWidget_name {
font-family: 'Montserrat';
font-weight: 500;
font-size: 13px;
color: #383838;
}

#SourceWidget_name_unread {
font-family: 'Montserrat';
font-weight: 600;
font-size: 13px;
color: #000;
}

#SourceWidget_timestamp {
font-family: 'Montserrat';
font-weight: 500;
font-size: 13px;
color: #383838;
}

#SourceWidget_timestamp_unread {
font-family: 'Montserrat';
font-weight: 600;
font-size: 13px;
color: #000;
}

0 comments on commit a9c318e

Please sign in to comment.