Skip to content

Commit 662caba

Browse files
committed
Merge branch feature/webapp-date-format
2 parents d4e592c + 9e01a1c commit 662caba

File tree

11 files changed

+285
-18
lines changed

11 files changed

+285
-18
lines changed

gallery.config-example.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,40 @@
364364
#- edit
365365
# Disables navigation to overview, prev and next
366366
#- nav
367+
#
368+
# Date format as in linux date command
369+
# %b month name
370+
# %d day of month, eg 01
371+
# %m month, eg 12
372+
# %Y 4 full year, eg 2025
373+
# %y 2-digit year, eg 25
374+
# %H hour eg 18
375+
# %M minute, eg 06
376+
# %S seconds, eg 45
377+
#
378+
# default: []
379+
#
380+
#format:
381+
# Date format
382+
#
383+
# default: '%d.%m.%Y'
384+
#date: '%d.%m.%Y'
385+
# Month year format
386+
#
387+
# default: '%b %Y'
388+
#monthYear: '%b %Y'
389+
# Year format
390+
#
391+
# default: '%Y'
392+
#year: '%Y'
393+
# time format
394+
#
395+
# default: '%H:%M:%S'
396+
#time: '%H:%M:%S'
397+
# time format with hour and minute
398+
#
399+
# default: '%H:%M'
400+
#hourMinute: '%H:%M'
367401

368402
# Plugin manager settings
369403
#pluginManager:

gallery.config.schema.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,41 @@
703703
}
704704
}
705705
}
706+
},
707+
"format": {
708+
"$schema": "https://json-schema.org/draft/2020-12/schema",
709+
"$id": "https://raw.githubusercontent.com/xemle/home-gallery/master/gallery.config.schema.json#webapp.format",
710+
"title": "FormatUtils",
711+
"description": "Date format as in linux date command\n %b month name\n %d day of month, eg 01\n %m month, eg 12\n %Y 4 full year, eg 2025\n %y 2-digit year, eg 25\n %H hour eg 18\n %M minute, eg 06\n %S seconds, eg 45",
712+
"default": [],
713+
"type": "object",
714+
"properties": {
715+
"date": {
716+
"description": "Date format",
717+
"type": "string",
718+
"default": "%d.%m.%Y"
719+
},
720+
"monthYear": {
721+
"description": "Month year format",
722+
"type": "string",
723+
"default": "%b %Y"
724+
},
725+
"year": {
726+
"description": "Year format",
727+
"type": "string",
728+
"default": "%Y"
729+
},
730+
"time": {
731+
"description": "time format",
732+
"type": "string",
733+
"default": "%H:%M:%S"
734+
},
735+
"hourMinute": {
736+
"description": "time format with hour and minute",
737+
"type": "string",
738+
"default": "%H:%M"
739+
}
740+
}
706741
}
707742
}
708743
},

packages/webapp/src/config/AppConfig.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export interface AppConfig {
4646
disabled?: FeatureFlags;
4747
sources?: MediaSources;
4848
pages?: Pages;
49+
format?: FormatUtils;
4950
[k: string]: unknown;
5051
}
5152
export interface PluginManager {
@@ -67,3 +68,37 @@ export interface MediaViewPage {
6768
disabled?: MediaViewDisableFlags;
6869
[k: string]: unknown;
6970
}
71+
/**
72+
* Date format as in linux date command
73+
* %b month name
74+
* %d day of month, eg 01
75+
* %m month, eg 12
76+
* %Y 4 full year, eg 2025
77+
* %y 2-digit year, eg 25
78+
* %H hour eg 18
79+
* %M minute, eg 06
80+
* %S seconds, eg 45
81+
*/
82+
export interface FormatUtils {
83+
/**
84+
* Date format
85+
*/
86+
date?: string;
87+
/**
88+
* Month year format
89+
*/
90+
monthYear?: string;
91+
/**
92+
* Year format
93+
*/
94+
year?: string;
95+
/**
96+
* time format
97+
*/
98+
time?: string;
99+
/**
100+
* time format with hour and minute
101+
*/
102+
hourMinute?: string;
103+
[k: string]: unknown;
104+
}

packages/webapp/src/list/scrollbar/ScrollbarHandle.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export const ScrollbarHandle = ({dispatch, top, visible, detailText, showDetail}
8484
return (
8585
<>
8686
<div className={classNames('absolute right-0 mr-1 flex gap-2 items-center justify-center hover:cursor-pointer', {hidden: !visible})} ref={handleRef} style={{top: top}}>
87-
{detailText && showDetail && <div className={classNames('bg-gray-800/90 px-4 py-2 rounded text-gray-300')}>{detailText}</div>}
87+
{detailText && showDetail && <div className={classNames('bg-gray-800/90 px-4 py-2 rounded text-gray-300 text-nowrap')}>{detailText}</div>}
8888
<div className="flex flex-col gap-1 bg-gray-800 rounded" ref={thumbRef} >
8989
<span ref={upButton} className="px-2 py-1 group hover:bg-gray-700 hover:text-gray-300">
9090
<FontAwesomeIcon icon={icons.faChevronUp} className="text-gray-500 group-hover:text-gray-300"/>

packages/webapp/src/list/scrollbar/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useScrollPageSpeedSimple, SimplePageSpeed } from './useScrollPageSpeed'
99
import { initialState, reducer } from './state'
1010
import type { ScrollbarActions, ScrollbarOverviewItem, ScrollbarState, VisibleHandle } from './state'
1111
import { overviewItemMapper, type TopDateItem } from "./overviewItemMapper";
12+
import { useAppConfig } from "../../config/useAppConfig";
1213

1314
export interface ScrollbarProps {
1415
containerRef: React.RefObject<any>,
@@ -20,6 +21,7 @@ export interface ScrollbarProps {
2021
export const Scrollbar = ({containerRef, style, pageHeight, topDateItems}: ScrollbarProps) => {
2122
const propState = {containerRef, pageHeight}
2223

24+
const { format } = useAppConfig()
2325
const scrollTop = useScrollTop(containerRef)
2426
const [scrollSpeed, setScrollViewHeight] = useScrollPageSpeedSimple(containerRef, pageHeight)
2527

@@ -30,7 +32,7 @@ export const Scrollbar = ({containerRef, style, pageHeight, topDateItems}: Scrol
3032
useEffect(() => dispatch({type: 'pageHeight', pageHeight}), [pageHeight])
3133

3234
useEffect(() => {
33-
const [overviewItems, detailTextFn] = overviewItemMapper(topDateItems, pageHeight, handleHeight / 2)
35+
const [overviewItems, detailTextFn] = overviewItemMapper(topDateItems, pageHeight, handleHeight / 2, format || {})
3436
dispatch({type: 'overviewItems', overviewItems, detailTextFn})
3537
}, [topDateItems, pageHeight, handleHeight])
3638

packages/webapp/src/list/scrollbar/overviewItemMapper.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { type ScrollbarOverviewItem } from "./state"
22

33
import { formatDate } from '../../utils/format'
4+
import { FormatUtils } from "../../utils/FormatUtils"
45

56
const isInDateOrder = items => {
67
const isDesc = items[0].date > items[items.length - 1].date
@@ -17,7 +18,7 @@ const isInDateOrder = items => {
1718
const HOUR_MS = 1000 * 60 * 60
1819
const DAY_MS = HOUR_MS * 24
1920

20-
const setDateValue = items => {
21+
const setDateValue =(items, format: FormatUtils) => {
2122
const firstDate = new Date(items[0].date)
2223
const lastDate = new Date(items[items.length - 1].date)
2324
const diff = Math.abs(lastDate.getTime() - firstDate.getTime())
@@ -26,15 +27,15 @@ const setDateValue = items => {
2627

2728
let dateValueFn: (date: Date) => string
2829
if (hourDiff < 6) {
29-
dateValueFn = date => formatDate('%H:%M:%S', date)
30+
dateValueFn = date => formatDate(format.time || '%H:%M:%S', date)
3031
} else if (hourDiff <= 24) {
31-
dateValueFn = date => formatDate('%H:%M', date)
32+
dateValueFn = date => formatDate(format.hourMinute || '%H:%M', date)
3233
} else if (dayDiff < 90) {
33-
dateValueFn = date => formatDate('%d.%m.%y', date)
34+
dateValueFn = date => formatDate(format.date || '%d.%m.%y', date)
3435
} else if (dayDiff < 700) {
35-
dateValueFn = date => formatDate('%b %Y', date)
36+
dateValueFn = date => formatDate(format.monthYear || '%b %Y', date)
3637
} else {
37-
dateValueFn = date => formatDate('%Y', date)
38+
dateValueFn = date => formatDate(format.year || '%Y', date)
3839
}
3940

4041
items.forEach(item => item.dateValue = dateValueFn(item.date))
@@ -47,15 +48,15 @@ export interface TopDateItem {
4748
dateValue: string
4849
}
4950

50-
export const overviewItemMapper = (topDateItems: TopDateItem[], viewHeight: number, padding: number): [ScrollbarOverviewItem[], (number) => string] => {
51+
export const overviewItemMapper = (topDateItems: TopDateItem[], viewHeight: number, padding: number, format: FormatUtils = {}): [ScrollbarOverviewItem[], (number) => string] => {
5152
if (!topDateItems.length) {
5253
return [[], () => '']
5354
}
5455

5556
if (!isInDateOrder(topDateItems)) {
5657
return [[], () => '']
5758
}
58-
setDateValue(topDateItems)
59+
setDateValue(topDateItems, format)
5960

6061
const lastItem = topDateItems[topDateItems.length - 1]
6162
const maxTop = (lastItem.top + lastItem.height) - viewHeight
@@ -91,7 +92,7 @@ export const overviewItemMapper = (topDateItems: TopDateItem[], viewHeight: numb
9192

9293
const detailTextFn = (scrollTop) => {
9394
const lastItem = topDateItems.filter(item => item.top <= scrollTop).pop()
94-
return `${lastItem?.date ? formatDate('%d.%m.%y', lastItem?.date) : ''}`
95+
return `${lastItem?.date ? formatDate(format.date || '%d.%m.%y', lastItem?.date) : ''}`
9596
}
9697

9798
return [overviewItems, detailTextFn]

packages/webapp/src/single/Details.tsx

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { MediaViewDisableFlags } from "./MediaViewPage";
1414
export const Details = ({entry, dispatch}: {entry: Entry, dispatch: any}) => {
1515
const appConfig = useAppConfig()
1616
const disabledFlags = appConfig.pages?.mediaView?.disabled || [] as MediaViewDisableFlags
17+
const dateFormat = appConfig.format?.date || '%d.%m.%y'
18+
const timeFormat = appConfig.format?.time || '%H:%M:%S'
1719
const {openDialog, setDialogVisible} = useTagDialog()
1820

1921
if (!entry) {
@@ -168,14 +170,10 @@ export const Details = ({entry, dispatch}: {entry: Entry, dispatch: any}) => {
168170
</div>
169171
<div>
170172
<p>
171-
{searchLink(formatDate('%d', entry.date), `year:${entry.date.substr(0, 4)} month:${entry.date.substr(5, 2)} day:${entry.date.substr(8, 2)}`)}
172-
<span className="px-1">.</span>
173-
{searchLink(formatDate('%m', entry.date), `year:${entry.date.substr(0, 4)} month:${entry.date.substr(5, 2)}`)}
174-
<span className="px-1">.</span>
175-
{searchLink(formatDate('%Y', entry.date), `year:${entry.date.substr(0, 4)}`)}
173+
<DateFormat entry={entry} format={dateFormat} searchLink={searchLink}/>
176174
</p>
177175
<p>
178-
{formatDate('%H:%M:%S', entry.date)}
176+
{formatDate(timeFormat, entry.date)}
179177
</p>
180178
</div>
181179
</div>
@@ -305,3 +303,53 @@ export const Details = ({entry, dispatch}: {entry: Entry, dispatch: any}) => {
305303
)
306304
}
307305

306+
function DateFormat({entry, format, searchLink}) {
307+
const result: React.ReactNode[] = []
308+
309+
if (!entry?.date) {
310+
return result
311+
}
312+
313+
let last = 0;
314+
let pos = format.indexOf('%', last)
315+
316+
while (pos >= 0 && pos < format.length - 1) {
317+
if (pos > last) {
318+
const head = (<span className="px-1">{format.substring(last, pos)}</span>)
319+
result.push(head)
320+
}
321+
const code = format.substring(pos, pos + 2)
322+
323+
let link: React.JSX.Element | null = null
324+
switch (code) {
325+
case '%Y':
326+
link = searchLink(formatDate(code, entry.date), `year:${entry.date.substr(0, 4)}`)
327+
break
328+
case '%y':
329+
link = searchLink(formatDate(code, entry.date), `year:${entry.date.substr(0, 4)}`)
330+
break
331+
case '%m':
332+
link = searchLink(formatDate(code, entry.date), `year:${entry.date.substr(0, 4)} month:${entry.date.substr(5, 2)}`)
333+
break
334+
case '%d':
335+
link = searchLink(formatDate(code, entry.date), `year:${entry.date.substr(0, 4)} month:${entry.date.substr(5, 2)} day:${entry.date.substr(8, 2)}`)
336+
break
337+
default:
338+
link = <span className="px-1">{formatDate(code, entry.date)}</span>
339+
}
340+
341+
if (link) {
342+
result.push(link)
343+
}
344+
345+
last = pos + 2
346+
pos = format.indexOf('%', last)
347+
}
348+
349+
if (last < format.length) {
350+
const tail = (<span className="px-1">{format.substring(last)}</span>)
351+
result.push(tail)
352+
}
353+
354+
return result
355+
}

packages/webapp/src/single/MediaViewPage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* and run json-schema-to-typescript to regenerate this file.
66
*/
77

8-
export type MediaViewDisableFlags = ("detail" | "map" | "similar" | "annotation" | "edit")[];
8+
export type MediaViewDisableFlags = (("detail" | "map" | "similar" | "annotation" | "edit") | "nav")[];
99

1010
/**
1111
* Customize single media view
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://raw.githubusercontent.com/xemle/home-gallery/master/gallery.config.schema.json#webapp.format",
4+
"title": "FormatUtils",
5+
"description": "Date format as in linux date command\n %b month name\n %d day of month, eg 01\n %m month, eg 12\n %Y 4 full year, eg 2025\n %y 2-digit year, eg 25\n %H hour eg 18\n %M minute, eg 06\n %S seconds, eg 45",
6+
"default": [],
7+
"type": "object",
8+
"properties": {
9+
"date": {
10+
"description": "Date format",
11+
"type": "string",
12+
"default": "%d.%m.%Y"
13+
},
14+
"monthYear": {
15+
"description": "Month year format",
16+
"type": "string",
17+
"default": "%b %Y"
18+
},
19+
"year": {
20+
"description": "Year format",
21+
"type": "string",
22+
"default": "%Y"
23+
},
24+
"time": {
25+
"description": "time format",
26+
"type": "string",
27+
"default": "%H:%M:%S"
28+
},
29+
"hourMinute": {
30+
"description": "time format with hour and minute",
31+
"type": "string",
32+
"default": "%H:%M"
33+
}
34+
35+
}
36+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/* eslint-disable */
2+
/**
3+
* This file was automatically generated by json-schema-to-typescript.
4+
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
5+
* and run json-schema-to-typescript to regenerate this file.
6+
*/
7+
8+
/**
9+
* Date format as in linux date command
10+
* %b month name
11+
* %d day of month, eg 01
12+
* %m month, eg 12
13+
* %Y 4 full year, eg 2025
14+
* %y 2-digit year, eg 25
15+
* %H hour eg 18
16+
* %M minute, eg 06
17+
* %S seconds, eg 45
18+
*/
19+
export interface FormatUtils {
20+
/**
21+
* Date format
22+
*/
23+
date?: string;
24+
/**
25+
* Month year format
26+
*/
27+
monthYear?: string;
28+
/**
29+
* Year format
30+
*/
31+
year?: string;
32+
/**
33+
* time format
34+
*/
35+
time?: string;
36+
/**
37+
* time format with hour and minute
38+
*/
39+
hourMinute?: string;
40+
[k: string]: unknown;
41+
}

0 commit comments

Comments
 (0)