Skip to content

Commit c5498d6

Browse files
authored
Merge pull request #685 from danactive/fix-drag
Add cluster below label
2 parents bfa5d74 + d4d05e9 commit c5498d6

File tree

27 files changed

+1155
-720
lines changed

27 files changed

+1155
-720
lines changed

app/[gallery]/[album]/page.tsx

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,56 +7,53 @@ import getAlbums from '../../../src/lib/albums'
77
import getGalleries from '../../../src/lib/galleries'
88
import indexKeywords, { addGeographyToSearch } from '../../../src/lib/search'
99
import type { Album } from '../../../src/types/pages'
10+
import { generateClusters } from '../../../src/lib/generate-clusters'
1011

1112
export async function generateMetadata(
1213
{ params }: { params: Promise<Album.Params> },
1314
parent: ResolvingMetadata,
1415
): Promise<Metadata> {
1516
const album = (await params).album
16-
17-
return {
18-
title: `Album ${album} - History App`,
19-
}
17+
return { title: `Album ${album} - History App` }
2018
}
2119

2220
async function buildStaticPaths() {
2321
const { galleries } = await getGalleries()
2422
const groups = await Promise.all(galleries.map(async (gallery) => {
2523
const { [gallery]: { albums } } = await getAlbums(gallery)
26-
return albums.map(({ name: album }) => ({ params: { gallery, album } }))
24+
return albums.map(({ name: album }) => ({ gallery, album }))
2725
}))
2826
return groups.flat()
2927
}
3028

31-
async function getAlbumItems({ album, gallery }: Album.Params): Promise<Album.ComponentProps> {
29+
export async function generateStaticParams() {
30+
return buildStaticPaths()
31+
}
32+
33+
async function getAlbumItems({ album, gallery }: Album.Params): Promise<Album.ItemData> {
3234
const { album: { items, meta } } = await getAlbum(gallery, album)
3335
const preparedItems = items.map((item) => ({
3436
...item,
3537
search: addGeographyToSearch(item),
3638
corpus: [item.description, item.caption, item.location, item.city, item.search].join(' '),
3739
}))
38-
39-
return {
40-
items: preparedItems, meta, ...indexKeywords(preparedItems),
41-
}
42-
}
43-
44-
export async function generateStaticParams() {
45-
return buildStaticPaths()
40+
return { items: preparedItems, meta, ...indexKeywords(preparedItems) }
4641
}
4742

4843
export default async function AlbumServer(props: { params: Promise<Album.Params> }) {
4944
const params = await props.params
50-
51-
const {
52-
album,
53-
gallery,
54-
} = params
45+
const { album, gallery } = params
5546

5647
const { items, meta, indexedKeywords } = await getAlbumItems({ album, gallery })
48+
const clusterMarkers = generateClusters(items)
5749
return (
5850
<Suspense fallback={<div>Loading...</div>}>
59-
<AlbumPageComponent items={items} meta={meta} indexedKeywords={indexedKeywords} />
51+
<AlbumPageComponent
52+
items={items}
53+
meta={meta}
54+
indexedKeywords={indexedKeywords}
55+
clusteredMarkers={clusterMarkers}
56+
/>
6057
</Suspense>
6158
)
6259
}

app/[gallery]/all/__tests__/getAllData.sort.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getAllData } from '../page'
1+
import { getAllData } from '../../../../src/lib/all'
22
import type { Gallery } from '../../../../src/types/common'
33

44
// Reduce mocks: keep only data providers (getAlbums/getAlbum). Use real config & search.

app/[gallery]/all/page.tsx

Lines changed: 11 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,15 @@
11
import type { Metadata } from 'next'
22
import { Suspense } from 'react'
33

4-
import config from '../../../src/models/config'
54
import AllClient from '../../../src/components/All/AllClient'
6-
import getAlbum from '../../../src/lib/album'
7-
import getAlbums from '../../../src/lib/albums'
5+
import { getAllData } from '../../../src/lib/all'
86
import getGalleries from '../../../src/lib/galleries'
9-
import indexKeywords, { addGeographyToSearch } from '../../../src/lib/search'
10-
import type { AlbumMeta, Item, ServerSideAllItem } from '../../../src/types/common'
117
import type { All } from '../../../src/types/pages'
12-
13-
export async function getAllData({ gallery }: All.Params): Promise<All.ComponentProps> {
14-
const { [gallery]: { albums } } = await getAlbums(gallery)
15-
16-
const prepareItems = (
17-
{ albumName, albumCoordinateAccuracy, items }:
18-
{
19-
albumName: AlbumMeta['albumName'],
20-
albumCoordinateAccuracy: NonNullable<AlbumMeta['geo']>['zoom'],
21-
items: Item[],
22-
},
23-
) => items.map((item) => ({
24-
...item,
25-
gallery,
26-
album: albumName,
27-
corpus: [item.description, item.caption, item.location, item.city, item.search].join(' '),
28-
coordinateAccuracy: item.coordinateAccuracy ?? albumCoordinateAccuracy,
29-
search: addGeographyToSearch(item),
30-
}))
31-
32-
// reverse order for albums in ascending order (oldest on top)
33-
const allItems = (await albums.reduce(async (previousPromise, album) => {
34-
const prev = await previousPromise
35-
const { album: { items, meta } } = await getAlbum(gallery, album.name)
36-
const albumCoordinateAccuracy = meta?.geo?.zoom ?? config.defaultZoom
37-
const preparedItems = prepareItems({
38-
albumName: album.name,
39-
albumCoordinateAccuracy,
40-
items,
41-
})
42-
return prev.concat(preparedItems.reverse())
43-
}, Promise.resolve([] as ServerSideAllItem[]))).reverse()
44-
45-
return {
46-
items: allItems, ...indexKeywords(allItems),
47-
}
48-
}
8+
import { generateClusters } from '../../../src/lib/generate-clusters'
499

5010
export async function generateStaticParams() {
5111
const { galleries } = await getGalleries()
52-
return galleries.map((gallery) => ({ params: { gallery } }))
12+
return galleries.map((gallery) => ({ gallery }))
5313
}
5414

5515
export const metadata: Metadata = {
@@ -58,15 +18,17 @@ export const metadata: Metadata = {
5818

5919
export default async function AllServer(props: { params: Promise<All.Params> }) {
6020
const params = await props.params
21+
const { gallery } = params
6122

62-
const {
63-
gallery,
64-
} = params
65-
66-
const { items = [], indexedKeywords }: All.ComponentProps = await getAllData({ gallery })
23+
const { items = [], indexedKeywords } = await getAllData({ gallery })
24+
const clusterMarkers = generateClusters(items)
6725
return (
6826
<Suspense fallback={<div>Loading...</div>}>
69-
<AllClient items={items} indexedKeywords={indexedKeywords} />
27+
<AllClient
28+
items={items}
29+
indexedKeywords={indexedKeywords}
30+
clusteredMarkers={clusterMarkers}
31+
/>
7032
</Suspense>
7133
)
7234
}

app/[gallery]/persons/page.tsx

Lines changed: 47 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,68 @@
11
import { Suspense } from 'react'
2-
32
import type { Metadata } from 'next'
3+
44
import PersonsClient from '../../../src/components/Persons/PersonsClient'
5-
import getAlbum from '../../../src/lib/album'
6-
import getAlbums from '../../../src/lib/albums'
75
import getGalleries from '../../../src/lib/galleries'
8-
import indexKeywords, { addGeographyToSearch } from '../../../src/lib/search'
9-
import config from '../../../src/models/config'
10-
import type { AlbumMeta, Gallery, Item, ServerSideAllItem } from '../../../src/types/common'
11-
import type { All } from '../../../src/types/pages'
6+
import { getPersonsData } from '../../../src/lib/persons'
7+
import type { Gallery } from '../../../src/types/common'
8+
import { generateClusters } from '../../../src/lib/generate-clusters'
9+
import type { Item } from '../../../src/types/common'
10+
11+
type AgeSummary = {
12+
ages: { age: number; count: number }[];
13+
};
14+
15+
function buildAgeSummary(items: Item[]): AgeSummary {
16+
const counts = new Map<number, number>()
17+
items.forEach(it => {
18+
if (!it.persons || !it.filename) return
19+
const filenameDate = Array.isArray(it.filename)
20+
? (it.filename[0] ?? '').substring(0, 10)
21+
: String(it.filename).substring(0, 10)
22+
const photoDate = (it as any).photoDate || filenameDate
23+
it.persons.forEach(p => {
24+
if (!p.dob) return
25+
const birth = new Date(p.dob.substring(0, 10))
26+
const shot = new Date(photoDate.substring(0, 10))
27+
if (Number.isNaN(birth.getTime()) || Number.isNaN(shot.getTime())) return
28+
let age = shot.getFullYear() - birth.getFullYear()
29+
const m = shot.getMonth() - birth.getMonth()
30+
if (m < 0 || (m === 0 && shot.getDate() < birth.getDate())) age -= 1
31+
if (age >= 0) counts.set(age, (counts.get(age) || 0) + 1)
32+
})
33+
})
34+
return {
35+
ages: Array.from(counts.entries())
36+
.map(([age, count]) => ({ age, count }))
37+
.sort((a, b) => a.age - b.age),
38+
}
39+
}
1240

1341
export const metadata: Metadata = {
1442
title: 'Persons - History App',
1543
}
1644

1745
export async function generateStaticParams() {
1846
const { galleries } = await getGalleries()
19-
20-
return galleries.map((gallery) => ({
21-
gallery,
22-
}))
23-
}
24-
25-
async function getPersonsData({ gallery }: All.Params): Promise<All.ComponentProps> {
26-
const { [gallery]: { albums } } = await getAlbums(gallery)
27-
28-
const prepareItems = (
29-
{ albumName, albumCoordinateAccuracy, items }:
30-
{
31-
albumName: AlbumMeta['albumName'],
32-
albumCoordinateAccuracy: NonNullable<AlbumMeta['geo']>['zoom'],
33-
items: Item[],
34-
},
35-
) => items.map((item) => ({
36-
...item,
37-
gallery,
38-
album: albumName,
39-
corpus: [item.description, item.caption, item.location, item.city, item.search].join(' '),
40-
coordinateAccuracy: item.coordinateAccuracy ?? albumCoordinateAccuracy,
41-
search: addGeographyToSearch(item),
42-
}))
43-
44-
// reverse order for albums in ascending order (oldest on top)
45-
const allItems = (await albums.reduce(async (previousPromise, album) => {
46-
const prev = await previousPromise
47-
const { album: { items, meta } } = await getAlbum(gallery, album.name)
48-
const albumCoordinateAccuracy = meta?.geo?.zoom ?? config.defaultZoom
49-
const preparedItems = prepareItems({
50-
albumName: album.name,
51-
albumCoordinateAccuracy,
52-
items,
53-
})
54-
return prev.concat(preparedItems)
55-
}, Promise.resolve([] as ServerSideAllItem[]))).reverse()
56-
57-
return {
58-
items: allItems, ...indexKeywords(allItems),
59-
}
47+
return galleries.map((gallery) => ({ gallery }))
6048
}
6149

6250
export default async function PersonsServer(props: { params: Promise<{ gallery: Gallery }> }) {
6351
const params = await props.params
64-
65-
const {
66-
gallery,
67-
} = params
52+
const { gallery } = params
6853

6954
const { items, indexedKeywords } = await getPersonsData({ gallery })
55+
const clusterMarkers = generateClusters(items)
56+
const initialAgeSummary = buildAgeSummary(items)
57+
7058
return (
71-
<Suspense fallback={<div>Loading...</div>}>
72-
<PersonsClient items={items} indexedKeywords={indexedKeywords} />
59+
<Suspense fallback={<div>Loading Persons...</div>}>
60+
<PersonsClient
61+
items={items}
62+
indexedKeywords={indexedKeywords}
63+
clusteredMarkers={clusterMarkers}
64+
initialAgeSummary={initialAgeSummary}
65+
/>
7366
</Suspense>
7467
)
7568
}

app/[gallery]/today/page.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import AlbumPageComponent from '../../../src/components/Album/AlbumClient'
55
import getAlbum from '../../../src/lib/album'
66
import getAlbums from '../../../src/lib/albums'
77
import getGalleries from '../../../src/lib/galleries'
8+
import { generateClusters } from '../../../src/lib/generate-clusters'
89
import indexKeywords, { addGeographyToSearch } from '../../../src/lib/search'
910
import config from '../../../src/models/config'
1011
import type { AlbumMeta, Gallery, Item } from '../../../src/types/common'
@@ -65,9 +66,10 @@ async function getTodayItems(gallery: Gallery) {
6566
export default async function TodayServer(props: { params: Promise<{ gallery: Gallery }> }) {
6667
const params = await props.params
6768
const { items, indexedKeywords } = await getTodayItems(params.gallery)
69+
const clusteredMarkers = generateClusters(items)
6870
return (
69-
<Suspense fallback={<div>Loading...</div>}>
70-
<AlbumPageComponent items={items} indexedKeywords={indexedKeywords} />
71+
<Suspense fallback={<div>Loading Today...</div>}>
72+
<AlbumPageComponent items={items} indexedKeywords={indexedKeywords} clusteredMarkers={clusteredMarkers} />
7173
</Suspense>
7274
)
7375
}

config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"video": ["mp4", "webm"]
1919
},
2020
"rawFileTypes": {
21-
"photo": ["arw", "raw", "dng"],
21+
"photo": ["arw", "raw", "dng", "heic", "heif"],
2222
"video": ["avi", "mov", "m2ts", "mts"]
2323
}
2424
}

eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export default [
4343
'jsdoc/check-tag-names': 'error', // Validate JSDoc tag names
4444
'jsdoc/check-types': 'error', // Ensure TS types are correctly used
4545
'comma-dangle': ['error', 'always-multiline'],
46+
// 'no-unused-vars': 2, // Turn off base rule (TypeScript handles this better)
4647
},
4748
settings: {
4849
jsdoc: {

0 commit comments

Comments
 (0)