|
|
|
@@ -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 }) {
|
|
|
|
|
))}
|
|
|
|
|
</select>
|
|
|
|
|
|
|
|
|
|
{/* 3b. Month of the year */}
|
|
|
|
|
<select
|
|
|
|
|
className="adm-input"
|
|
|
|
|
data-key="lib-filter-month"
|
|
|
|
|
aria-label={t('lib.month')}
|
|
|
|
|
value={month}
|
|
|
|
|
onChange={(e) => setMonth(e.target.value)}
|
|
|
|
|
style={{ flexShrink: 0 }}
|
|
|
|
|
>
|
|
|
|
|
<option value="all">{t('lib.monthAll')}</option>
|
|
|
|
|
{Array.from({ length: 12 }, (_, i) => i + 1).map((m) => (
|
|
|
|
|
<option key={m} value={String(m)}>
|
|
|
|
|
{String(m).padStart(2, '0')}
|
|
|
|
|
</option>
|
|
|
|
|
))}
|
|
|
|
|
</select>
|
|
|
|
|
|
|
|
|
|
{/* 3c. Day of the month */}
|
|
|
|
|
<select
|
|
|
|
|
className="adm-input"
|
|
|
|
|
data-key="lib-filter-day"
|
|
|
|
|
aria-label={t('lib.day')}
|
|
|
|
|
value={day}
|
|
|
|
|
onChange={(e) => setDay(e.target.value)}
|
|
|
|
|
style={{ flexShrink: 0 }}
|
|
|
|
|
>
|
|
|
|
|
<option value="all">{t('lib.dayAll')}</option>
|
|
|
|
|
{Array.from({ length: 31 }, (_, i) => i + 1).map((d) => (
|
|
|
|
|
<option key={d} value={String(d)}>
|
|
|
|
|
{String(d).padStart(2, '0')}
|
|
|
|
|
</option>
|
|
|
|
|
))}
|
|
|
|
|
</select>
|
|
|
|
|
|
|
|
|
|
{/* 4. Time taken (From / To dropdown selects) */}
|
|
|
|
|
<TimeRangeSelectInput from={from} to={to} onChange={(f: string, tStr: string) => { setFrom(f); setTo(tStr); }} t={t} />
|
|
|
|
|
|
|
|
|
|