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;