@phosart/common 0.4.41 → 0.4.43

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.
@@ -1,9 +1,10 @@
1
1
  <script lang="ts">
2
2
  import '@fortawesome/fontawesome-free/css/all.min.css';
3
3
 
4
- import type { ArtPiece } from '../util/art.js';
4
+ import type { ArtPiece, Picture } from '../util/art.js';
5
5
  import ImageView from './ImageView.svelte';
6
6
  import { useLibraryConfig } from '../util/phosart_config.svelte.js';
7
+ import Image from '../Image.svelte';
7
8
 
8
9
  interface Props {
9
10
  piece: ArtPiece;
@@ -22,22 +23,43 @@
22
23
  onprev();
23
24
  }
24
25
 
26
+ let infoHeight = $derived(
27
+ parseInt(getComputedStyle(document.documentElement).getPropertyValue('--info-height') ?? '100')
28
+ );
29
+
25
30
  let containerWidth: number = $state(0);
26
31
  let containerHeight: number = $state(0);
27
- let containerHeightLessInfo = $derived(Math.max(0, containerHeight - 50));
32
+ let containerHeightLessInfo = $derived(Math.max(0, containerHeight - infoHeight));
33
+ let isComic = $derived(piece.alts_display === 'comic_panels');
34
+ let bounded: HTMLDivElement | null = $state(null);
35
+
36
+ function scale(image: Picture) {
37
+ const scaleByHeight =
38
+ !isComic &&
39
+ containerWidth / containerHeightLessInfo > image.full.fallback.w / image.full.fallback.h;
40
+ const scalingFactor: number = scaleByHeight
41
+ ? containerHeightLessInfo / image.full.fallback.h
42
+ : containerWidth / image.full.fallback.w;
43
+
44
+ return scalingFactor;
45
+ }
28
46
 
29
- let scaleByHeight = $derived(
30
- containerWidth / containerHeightLessInfo >
31
- piece.image.full.fallback.w / piece.image.full.fallback.h
32
- );
33
- let scalingFactor: number = $derived(
34
- scaleByHeight
35
- ? containerHeightLessInfo / piece.image.full.fallback.h
36
- : containerWidth / piece.image.full.fallback.w
37
- );
47
+ function width(image: Picture) {
48
+ return image.full.fallback.w * scale(image);
49
+ }
50
+
51
+ function height(image: Picture) {
52
+ return image.full.fallback.h * scale(image);
53
+ }
38
54
 
39
- let w = $derived(piece.image.full.fallback.w * scalingFactor);
40
- let h = $derived(piece.image.full.fallback.h * scalingFactor + 50);
55
+ function scrollDown() {
56
+ if (!isComic) return;
57
+
58
+ bounded?.scrollBy({ behavior: 'smooth', top: containerHeight });
59
+ }
60
+
61
+ let w = $derived(width(piece.image));
62
+ let h = $derived(height(piece.image) + 50);
41
63
 
42
64
  let config = useLibraryConfig();
43
65
 
@@ -56,17 +78,65 @@
56
78
  ></div>
57
79
  </div>
58
80
 
59
- <div class="main-container">
60
- <div class="bounding-div" bind:clientHeight={containerHeight} bind:clientWidth={containerWidth}>
61
- <div
62
- class="bounded-div"
63
- onclick={(e) => e.stopPropagation()}
64
- onkeypress={(e) => e.stopPropagation()}
65
- role="button"
66
- tabindex={-1}
67
- style="width: {w}px; height: {h}px"
68
- >
69
- <ImageView {piece} {nameInHeader} />
81
+ <div class="main-container" style={isComic ? 'overflow: visible' : ''}>
82
+ <div
83
+ class="bounding-div"
84
+ style={isComic ? 'overflow-y: scroll; align-items: flex-start; z-index: 100' : ''}
85
+ bind:clientHeight={containerHeight}
86
+ bind:clientWidth={containerWidth}
87
+ bind:this={bounded}
88
+ >
89
+ <div class="flex flex-col">
90
+ <div
91
+ class="bounded-div"
92
+ onclick={(e) => {
93
+ e.stopPropagation();
94
+ scrollDown();
95
+ }}
96
+ onkeypress={(e) => {
97
+ e.stopPropagation();
98
+ scrollDown();
99
+ }}
100
+ role="button"
101
+ tabindex={-1}
102
+ style="width: {w}px; height: {h}px"
103
+ >
104
+ <ImageView {piece} {nameInHeader}>
105
+ {#snippet display(image, onloaded)}
106
+ <div class="image-container">
107
+ <Image
108
+ video={image.video?.full}
109
+ controls
110
+ picture={image.image.full}
111
+ alt={image.alt}
112
+ {onloaded}
113
+ />
114
+ </div>
115
+ {/snippet}
116
+ </ImageView>
117
+ </div>
118
+ {#if isComic && piece.alts}
119
+ {#each piece.alts as alt (JSON.stringify(alt))}
120
+ <div
121
+ class="bounded-div"
122
+ onclick={(e) => {
123
+ e.stopPropagation();
124
+ scrollDown();
125
+ }}
126
+ onkeypress={(e) => {
127
+ e.stopPropagation();
128
+ scrollDown();
129
+ }}
130
+ role="button"
131
+ tabindex={-1}
132
+ style="width: {width(alt.image)}px; height: {height(alt.image)}px"
133
+ >
134
+ <div class="image-container">
135
+ <Image video={alt.video?.full} controls picture={alt.image.full} alt={alt.alt} />
136
+ </div>
137
+ </div>
138
+ {/each}
139
+ {/if}
70
140
  </div>
71
141
  </div>
72
142
  </div>
@@ -84,16 +154,7 @@
84
154
  </div>
85
155
 
86
156
  <style>
87
- :global(:root) {
88
- --info-height: 50px;
89
- --carousel-height: 100px;
90
- }
91
-
92
157
  @media only screen and (max-width: 800px) {
93
- :global(:root) {
94
- --info-height: 50px;
95
- --carousel-height: 75px;
96
- }
97
158
  .nav-container.nav-container {
98
159
  width: 0;
99
160
  }
@@ -105,10 +166,10 @@
105
166
  justify-content: center;
106
167
  align-items: center;
107
168
  overflow: visible;
108
- max-height: calc(100% - 50px - var(--carousel-height));
169
+ max-height: calc(100% - var(--carousel-height));
109
170
  position: relative;
110
171
  height: 100%;
111
- padding: 2rem;
172
+ padding: 1rem;
112
173
  }
113
174
  .nav-container {
114
175
  color: white;
@@ -156,4 +217,12 @@
156
217
  display: flex;
157
218
  position: relative;
158
219
  }
220
+
221
+ .image-container {
222
+ position: absolute;
223
+ top: var(--info-height);
224
+ bottom: 0;
225
+ left: 0;
226
+ right: 0;
227
+ }
159
228
  </style>
@@ -1 +1 @@
1
- {"version":3,"file":"ImageSection.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/ModalGallery/ImageSection.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,+CAA+C,CAAC;AAEvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAK9C,UAAU,KAAK;IACd,KAAK,EAAE,QAAQ,CAAC;IAChB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,IAAI,CAAC;CACnB;AAgEF,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"ImageSection.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/ModalGallery/ImageSection.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,+CAA+C,CAAC;AAEvD,OAAO,KAAK,EAAE,QAAQ,EAAW,MAAM,gBAAgB,CAAC;AAMvD,UAAU,KAAK;IACd,KAAK,EAAE,QAAQ,CAAC;IAChB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,IAAI,CAAC;CACnB;AAmHF,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -1,20 +1,22 @@
1
1
  <script lang="ts">
2
2
  import { run } from 'svelte/legacy';
3
3
 
4
- import Image from '../Image.svelte';
5
-
6
4
  import Spinner from './Spinner.svelte';
7
5
  import Headline from '../Postcard/Headline.svelte';
8
6
  import Description from '../Postcard/Description.svelte';
9
7
  import type { ArtPiece } from '../util/art.js';
10
8
  import { useLibraryConfig } from '../util/phosart_config.svelte.js';
9
+ import type { Snippet } from 'svelte';
11
10
 
12
11
  interface Props {
13
12
  piece: ArtPiece;
14
13
  nameInHeader: boolean;
14
+ display: Snippet<
15
+ [image: ArtPiece | NonNullable<ArtPiece['alts']>[number], onloaded: () => void]
16
+ >;
15
17
  }
16
18
 
17
- let { piece, nameInHeader }: Props = $props();
19
+ let { piece, nameInHeader, display }: Props = $props();
18
20
 
19
21
  let config = useLibraryConfig();
20
22
 
@@ -32,20 +34,24 @@
32
34
 
33
35
  <Spinner {loading} />
34
36
 
35
- <div class="image-container">
36
- <Image
37
- video={image.video?.full}
38
- controls
39
- picture={image.image.full}
40
- alt={image.alt}
41
- onloaded={() => (loading = false)}
42
- />
43
- </div>
37
+ {@render display(image, () => (loading = false))}
44
38
 
45
- <div class="headline-container">
39
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
40
+ <div
41
+ class="headline-container"
42
+ onclick={(e) => e.stopPropagation()}
43
+ onkeypress={(e) => e.stopPropagation()}
44
+ role="contentinfo"
45
+ >
46
46
  <Headline {piece} bind:showingDescription showName={!config.modal?.hideNames && nameInHeader} />
47
47
  </div>
48
- <div class="description-container">
48
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
49
+ <div
50
+ class="description-container"
51
+ onclick={(e) => e.stopPropagation()}
52
+ onkeypress={(e) => e.stopPropagation()}
53
+ role="contentinfo"
54
+ >
49
55
  <Description
50
56
  {piece}
51
57
  bind:visible={showingDescription}
@@ -56,13 +62,6 @@
56
62
  </div>
57
63
 
58
64
  <style>
59
- .image-container {
60
- position: absolute;
61
- top: var(--info-height);
62
- bottom: 0;
63
- left: 0;
64
- right: 0;
65
- }
66
65
  .description-container {
67
66
  position: absolute;
68
67
  top: var(--info-height);
@@ -1,7 +1,12 @@
1
1
  import type { ArtPiece } from '../util/art.ts';
2
+ import type { Snippet } from 'svelte';
2
3
  interface Props {
3
4
  piece: ArtPiece;
4
5
  nameInHeader: boolean;
6
+ display: Snippet<[
7
+ image: ArtPiece | NonNullable<ArtPiece['alts']>[number],
8
+ onloaded: () => void
9
+ ]>;
5
10
  }
6
11
  declare const ImageView: import("svelte").Component<Props, {}, "">;
7
12
  type ImageView = ReturnType<typeof ImageView>;
@@ -1 +1 @@
1
- {"version":3,"file":"ImageView.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/ModalGallery/ImageView.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAI9C,UAAU,KAAK;IACd,KAAK,EAAE,QAAQ,CAAC;IAChB,YAAY,EAAE,OAAO,CAAC;CACtB;AA8CF,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"ImageView.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/ModalGallery/ImageView.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGrC,UAAU,KAAK;IACd,KAAK,EAAE,QAAQ,CAAC;IAChB,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,OAAO,CACf;QAAC,KAAK,EAAE,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;QAAE,QAAQ,EAAE,MAAM,IAAI;KAAC,CAC/E,CAAC;CACF;AA6CF,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -45,6 +45,10 @@
45
45
 
46
46
  const isGalleryOpen = $state(useIsModelGalleryOpen());
47
47
 
48
+ const currentIsComic = $derived(
49
+ typeof selected === 'number' ? pieces[selected]?.alts_display === 'comic_panels' : false
50
+ );
51
+
48
52
  onMount(() => {
49
53
  const handler = (e: KeyboardEvent) => {
50
54
  if (e.code === 'ArrowLeft' && selected !== null) {
@@ -57,16 +61,15 @@
57
61
  const hashchange = (hce: { oldURL?: string; newURL: string }) => {
58
62
  const hash = new URL(hce.newURL).hash.replace(/^#/, '');
59
63
  if (browser) {
60
- console.log('H ->', hash);
61
- const foundSelected = pieces.findIndex((piece) => piece.slug === hash);
64
+ const foundSelected = pieces.findIndex((piece) => piece.slug === decodeURIComponent(hash));
62
65
  if (!isAnyModelGalleryOpen()) {
63
66
  if (foundSelected !== -1) {
64
67
  selected = foundSelected;
65
68
  }
66
69
  } else if (isGalleryOpen.open) {
67
- if (!hash || hash == '#') {
70
+ if (!hash || hash == '#' || foundSelected === -1) {
68
71
  selected = null;
69
- } else if (foundSelected !== -1) {
72
+ } else {
70
73
  selected = foundSelected;
71
74
  }
72
75
  }
@@ -90,13 +93,12 @@
90
93
  if (isAnyModelGalleryOpen() === false && window.location.hash) {
91
94
  window.location.hash = '##';
92
95
  } else if (selected !== null && pieces[selected]) {
93
- console.log('H <-', pieces[selected].slug);
94
96
  window.location.hash = '#' + encodeURIComponent(pieces[selected].slug);
95
97
  }
96
98
  }
97
99
 
98
100
  $effect.pre(() => {
99
- isGalleryOpen.open = selected !== null;
101
+ isGalleryOpen.open = selected !== null && !!pieces[selected];
100
102
  });
101
103
  $effect.pre(() => {
102
104
  if (hashUpdateReady) {
@@ -105,8 +107,8 @@
105
107
  });
106
108
  </script>
107
109
 
108
- <Modal open={selected !== null} onclose={() => (selected = null)}>
109
- <div class="gallery-container">
110
+ <Modal open={selected !== null && !!pieces[selected]} onclose={() => (selected = null)}>
111
+ <div class="gallery-container" style={currentIsComic ? '--carousel-height: 25px' : ''}>
110
112
  <div
111
113
  role="button"
112
114
  tabindex={-1}
@@ -121,7 +123,7 @@
121
123
  ></div>
122
124
  <div style="flex-grow: 1"></div>
123
125
 
124
- {#if selected !== null}
126
+ {#if selected !== null && !!pieces[selected]}
125
127
  <HighResContext>
126
128
  <ImageSection
127
129
  piece={pieces[selected]}
@@ -139,6 +141,18 @@
139
141
  </Modal>
140
142
 
141
143
  <style>
144
+ @media only screen and (max-width: 800px) {
145
+ :global(:root) {
146
+ --info-height: 50px;
147
+ --carousel-height: 75px;
148
+ }
149
+ }
150
+
151
+ :global(:root) {
152
+ --info-height: 50px;
153
+ --carousel-height: 50px;
154
+ }
155
+
142
156
  .gallery-container {
143
157
  display: flex;
144
158
  flex-direction: column;
@@ -1 +1 @@
1
- {"version":3,"file":"ModalGallery.svelte.d.ts","sourceRoot":"","sources":["../src/lib/ModalGallery.svelte.ts"],"names":[],"mappings":"AAGC,OAAO,+CAA+C,CAAC;AAIvD,wBAAgB,qBAAqB;UAIlB,OAAO;EAOzB;AAED,wBAAgB,qBAAqB,YAEpC;AAGF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAQ7C,UAAU,KAAK;IACd,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;CACjB;AA4GF,QAAA,MAAM,YAAY,uEAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"ModalGallery.svelte.d.ts","sourceRoot":"","sources":["../src/lib/ModalGallery.svelte.ts"],"names":[],"mappings":"AAGC,OAAO,+CAA+C,CAAC;AAIvD,wBAAgB,qBAAqB;UAIlB,OAAO;EAOzB;AAED,wBAAgB,qBAAqB,YAEpC;AAGF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAQ7C,UAAU,KAAK;IACd,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;CACjB;AA8GF,QAAA,MAAM,YAAY,uEAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -52,9 +52,6 @@ export declare const builtinSettings: {
52
52
  readonly type: "selection";
53
53
  readonly options: ["static", "vercel"];
54
54
  };
55
- readonly supportsComics: {
56
- readonly type: "boolean";
57
- };
58
55
  };
59
56
  export type BuiltinSettings = typeof builtinSettings;
60
57
  type MaterializedOptionFor<T extends ThemeSettingsSchema[string]> = T extends z.infer<typeof ZColorOption> ? `#${string}` : T extends z.infer<typeof ZSelectionOption> ? [...T['options']] : T extends z.infer<typeof ZMultiSelectionOption> ? Array<[...T['options']]> : T extends z.infer<typeof ZStringList> ? Array<string> : T extends z.infer<typeof ZTagsOption> ? Array<string> : T extends z.infer<typeof ZStringOption> ? string : T extends z.infer<typeof ZBoolOption> ? boolean | undefined : never;
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/lib/server/theme/schema.ts"],"names":[],"mappings":"AAGA,OAAO,CAAC,MAAM,KAAK,CAAC;AAcpB,QAAA,MAAM,YAAY;;mBAAyC,CAAC;AAC5D,QAAA,MAAM,gBAAgB;;;;mBAIpB,CAAC;AACH,QAAA,MAAM,qBAAqB;;;;mBAIzB,CAAC;AACH,QAAA,MAAM,aAAa;;mBAA0C,CAAC;AAC9D,QAAA,MAAM,WAAW;;mBAA2C,CAAC;AAC7D,QAAA,MAAM,WAAW;;mBAA4C,CAAC;AAC9D,QAAA,MAAM,WAAW;;mBAA+C,CAAC;AACjE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;sBAWhC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAEvE,eAAO,MAAM,eAAe;;;;;;;;;;;CAIY,CAAC;AACzC,MAAM,MAAM,eAAe,GAAG,OAAO,eAAe,CAAC;AAErD,KAAK,qBAAqB,CAAC,CAAC,SAAS,mBAAmB,CAAC,MAAM,CAAC,IAC/D,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,GACnC,IAAI,MAAM,EAAE,GACZ,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,GACzC,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,GACjB,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,GAC9C,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GACxB,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,GACpC,KAAK,CAAC,MAAM,CAAC,GACb,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,GACpC,KAAK,CAAC,MAAM,CAAC,GACb,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,GACtC,MAAM,GACN,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,GACpC,OAAO,GAAG,SAAS,GACnB,KAAK,CAAC;AAEhB,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,mBAAmB,IAAI;KACvD,CAAC,IAAI,MAAM,CAAC,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC3C,CAAC;AAEF,wBAAsB,eAAe,CAAC,CAAC,SAAS,mBAAmB,KAAK,OAAO,CAAC,CAAC,CAAC,CAqBjF;AAyCD,wBAAgB,cAAc,CAAC,CAAC,SAAS,mBAAmB,EAC3D,MAAM,EAAE,CAAC,EACT,GAAG,EAAE,OAAO,GACV,GAAG,IAAI,WAAW,CAAC,CAAC,GAAG,eAAe,CAAC,CAEzC;AAoGD,wBAAsB,eAAe,CAAC,CAAC,SAAS,mBAAmB,EAClE,MAAM,EAAE,CAAC,GACP,OAAO,CAAC,WAAW,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC,CAoB3C;AAED,wBAAsB,gBAAgB,CAAC,CAAC,SAAS,mBAAmB,EACnE,MAAM,EAAE,CAAC,EACT,MAAM,EAAE,WAAW,CAAC,CAAC,GAAG,eAAe,CAAC,GACtC,OAAO,CAAC,IAAI,CAAC,CAIf"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/lib/server/theme/schema.ts"],"names":[],"mappings":"AAGA,OAAO,CAAC,MAAM,KAAK,CAAC;AAcpB,QAAA,MAAM,YAAY;;mBAAyC,CAAC;AAC5D,QAAA,MAAM,gBAAgB;;;;mBAIpB,CAAC;AACH,QAAA,MAAM,qBAAqB;;;;mBAIzB,CAAC;AACH,QAAA,MAAM,aAAa;;mBAA0C,CAAC;AAC9D,QAAA,MAAM,WAAW;;mBAA2C,CAAC;AAC7D,QAAA,MAAM,WAAW;;mBAA4C,CAAC;AAC9D,QAAA,MAAM,WAAW;;mBAA+C,CAAC;AACjE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;sBAWhC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAEvE,eAAO,MAAM,eAAe;;;;;;;;CAGY,CAAC;AACzC,MAAM,MAAM,eAAe,GAAG,OAAO,eAAe,CAAC;AAErD,KAAK,qBAAqB,CAAC,CAAC,SAAS,mBAAmB,CAAC,MAAM,CAAC,IAC/D,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,GACnC,IAAI,MAAM,EAAE,GACZ,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,GACzC,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,GACjB,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,GAC9C,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GACxB,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,GACpC,KAAK,CAAC,MAAM,CAAC,GACb,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,GACpC,KAAK,CAAC,MAAM,CAAC,GACb,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,GACtC,MAAM,GACN,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,GACpC,OAAO,GAAG,SAAS,GACnB,KAAK,CAAC;AAEhB,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,mBAAmB,IAAI;KACvD,CAAC,IAAI,MAAM,CAAC,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC3C,CAAC;AAEF,wBAAsB,eAAe,CAAC,CAAC,SAAS,mBAAmB,KAAK,OAAO,CAAC,CAAC,CAAC,CAqBjF;AAyCD,wBAAgB,cAAc,CAAC,CAAC,SAAS,mBAAmB,EAC3D,MAAM,EAAE,CAAC,EACT,GAAG,EAAE,OAAO,GACV,GAAG,IAAI,WAAW,CAAC,CAAC,GAAG,eAAe,CAAC,CAEzC;AAoGD,wBAAsB,eAAe,CAAC,CAAC,SAAS,mBAAmB,EAClE,MAAM,EAAE,CAAC,GACP,OAAO,CAAC,WAAW,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC,CAoB3C;AAED,wBAAsB,gBAAgB,CAAC,CAAC,SAAS,mBAAmB,EACnE,MAAM,EAAE,CAAC,EACT,MAAM,EAAE,WAAW,CAAC,CAAC,GAAG,eAAe,CAAC,GACtC,OAAO,CAAC,IAAI,CAAC,CAIf"}
@@ -39,8 +39,7 @@ export const ZThemeSettingsSchema = z.record(z.string(), z.union([
39
39
  ]));
40
40
  export const builtinSettings = {
41
41
  defaultArtist: { type: 'string' },
42
- websiteMode: { type: 'selection', options: ['static', 'vercel'] },
43
- supportsComics: { type: 'boolean' }
42
+ websiteMode: { type: 'selection', options: ['static', 'vercel'] }
44
43
  };
45
44
  export async function readThemeSchema() {
46
45
  let text;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phosart/common",
3
- "version": "0.4.41",
3
+ "version": "0.4.43",
4
4
  "files": [
5
5
  "dist",
6
6
  "!dist/**/*.test.*",
@@ -1,9 +1,10 @@
1
1
  <script lang="ts">
2
2
  import '@fortawesome/fontawesome-free/css/all.min.css';
3
3
 
4
- import type { ArtPiece } from '../util/art.ts';
4
+ import type { ArtPiece, Picture } from '../util/art.ts';
5
5
  import ImageView from './ImageView.svelte';
6
6
  import { useLibraryConfig } from '../util/phosart_config.svelte.ts';
7
+ import Image from '$lib/Image.svelte';
7
8
 
8
9
  interface Props {
9
10
  piece: ArtPiece;
@@ -22,22 +23,43 @@
22
23
  onprev();
23
24
  }
24
25
 
26
+ let infoHeight = $derived(
27
+ parseInt(getComputedStyle(document.documentElement).getPropertyValue('--info-height') ?? '100')
28
+ );
29
+
25
30
  let containerWidth: number = $state(0);
26
31
  let containerHeight: number = $state(0);
27
- let containerHeightLessInfo = $derived(Math.max(0, containerHeight - 50));
32
+ let containerHeightLessInfo = $derived(Math.max(0, containerHeight - infoHeight));
33
+ let isComic = $derived(piece.alts_display === 'comic_panels');
34
+ let bounded: HTMLDivElement | null = $state(null);
35
+
36
+ function scale(image: Picture) {
37
+ const scaleByHeight =
38
+ !isComic &&
39
+ containerWidth / containerHeightLessInfo > image.full.fallback.w / image.full.fallback.h;
40
+ const scalingFactor: number = scaleByHeight
41
+ ? containerHeightLessInfo / image.full.fallback.h
42
+ : containerWidth / image.full.fallback.w;
43
+
44
+ return scalingFactor;
45
+ }
28
46
 
29
- let scaleByHeight = $derived(
30
- containerWidth / containerHeightLessInfo >
31
- piece.image.full.fallback.w / piece.image.full.fallback.h
32
- );
33
- let scalingFactor: number = $derived(
34
- scaleByHeight
35
- ? containerHeightLessInfo / piece.image.full.fallback.h
36
- : containerWidth / piece.image.full.fallback.w
37
- );
47
+ function width(image: Picture) {
48
+ return image.full.fallback.w * scale(image);
49
+ }
50
+
51
+ function height(image: Picture) {
52
+ return image.full.fallback.h * scale(image);
53
+ }
38
54
 
39
- let w = $derived(piece.image.full.fallback.w * scalingFactor);
40
- let h = $derived(piece.image.full.fallback.h * scalingFactor + 50);
55
+ function scrollDown() {
56
+ if (!isComic) return;
57
+
58
+ bounded?.scrollBy({ behavior: 'smooth', top: containerHeight });
59
+ }
60
+
61
+ let w = $derived(width(piece.image));
62
+ let h = $derived(height(piece.image) + 50);
41
63
 
42
64
  let config = useLibraryConfig();
43
65
 
@@ -56,17 +78,65 @@
56
78
  ></div>
57
79
  </div>
58
80
 
59
- <div class="main-container">
60
- <div class="bounding-div" bind:clientHeight={containerHeight} bind:clientWidth={containerWidth}>
61
- <div
62
- class="bounded-div"
63
- onclick={(e) => e.stopPropagation()}
64
- onkeypress={(e) => e.stopPropagation()}
65
- role="button"
66
- tabindex={-1}
67
- style="width: {w}px; height: {h}px"
68
- >
69
- <ImageView {piece} {nameInHeader} />
81
+ <div class="main-container" style={isComic ? 'overflow: visible' : ''}>
82
+ <div
83
+ class="bounding-div"
84
+ style={isComic ? 'overflow-y: scroll; align-items: flex-start; z-index: 100' : ''}
85
+ bind:clientHeight={containerHeight}
86
+ bind:clientWidth={containerWidth}
87
+ bind:this={bounded}
88
+ >
89
+ <div class="flex flex-col">
90
+ <div
91
+ class="bounded-div"
92
+ onclick={(e) => {
93
+ e.stopPropagation();
94
+ scrollDown();
95
+ }}
96
+ onkeypress={(e) => {
97
+ e.stopPropagation();
98
+ scrollDown();
99
+ }}
100
+ role="button"
101
+ tabindex={-1}
102
+ style="width: {w}px; height: {h}px"
103
+ >
104
+ <ImageView {piece} {nameInHeader}>
105
+ {#snippet display(image, onloaded)}
106
+ <div class="image-container">
107
+ <Image
108
+ video={image.video?.full}
109
+ controls
110
+ picture={image.image.full}
111
+ alt={image.alt}
112
+ {onloaded}
113
+ />
114
+ </div>
115
+ {/snippet}
116
+ </ImageView>
117
+ </div>
118
+ {#if isComic && piece.alts}
119
+ {#each piece.alts as alt (JSON.stringify(alt))}
120
+ <div
121
+ class="bounded-div"
122
+ onclick={(e) => {
123
+ e.stopPropagation();
124
+ scrollDown();
125
+ }}
126
+ onkeypress={(e) => {
127
+ e.stopPropagation();
128
+ scrollDown();
129
+ }}
130
+ role="button"
131
+ tabindex={-1}
132
+ style="width: {width(alt.image)}px; height: {height(alt.image)}px"
133
+ >
134
+ <div class="image-container">
135
+ <Image video={alt.video?.full} controls picture={alt.image.full} alt={alt.alt} />
136
+ </div>
137
+ </div>
138
+ {/each}
139
+ {/if}
70
140
  </div>
71
141
  </div>
72
142
  </div>
@@ -84,16 +154,7 @@
84
154
  </div>
85
155
 
86
156
  <style>
87
- :global(:root) {
88
- --info-height: 50px;
89
- --carousel-height: 100px;
90
- }
91
-
92
157
  @media only screen and (max-width: 800px) {
93
- :global(:root) {
94
- --info-height: 50px;
95
- --carousel-height: 75px;
96
- }
97
158
  .nav-container.nav-container {
98
159
  width: 0;
99
160
  }
@@ -105,10 +166,10 @@
105
166
  justify-content: center;
106
167
  align-items: center;
107
168
  overflow: visible;
108
- max-height: calc(100% - 50px - var(--carousel-height));
169
+ max-height: calc(100% - var(--carousel-height));
109
170
  position: relative;
110
171
  height: 100%;
111
- padding: 2rem;
172
+ padding: 1rem;
112
173
  }
113
174
  .nav-container {
114
175
  color: white;
@@ -156,4 +217,12 @@
156
217
  display: flex;
157
218
  position: relative;
158
219
  }
220
+
221
+ .image-container {
222
+ position: absolute;
223
+ top: var(--info-height);
224
+ bottom: 0;
225
+ left: 0;
226
+ right: 0;
227
+ }
159
228
  </style>
@@ -1,20 +1,22 @@
1
1
  <script lang="ts">
2
2
  import { run } from 'svelte/legacy';
3
3
 
4
- import Image from '../Image.svelte';
5
-
6
4
  import Spinner from './Spinner.svelte';
7
5
  import Headline from '../Postcard/Headline.svelte';
8
6
  import Description from '../Postcard/Description.svelte';
9
7
  import type { ArtPiece } from '../util/art.ts';
10
8
  import { useLibraryConfig } from '../util/phosart_config.svelte.ts';
9
+ import type { Snippet } from 'svelte';
11
10
 
12
11
  interface Props {
13
12
  piece: ArtPiece;
14
13
  nameInHeader: boolean;
14
+ display: Snippet<
15
+ [image: ArtPiece | NonNullable<ArtPiece['alts']>[number], onloaded: () => void]
16
+ >;
15
17
  }
16
18
 
17
- let { piece, nameInHeader }: Props = $props();
19
+ let { piece, nameInHeader, display }: Props = $props();
18
20
 
19
21
  let config = useLibraryConfig();
20
22
 
@@ -32,20 +34,24 @@
32
34
 
33
35
  <Spinner {loading} />
34
36
 
35
- <div class="image-container">
36
- <Image
37
- video={image.video?.full}
38
- controls
39
- picture={image.image.full}
40
- alt={image.alt}
41
- onloaded={() => (loading = false)}
42
- />
43
- </div>
37
+ {@render display(image, () => (loading = false))}
44
38
 
45
- <div class="headline-container">
39
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
40
+ <div
41
+ class="headline-container"
42
+ onclick={(e) => e.stopPropagation()}
43
+ onkeypress={(e) => e.stopPropagation()}
44
+ role="contentinfo"
45
+ >
46
46
  <Headline {piece} bind:showingDescription showName={!config.modal?.hideNames && nameInHeader} />
47
47
  </div>
48
- <div class="description-container">
48
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
49
+ <div
50
+ class="description-container"
51
+ onclick={(e) => e.stopPropagation()}
52
+ onkeypress={(e) => e.stopPropagation()}
53
+ role="contentinfo"
54
+ >
49
55
  <Description
50
56
  {piece}
51
57
  bind:visible={showingDescription}
@@ -56,13 +62,6 @@
56
62
  </div>
57
63
 
58
64
  <style lang="postcss">
59
- .image-container {
60
- position: absolute;
61
- top: var(--info-height);
62
- bottom: 0;
63
- left: 0;
64
- right: 0;
65
- }
66
65
  .description-container {
67
66
  position: absolute;
68
67
  top: var(--info-height);
@@ -45,6 +45,10 @@
45
45
 
46
46
  const isGalleryOpen = $state(useIsModelGalleryOpen());
47
47
 
48
+ const currentIsComic = $derived(
49
+ typeof selected === 'number' ? pieces[selected]?.alts_display === 'comic_panels' : false
50
+ );
51
+
48
52
  onMount(() => {
49
53
  const handler = (e: KeyboardEvent) => {
50
54
  if (e.code === 'ArrowLeft' && selected !== null) {
@@ -57,16 +61,15 @@
57
61
  const hashchange = (hce: { oldURL?: string; newURL: string }) => {
58
62
  const hash = new URL(hce.newURL).hash.replace(/^#/, '');
59
63
  if (browser) {
60
- console.log('H ->', hash);
61
- const foundSelected = pieces.findIndex((piece) => piece.slug === hash);
64
+ const foundSelected = pieces.findIndex((piece) => piece.slug === decodeURIComponent(hash));
62
65
  if (!isAnyModelGalleryOpen()) {
63
66
  if (foundSelected !== -1) {
64
67
  selected = foundSelected;
65
68
  }
66
69
  } else if (isGalleryOpen.open) {
67
- if (!hash || hash == '#') {
70
+ if (!hash || hash == '#' || foundSelected === -1) {
68
71
  selected = null;
69
- } else if (foundSelected !== -1) {
72
+ } else {
70
73
  selected = foundSelected;
71
74
  }
72
75
  }
@@ -90,13 +93,12 @@
90
93
  if (isAnyModelGalleryOpen() === false && window.location.hash) {
91
94
  window.location.hash = '##';
92
95
  } else if (selected !== null && pieces[selected]) {
93
- console.log('H <-', pieces[selected].slug);
94
96
  window.location.hash = '#' + encodeURIComponent(pieces[selected].slug);
95
97
  }
96
98
  }
97
99
 
98
100
  $effect.pre(() => {
99
- isGalleryOpen.open = selected !== null;
101
+ isGalleryOpen.open = selected !== null && !!pieces[selected];
100
102
  });
101
103
  $effect.pre(() => {
102
104
  if (hashUpdateReady) {
@@ -105,8 +107,8 @@
105
107
  });
106
108
  </script>
107
109
 
108
- <Modal open={selected !== null} onclose={() => (selected = null)}>
109
- <div class="gallery-container">
110
+ <Modal open={selected !== null && !!pieces[selected]} onclose={() => (selected = null)}>
111
+ <div class="gallery-container" style={currentIsComic ? '--carousel-height: 25px' : ''}>
110
112
  <div
111
113
  role="button"
112
114
  tabindex={-1}
@@ -121,7 +123,7 @@
121
123
  ></div>
122
124
  <div style="flex-grow: 1"></div>
123
125
 
124
- {#if selected !== null}
126
+ {#if selected !== null && !!pieces[selected]}
125
127
  <HighResContext>
126
128
  <ImageSection
127
129
  piece={pieces[selected]}
@@ -139,6 +141,18 @@
139
141
  </Modal>
140
142
 
141
143
  <style>
144
+ @media only screen and (max-width: 800px) {
145
+ :global(:root) {
146
+ --info-height: 50px;
147
+ --carousel-height: 75px;
148
+ }
149
+ }
150
+
151
+ :global(:root) {
152
+ --info-height: 50px;
153
+ --carousel-height: 50px;
154
+ }
155
+
142
156
  .gallery-container {
143
157
  display: flex;
144
158
  flex-direction: column;
@@ -47,8 +47,7 @@ export type ThemeSettingsSchema = z.infer<typeof ZThemeSettingsSchema>;
47
47
 
48
48
  export const builtinSettings = {
49
49
  defaultArtist: { type: 'string' },
50
- websiteMode: { type: 'selection', options: ['static', 'vercel'] },
51
- supportsComics: { type: 'boolean' }
50
+ websiteMode: { type: 'selection', options: ['static', 'vercel'] }
52
51
  } as const satisfies ThemeSettingsSchema;
53
52
  export type BuiltinSettings = typeof builtinSettings;
54
53