Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display persons in album w/ Age #635

Merged
merged 8 commits into from
Nov 18, 2024
2 changes: 1 addition & 1 deletion next-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
/// <reference types="next/navigation-types/compat/navigation" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
4,087 changes: 1,375 additions & 2,712 deletions package-lock.json

Large diffs are not rendered by default.

46 changes: 23 additions & 23 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,55 +16,55 @@
"release": "standard-version"
},
"dependencies": {
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@hello-pangea/dnd": "^16.6.0",
"@mui/joy": "^5.0.0-beta.32",
"boom": "^7.3.0",
"camelcase": "^6.3.0",
"color-thief-react": "^2.1.0",
"glob": "^10.4.2",
"glob": "^10.4.5",
"heic-convert": "^2.1.0",
"mapbox-gl": "^3.4.0",
"mapbox-gl": "^3.8.0",
"mime-types": "^2.1.35",
"next": "^14.2.10",
"next": "^14.2.18",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-image-gallery": "^1.3.0",
"react-map-gl": "^7.1.7",
"stylis": "^4.3.2",
"stylis": "^4.3.4",
"xml2js": "^0.6.2"
},
"devDependencies": {
"@playwright/test": "^1.45.0",
"@testing-library/jest-dom": "^6.4.6",
"@playwright/test": "^1.48.2",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^15.0.7",
"@types/boom": "^7.3.5",
"@types/geojson": "^7946.0.14",
"@types/heic-convert": "^1.2.3",
"@types/jest": "^29.5.12",
"@types/jest": "^29.5.14",
"@types/mime-types": "^2.1.4",
"@types/node": "^20.14.9",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/node": "^20.17.6",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/react-image-gallery": "^1.2.4",
"@types/xml2js": "^0.4.14",
"eslint": "^8.57.0",
"eslint": "^8.57.1",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-next": "^14.2.4",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jest-dom": "^5.4.0",
"eslint-plugin-jsx-a11y": "^6.9.0",
"eslint-plugin-react": "^7.34.3",
"eslint-config-next": "^14.2.18",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jest-dom": "^5.5.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-testing-library": "^6.2.2",
"eslint-plugin-testing-library": "^6.4.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"next-test-api-route-handler": "^4.0.8",
"snyk": "^1.1292.1",
"next-test-api-route-handler": "^4.0.14",
"snyk": "^1.1294.0",
"standard-version": "^9.5.0",
"ts-jest": "^29.1.5",
"typescript": "^5.5.2"
"ts-jest": "^29.2.5",
"typescript": "^5.6.3"
},
"engines": {
"node": "20",
Expand Down
5 changes: 5 additions & 0 deletions public/galleries/demo/persons.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<persons>
<person first="Mister" last="Gingerbread" dob="2004-01-02" />
<person first="Missus" last="Gingerbread" dob="2004-01-02" />
</persons>
2 changes: 1 addition & 1 deletion public/galleries/demo/sample.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<photo_loc>Dining Room</photo_loc>
<thumb_caption>Gingerbread</thumb_caption>
<photo_desc>Gingerbread persons</photo_desc>
<search>best^, baked goods, house</search>
<search>best^, baked goods, house, Mister Gingerbread, Missus Gingerbread</search>
</item>
<item id="2">
<filename>2005-07-30-01.jpg</filename>
Expand Down
1 change: 1 addition & 0 deletions src/components/SlippyMap/__tests__/options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ describe('Options - <SlippyMap />', () => {
caption: 'Mock caption',
description: null,
search: null,
persons: null,
title: 'Mock title',
coordinates: null,
coordinateAccuracy: null,
Expand Down
3 changes: 2 additions & 1 deletion src/components/SlippyMap/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,13 @@ export default function SlippyMap(
if (err) {
return
}
const zoomNotNull = clickZoom === null ? undefined : clickZoom // dep upgrade changed type rules

if (mapRef?.current) {
mapRef.current.flyTo({
// @ts-ignore
center: feature.geometry.coordinates,
zoom: clickZoom,
zoom: zoomNotNull,
})
}
})
Expand Down
7 changes: 6 additions & 1 deletion src/hooks/memory.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
margin-right: 1rem;
}

.location {
.person {
display: inline;
margin-right: 1rem;
}

.filename {
display: inline;
margin-right: 1rem;
}
4 changes: 3 additions & 1 deletion src/hooks/useMemory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type ReactImageGallery from 'react-image-gallery'
import Link from '../components/Link'
import type { Item } from '../types/common'
import styles from './memory.module.css'
import applyAge from '../utils/person'

interface Viewed {
(index: number): void;
Expand All @@ -28,7 +29,8 @@ const useMemory = (
const memoryHtml = details ? (
<>
<h3 className={styles.city}>{details.title}</h3>
<h4 className={styles.location}>{details.filename}</h4>
{details.persons && <h4 className={styles.person}>{applyAge(details.persons, details.filename)}</h4>}
<h5 className={styles.filename}>{details.filename}</h5>
{details.reference && <Link href={details.reference[0]}>{decodeURI(details.reference[1]).replaceAll('_', ' ')}</Link>}
</>
) : null
Expand Down
1 change: 1 addition & 0 deletions src/lib/__tests__/search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ describe('Search hook', () => {
caption: 'Mock caption',
description: null,
search: null,
persons: null,
title: 'Mock title',
coordinates: null,
coordinateAccuracy: null,
Expand Down
36 changes: 14 additions & 22 deletions src/lib/album.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,7 @@
import camelCase from 'camelcase'
import fs from 'node:fs/promises'
import xml2js, { type ParserOptions } from 'xml2js'

import transformJsonSchema, { errorSchema, type ErrorOptionalMessage } from '../models/album'
import transformAlbumSchema, { errorSchema, type ErrorOptionalMessage } from '../models/album'
import type { Album, AlbumMeta } from '../types/common'

const parseOptions: ParserOptions = { explicitArray: false, normalizeTags: true, tagNameProcessors: [(name) => camelCase(name)] }
const parser = new xml2js.Parser(parseOptions)

/**
* Get album XML from local filesystem
* @param {string} gallery name of gallery
* @param {string} album name of album
* @returns {string} album XML
*/
async function getXmlFromFilesystem(gallery: NonNullable<AlbumMeta['gallery']>, album: string) {
const fileBuffer = await fs.readFile(`public/galleries/${gallery}/${album}.xml`)
return parser.parseStringPromise(fileBuffer)
}
import getPersons from './persons'
import { getAlbumFromFilesystem } from './xml'

type Envelope = { body: Album, status: number }
type ErrorOptionalMessageBody = {
Expand Down Expand Up @@ -48,8 +32,16 @@ async function get(
if (!album === null || album === undefined || Array.isArray(album)) {
throw new ReferenceError('Album name is missing')
}
const xml = await getXmlFromFilesystem(gallery, album)
const body = transformJsonSchema(xml)
const jsonAlbum = await getAlbumFromFilesystem(gallery, album)

let relativeDate = null
if (jsonAlbum.album.item) {
const filenames = Array.isArray(jsonAlbum.album.item) ? jsonAlbum.album.item[0].filename : jsonAlbum.album.item.filename
const filename = Array.isArray(filenames) ? filenames[0] : filenames
relativeDate = new Date(filename.substring(0, 10))
}

const body = transformAlbumSchema(jsonAlbum, await getPersons(gallery))

if (returnEnvelope) {
return { body, status: 200 }
Expand All @@ -68,5 +60,5 @@ async function get(
}
}

export { transformJsonSchema }
export { transformAlbumSchema as transformJsonSchema }
export default get
17 changes: 1 addition & 16 deletions src/lib/albums.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import camelCase from 'camelcase'
import fs from 'node:fs/promises'
import xml2js, { type ParserOptions } from 'xml2js'

import utilsFactory from './utils'
import type {
AlbumMeta,
GalleryAlbum,
XmlGallery,
XmlGalleryAlbum,
} from '../types/common'
import { getGalleryFromFilesystem } from './xml'

type ErrorOptionalMessage = { albums: object[]; error?: { message: string } }
const errorSchema = (message: string): ErrorOptionalMessage => {
Expand All @@ -17,20 +14,8 @@ const errorSchema = (message: string): ErrorOptionalMessage => {
return { ...out, error: { message } }
}

const parseOptions: ParserOptions = { explicitArray: false, normalizeTags: true, tagNameProcessors: [(name) => camelCase(name)] }
const parser = new xml2js.Parser(parseOptions)
const utils = utilsFactory()

/**
* Get Gallery from local filesystem
* @param {string} gallery name of gallery
* @returns {string} album as JSON
*/
async function getGalleryFromFilesystem(gallery: NonNullable<AlbumMeta['gallery']>): Promise<XmlGallery> {
const fileBuffer = await fs.readFile(`public/galleries/${gallery}/gallery.xml`)
return parser.parseStringPromise(fileBuffer)
}

type GalleryAlbums = {
albums: GalleryAlbum[]
}
Expand Down
9 changes: 3 additions & 6 deletions src/lib/filesystems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,16 @@ async function get(
const globPath = path.join(publicPath, destinationPath)

if (!globPath.startsWith(publicPath)) {
return { body: errorSchema('Invalid system path'), status: 404 }
const body = errorSchema('Invalid system path')
return (returnEnvelope ? { body, status: 404 } : body)
}

const files = await glob(decodeURI(`${globPath}/*`))

const webPaths = files.map((file) => transform(file, { destinationPath, globPath })).sort((a, b) => a.name.localeCompare(b.name))

const body = { files: webPaths, destinationPath }
if (returnEnvelope) {
return { body, status: 200 }
}

return body
return (returnEnvelope ? { body, status: 200 } : body)
} catch (e) {
if (returnEnvelope) {
return { body: errorSchema('No files or folders are found'), status: 404 }
Expand Down
48 changes: 48 additions & 0 deletions src/lib/persons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import transformJsonSchema, { errorSchema, type ErrorOptionalMessage } from '../models/person'
import type { AlbumMeta, Person } from '../types/common'
import { getPersonsFromFilesystem } from './xml'

type Envelope = { body: Person[], status: number }
type ErrorOptionalMessageBody = {
body: ErrorOptionalMessage; status: number;
}
type ReturnAlbumOrErrors = Promise<Envelope | Person[] | ErrorOptionalMessage | ErrorOptionalMessageBody>
async function get<T extends boolean = false>(
gallery: AlbumMeta['gallery'],
returnEnvelope?: T,
): Promise<T extends true ? Envelope : Person[]>
/**
* Get Persons XML from local filesystem
* @param {string} gallery name of gallery
* @param {boolean} returnEnvelope will enable a return value with HTTP status code and body
* @returns {object} person
*/
async function get(
gallery: AlbumMeta['gallery'],
returnEnvelope: boolean,
): ReturnAlbumOrErrors {
try {
if (gallery === null || gallery === undefined) {
throw new ReferenceError('Gallery name is missing')
}
const json = await getPersonsFromFilesystem(gallery)
const body = transformJsonSchema(json)

if (returnEnvelope) {
return { body, status: 200 }
}

return body
} catch (e) {
const message = `No person file was found; gallery=${gallery};`
if (returnEnvelope) {
return { body: errorSchema(message), status: 404 }
}

// eslint-disable-next-line no-console
console.error('ERROR', message, e)
throw e
}
}

export default get
47 changes: 47 additions & 0 deletions src/lib/xml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import camelCase from 'camelcase'
import fs from 'node:fs/promises'
import xml2js, { type ParserOptions } from 'xml2js'

import type {
AlbumMeta, XmlAlbum, XmlGallery, XmlPersons,
} from '../types/common'

const parseOptions: ParserOptions = { explicitArray: false, normalizeTags: true, tagNameProcessors: [(name) => camelCase(name)] }
const parser = new xml2js.Parser(parseOptions)

/**
* Get Album XML from local filesystem
* @param {string} gallery name of gallery
* @param {string} album name of album
* @returns {string} album XML
*/
async function readAlbum(gallery: NonNullable<AlbumMeta['gallery']>, album: string): Promise<XmlAlbum> {
const fileBuffer = await fs.readFile(`public/galleries/${gallery}/${album}.xml`)
return parser.parseStringPromise(fileBuffer)
}

/**
* Get Gallery XML from local filesystem
* @param {string} gallery name of gallery
* @returns {string} album as JSON
*/
async function readGallery(gallery: NonNullable<AlbumMeta['gallery']>): Promise<XmlGallery> {
const fileBuffer = await fs.readFile(`public/galleries/${gallery}/gallery.xml`)
return parser.parseStringPromise(fileBuffer)
}

/**
* Get Persons XML from local filesystem
* @param {string} gallery name of gallery
* @returns {string} album as JSON
*/
async function readPersons(gallery: NonNullable<AlbumMeta['gallery']>): Promise<XmlPersons> {
const fileBuffer = await fs.readFile(`public/galleries/${gallery}/persons.xml`)
return parser.parseStringPromise(fileBuffer)
}

export {
readAlbum as getAlbumFromFilesystem,
readGallery as getGalleryFromFilesystem,
readPersons as getPersonsFromFilesystem,
}
Loading
Loading