diff --git a/docker/frontend/src/Library.tsx b/docker/frontend/src/Library.tsx index 92549e3..7f9238e 100644 --- a/docker/frontend/src/Library.tsx +++ b/docker/frontend/src/Library.tsx @@ -88,8 +88,8 @@ import { MobileLibrary } from './ui/MobileLibrary'; // // ponytail: no search and no flag. A roll of date folders is three levels deep at // most, and what a reader actually digs through a shelf with is the score they -// gave a frame, the year it was shot and the hours they shot it in — which are -// the three the thumbnail view filters on. Add the text box when a folder of +// gave a frame, and the year, month, day and hours it was shot in — which are +// what the thumbnail view filters on. Add the text box when a folder of // mixed names makes one worth typing into. interface Node { // The tree's own key: a folder name, or `folder/sub/dir`. @@ -116,6 +116,8 @@ const SORT_BY_KEY = 'recipescam.library.sortBy'; const SORT_DIR_KEY = 'recipescam.library.sortDir'; const MIN_STAR_KEY = 'recipescam.library.minStar'; const YEAR_KEY = 'recipescam.library.year'; +const MONTH_KEY = 'recipescam.library.month'; +const DAY_KEY = 'recipescam.library.day'; const FROM_KEY = 'recipescam.library.from'; const TO_KEY = 'recipescam.library.to'; // The frame the studio is holding (written on its side, see App.tsx): the shelf @@ -451,10 +453,10 @@ function Catalogue({ admin, user }: { admin: boolean; user: User | null }) { // mount: the address is what says the shelf is the screen up again. const [here, setHere] = useState(() => window.location.pathname.startsWith('/library')); // How the thumbnail view reads the open folder: every frame it holds, or only - // the ones that were given this many stars, shot in this year, shot between - // these hours — and in the order the reader picked. A way of looking, not part - // of the roll, so none of it outlives the visit; the strip below the frame does - // not follow it, because the strip is the shelf itself. + // the ones that were given this many stars, shot in this year, this month, on + // this day, between these hours — and in the order the reader picked. A way of + // looking, not part of the roll, so none of it outlives the visit; the strip + // below the frame does not follow it, because the strip is the shelf itself. const [sortBy, setSortBy] = useState<'taken' | 'mtime' | 'type' | 'name' | 'star'>( () => (recall(SORT_BY_KEY) as any) || 'taken' ); @@ -463,6 +465,10 @@ function Catalogue({ admin, user }: { admin: boolean; user: User | null }) { ); const [minStar, setMinStar] = useState(() => Number(recall(MIN_STAR_KEY)) || 0); const [year, setYear] = useState(() => recall(YEAR_KEY) || 'all'); + // Month and day of the shot, 'all' meaning every one. Numbers rather than + // names: a name is a language and this shelf is read in two of them. + const [month, setMonth] = useState(() => recall(MONTH_KEY) || 'all'); + const [day, setDay] = useState(() => recall(DAY_KEY) || 'all'); const [from, setFrom] = useState(() => recall(FROM_KEY) || 'any'); const [to, setTo] = useState(() => recall(TO_KEY) || 'any'); // What the shelf's search box holds while it is being typed in. It is not a @@ -475,6 +481,8 @@ function Catalogue({ admin, user }: { admin: boolean; user: User | null }) { useEffect(() => { keep(SORT_DIR_KEY, sortDir); }, [sortDir]); useEffect(() => { keep(MIN_STAR_KEY, String(minStar)); }, [minStar]); useEffect(() => { keep(YEAR_KEY, year); }, [year]); + useEffect(() => { keep(MONTH_KEY, month); }, [month]); + useEffect(() => { keep(DAY_KEY, day); }, [day]); useEffect(() => { keep(FROM_KEY, from); }, [from]); useEffect(() => { keep(TO_KEY, to); }, [to]); // How far the frame is magnified, and the pan offset relative to viewport center. @@ -1805,6 +1813,8 @@ function Catalogue({ admin, user }: { admin: boolean; user: User | null }) { if (minStar && (p.star ?? 0) < minStar) return false; const at = new Date(p.taken); if (year !== 'all' && at.getFullYear() !== Number(year)) return false; + if (month !== 'all' && at.getMonth() + 1 !== Number(month)) return false; + if (day !== 'all' && at.getDate() !== Number(day)) return false; if (from !== 'any' && to !== 'any') { const f = Number(from); const tNum = Number(to); @@ -1847,7 +1857,7 @@ function Catalogue({ admin, user }: { admin: boolean; user: User | null }) { } return diff * multiplier; }); - }, [shown, minStar, year, from, to, sortBy, sortDir]); + }, [shown, minStar, year, month, day, from, to, sortBy, sortDir]); const frame = listed.find((p) => p.id === up) ?? listed[0] ?? null; // The strip draws the tiles around the one under the eye, not the whole shelf. @@ -2947,6 +2957,40 @@ function Catalogue({ admin, user }: { admin: boolean; user: User | null }) { ))} + {/* 3b. Month of the year */} + + + {/* 3c. Day of the month */} + + {/* 4. Time taken (From / To dropdown selects) */} { setFrom(f); setTo(tStr); }} t={t} /> diff --git a/docker/frontend/src/i18n/en.ts b/docker/frontend/src/i18n/en.ts index 0f141ef..4119126 100644 --- a/docker/frontend/src/i18n/en.ts +++ b/docker/frontend/src/i18n/en.ts @@ -316,6 +316,10 @@ export const en: Dict = { 'lib.rotateRight': 'Turn this frame right', 'lib.year': 'YEAR', 'lib.yearAll': 'Any year', + 'lib.month': 'MONTH', + 'lib.monthAll': 'Any month', + 'lib.day': 'DAY', + 'lib.dayAll': 'Any day', 'lib.hourFrom': 'Shot from', 'lib.hourFromAny': 'Any hour', 'lib.hourTo': 'Shot until', diff --git a/docker/frontend/src/i18n/vi.ts b/docker/frontend/src/i18n/vi.ts index 71c3b41..189c8c2 100644 --- a/docker/frontend/src/i18n/vi.ts +++ b/docker/frontend/src/i18n/vi.ts @@ -328,6 +328,10 @@ export const vi = { 'lib.rotateRight': 'Xoay ảnh sang phải', 'lib.year': 'NĂM', 'lib.yearAll': 'Mọi năm', + 'lib.month': 'THÁNG', + 'lib.monthAll': 'Mọi tháng', + 'lib.day': 'NGÀY', + 'lib.dayAll': 'Mọi ngày', 'lib.hourFrom': 'Chụp từ', 'lib.hourFromAny': 'Mọi giờ', 'lib.hourTo': 'Chụp đến', diff --git a/docker/frontend/src/styles/app.css b/docker/frontend/src/styles/app.css index 2c62e06..8907556 100644 --- a/docker/frontend/src/styles/app.css +++ b/docker/frontend/src/styles/app.css @@ -1447,8 +1447,8 @@ form[data-key='immich-modal'] { card's border that much taller than the picture inside it. Rows keep the height their own frames give them however few of them there are. */ .lib-root .adm-grid { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding-right: 6px; overflow-anchor: none; align-content: start; } -/* What the thumbnail wall is read through: the four selects and the count of what - is left. One row above the wall, and it does not scroll with it. */ +/* What the thumbnail wall is read through: the selects and the count of what is + left. One row above the wall, and it does not scroll with it. */ .lib-root .adm-stage > .lib-filters, .lib-filters { flex: 0 0 auto;