Skip to content

Commit 3cd56a8

Browse files
feat: sort by "date added" to library (TagStudioDev#674)
* ui: add sorting mode dropdown * feat: pass sorting mode to Library.search_library * feat: implement sorting by creation date * ui: add dropdown for sorting direction * ui: update shown entries after changing sorting mode / direction * docs: mark sorting by "Date Created" as completed * fix: remove sorting options that have not been implemented * fix: rename sorting mode to "Date Added" * fix: check off the right item on the roadmap * feat: translate sorting UI * fix: address review comments
1 parent 936157c commit 3cd56a8

File tree

6 files changed

+84
-3
lines changed

6 files changed

+84
-3
lines changed

docs/updates/roadmap.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ Features are broken up into the following priority levels, with nested prioritie
9999
- [ ] Fuzzy Search [LOW] [#400](https://github.com/TagStudioDev/TagStudio/issues/400)
100100
- [ ] Sortable results [HIGH] [#68](https://github.com/TagStudioDev/TagStudio/issues/68)
101101
- [ ] Sort by relevance [HIGH]
102+
- [x] Sort by date added [HIGH]
102103
- [ ] Sort by date created [HIGH]
103104
- [ ] Sort by date modified [HIGH]
104105
- [ ] Sort by date taken (photos) [MEDIUM]
@@ -182,6 +183,7 @@ These version milestones are rough estimations for when the previous core featur
182183
- [ ] Field content search [HIGH]
183184
- [ ] Sortable results [HIGH]
184185
- [ ] Sort by relevance [HIGH]
186+
- [x] Sort by date added [HIGH]
185187
- [ ] Sort by date created [HIGH]
186188
- [ ] Sort by date modified [HIGH]
187189
- [ ] Sort by date taken (photos) [MEDIUM]

tagstudio/resources/translations/en.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,5 +212,7 @@
212212
"view.size.4": "Extra Large",
213213
"window.message.error_opening_library": "Error opening library.",
214214
"window.title.error": "Error",
215-
"window.title.open_create_library": "Open/Create Library"
215+
"window.title.open_create_library": "Open/Create Library",
216+
"sorting.direction.ascending": "Ascending",
217+
"sorting.direction.descending": "Descending"
216218
}

tagstudio/src/core/library/alchemy/enums.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,19 @@ class ItemType(enum.Enum):
5959
TAG_GROUP = 2
6060

6161

62+
class SortingModeEnum(enum.Enum):
63+
DATE_ADDED = "file.date_added"
64+
65+
6266
@dataclass
6367
class FilterState:
6468
"""Represent a state of the Library grid view."""
6569

6670
# these should remain
6771
page_index: int | None = 0
6872
page_size: int | None = 500
73+
sorting_mode: SortingModeEnum = SortingModeEnum.DATE_ADDED
74+
ascending: bool = True
6975

7076
# these should be erased on update
7177
# Abstract Syntax Tree Of the current Search Query
@@ -110,6 +116,12 @@ def from_tag_name(cls, tag_name: str) -> "FilterState":
110116
def with_page_size(self, page_size: int) -> "FilterState":
111117
return replace(self, page_size=page_size)
112118

119+
def with_sorting_mode(self, mode: SortingModeEnum) -> "FilterState":
120+
return replace(self, sorting_mode=mode)
121+
122+
def with_sorting_direction(self, ascending: bool) -> "FilterState":
123+
return replace(self, ascending=ascending)
124+
113125

114126
class FieldTypeEnum(enum.Enum):
115127
TEXT_LINE = "Text Line"

tagstudio/src/core/library/alchemy/library.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@
1414
from humanfriendly import format_timespan
1515
from sqlalchemy import (
1616
URL,
17+
ColumnExpressionArgument,
1718
Engine,
1819
NullPool,
1920
and_,
21+
asc,
2022
create_engine,
2123
delete,
24+
desc,
2225
exists,
2326
func,
2427
or_,
@@ -42,7 +45,7 @@
4245
)
4346
from ...enums import LibraryPrefs
4447
from .db import make_tables
45-
from .enums import FieldTypeEnum, FilterState, TagColor
48+
from .enums import FieldTypeEnum, FilterState, SortingModeEnum, TagColor
4649
from .fields import (
4750
BaseField,
4851
DatetimeField,
@@ -576,6 +579,13 @@ def search_library(
576579
query_count = select(func.count()).select_from(statement.alias("entries"))
577580
count_all: int = session.execute(query_count).scalar()
578581

582+
sort_on: ColumnExpressionArgument = Entry.id
583+
match search.sorting_mode:
584+
case SortingModeEnum.DATE_ADDED:
585+
sort_on = Entry.id
586+
587+
statement = statement.order_by(asc(sort_on) if search.ascending else desc(sort_on))
588+
579589
statement = statement.limit(search.limit).offset(search.offset)
580590

581591
logger.info(

tagstudio/src/qt/main_window.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,15 @@ def setupUi(self, MainWindow):
7272
spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
7373
self.horizontalLayout_3.addItem(spacerItem)
7474

75+
# Sorting Dropdowns
76+
self.sorting_mode_combobox = QComboBox(self.centralwidget)
77+
self.sorting_mode_combobox.setObjectName(u"sortingModeComboBox")
78+
self.horizontalLayout_3.addWidget(self.sorting_mode_combobox)
79+
80+
self.sorting_direction_combobox = QComboBox(self.centralwidget)
81+
self.sorting_direction_combobox.setObjectName(u"sortingDirectionCombobox")
82+
self.horizontalLayout_3.addWidget(self.sorting_direction_combobox)
83+
7584
# Thumbnail Size placeholder
7685
self.thumb_size_combobox = QComboBox(self.centralwidget)
7786
self.thumb_size_combobox.setObjectName(u"thumbSizeComboBox")

tagstudio/src/qt/ts_qt.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
FieldTypeEnum,
6666
FilterState,
6767
ItemType,
68+
SortingModeEnum,
6869
)
6970
from src.core.library.alchemy.fields import _FieldID
7071
from src.core.library.alchemy.library import Entry, LibraryStatus
@@ -551,16 +552,40 @@ def init_library_window(self):
551552
search_button.clicked.connect(
552553
lambda: self.filter_items(
553554
FilterState.from_search_query(self.main_window.searchField.text())
555+
.with_sorting_mode(self.sorting_mode)
556+
.with_sorting_direction(self.sorting_direction)
554557
)
555558
)
556559
# Search Field
557560
search_field: QLineEdit = self.main_window.searchField
558561
search_field.returnPressed.connect(
559-
# TODO - parse search field for filters
560562
lambda: self.filter_items(
561563
FilterState.from_search_query(self.main_window.searchField.text())
564+
.with_sorting_mode(self.sorting_mode)
565+
.with_sorting_direction(self.sorting_direction)
562566
)
563567
)
568+
# Sorting Dropdowns
569+
sort_mode_dropdown: QComboBox = self.main_window.sorting_mode_combobox
570+
for sort_mode in SortingModeEnum:
571+
sort_mode_dropdown.addItem(Translations[sort_mode.value], sort_mode)
572+
sort_mode_dropdown.setCurrentIndex(
573+
list(SortingModeEnum).index(self.filter.sorting_mode)
574+
) # set according to self.filter
575+
sort_mode_dropdown.currentIndexChanged.connect(self.sorting_mode_callback)
576+
577+
sort_dir_dropdown: QComboBox = self.main_window.sorting_direction_combobox
578+
sort_dir_dropdown.addItem("Ascending", userData=True)
579+
sort_dir_dropdown.addItem("Descending", userData=False)
580+
Translations.translate_with_setter(
581+
lambda text: sort_dir_dropdown.setItemText(0, text), "sorting.direction.ascending"
582+
)
583+
Translations.translate_with_setter(
584+
lambda text: sort_dir_dropdown.setItemText(1, text), "sorting.direction.descending"
585+
)
586+
sort_dir_dropdown.setCurrentIndex(0) # Default: Ascending
587+
sort_dir_dropdown.currentIndexChanged.connect(self.sorting_direction_callback)
588+
564589
# Thumbnail Size ComboBox
565590
thumb_size_combobox: QComboBox = self.main_window.thumb_size_combobox
566591
for size in self.thumb_sizes:
@@ -880,6 +905,24 @@ def run_macro(self, name: MacroID, grid_idx: int):
880905
content=strip_web_protocol(field.value),
881906
)
882907

908+
@property
909+
def sorting_direction(self) -> bool:
910+
"""Whether to Sort the results in ascending order."""
911+
return self.main_window.sorting_direction_combobox.currentData()
912+
913+
def sorting_direction_callback(self):
914+
logger.info("Sorting Direction Changed", ascending=self.sorting_direction)
915+
self.filter_items()
916+
917+
@property
918+
def sorting_mode(self) -> SortingModeEnum:
919+
"""What to sort by."""
920+
return self.main_window.sorting_mode_combobox.currentData()
921+
922+
def sorting_mode_callback(self):
923+
logger.info("Sorting Mode Changed", mode=self.sorting_mode)
924+
self.filter_items()
925+
883926
def thumb_size_callback(self, index: int):
884927
"""Perform actions needed when the thumbnail size selection is changed.
885928
@@ -1192,6 +1235,9 @@ def filter_items(self, filter: FilterState | None = None) -> None:
11921235

11931236
if filter:
11941237
self.filter = dataclasses.replace(self.filter, **dataclasses.asdict(filter))
1238+
else:
1239+
self.filter.sorting_mode = self.sorting_mode
1240+
self.filter.ascending = self.sorting_direction
11951241

11961242
# inform user about running search
11971243
self.main_window.statusbar.showMessage(Translations["status.library_search_query"])

0 commit comments

Comments
 (0)