Skip to content

feat: Sorting by "Date Added" #674

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/updates/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ Features are broken up into the following priority levels, with nested prioritie
- [ ] Fuzzy Search [LOW] [#400](https://github.com/TagStudioDev/TagStudio/issues/400)
- [ ] Sortable results [HIGH] [#68](https://github.com/TagStudioDev/TagStudio/issues/68)
- [ ] Sort by relevance [HIGH]
- [x] Sort by date added [HIGH]
- [ ] Sort by date created [HIGH]
- [ ] Sort by date modified [HIGH]
- [ ] Sort by date taken (photos) [MEDIUM]
Expand Down Expand Up @@ -182,6 +183,7 @@ These version milestones are rough estimations for when the previous core featur
- [ ] Field content search [HIGH]
- [ ] Sortable results [HIGH]
- [ ] Sort by relevance [HIGH]
- [x] Sort by date added [HIGH]
- [ ] Sort by date created [HIGH]
- [ ] Sort by date modified [HIGH]
- [ ] Sort by date taken (photos) [MEDIUM]
Expand Down
4 changes: 3 additions & 1 deletion tagstudio/resources/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -212,5 +212,7 @@
"view.size.4": "Extra Large",
"window.message.error_opening_library": "Error opening library.",
"window.title.error": "Error",
"window.title.open_create_library": "Open/Create Library"
"window.title.open_create_library": "Open/Create Library",
"sorting.direction.ascending": "Ascending",
"sorting.direction.descending": "Descending"
}
12 changes: 12 additions & 0 deletions tagstudio/src/core/library/alchemy/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,19 @@ class ItemType(enum.Enum):
TAG_GROUP = 2


class SortingModeEnum(enum.Enum):
DATE_ADDED = "file.date_added"


@dataclass
class FilterState:
"""Represent a state of the Library grid view."""

# these should remain
page_index: int | None = 0
page_size: int | None = 500
sorting_mode: SortingModeEnum = SortingModeEnum.DATE_ADDED
ascending: bool = True

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

def with_sorting_mode(self, mode: SortingModeEnum) -> "FilterState":
return replace(self, sorting_mode=mode)

def with_sorting_direction(self, ascending: bool) -> "FilterState":
return replace(self, ascending=ascending)


class FieldTypeEnum(enum.Enum):
TEXT_LINE = "Text Line"
Expand Down
12 changes: 11 additions & 1 deletion tagstudio/src/core/library/alchemy/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@
from humanfriendly import format_timespan
from sqlalchemy import (
URL,
ColumnExpressionArgument,
Engine,
NullPool,
and_,
asc,
create_engine,
delete,
desc,
exists,
func,
or_,
Expand All @@ -42,7 +45,7 @@
)
from ...enums import LibraryPrefs
from .db import make_tables
from .enums import FieldTypeEnum, FilterState, TagColor
from .enums import FieldTypeEnum, FilterState, SortingModeEnum, TagColor
from .fields import (
BaseField,
DatetimeField,
Expand Down Expand Up @@ -576,6 +579,13 @@ def search_library(
query_count = select(func.count()).select_from(statement.alias("entries"))
count_all: int = session.execute(query_count).scalar()

sort_on: ColumnExpressionArgument = Entry.id
match search.sorting_mode:
case SortingModeEnum.DATE_ADDED:
sort_on = Entry.id

statement = statement.order_by(asc(sort_on) if search.ascending else desc(sort_on))

statement = statement.limit(search.limit).offset(search.offset)

logger.info(
Expand Down
9 changes: 9 additions & 0 deletions tagstudio/src/qt/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ def setupUi(self, MainWindow):
spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
self.horizontalLayout_3.addItem(spacerItem)

# Sorting Dropdowns
self.sorting_mode_combobox = QComboBox(self.centralwidget)
self.sorting_mode_combobox.setObjectName(u"sortingModeComboBox")
self.horizontalLayout_3.addWidget(self.sorting_mode_combobox)

self.sorting_direction_combobox = QComboBox(self.centralwidget)
self.sorting_direction_combobox.setObjectName(u"sortingDirectionCombobox")
self.horizontalLayout_3.addWidget(self.sorting_direction_combobox)

# Thumbnail Size placeholder
self.thumb_size_combobox = QComboBox(self.centralwidget)
self.thumb_size_combobox.setObjectName(u"thumbSizeComboBox")
Expand Down
48 changes: 47 additions & 1 deletion tagstudio/src/qt/ts_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
FieldTypeEnum,
FilterState,
ItemType,
SortingModeEnum,
)
from src.core.library.alchemy.fields import _FieldID
from src.core.library.alchemy.library import Entry, LibraryStatus
Expand Down Expand Up @@ -538,16 +539,40 @@ def init_library_window(self):
search_button.clicked.connect(
lambda: self.filter_items(
FilterState.from_search_query(self.main_window.searchField.text())
.with_sorting_mode(self.sorting_mode)
.with_sorting_direction(self.sorting_direction)
)
)
# Search Field
search_field: QLineEdit = self.main_window.searchField
search_field.returnPressed.connect(
# TODO - parse search field for filters
lambda: self.filter_items(
FilterState.from_search_query(self.main_window.searchField.text())
.with_sorting_mode(self.sorting_mode)
.with_sorting_direction(self.sorting_direction)
)
)
# Sorting Dropdowns
sort_mode_dropdown: QComboBox = self.main_window.sorting_mode_combobox
for sort_mode in SortingModeEnum:
sort_mode_dropdown.addItem(Translations[sort_mode.value], sort_mode)
sort_mode_dropdown.setCurrentIndex(
list(SortingModeEnum).index(self.filter.sorting_mode)
) # set according to self.filter
sort_mode_dropdown.currentIndexChanged.connect(self.sorting_mode_callback)

sort_dir_dropdown: QComboBox = self.main_window.sorting_direction_combobox
sort_dir_dropdown.addItem("Ascending", userData=True)
sort_dir_dropdown.addItem("Descending", userData=False)
Translations.translate_with_setter(
lambda text: sort_dir_dropdown.setItemText(0, text), "sorting.direction.ascending"
)
Translations.translate_with_setter(
lambda text: sort_dir_dropdown.setItemText(1, text), "sorting.direction.descending"
)
sort_dir_dropdown.setCurrentIndex(0) # Default: Ascending
sort_dir_dropdown.currentIndexChanged.connect(self.sorting_direction_callback)

# Thumbnail Size ComboBox
thumb_size_combobox: QComboBox = self.main_window.thumb_size_combobox
for size in self.thumb_sizes:
Expand Down Expand Up @@ -867,6 +892,24 @@ def run_macro(self, name: MacroID, grid_idx: int):
content=strip_web_protocol(field.value),
)

@property
def sorting_direction(self) -> bool:
"""Whether to Sort the results in ascending order."""
return self.main_window.sorting_direction_combobox.currentData()

def sorting_direction_callback(self):
logger.info("Sorting Direction Changed", ascending=self.sorting_direction)
self.filter_items()

@property
def sorting_mode(self) -> SortingModeEnum:
"""What to sort by."""
return self.main_window.sorting_mode_combobox.currentData()

def sorting_mode_callback(self):
logger.info("Sorting Mode Changed", mode=self.sorting_mode)
self.filter_items()

def thumb_size_callback(self, index: int):
"""Perform actions needed when the thumbnail size selection is changed.

Expand Down Expand Up @@ -1179,6 +1222,9 @@ def filter_items(self, filter: FilterState | None = None) -> None:

if filter:
self.filter = dataclasses.replace(self.filter, **dataclasses.asdict(filter))
else:
self.filter.sorting_mode = self.sorting_mode
self.filter.ascending = self.sorting_direction

# inform user about running search
self.main_window.statusbar.showMessage(Translations["status.library_search_query"])
Expand Down
Loading