@things-factory/gallery 10.1.248 → 10.1.250

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.
Files changed (75) hide show
  1. package/dist-client/bootstrap.d.ts +2 -0
  2. package/dist-client/bootstrap.js +26 -0
  3. package/dist-client/bootstrap.js.map +1 -0
  4. package/dist-client/components/gallery-forms.d.ts +31 -0
  5. package/dist-client/components/gallery-forms.js +223 -0
  6. package/dist-client/components/gallery-forms.js.map +1 -0
  7. package/dist-client/components/gallery-saver-setting-let.d.ts +11 -0
  8. package/dist-client/components/gallery-saver-setting-let.js +81 -0
  9. package/dist-client/components/gallery-saver-setting-let.js.map +1 -0
  10. package/dist-client/components/gallery-show.d.ts +46 -0
  11. package/dist-client/components/gallery-show.js +316 -0
  12. package/dist-client/components/gallery-show.js.map +1 -0
  13. package/dist-client/components/gallery-uploader.d.ts +57 -0
  14. package/dist-client/components/gallery-uploader.js +624 -0
  15. package/dist-client/components/gallery-uploader.js.map +1 -0
  16. package/dist-client/components/gallery-viewer.d.ts +30 -0
  17. package/dist-client/components/gallery-viewer.js +429 -0
  18. package/dist-client/components/gallery-viewer.js.map +1 -0
  19. package/dist-client/index.d.ts +1 -0
  20. package/dist-client/index.js +2 -0
  21. package/dist-client/index.js.map +1 -0
  22. package/dist-client/pages/gallery-page.d.ts +54 -0
  23. package/dist-client/pages/gallery-page.js +590 -0
  24. package/dist-client/pages/gallery-page.js.map +1 -0
  25. package/dist-client/route.d.ts +1 -0
  26. package/dist-client/route.js +9 -0
  27. package/dist-client/route.js.map +1 -0
  28. package/dist-client/tsconfig.tsbuildinfo +1 -0
  29. package/dist-client/utils/gallery-api.d.ts +99 -0
  30. package/dist-client/utils/gallery-api.js +46 -0
  31. package/dist-client/utils/gallery-api.js.map +1 -0
  32. package/dist-client/utils/gallery-badge.d.ts +4 -0
  33. package/dist-client/utils/gallery-badge.js +45 -0
  34. package/dist-client/utils/gallery-badge.js.map +1 -0
  35. package/dist-client/utils/gallery-saver.d.ts +6 -0
  36. package/dist-client/utils/gallery-saver.js +69 -0
  37. package/dist-client/utils/gallery-saver.js.map +1 -0
  38. package/dist-client/utils/gallery-words.d.ts +7 -0
  39. package/dist-client/utils/gallery-words.js +20 -0
  40. package/dist-client/utils/gallery-words.js.map +1 -0
  41. package/dist-client/utils/photo-prep.d.ts +10 -0
  42. package/dist-client/utils/photo-prep.js +102 -0
  43. package/dist-client/utils/photo-prep.js.map +1 -0
  44. package/dist-server/attachment-reader.d.ts +1 -0
  45. package/dist-server/attachment-reader.js +13 -0
  46. package/dist-server/attachment-reader.js.map +1 -0
  47. package/dist-server/index.d.ts +1 -0
  48. package/dist-server/index.js +11 -0
  49. package/dist-server/index.js.map +1 -0
  50. package/dist-server/role-tasks.d.ts +2 -0
  51. package/dist-server/role-tasks.js +15 -0
  52. package/dist-server/role-tasks.js.map +1 -0
  53. package/dist-server/service/gallery/gallery-resolver.d.ts +29 -0
  54. package/dist-server/service/gallery/gallery-resolver.js +613 -0
  55. package/dist-server/service/gallery/gallery-resolver.js.map +1 -0
  56. package/dist-server/service/gallery/gallery-rules.d.ts +90 -0
  57. package/dist-server/service/gallery/gallery-rules.js +182 -0
  58. package/dist-server/service/gallery/gallery-rules.js.map +1 -0
  59. package/dist-server/service/gallery/gallery-service.d.ts +37 -0
  60. package/dist-server/service/gallery/gallery-service.js +234 -0
  61. package/dist-server/service/gallery/gallery-service.js.map +1 -0
  62. package/dist-server/service/gallery/gallery-types.d.ts +65 -0
  63. package/dist-server/service/gallery/gallery-types.js +239 -0
  64. package/dist-server/service/gallery/gallery-types.js.map +1 -0
  65. package/dist-server/service/gallery/gallery.d.ts +84 -0
  66. package/dist-server/service/gallery/gallery.js +335 -0
  67. package/dist-server/service/gallery/gallery.js.map +1 -0
  68. package/dist-server/service/gallery/index.d.ts +4 -0
  69. package/dist-server/service/gallery/index.js +8 -0
  70. package/dist-server/service/gallery/index.js.map +1 -0
  71. package/dist-server/service/index.d.ts +7 -0
  72. package/dist-server/service/index.js +23 -0
  73. package/dist-server/service/index.js.map +1 -0
  74. package/dist-server/tsconfig.tsbuildinfo +1 -0
  75. package/package.json +8 -8
@@ -0,0 +1,316 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ /*
3
+ * The gallery's photos across the whole window, one at a time (ADR-0165 decision 9): seen large from a post, played as
4
+ * a slideshow from the gallery, and the screen saver after the person has been away. One show for the three - what
5
+ * differs is where it starts, whether it plays, and whether anything the person does closes it (the saver).
6
+ */
7
+ import '@material/web/icon/icon.js';
8
+ import { css, html, LitElement, nothing } from 'lit';
9
+ import { customElement, property, state } from 'lit/decorators.js';
10
+ import { i18next, localize } from '@operato/i18n';
11
+ import { galleryApi } from '../utils/gallery-api.js';
12
+ import { day, t } from '../utils/gallery-words.js';
13
+ /** How long one photo stays while it plays. */
14
+ const DWELL = 7000;
15
+ /** The most photos one show walks through - pages of the gallery's own size are asked until this. */
16
+ const MOST = 300;
17
+ const PAGE = 60;
18
+ let GalleryShow = class GalleryShow extends localize(i18next)(LitElement) {
19
+ constructor() {
20
+ super(...arguments);
21
+ this.saver = false;
22
+ this.frames = [];
23
+ this.at = 0;
24
+ this.playing = false;
25
+ this.loaded = false;
26
+ this.wholeScreen = false;
27
+ this.disposed = false;
28
+ this.onKey = (e) => {
29
+ if (this.saver)
30
+ return;
31
+ const handled = {
32
+ ArrowLeft: () => this.step(-1),
33
+ ArrowRight: () => this.step(1),
34
+ ' ': () => this.play(!this.playing),
35
+ Escape: () => this.close()
36
+ };
37
+ const act = handled[e.key];
38
+ if (!act)
39
+ return;
40
+ e.preventDefault();
41
+ e.stopPropagation();
42
+ act();
43
+ };
44
+ /** Leaving the whole screen (Esc there is the browser's) leaves the show too. */
45
+ this.onFullscreen = () => {
46
+ if (this.wholeScreen && !document.fullscreenElement)
47
+ this.close();
48
+ };
49
+ this.onWake = (e) => {
50
+ /* The pointer resting on the screen still says it moved, by a pixel or so - a real move is farther. */
51
+ if (e.type === 'pointermove' && Math.abs(e.movementX) + Math.abs(e.movementY) < 4)
52
+ return;
53
+ e.preventDefault();
54
+ e.stopPropagation();
55
+ this.close();
56
+ };
57
+ }
58
+ static { this.styles = css `
59
+ :host {
60
+ position: fixed;
61
+ inset: 0;
62
+ z-index: 10000;
63
+ display: block;
64
+ background: #000;
65
+ color: #fff;
66
+ user-select: none;
67
+ }
68
+ :host([saver]) {
69
+ cursor: none;
70
+ }
71
+ img {
72
+ position: absolute;
73
+ inset: 0;
74
+ width: 100%;
75
+ height: 100%;
76
+ object-fit: contain;
77
+ animation: appear 800ms ease-out;
78
+ }
79
+ @keyframes appear {
80
+ from {
81
+ opacity: 0;
82
+ }
83
+ to {
84
+ opacity: 1;
85
+ }
86
+ }
87
+ .about {
88
+ position: absolute;
89
+ inset-inline: 0;
90
+ bottom: 0;
91
+ padding: 48px 24px 20px;
92
+ background: linear-gradient(transparent, rgb(0 0 0 / 60%));
93
+ font-size: 14px;
94
+ line-height: 1.5;
95
+ }
96
+ .about .caption {
97
+ display: -webkit-box;
98
+ -webkit-line-clamp: 2;
99
+ -webkit-box-orient: vertical;
100
+ overflow: hidden;
101
+ white-space: pre-wrap;
102
+ font-size: 16px;
103
+ }
104
+ .about .who {
105
+ opacity: 0.75;
106
+ }
107
+ .bar {
108
+ position: absolute;
109
+ top: 12px;
110
+ inset-inline-end: 12px;
111
+ display: flex;
112
+ align-items: center;
113
+ gap: 8px;
114
+ }
115
+ .bar .count {
116
+ padding: 0 8px;
117
+ font-size: 13px;
118
+ opacity: 0.8;
119
+ }
120
+ button {
121
+ display: inline-flex;
122
+ padding: 8px;
123
+ border: 0;
124
+ border-radius: 50%;
125
+ background: rgb(0 0 0 / 45%);
126
+ color: #fff;
127
+ cursor: pointer;
128
+ }
129
+ button:hover {
130
+ background: rgb(255 255 255 / 20%);
131
+ }
132
+ .turn {
133
+ position: absolute;
134
+ top: 50%;
135
+ transform: translateY(-50%);
136
+ }
137
+ .previous {
138
+ inset-inline-start: 12px;
139
+ }
140
+ .next {
141
+ inset-inline-end: 12px;
142
+ }
143
+ .empty {
144
+ position: absolute;
145
+ inset: 0;
146
+ display: flex;
147
+ align-items: center;
148
+ justify-content: center;
149
+ opacity: 0.8;
150
+ }
151
+ `; }
152
+ /** Opens a show over the whole window, and the whole screen when the browser lets it. */
153
+ static open(options) {
154
+ const show = document.createElement('gallery-show');
155
+ show.options = options;
156
+ document.body.appendChild(show);
157
+ return show;
158
+ }
159
+ render() {
160
+ const frame = this.frames[this.at];
161
+ if (!frame)
162
+ return this.loaded
163
+ ? html `<div class="empty">${t('gallery.text.show-empty')}</div>${this.saver ? nothing : this.renderBar()}`
164
+ : nothing;
165
+ const { post, photo } = frame;
166
+ return html `
167
+ ${ /* A new element for each photo, so it fades in. */[photo.id].map(() => html `<img src=${photo.fullpath} alt=${post.caption ?? ''} />`)}
168
+ <div class="about">
169
+ ${post.caption ? html `<div class="caption">${post.caption}</div>` : nothing}
170
+ <div class="who">${post.creator?.name ?? ''} · ${day(post.takenAt)}${post.event ? ` · ${post.event.name}` : ''}</div>
171
+ </div>
172
+ ${this.saver
173
+ ? nothing
174
+ : html `${this.renderBar()}
175
+ ${this.frames.length > 1
176
+ ? html `<button class="turn previous" aria-label=${t('gallery.button.previous')} @click=${() => this.step(-1)}>
177
+ <md-icon>chevron_left</md-icon>
178
+ </button>
179
+ <button class="turn next" aria-label=${t('gallery.button.next')} @click=${() => this.step(1)}>
180
+ <md-icon>chevron_right</md-icon>
181
+ </button>`
182
+ : nothing}`}
183
+ `;
184
+ }
185
+ renderBar() {
186
+ return html `<div class="bar">
187
+ ${this.frames.length ? html `<span class="count">${this.at + 1} / ${this.frames.length}</span>` : nothing}
188
+ ${this.frames.length > 1
189
+ ? html `<button
190
+ aria-label=${t(this.playing ? 'gallery.button.pause' : 'gallery.button.play')}
191
+ title=${t(this.playing ? 'gallery.button.pause' : 'gallery.button.play')}
192
+ @click=${() => this.play(!this.playing)}
193
+ >
194
+ <md-icon>${this.playing ? 'pause' : 'play_arrow'}</md-icon>
195
+ </button>`
196
+ : nothing}
197
+ <button aria-label=${t('gallery.button.close')} title=${t('gallery.button.close')} @click=${() => this.close()}>
198
+ <md-icon>close</md-icon>
199
+ </button>
200
+ </div>`;
201
+ }
202
+ connectedCallback() {
203
+ super.connectedCallback();
204
+ this.saver = !!this.options?.saver;
205
+ window.addEventListener('keydown', this.onKey, true);
206
+ document.addEventListener('fullscreenchange', this.onFullscreen);
207
+ if (this.saver)
208
+ for (const name of WAKE)
209
+ window.addEventListener(name, this.onWake, true);
210
+ /* The whole screen needs the press that opened it - the saver has none, and covers the window only. */
211
+ if (!this.saver && this.requestFullscreen)
212
+ this.requestFullscreen()
213
+ .then(() => (this.wholeScreen = true))
214
+ .catch(() => { });
215
+ this.load();
216
+ }
217
+ disconnectedCallback() {
218
+ super.disconnectedCallback();
219
+ this.disposed = true;
220
+ clearTimeout(this.timer);
221
+ window.removeEventListener('keydown', this.onKey, true);
222
+ document.removeEventListener('fullscreenchange', this.onFullscreen);
223
+ for (const name of WAKE)
224
+ window.removeEventListener(name, this.onWake, true);
225
+ }
226
+ async load() {
227
+ const { filter, start, saver } = this.options;
228
+ const frames = [];
229
+ try {
230
+ for (let skip = 0; frames.length < MOST; skip += PAGE) {
231
+ const page = await galleryApi.posts(filter, skip, PAGE);
232
+ for (const post of page.items)
233
+ if (!post.hiddenAt)
234
+ for (const photo of post.photos)
235
+ frames.push({ post, photo });
236
+ if (this.disposed || skip + PAGE >= page.total)
237
+ break;
238
+ }
239
+ }
240
+ catch {
241
+ /* The show has what came; a saver with nothing closes itself below. */
242
+ }
243
+ if (this.disposed)
244
+ return;
245
+ if (saver)
246
+ shuffle(frames);
247
+ this.frames = frames.slice(0, MOST);
248
+ const found = start ? this.frames.findIndex(one => one.post.id === start.postId) : -1;
249
+ this.at = found < 0 ? 0 : Math.min(found + start.index, this.frames.length - 1);
250
+ this.loaded = true;
251
+ if (saver && !this.frames.length)
252
+ return this.close();
253
+ this.play(!!this.options.playing || !!saver);
254
+ }
255
+ play(on) {
256
+ this.playing = on;
257
+ this.schedule();
258
+ }
259
+ /** The next photo after the dwell, asked ahead so it is there when it turns. */
260
+ schedule() {
261
+ clearTimeout(this.timer);
262
+ if (!this.playing || this.frames.length < 2)
263
+ return;
264
+ const next = this.frames[(this.at + 1) % this.frames.length];
265
+ new Image().src = next.photo.fullpath;
266
+ this.timer = setTimeout(() => this.step(1), DWELL);
267
+ }
268
+ step(by) {
269
+ const count = this.frames.length;
270
+ if (!count)
271
+ return;
272
+ this.at = (this.at + by + count) % count;
273
+ this.schedule();
274
+ }
275
+ close() {
276
+ if (this.disposed)
277
+ return;
278
+ if (this.wholeScreen && document.fullscreenElement)
279
+ document.exitFullscreen().catch(() => { });
280
+ this.remove();
281
+ this.dispatchEvent(new CustomEvent('closed'));
282
+ }
283
+ };
284
+ __decorate([
285
+ property({ type: Boolean, reflect: true }),
286
+ __metadata("design:type", Object)
287
+ ], GalleryShow.prototype, "saver", void 0);
288
+ __decorate([
289
+ state(),
290
+ __metadata("design:type", Array)
291
+ ], GalleryShow.prototype, "frames", void 0);
292
+ __decorate([
293
+ state(),
294
+ __metadata("design:type", Object)
295
+ ], GalleryShow.prototype, "at", void 0);
296
+ __decorate([
297
+ state(),
298
+ __metadata("design:type", Object)
299
+ ], GalleryShow.prototype, "playing", void 0);
300
+ __decorate([
301
+ state(),
302
+ __metadata("design:type", Object)
303
+ ], GalleryShow.prototype, "loaded", void 0);
304
+ GalleryShow = __decorate([
305
+ customElement('gallery-show')
306
+ ], GalleryShow);
307
+ export { GalleryShow };
308
+ /** What wakes the screen from the saver. */
309
+ const WAKE = ['pointermove', 'pointerdown', 'keydown', 'wheel', 'touchstart'];
310
+ function shuffle(list) {
311
+ for (let i = list.length - 1; i > 0; i--) {
312
+ const j = Math.floor(Math.random() * (i + 1));
313
+ [list[i], list[j]] = [list[j], list[i]];
314
+ }
315
+ }
316
+ //# sourceMappingURL=gallery-show.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gallery-show.js","sourceRoot":"","sources":["../../client/components/gallery-show.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The gallery's photos across the whole window, one at a time (ADR-0165 decision 9): seen large from a post, played as\n * a slideshow from the gallery, and the screen saver after the person has been away. One show for the three - what\n * differs is where it starts, whether it plays, and whether anything the person does closes it (the saver).\n */\nimport '@material/web/icon/icon.js'\n\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\n\nimport { galleryApi, type Filter, type Photo, type Post } from '../utils/gallery-api.js'\nimport { day, t } from '../utils/gallery-words.js'\n\n/** How long one photo stays while it plays. */\nconst DWELL = 7000\n/** The most photos one show walks through - pages of the gallery's own size are asked until this. */\nconst MOST = 300\nconst PAGE = 60\n\ntype Frame = { post: Post; photo: Photo }\n\nexport type ShowOptions = {\n /** The posts the show walks through - the gallery's narrowing where it was opened. */\n filter: Filter\n /** Where it starts: a post and the photo in it. Without it, the first photo. */\n start?: { postId: string; index: number }\n /** Plays at once. */\n playing?: boolean\n /** The screen saver: plays in a random order, and closes at anything the person does. */\n saver?: boolean\n}\n\n@customElement('gallery-show')\nexport class GalleryShow extends localize(i18next)(LitElement) {\n static styles = css`\n :host {\n position: fixed;\n inset: 0;\n z-index: 10000;\n display: block;\n background: #000;\n color: #fff;\n user-select: none;\n }\n :host([saver]) {\n cursor: none;\n }\n img {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n object-fit: contain;\n animation: appear 800ms ease-out;\n }\n @keyframes appear {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n }\n .about {\n position: absolute;\n inset-inline: 0;\n bottom: 0;\n padding: 48px 24px 20px;\n background: linear-gradient(transparent, rgb(0 0 0 / 60%));\n font-size: 14px;\n line-height: 1.5;\n }\n .about .caption {\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n white-space: pre-wrap;\n font-size: 16px;\n }\n .about .who {\n opacity: 0.75;\n }\n .bar {\n position: absolute;\n top: 12px;\n inset-inline-end: 12px;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n .bar .count {\n padding: 0 8px;\n font-size: 13px;\n opacity: 0.8;\n }\n button {\n display: inline-flex;\n padding: 8px;\n border: 0;\n border-radius: 50%;\n background: rgb(0 0 0 / 45%);\n color: #fff;\n cursor: pointer;\n }\n button:hover {\n background: rgb(255 255 255 / 20%);\n }\n .turn {\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n }\n .previous {\n inset-inline-start: 12px;\n }\n .next {\n inset-inline-end: 12px;\n }\n .empty {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n opacity: 0.8;\n }\n `\n\n /** Opens a show over the whole window, and the whole screen when the browser lets it. */\n static open(options: ShowOptions): GalleryShow {\n const show = document.createElement('gallery-show') as GalleryShow\n show.options = options\n document.body.appendChild(show)\n return show\n }\n\n @property({ type: Boolean, reflect: true }) saver = false\n options!: ShowOptions\n\n @state() private frames: Frame[] = []\n @state() private at = 0\n @state() private playing = false\n @state() private loaded = false\n\n private timer?: ReturnType<typeof setTimeout>\n private wholeScreen = false\n private disposed = false\n\n render() {\n const frame = this.frames[this.at]\n if (!frame)\n return this.loaded\n ? html`<div class=\"empty\">${t('gallery.text.show-empty')}</div>${this.saver ? nothing : this.renderBar()}`\n : nothing\n const { post, photo } = frame\n return html`\n ${/* A new element for each photo, so it fades in. */ [photo.id].map(() => html`<img src=${photo.fullpath} alt=${post.caption ?? ''} />`)}\n <div class=\"about\">\n ${post.caption ? html`<div class=\"caption\">${post.caption}</div>` : nothing}\n <div class=\"who\">${post.creator?.name ?? ''} · ${day(post.takenAt)}${post.event ? ` · ${post.event.name}` : ''}</div>\n </div>\n ${\n this.saver\n ? nothing\n : html`${this.renderBar()}\n ${this.frames.length > 1\n ? html`<button class=\"turn previous\" aria-label=${t('gallery.button.previous')} @click=${() => this.step(-1)}>\n <md-icon>chevron_left</md-icon>\n </button>\n <button class=\"turn next\" aria-label=${t('gallery.button.next')} @click=${() => this.step(1)}>\n <md-icon>chevron_right</md-icon>\n </button>`\n : nothing}`\n }\n `\n }\n\n private renderBar() {\n return html`<div class=\"bar\">\n ${this.frames.length ? html`<span class=\"count\">${this.at + 1} / ${this.frames.length}</span>` : nothing}\n ${\n this.frames.length > 1\n ? html`<button\n aria-label=${t(this.playing ? 'gallery.button.pause' : 'gallery.button.play')}\n title=${t(this.playing ? 'gallery.button.pause' : 'gallery.button.play')}\n @click=${() => this.play(!this.playing)}\n >\n <md-icon>${this.playing ? 'pause' : 'play_arrow'}</md-icon>\n </button>`\n : nothing\n }\n <button aria-label=${t('gallery.button.close')} title=${t('gallery.button.close')} @click=${() => this.close()}>\n <md-icon>close</md-icon>\n </button>\n </div>`\n }\n\n connectedCallback() {\n super.connectedCallback()\n this.saver = !!this.options?.saver\n window.addEventListener('keydown', this.onKey, true)\n document.addEventListener('fullscreenchange', this.onFullscreen)\n if (this.saver) for (const name of WAKE) window.addEventListener(name, this.onWake, true)\n /* The whole screen needs the press that opened it - the saver has none, and covers the window only. */\n if (!this.saver && this.requestFullscreen)\n this.requestFullscreen()\n .then(() => (this.wholeScreen = true))\n .catch(() => {})\n this.load()\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.disposed = true\n clearTimeout(this.timer)\n window.removeEventListener('keydown', this.onKey, true)\n document.removeEventListener('fullscreenchange', this.onFullscreen)\n for (const name of WAKE) window.removeEventListener(name, this.onWake, true)\n }\n\n private async load() {\n const { filter, start, saver } = this.options\n const frames: Frame[] = []\n try {\n for (let skip = 0; frames.length < MOST; skip += PAGE) {\n const page = await galleryApi.posts(filter, skip, PAGE)\n for (const post of page.items) if (!post.hiddenAt) for (const photo of post.photos) frames.push({ post, photo })\n if (this.disposed || skip + PAGE >= page.total) break\n }\n } catch {\n /* The show has what came; a saver with nothing closes itself below. */\n }\n if (this.disposed) return\n if (saver) shuffle(frames)\n this.frames = frames.slice(0, MOST)\n const found = start ? this.frames.findIndex(one => one.post.id === start.postId) : -1\n this.at = found < 0 ? 0 : Math.min(found + start!.index, this.frames.length - 1)\n this.loaded = true\n if (saver && !this.frames.length) return this.close()\n this.play(!!this.options.playing || !!saver)\n }\n\n private play(on: boolean) {\n this.playing = on\n this.schedule()\n }\n\n /** The next photo after the dwell, asked ahead so it is there when it turns. */\n private schedule() {\n clearTimeout(this.timer)\n if (!this.playing || this.frames.length < 2) return\n const next = this.frames[(this.at + 1) % this.frames.length]\n new Image().src = next.photo.fullpath\n this.timer = setTimeout(() => this.step(1), DWELL)\n }\n\n private step(by: number) {\n const count = this.frames.length\n if (!count) return\n this.at = (this.at + by + count) % count\n this.schedule()\n }\n\n private onKey = (e: KeyboardEvent) => {\n if (this.saver) return\n const handled: Record<string, () => void> = {\n ArrowLeft: () => this.step(-1),\n ArrowRight: () => this.step(1),\n ' ': () => this.play(!this.playing),\n Escape: () => this.close()\n }\n const act = handled[e.key]\n if (!act) return\n e.preventDefault()\n e.stopPropagation()\n act()\n }\n\n /** Leaving the whole screen (Esc there is the browser's) leaves the show too. */\n private onFullscreen = () => {\n if (this.wholeScreen && !document.fullscreenElement) this.close()\n }\n\n private onWake = (e: Event) => {\n /* The pointer resting on the screen still says it moved, by a pixel or so - a real move is farther. */\n if (e.type === 'pointermove' && Math.abs((e as PointerEvent).movementX) + Math.abs((e as PointerEvent).movementY) < 4)\n return\n e.preventDefault()\n e.stopPropagation()\n this.close()\n }\n\n close() {\n if (this.disposed) return\n if (this.wholeScreen && document.fullscreenElement) document.exitFullscreen().catch(() => {})\n this.remove()\n this.dispatchEvent(new CustomEvent('closed'))\n }\n}\n\n/** What wakes the screen from the saver. */\nconst WAKE = ['pointermove', 'pointerdown', 'keydown', 'wheel', 'touchstart']\n\nfunction shuffle<T>(list: T[]) {\n for (let i = list.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1))\n ;[list[i], list[j]] = [list[j], list[i]]\n }\n}\n"]}
@@ -0,0 +1,57 @@
1
+ import '@material/web/button/filled-button.js';
2
+ import '@material/web/button/text-button.js';
3
+ import '@material/web/icon/icon.js';
4
+ import '@operato/input/ox-input-file.js';
5
+ import '@operato/input/ox-input-select-buttons.js';
6
+ import '@operato/popup/ox-popup-actions.js';
7
+ import { LitElement } from 'lit';
8
+ import { type Post, type Space } from '../utils/gallery-api.js';
9
+ declare const GalleryUploader_base: (new (...args: any[]) => LitElement) & typeof LitElement;
10
+ export declare class GalleryUploader extends GalleryUploader_base {
11
+ static styles: import("lit").CSSResult[];
12
+ /** The post being changed - nothing when posting a new one. */
13
+ post?: Post;
14
+ /** The photos the page picked before the window opened. */
15
+ files: File[];
16
+ /** Where a new post goes - the screen it was opened from. */
17
+ space: Space;
18
+ eventId?: string | null;
19
+ private added;
20
+ private kept;
21
+ private caption;
22
+ private tags;
23
+ private tagText;
24
+ /** Why the tag being typed was not taken - said under the tags, the text kept so it can be shortened. */
25
+ private tagRefusal;
26
+ private suggestions;
27
+ private chosenSpace;
28
+ /** A post of the own gallery is the person's alone until shared (decision 2) - off when posting. */
29
+ private shared;
30
+ private chosenEvent;
31
+ private takenOn;
32
+ private choices;
33
+ private allChoices;
34
+ private progress?;
35
+ private busy;
36
+ private picker;
37
+ private camera;
38
+ private suggestTimer?;
39
+ render(): import("lit-html").TemplateResult<1>;
40
+ private choiceChip;
41
+ firstUpdated(): Promise<void>;
42
+ disconnectedCallback(): void;
43
+ private take;
44
+ private add;
45
+ private drop;
46
+ private takenAt;
47
+ private loadChoices;
48
+ private changeDay;
49
+ private chooseSpace;
50
+ private typeTag;
51
+ private tagKey;
52
+ /** Takes the typed tag, or says why not and keeps it - a longer tag was cut at 30 without a word (2026-10-30). */
53
+ private addTag;
54
+ private save;
55
+ private close;
56
+ }
57
+ export {};