@phosart/common 0.4.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -0
- package/dist/FullGallery.svelte +51 -0
- package/dist/FullGallery.svelte.d.ts +17 -0
- package/dist/FullGallery.svelte.d.ts.map +1 -0
- package/dist/Gallery.svelte +30 -0
- package/dist/Gallery.svelte.d.ts +14 -0
- package/dist/Gallery.svelte.d.ts.map +1 -0
- package/dist/GalleryPreview.svelte +60 -0
- package/dist/GalleryPreview.svelte.d.ts +9 -0
- package/dist/GalleryPreview.svelte.d.ts.map +1 -0
- package/dist/HighResContext.svelte +21 -0
- package/dist/HighResContext.svelte.d.ts +8 -0
- package/dist/HighResContext.svelte.d.ts.map +1 -0
- package/dist/Image.svelte +171 -0
- package/dist/Image.svelte.d.ts +14 -0
- package/dist/Image.svelte.d.ts.map +1 -0
- package/dist/Modal.svelte +87 -0
- package/dist/Modal.svelte.d.ts +9 -0
- package/dist/Modal.svelte.d.ts.map +1 -0
- package/dist/ModalGallery/Carousel.svelte +76 -0
- package/dist/ModalGallery/Carousel.svelte.d.ts +10 -0
- package/dist/ModalGallery/Carousel.svelte.d.ts.map +1 -0
- package/dist/ModalGallery/ImageSection.svelte +156 -0
- package/dist/ModalGallery/ImageSection.svelte.d.ts +11 -0
- package/dist/ModalGallery/ImageSection.svelte.d.ts.map +1 -0
- package/dist/ModalGallery/ImageView.svelte +92 -0
- package/dist/ModalGallery/ImageView.svelte.d.ts +9 -0
- package/dist/ModalGallery/ImageView.svelte.d.ts.map +1 -0
- package/dist/ModalGallery/Spinner.svelte +71 -0
- package/dist/ModalGallery/Spinner.svelte.d.ts +7 -0
- package/dist/ModalGallery/Spinner.svelte.d.ts.map +1 -0
- package/dist/ModalGallery.svelte +165 -0
- package/dist/ModalGallery.svelte.d.ts +16 -0
- package/dist/ModalGallery.svelte.d.ts.map +1 -0
- package/dist/OpengraphMeta.svelte +125 -0
- package/dist/OpengraphMeta.svelte.d.ts +15 -0
- package/dist/OpengraphMeta.svelte.d.ts.map +1 -0
- package/dist/Postcard/ArtistLink.svelte +46 -0
- package/dist/Postcard/ArtistLink.svelte.d.ts +9 -0
- package/dist/Postcard/ArtistLink.svelte.d.ts.map +1 -0
- package/dist/Postcard/Chip.svelte +100 -0
- package/dist/Postcard/Chip.svelte.d.ts +12 -0
- package/dist/Postcard/Chip.svelte.d.ts.map +1 -0
- package/dist/Postcard/Description/Character.svelte +79 -0
- package/dist/Postcard/Description/Character.svelte.d.ts +9 -0
- package/dist/Postcard/Description/Character.svelte.d.ts.map +1 -0
- package/dist/Postcard/Description.svelte +146 -0
- package/dist/Postcard/Description.svelte.d.ts +13 -0
- package/dist/Postcard/Description.svelte.d.ts.map +1 -0
- package/dist/Postcard/Headline.svelte +70 -0
- package/dist/Postcard/Headline.svelte.d.ts +10 -0
- package/dist/Postcard/Headline.svelte.d.ts.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/server/artist.d.ts +7 -0
- package/dist/server/artist.d.ts.map +1 -0
- package/dist/server/artist.js +40 -0
- package/dist/server/character.d.ts +9 -0
- package/dist/server/character.d.ts.map +1 -0
- package/dist/server/character.js +76 -0
- package/dist/server/directories.d.ts +4 -0
- package/dist/server/directories.d.ts.map +1 -0
- package/dist/server/directories.js +39 -0
- package/dist/server/fastcache.d.ts +11 -0
- package/dist/server/fastcache.d.ts.map +1 -0
- package/dist/server/fastcache.js +43 -0
- package/dist/server/filter.d.ts +6 -0
- package/dist/server/filter.d.ts.map +1 -0
- package/dist/server/filter.js +53 -0
- package/dist/server/gallery.d.ts +16 -0
- package/dist/server/gallery.d.ts.map +1 -0
- package/dist/server/gallery.js +162 -0
- package/dist/server/imageprocess.d.ts +18 -0
- package/dist/server/imageprocess.d.ts.map +1 -0
- package/dist/server/imageprocess.js +243 -0
- package/dist/server/index.d.ts +15 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +15 -0
- package/dist/server/models/Artist.d.ts +7 -0
- package/dist/server/models/Artist.d.ts.map +1 -0
- package/dist/server/models/Artist.js +15 -0
- package/dist/server/models/Character.d.ts +108 -0
- package/dist/server/models/Character.d.ts.map +1 -0
- package/dist/server/models/Character.js +21 -0
- package/dist/server/models/Gallery.d.ts +373 -0
- package/dist/server/models/Gallery.d.ts.map +1 -0
- package/dist/server/models/Gallery.js +60 -0
- package/dist/server/models/image.d.ts +64 -0
- package/dist/server/models/image.d.ts.map +1 -0
- package/dist/server/models/image.js +17 -0
- package/dist/server/pack.d.ts +3 -0
- package/dist/server/pack.d.ts.map +1 -0
- package/dist/server/pack.js +26 -0
- package/dist/server/theme/schema.d.ts +57 -0
- package/dist/server/theme/schema.d.ts.map +1 -0
- package/dist/server/theme/schema.js +217 -0
- package/dist/server/util.d.ts +24 -0
- package/dist/server/util.d.ts.map +1 -0
- package/dist/server/util.js +71 -0
- package/dist/util/art.d.ts +52 -0
- package/dist/util/art.d.ts.map +1 -0
- package/dist/util/art.js +57 -0
- package/dist/util/artistcontext.svelte.d.ts +8 -0
- package/dist/util/artistcontext.svelte.d.ts.map +1 -0
- package/dist/util/artistcontext.svelte.js +18 -0
- package/dist/util/charactercontext.svelte.d.ts +4 -0
- package/dist/util/charactercontext.svelte.d.ts.map +1 -0
- package/dist/util/charactercontext.svelte.js +11 -0
- package/dist/util/date.d.ts +2 -0
- package/dist/util/date.d.ts.map +1 -0
- package/dist/util/date.js +6 -0
- package/dist/util/index.d.ts +13 -0
- package/dist/util/index.d.ts.map +1 -0
- package/dist/util/index.js +10 -0
- package/dist/util/markdown.d.ts +2 -0
- package/dist/util/markdown.d.ts.map +1 -0
- package/dist/util/markdown.js +16 -0
- package/dist/util/phosart_config.svelte.d.ts +44 -0
- package/dist/util/phosart_config.svelte.d.ts.map +1 -0
- package/dist/util/phosart_config.svelte.js +51 -0
- package/dist/util/search.d.ts +3 -0
- package/dist/util/search.d.ts.map +1 -0
- package/dist/util/search.js +24 -0
- package/dist/util/smoothscroll.d.ts +4 -0
- package/dist/util/smoothscroll.d.ts.map +1 -0
- package/dist/util/smoothscroll.js +21 -0
- package/dist/util/tree.d.ts +19 -0
- package/dist/util/tree.d.ts.map +1 -0
- package/dist/util/tree.js +58 -0
- package/dist/util/util.d.ts +4 -0
- package/dist/util/util.d.ts.map +1 -0
- package/dist/util/util.js +22 -0
- package/package.json +102 -0
- package/src/lib/FullGallery.svelte +51 -0
- package/src/lib/Gallery.svelte +30 -0
- package/src/lib/GalleryPreview.svelte +60 -0
- package/src/lib/HighResContext.svelte +21 -0
- package/src/lib/Image.svelte +171 -0
- package/src/lib/Modal.svelte +87 -0
- package/src/lib/ModalGallery/Carousel.svelte +76 -0
- package/src/lib/ModalGallery/ImageSection.svelte +156 -0
- package/src/lib/ModalGallery/ImageView.svelte +92 -0
- package/src/lib/ModalGallery/Spinner.svelte +71 -0
- package/src/lib/ModalGallery.svelte +165 -0
- package/src/lib/OpengraphMeta.svelte +125 -0
- package/src/lib/Postcard/ArtistLink.svelte +46 -0
- package/src/lib/Postcard/Chip.svelte +100 -0
- package/src/lib/Postcard/Description/Character.svelte +79 -0
- package/src/lib/Postcard/Description.svelte +146 -0
- package/src/lib/Postcard/Headline.svelte +70 -0
- package/src/lib/index.ts +20 -0
- package/src/lib/server/artist.ts +50 -0
- package/src/lib/server/character.ts +113 -0
- package/src/lib/server/directories.ts +45 -0
- package/src/lib/server/fastcache.ts +66 -0
- package/src/lib/server/filter.ts +71 -0
- package/src/lib/server/gallery.ts +259 -0
- package/src/lib/server/imageprocess.ts +382 -0
- package/src/lib/server/index.ts +57 -0
- package/src/lib/server/models/Artist.ts +19 -0
- package/src/lib/server/models/Character.ts +24 -0
- package/src/lib/server/models/Gallery.ts +70 -0
- package/src/lib/server/models/image.ts +20 -0
- package/src/lib/server/pack.ts +31 -0
- package/src/lib/server/theme/schema.ts +286 -0
- package/src/lib/server/util.ts +102 -0
- package/src/lib/util/art.ts +136 -0
- package/src/lib/util/artistcontext.svelte.ts +25 -0
- package/src/lib/util/charactercontext.svelte.ts +15 -0
- package/src/lib/util/date.ts +7 -0
- package/src/lib/util/index.ts +29 -0
- package/src/lib/util/markdown.ts +17 -0
- package/src/lib/util/phosart_config.svelte.ts +101 -0
- package/src/lib/util/search.ts +28 -0
- package/src/lib/util/smoothscroll.ts +21 -0
- package/src/lib/util/tree.ts +75 -0
- package/src/lib/util/util.ts +37 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { $DATA } from './directories.ts';
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import * as yaml from 'yaml';
|
|
5
|
+
import { FullCharacter, RawCharacter } from './models/Character.ts';
|
|
6
|
+
import type { z } from 'zod';
|
|
7
|
+
import { processImageFastcache } from './imageprocess.ts';
|
|
8
|
+
import { cacheVersion, getCache, fullPath, relPath } from './util.ts';
|
|
9
|
+
import { flushFastCache, readFastCache, type FastCache } from './fastcache.ts';
|
|
10
|
+
import { galleries } from './gallery.ts';
|
|
11
|
+
import { normalizeCharacter, type NormalizedCharacter } from '../util/art.ts';
|
|
12
|
+
|
|
13
|
+
export type CharacterCache = Record<string, z.infer<typeof FullCharacter>>;
|
|
14
|
+
export type RawCharacterCache = Record<string, z.infer<typeof RawCharacter>>;
|
|
15
|
+
|
|
16
|
+
const CHARACTER_PATH = path.join('characters', 'characters.yaml');
|
|
17
|
+
|
|
18
|
+
async function doResolveImage(
|
|
19
|
+
filename: string,
|
|
20
|
+
character: z.infer<typeof RawCharacter>,
|
|
21
|
+
fc: FastCache
|
|
22
|
+
): Promise<z.infer<typeof FullCharacter>> {
|
|
23
|
+
const full = processImageFastcache(
|
|
24
|
+
fc,
|
|
25
|
+
fullPath(filename, character.picture.image),
|
|
26
|
+
relPath(filename, character.picture.image)
|
|
27
|
+
);
|
|
28
|
+
const thumb = character.thumbnail
|
|
29
|
+
? processImageFastcache(
|
|
30
|
+
fc,
|
|
31
|
+
fullPath(filename, character.thumbnail.image),
|
|
32
|
+
relPath(filename, character.thumbnail.image)
|
|
33
|
+
)
|
|
34
|
+
: undefined;
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
...character,
|
|
38
|
+
picture: { ...(await full), alt: character.picture.alt },
|
|
39
|
+
thumbnail: thumb
|
|
40
|
+
? {
|
|
41
|
+
...(await thumb),
|
|
42
|
+
alt: character.thumbnail!.alt
|
|
43
|
+
}
|
|
44
|
+
: undefined
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function rawCharacters(): Promise<RawCharacterCache> {
|
|
49
|
+
const cached = getCache().rawCharacterCache;
|
|
50
|
+
const nextVersion = await cacheVersion();
|
|
51
|
+
if (cached.cache && cached.version === nextVersion) {
|
|
52
|
+
return cached.cache;
|
|
53
|
+
}
|
|
54
|
+
const document = yaml.parse(
|
|
55
|
+
await fs.readFile(path.join($DATA(), CHARACTER_PATH), { encoding: 'utf-8' })
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
59
|
+
const characters: Record<string, z.infer<typeof RawCharacter>> = (document as any[])
|
|
60
|
+
.flatMap((obj, idx) => {
|
|
61
|
+
const ch = RawCharacter.parse({ ...obj, index: idx++ });
|
|
62
|
+
return ch;
|
|
63
|
+
})
|
|
64
|
+
.reduce((acc, cur) => ({ ...acc, [cur.name]: cur }), {});
|
|
65
|
+
|
|
66
|
+
getCache().rawCharacterCache.cache = characters;
|
|
67
|
+
getCache().rawCharacterCache.version = nextVersion;
|
|
68
|
+
return characters;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function characters(): Promise<CharacterCache> {
|
|
72
|
+
const cached = getCache().characterCache;
|
|
73
|
+
const nextVersion = await cacheVersion();
|
|
74
|
+
if (cached.cache && cached.version === nextVersion) {
|
|
75
|
+
return cached.cache;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const characters = await rawCharacters();
|
|
79
|
+
const fc = await readFastCache();
|
|
80
|
+
|
|
81
|
+
const resolved = Object.fromEntries<z.infer<typeof FullCharacter>>(
|
|
82
|
+
await Promise.all(
|
|
83
|
+
Object.entries(characters).map<Promise<[string, z.infer<typeof FullCharacter>]>>(
|
|
84
|
+
async ([k, v]) => {
|
|
85
|
+
const resolved = await doResolveImage(CHARACTER_PATH, v, fc);
|
|
86
|
+
return [k, resolved];
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
await flushFastCache(fc);
|
|
93
|
+
getCache().characterCache.cache = resolved;
|
|
94
|
+
getCache().characterCache.version = nextVersion;
|
|
95
|
+
return resolved;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function getAllCharacters(): Promise<Array<NormalizedCharacter>> {
|
|
99
|
+
const all = await characters();
|
|
100
|
+
const mapped: NormalizedCharacter[] = Object.values(await galleries())
|
|
101
|
+
.flatMap((g) => g.pieces)
|
|
102
|
+
.flatMap((p) => p.characters)
|
|
103
|
+
.flatMap((ch) => normalizeCharacter(ch, all))
|
|
104
|
+
.concat(Object.values(all).map((ch) => normalizeCharacter(ch.name, all)));
|
|
105
|
+
|
|
106
|
+
// Collapse by name, from pair
|
|
107
|
+
const record: Record<string, NormalizedCharacter> = {};
|
|
108
|
+
for (const m of mapped) {
|
|
109
|
+
record[`${m.name}\x00${m.from}`] = m;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return Object.values(record);
|
|
113
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { join, dirname } from 'node:path';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
|
|
4
|
+
function tryFindRoot(start: string) {
|
|
5
|
+
let cur = dirname(start);
|
|
6
|
+
while (cur.length > 1) {
|
|
7
|
+
if (cur.includes('node_modules')) {
|
|
8
|
+
cur = join(cur, '..');
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
// If package.json parses correctly...
|
|
14
|
+
JSON.parse(fs.readFileSync(join(cur, 'package.json'), { encoding: 'utf-8' }));
|
|
15
|
+
return cur;
|
|
16
|
+
} catch {
|
|
17
|
+
cur = join(cur, '..');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let cachedRoot: string | null = null;
|
|
24
|
+
|
|
25
|
+
function findRoot() {
|
|
26
|
+
if (cachedRoot) {
|
|
27
|
+
return cachedRoot;
|
|
28
|
+
}
|
|
29
|
+
let root: string | null = null;
|
|
30
|
+
if (process.env.PROJECT_ROOT) {
|
|
31
|
+
root = tryFindRoot(process.env.PROJECT_ROOT);
|
|
32
|
+
}
|
|
33
|
+
root = root ?? tryFindRoot(process.argv[1]) ?? tryFindRoot(process.cwd());
|
|
34
|
+
|
|
35
|
+
if (!root) {
|
|
36
|
+
throw new Error('Failed to find root');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
cachedRoot = root;
|
|
40
|
+
return root;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const $ROOT = () => join(findRoot(), 'src');
|
|
44
|
+
export const $DATA = () => join($ROOT(), 'data');
|
|
45
|
+
export const $PUBLIC = () => join($ROOT(), '..', 'static', '_');
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
|
+
import { $DATA } from './directories.ts';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { Logger } from 'tslog';
|
|
5
|
+
import { getLogLevel } from './util.ts';
|
|
6
|
+
import { readPack, writePack } from './pack.ts';
|
|
7
|
+
const FastcacheLogger = new Logger({ minLevel: getLogLevel() });
|
|
8
|
+
|
|
9
|
+
const $FASTCACHEPATH = () => path.join($DATA(), '.fastcache.pack.gz');
|
|
10
|
+
|
|
11
|
+
export interface FastCache {
|
|
12
|
+
[relpath: string]: {
|
|
13
|
+
mtime: number;
|
|
14
|
+
hash: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function getFastHash(
|
|
19
|
+
fc: FastCache,
|
|
20
|
+
fullpath: string,
|
|
21
|
+
relpath: string
|
|
22
|
+
): Promise<[string | null, number | null]> {
|
|
23
|
+
const cacheinfo = fc[relpath];
|
|
24
|
+
if (!cacheinfo) return [null, null];
|
|
25
|
+
|
|
26
|
+
const fstat = await stat(fullpath);
|
|
27
|
+
const mtime = fstat.mtimeMs;
|
|
28
|
+
|
|
29
|
+
if (mtime !== cacheinfo.mtime) {
|
|
30
|
+
return [null, mtime];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return [cacheinfo.hash, mtime];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function updateFastCache(
|
|
37
|
+
fc: FastCache,
|
|
38
|
+
fullpath: string,
|
|
39
|
+
relpath: string,
|
|
40
|
+
hash: string,
|
|
41
|
+
premtime?: number | null
|
|
42
|
+
) {
|
|
43
|
+
const mtime = premtime ?? (await stat(fullpath)).mtimeMs;
|
|
44
|
+
|
|
45
|
+
fc[relpath] = {
|
|
46
|
+
mtime,
|
|
47
|
+
hash
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function flushFastCache(fc: FastCache) {
|
|
52
|
+
try {
|
|
53
|
+
await writePack($FASTCACHEPATH(), fc);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
FastcacheLogger.warn('Failed to write fastcache', err);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function readFastCache(): Promise<FastCache> {
|
|
60
|
+
try {
|
|
61
|
+
return await readPack($FASTCACHEPATH());
|
|
62
|
+
} catch (err) {
|
|
63
|
+
FastcacheLogger.warn('Failed to read fastcache', err);
|
|
64
|
+
return {};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeArtist,
|
|
3
|
+
normalizeCharacter,
|
|
4
|
+
type ArtPiece,
|
|
5
|
+
type ResourceRef
|
|
6
|
+
} from '../util/art.ts';
|
|
7
|
+
|
|
8
|
+
function filterOne(piece: ArtPiece, by: ResourceRef, negated: boolean): boolean {
|
|
9
|
+
if (!by.type || !by.resource) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
switch (by.type) {
|
|
14
|
+
case 'artist': {
|
|
15
|
+
const artists = normalizeArtist(piece.artist);
|
|
16
|
+
let hasArtist = artists.some(
|
|
17
|
+
(artist) => artist.name.toLowerCase() === by.resource.name.toLowerCase()
|
|
18
|
+
);
|
|
19
|
+
if (negated) {
|
|
20
|
+
hasArtist = !hasArtist;
|
|
21
|
+
}
|
|
22
|
+
return hasArtist;
|
|
23
|
+
}
|
|
24
|
+
case 'character': {
|
|
25
|
+
const characters = normalizeCharacter(piece.characters);
|
|
26
|
+
let hasCharacter = characters.some(
|
|
27
|
+
(ch) =>
|
|
28
|
+
ch.name.toLowerCase() === by.resource.name.toLowerCase() &&
|
|
29
|
+
(ch.from?.toLowerCase() === by.resource.from?.toLowerCase() ||
|
|
30
|
+
(!ch.from && !by.resource.from))
|
|
31
|
+
);
|
|
32
|
+
if (negated) {
|
|
33
|
+
hasCharacter = !hasCharacter;
|
|
34
|
+
}
|
|
35
|
+
return hasCharacter;
|
|
36
|
+
}
|
|
37
|
+
case 'piece': {
|
|
38
|
+
let isPiece = by.resource.slug === piece.slug;
|
|
39
|
+
if (negated) {
|
|
40
|
+
isPiece = !isPiece;
|
|
41
|
+
}
|
|
42
|
+
return isPiece;
|
|
43
|
+
}
|
|
44
|
+
case 'tag': {
|
|
45
|
+
let hasTag = piece.tags.map((t) => t.toLowerCase()).includes(by.resource.toLowerCase());
|
|
46
|
+
if (negated) {
|
|
47
|
+
hasTag = !hasTag;
|
|
48
|
+
}
|
|
49
|
+
return hasTag;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function filter(
|
|
55
|
+
gallery: ArtPiece[] | Record<string, ArtPiece>,
|
|
56
|
+
by?: ResourceRef,
|
|
57
|
+
{ negated, sorted }: { negated?: boolean; sorted?: boolean } = {}
|
|
58
|
+
): ArtPiece[] {
|
|
59
|
+
if (!Array.isArray(gallery)) gallery = Object.values(gallery);
|
|
60
|
+
|
|
61
|
+
const results = by
|
|
62
|
+
? gallery.filter((piece) => {
|
|
63
|
+
return filterOne(piece, by, negated ?? false);
|
|
64
|
+
})
|
|
65
|
+
: gallery;
|
|
66
|
+
|
|
67
|
+
if (!sorted) {
|
|
68
|
+
return results;
|
|
69
|
+
}
|
|
70
|
+
return results.sort((a, b) => b.date.getTime() - a.date.getTime());
|
|
71
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { glob } from 'glob';
|
|
2
|
+
import * as yaml from 'yaml';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import * as fs from 'node:fs/promises';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { Logger } from 'tslog';
|
|
7
|
+
import {
|
|
8
|
+
BaseArtPiece,
|
|
9
|
+
BaseGallery,
|
|
10
|
+
FullArtPiece,
|
|
11
|
+
FullGallery,
|
|
12
|
+
RawGallery
|
|
13
|
+
} from './models/Gallery.ts';
|
|
14
|
+
import { $DATA } from './directories.ts';
|
|
15
|
+
import { processImageFastcache, processVideoFastcache } from './imageprocess.ts';
|
|
16
|
+
import {
|
|
17
|
+
cacheVersion,
|
|
18
|
+
clearCache,
|
|
19
|
+
getCache,
|
|
20
|
+
hash,
|
|
21
|
+
fullPath,
|
|
22
|
+
relPath,
|
|
23
|
+
getLogLevel
|
|
24
|
+
} from './util.ts';
|
|
25
|
+
import { flushFastCache, readFastCache, type FastCache } from './fastcache.ts';
|
|
26
|
+
import type { ArtPiece } from '../util/art.ts';
|
|
27
|
+
import { asRecord } from '../util/util.ts';
|
|
28
|
+
const GalleryLogger = new Logger({ minLevel: getLogLevel() });
|
|
29
|
+
|
|
30
|
+
type FileName = string;
|
|
31
|
+
type BaseGalleryT = z.infer<typeof BaseGallery>;
|
|
32
|
+
type RawGalleryT = z.infer<typeof RawGallery>;
|
|
33
|
+
type FullGalleryT = z.infer<typeof FullGallery>;
|
|
34
|
+
type GalleryListing<T> = Record<FileName, T>;
|
|
35
|
+
|
|
36
|
+
export type GalleryCache = GalleryListing<FullGalleryT>;
|
|
37
|
+
export type RawGalleryCache = GalleryListing<RawGalleryT>;
|
|
38
|
+
|
|
39
|
+
type BaseArtPieceT = z.infer<typeof BaseArtPiece>;
|
|
40
|
+
type FullArtPieceT = z.infer<typeof FullArtPiece>;
|
|
41
|
+
|
|
42
|
+
async function resolveExtendsRec(
|
|
43
|
+
fname: string,
|
|
44
|
+
gallery: RawGalleryT,
|
|
45
|
+
rec: Record<FileName, RawGalleryT>,
|
|
46
|
+
fc: FastCache
|
|
47
|
+
): Promise<FullGalleryT> {
|
|
48
|
+
if (!('$extends' in gallery)) {
|
|
49
|
+
return await resolveReferences(gallery, fname, fc);
|
|
50
|
+
}
|
|
51
|
+
const full: FullGalleryT = { pieces: [] };
|
|
52
|
+
|
|
53
|
+
for (const relative of gallery.$extends) {
|
|
54
|
+
const base = path.dirname(fname);
|
|
55
|
+
const target = path.join(base, relative);
|
|
56
|
+
if (rec[target]) {
|
|
57
|
+
const result = await resolveExtendsRec(target, rec[target], rec, fc);
|
|
58
|
+
if ('$extends' in result) {
|
|
59
|
+
throw new Error('Impossible');
|
|
60
|
+
}
|
|
61
|
+
full.pieces.push(...result.pieces);
|
|
62
|
+
} else {
|
|
63
|
+
GalleryLogger.warn('[GALLERY] Could not find target for', target, 'when parsing', fname);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return full;
|
|
68
|
+
}
|
|
69
|
+
async function resolveExtends(
|
|
70
|
+
rec: GalleryListing<RawGalleryT>,
|
|
71
|
+
fc: FastCache
|
|
72
|
+
): Promise<GalleryListing<FullGalleryT>> {
|
|
73
|
+
const listing: GalleryListing<FullGalleryT> = {};
|
|
74
|
+
for (const [fname, gallery] of Object.entries(rec)) {
|
|
75
|
+
if ('$extends' in gallery) {
|
|
76
|
+
listing[fname] = await resolveExtendsRec(fname, gallery, rec, fc);
|
|
77
|
+
} else {
|
|
78
|
+
listing[fname] = await resolveReferences(gallery, fname, fc);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return listing;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function resolvePieceReferences(
|
|
85
|
+
piece: BaseArtPieceT,
|
|
86
|
+
filename: FileName,
|
|
87
|
+
fc: FastCache
|
|
88
|
+
): Promise<FullArtPieceT> {
|
|
89
|
+
const relpath = relPath(filename, piece.image);
|
|
90
|
+
const fullpath = fullPath(filename, piece.image);
|
|
91
|
+
const image = await processImageFastcache(fc, fullpath, relpath, { position: piece.position });
|
|
92
|
+
|
|
93
|
+
const alts = await Promise.all(
|
|
94
|
+
piece.alts?.map(async (alt) => ({
|
|
95
|
+
...alt,
|
|
96
|
+
image: await processImageFastcache(
|
|
97
|
+
fc,
|
|
98
|
+
fullPath(filename, alt.image),
|
|
99
|
+
relPath(filename, alt.image),
|
|
100
|
+
{ position: piece.position }
|
|
101
|
+
),
|
|
102
|
+
video: alt.video
|
|
103
|
+
? {
|
|
104
|
+
full: await processVideoFastcache(
|
|
105
|
+
fc,
|
|
106
|
+
fullPath(filename, alt.video.full),
|
|
107
|
+
relPath(filename, alt.video.full),
|
|
108
|
+
'full'
|
|
109
|
+
),
|
|
110
|
+
thumb: await processVideoFastcache(
|
|
111
|
+
fc,
|
|
112
|
+
fullPath(filename, alt.video.thumb),
|
|
113
|
+
relPath(filename, alt.video.thumb),
|
|
114
|
+
'thumb'
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
: undefined
|
|
118
|
+
})) ?? []
|
|
119
|
+
);
|
|
120
|
+
const video = piece.video
|
|
121
|
+
? {
|
|
122
|
+
full: await processVideoFastcache(
|
|
123
|
+
fc,
|
|
124
|
+
fullPath(filename, piece.video.full),
|
|
125
|
+
relPath(filename, piece.video.full),
|
|
126
|
+
'full'
|
|
127
|
+
),
|
|
128
|
+
thumb: await processVideoFastcache(
|
|
129
|
+
fc,
|
|
130
|
+
fullPath(filename, piece.video.thumb),
|
|
131
|
+
relPath(filename, piece.video.thumb),
|
|
132
|
+
'thumb'
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
: undefined;
|
|
136
|
+
|
|
137
|
+
return { ...piece, image, alts, video };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function resolveReferences(
|
|
141
|
+
gallery: BaseGalleryT,
|
|
142
|
+
fname: FileName,
|
|
143
|
+
fc: FastCache
|
|
144
|
+
): Promise<FullGalleryT> {
|
|
145
|
+
return {
|
|
146
|
+
pieces: await Promise.all(
|
|
147
|
+
gallery.pieces.map((piece) => resolvePieceReferences(piece, fname, fc))
|
|
148
|
+
)
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function rawGalleries(): Promise<RawGalleryCache> {
|
|
153
|
+
const cached = getCache().rawGalleryCache;
|
|
154
|
+
const nextVersion = await cacheVersion();
|
|
155
|
+
if (cached.cache && cached.version === nextVersion) {
|
|
156
|
+
return cached.cache;
|
|
157
|
+
}
|
|
158
|
+
const allGalleries = await glob('./**/*.gallery', { cwd: $DATA() });
|
|
159
|
+
const documents = (
|
|
160
|
+
await Promise.all(
|
|
161
|
+
allGalleries.map((fn) =>
|
|
162
|
+
fs
|
|
163
|
+
.readFile(path.join($DATA(), fn), { encoding: 'utf-8' })
|
|
164
|
+
.then((text) => ({ filename: fn, text }))
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
.map(({ filename, text }) => ({ filename, obj: yaml.parse(text) }))
|
|
169
|
+
.map(({ filename, obj }) => {
|
|
170
|
+
GalleryLogger.silly('[GALLERY] Parsing', filename);
|
|
171
|
+
obj.pieces = obj.pieces
|
|
172
|
+
? obj.pieces.map((p: BaseArtPieceT) => ({ ...p, slug: p.id ?? hash(p) }))
|
|
173
|
+
: undefined;
|
|
174
|
+
return { filename, obj: RawGallery.parse(obj) };
|
|
175
|
+
})
|
|
176
|
+
.reduce<Record<FileName, RawGalleryT>>(
|
|
177
|
+
(rec, { filename, obj }) => ({ ...rec, [filename.replaceAll(path.sep, '/')]: obj }),
|
|
178
|
+
{}
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
getCache().rawGalleryCache.cache = documents;
|
|
182
|
+
getCache().rawGalleryCache.version = nextVersion;
|
|
183
|
+
return documents;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export async function galleries(doRetry: boolean = true) {
|
|
187
|
+
const cached = getCache().galleryCache;
|
|
188
|
+
const nextVersion = await cacheVersion();
|
|
189
|
+
if (cached.cache && cached.version === nextVersion) {
|
|
190
|
+
return cached.cache;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const documents = Object.fromEntries(
|
|
194
|
+
Object.entries(await rawGalleries())
|
|
195
|
+
.map(([path, g]) => {
|
|
196
|
+
if ('pieces' in g) {
|
|
197
|
+
g = {
|
|
198
|
+
pieces: g.pieces
|
|
199
|
+
.map((p) => {
|
|
200
|
+
if (p.deindexed) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
if (p.alts) {
|
|
204
|
+
p = { ...p, alts: p.alts.filter((a) => !a.deindexed) };
|
|
205
|
+
}
|
|
206
|
+
return p;
|
|
207
|
+
})
|
|
208
|
+
.filter(truthy)
|
|
209
|
+
};
|
|
210
|
+
if (g.pieces.length === 0) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
return [path, g];
|
|
214
|
+
} else {
|
|
215
|
+
if (g.$extends.length === 0) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
return [path, g];
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
.filter(truthy)
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
const fc = await readFastCache();
|
|
226
|
+
const out = await resolveExtends(documents, fc);
|
|
227
|
+
await flushFastCache(fc);
|
|
228
|
+
getCache().galleryCache.cache = out;
|
|
229
|
+
getCache().galleryCache.version = nextVersion;
|
|
230
|
+
return out;
|
|
231
|
+
} catch (e) {
|
|
232
|
+
if (doRetry) {
|
|
233
|
+
GalleryLogger.warn('Encountered error, clearing cache and trying again', e);
|
|
234
|
+
clearCache();
|
|
235
|
+
return await galleries(/* doRetry = */ false);
|
|
236
|
+
} else {
|
|
237
|
+
throw e;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function truthy<T>(t: T | null | undefined): t is T {
|
|
243
|
+
return !!t;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export async function allPieces(): Promise<Record<string, ArtPiece>> {
|
|
247
|
+
return asRecord(
|
|
248
|
+
Object.values(await galleries()).flatMap((p) => p.pieces),
|
|
249
|
+
(p) => p.slug
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export async function getPieceBySlug(slug: string): Promise<ArtPiece | null> {
|
|
254
|
+
return (await allPieces())[slug] ?? null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function hasNsfw(gallery: ArtPiece[]): boolean {
|
|
258
|
+
return gallery.some((p) => p.nsfw);
|
|
259
|
+
}
|