Skip to content

Commit 1f25776

Browse files
committedJul 20, 2024
sort by asc
1 parent f865cc7 commit 1f25776

File tree

5 files changed

+48
-13
lines changed

5 files changed

+48
-13
lines changed
 

‎frontend/src/routes/imageDetail.tsx

+6-3
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,12 @@ function DetailOverlaySidebar({
133133
))}
134134
</div>
135135
<div className="col-3 border-start p-2 overflow-y-auto bg-body-tertiary">
136-
{selectedImage.tweet_id && selectedImage.tweet_username && (
137-
<p className="card-text">source: <a href={`https://twitter.com/_/status/${selectedImage.tweet_id}`} target="_blank">@{selectedImage.tweet_username}</a></p>
138-
)}
136+
<p className="card-text">
137+
{selectedImage.tweet_id && selectedImage.tweet_username && (
138+
<div>source: <a href={`https://twitter.com/_/status/${selectedImage.tweet_id}`} target="_blank">@{selectedImage.tweet_username}</a></div>
139+
)}
140+
<div>collected at: {new Date(selectedImage.collected_at).toLocaleDateString()}</div>
141+
</p>
139142

140143
{(faces?.length ?? 0) > 0 && (<>
141144
<div className="mb-2 fw-bold">faces</div>

‎frontend/src/routes/searchResult.tsx

+9-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as React from 'react'
22
import { Link, LoaderFunction, Outlet, useLoaderData, useLocation } from 'react-router-dom'
33
import { useState } from 'react'
44
import { applyQuickFilters } from '../utils/tagQuery'
5-
import { extractIndexSearchParams, extractRootSearchParams, removeTag } from '../utils/search'
5+
import { extractIndexSearchParams, extractRootSearchParams, removeTag, setSort } from '../utils/search'
66
import { RootLink, useExtractedSearchParams } from '../components/SearchLink'
77

88
export const searchResultLoader: LoaderFunction = async ({ request }) => {
@@ -14,6 +14,7 @@ export const searchResultLoader: LoaderFunction = async ({ request }) => {
1414
images: await (await fetch('/api/images?' + new URLSearchParams({
1515
...(tag && {tag}),
1616
...(page && {page: String(page)}),
17+
...(root.sort && {sort: root.sort}),
1718
}))).json()
1819
}
1920
}
@@ -38,7 +39,13 @@ export function SearchResultRoute() {
3839
))}
3940
</div>
4041
)}
41-
<p className="mb-4">total {images.count} images</p>
42+
43+
<p className="mb-4">
44+
total {images.count} images
45+
/ sort by date:{' '}
46+
<RootLink search={setSort(search, 'desc')} className={search.sort === 'desc' ? 'fw-bold' : ''}>desc</RootLink>{' '}
47+
<RootLink search={setSort(search, 'asc')} className={search.sort === 'asc' ? 'fw-bold' : ''}>asc</RootLink>
48+
</p>
4249

4350
<div className="d-flex flex-wrap">
4451
{imageList.map((image: any) => (

‎frontend/src/utils/search.ts

+10
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@ export type RootSearchParams = {
44
tag?: string
55
sfw?: TagQuery.QuickFilterState
66
realistic?: TagQuery.QuickFilterState
7+
sort?: 'asc' | 'desc'
78
}
89

910
export function extractRootSearchParams(searchParams: URLSearchParams): RootSearchParams {
11+
const sortParam = searchParams.get('sort')
1012
return {
1113
tag: searchParams.get('tag') ?? undefined,
1214
sfw: searchParams.get('sfw') as TagQuery.QuickFilterState,
1315
realistic: searchParams.get('realistic') as TagQuery.QuickFilterState,
16+
sort: sortParam === 'asc' || sortParam === 'desc' ? sortParam : 'desc',
1417
}
1518
}
1619

@@ -59,3 +62,10 @@ export function setQuickFilterState(search: RootSearchParams, key: 'sfw' | 'real
5962
[key]: state,
6063
}
6164
}
65+
66+
export function setSort(search: RootSearchParams, sort: 'asc' | 'desc'): RootSearchParams {
67+
return {
68+
...search,
69+
sort,
70+
}
71+
}

‎pix/api/images.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import datetime
2-
from typing import List, Optional, Union
2+
from typing import List, Literal, Optional, Union
33
from fastapi import APIRouter, HTTPException
44
import numpy as np
55
from pydantic import BaseModel
@@ -53,15 +53,15 @@ class ListImagesResult(BaseModel):
5353

5454

5555
@images_router.get("/api/images")
56-
def list_images(page: int = 1, tag: Optional[str] = None) -> ListImagesResult:
56+
def list_images(page: int = 1, tag: Optional[str] = None, sort: Optional[Literal['asc', 'desc']] = None) -> ListImagesResult:
5757
limit = 20
5858
offset = (page - 1) * limit
5959
image_repo = AppGraph.get_instance(ImageRepo)
6060
if tag:
61-
images = image_repo.list_by_tag_collected_at_desc(tag, offset, limit + 1)
61+
images = image_repo.list_by_tag_collected_at_desc(tag, offset, limit + 1, descending=sort != 'asc')
6262
count = image_repo.count_by_tag(tag)
6363
else:
64-
images = image_repo.list_by_collected_at_desc(offset, limit + 1)
64+
images = image_repo.list_by_collected_at_desc(offset, limit + 1, descending=sort != 'asc')
6565
count = image_repo.count()
6666
has_next_page = len(images) > limit
6767
images = images[:limit]

‎pix/model/image.py

+19-4
Original file line numberDiff line numberDiff line change
@@ -117,21 +117,27 @@ class ImageRepo(Repo[Image]):
117117
def count(self) -> int:
118118
return self.db.execute(sa.select(sa.func.count()).select_from(self.table)).first()[0]
119119

120-
def list_by_collected_at_desc(self, offset: int, limit: int) -> List[Image]:
120+
def list_by_collected_at_desc(self, offset: int, limit: int, descending: bool = True) -> List[Image]:
121+
order_by = self.idx_collected_at.c.collected_at
122+
if descending:
123+
order_by = order_by.desc()
121124
return [self._doc_from_row(row) for row in self.db.execute(
122125
sa.select(self.table)
123126
.join(self.idx_collected_at.table, self.table.c.id == self.idx_collected_at.c.id)
124-
.order_by(self.idx_collected_at.c.collected_at.desc())
127+
.order_by(order_by)
125128
.offset(offset)
126129
.limit(limit)
127130
)]
128131

129-
def list_by_tag_collected_at_desc(self, tag: str, offset: int, limit: int) -> List[Image]:
132+
def list_by_tag_collected_at_desc(self, tag: str, offset: int, limit: int, descending: bool = True) -> List[Image]:
133+
order_by = self.idx_collected_at.c.collected_at
134+
if descending:
135+
order_by = order_by.desc()
130136
return [self._doc_from_row(row) for row in self.db.execute(
131137
sa.select(self.table)
132138
.join(self.idx_collected_at.table, self.idx_collected_at.c.id == self.table.c.id)
133139
.where(*self._tag_condition(self.table, tag))
134-
.order_by(self.idx_collected_at.c.collected_at.desc())
140+
.order_by(order_by)
135141
.offset(offset)
136142
.limit(limit)
137143
)]
@@ -169,6 +175,15 @@ def list_needs_embedding(self, embedding_type: str) -> List[Image]:
169175
))
170176
.where(self.idx_embedding_types.c.embedding_type.is_(None))
171177
)]
178+
179+
def list_has_embedding(self, embedding_type: str) -> List[Image]:
180+
return [self._doc_from_row(row) for row in self.db.execute(
181+
sa.select(self.table)
182+
.join(self.idx_embedding_types.table, (
183+
(self.table.c.id == self.idx_embedding_types.c.id)
184+
& (self.idx_embedding_types.c.embedding_type == embedding_type)
185+
))
186+
)]
172187

173188
def list_all_tags_with_count(self, q: Union[str, None]) -> List[Tuple[str, int]]:
174189
query = sa.select(self.idx_tag_score.c.tag, sa.func.count())

0 commit comments

Comments
 (0)
Please sign in to comment.