@things-factory/gallery 10.1.234 → 10.1.236
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/client/bootstrap.ts +12 -0
- package/client/components/gallery-saver-setting-let.ts +78 -0
- package/client/components/gallery-show.ts +312 -0
- package/client/components/gallery-uploader.ts +16 -1
- package/client/components/gallery-viewer.ts +17 -4
- package/client/pages/gallery-page.ts +18 -16
- package/client/utils/gallery-api.ts +4 -2
- package/client/utils/gallery-saver.ts +72 -0
- package/dist-client/bootstrap.d.ts +1 -0
- package/dist-client/bootstrap.js +10 -0
- package/dist-client/bootstrap.js.map +1 -1
- package/dist-client/components/gallery-saver-setting-let.d.ts +11 -0
- package/dist-client/components/gallery-saver-setting-let.js +81 -0
- package/dist-client/components/gallery-saver-setting-let.js.map +1 -0
- package/dist-client/components/gallery-show.d.ts +46 -0
- package/dist-client/components/gallery-show.js +316 -0
- package/dist-client/components/gallery-show.js.map +1 -0
- package/dist-client/components/gallery-uploader.d.ts +2 -0
- package/dist-client/components/gallery-uploader.js +20 -1
- package/dist-client/components/gallery-uploader.js.map +1 -1
- package/dist-client/components/gallery-viewer.d.ts +2 -2
- package/dist-client/components/gallery-viewer.js +16 -3
- package/dist-client/components/gallery-viewer.js.map +1 -1
- package/dist-client/pages/gallery-page.d.ts +0 -1
- package/dist-client/pages/gallery-page.js +20 -16
- package/dist-client/pages/gallery-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/gallery-api.d.ts +4 -0
- package/dist-client/utils/gallery-api.js +1 -1
- package/dist-client/utils/gallery-api.js.map +1 -1
- package/dist-client/utils/gallery-saver.d.ts +6 -0
- package/dist-client/utils/gallery-saver.js +69 -0
- package/dist-client/utils/gallery-saver.js.map +1 -0
- package/dist-server/service/gallery/gallery-resolver.js +11 -2
- package/dist-server/service/gallery/gallery-resolver.js.map +1 -1
- package/dist-server/service/gallery/gallery-rules.d.ts +4 -2
- package/dist-server/service/gallery/gallery-rules.js +4 -3
- package/dist-server/service/gallery/gallery-rules.js.map +1 -1
- package/dist-server/service/gallery/gallery-types.d.ts +3 -0
- package/dist-server/service/gallery/gallery-types.js +12 -0
- package/dist-server/service/gallery/gallery-types.js.map +1 -1
- package/dist-server/service/gallery/gallery.d.ts +2 -1
- package/dist-server/service/gallery/gallery.js +7 -2
- package/dist-server/service/gallery/gallery.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -4
- package/server/service/gallery/gallery-resolver.ts +9 -2
- package/server/service/gallery/gallery-rules.ts +5 -4
- package/server/service/gallery/gallery-types.ts +9 -0
- package/server/service/gallery/gallery.ts +7 -2
- package/test/gallery-persistence.test.ts +24 -3
- package/test/gallery-rules.test.ts +10 -4
- package/translations/en.json +15 -2
- package/translations/es.json +15 -2
- package/translations/ja.json +15 -2
- package/translations/ko.json +15 -2
- package/translations/ms.json +15 -2
- package/translations/zh.json +15 -2
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The gallery as the screen saver (ADR-0165 decision 9): after the minutes set on this device with no press, move or
|
|
3
|
+
* key, the gallery everyone sees - the company's and the shared own photos - plays over the window until the person
|
|
4
|
+
* comes back. Kept on the device (`clientSettingStore`),
|
|
5
|
+
* since it is the screen that is left - a meeting room's display, not the person - and off until it is turned on.
|
|
6
|
+
*/
|
|
7
|
+
import { clientSettingStore } from '@operato/shell'
|
|
8
|
+
import { auth } from '@things-factory/auth-base/dist-client'
|
|
9
|
+
|
|
10
|
+
import { GalleryShow } from '../components/gallery-show.js'
|
|
11
|
+
|
|
12
|
+
const KEY = 'gallery-saver'
|
|
13
|
+
/** The waits the setting offers, in minutes; 0 is off. */
|
|
14
|
+
export const SAVER_MINUTES = [0, 5, 10, 30]
|
|
15
|
+
/** What says the person is here. */
|
|
16
|
+
const PRESENCE = ['pointermove', 'pointerdown', 'keydown', 'wheel', 'touchstart']
|
|
17
|
+
|
|
18
|
+
export async function saverMinutes(): Promise<number> {
|
|
19
|
+
try {
|
|
20
|
+
const minutes = Number((await clientSettingStore.get(KEY))?.value?.minutes)
|
|
21
|
+
return SAVER_MINUTES.includes(minutes) ? minutes : 0
|
|
22
|
+
} catch {
|
|
23
|
+
return 0
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function setSaverMinutes(minutes: number) {
|
|
28
|
+
await clientSettingStore.put({ key: KEY, value: { minutes } })
|
|
29
|
+
window.dispatchEvent(new CustomEvent('gallery-saver-changed'))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let minutes = 0
|
|
33
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
34
|
+
let signedIn = false
|
|
35
|
+
let showing: GalleryShow | undefined
|
|
36
|
+
|
|
37
|
+
function wait() {
|
|
38
|
+
clearTimeout(timer)
|
|
39
|
+
if (!minutes || !signedIn || showing) return
|
|
40
|
+
timer = setTimeout(start, minutes * 60_000)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function start() {
|
|
44
|
+
/* A hidden tab is not looked at; it waits for the next quiet stretch after it is shown. */
|
|
45
|
+
if (document.visibilityState !== 'visible' || document.querySelector('gallery-show')) return wait()
|
|
46
|
+
/* What everyone sees - never the photos the device's person keeps to themselves. */
|
|
47
|
+
showing = GalleryShow.open({ filter: { everyone: true }, saver: true })
|
|
48
|
+
showing.addEventListener('closed', () => {
|
|
49
|
+
showing = undefined
|
|
50
|
+
wait()
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Watches for the quiet stretch, once per application. */
|
|
55
|
+
export function watchForSaver() {
|
|
56
|
+
const read = async () => {
|
|
57
|
+
minutes = await saverMinutes()
|
|
58
|
+
wait()
|
|
59
|
+
}
|
|
60
|
+
for (const name of PRESENCE) window.addEventListener(name, () => !showing && wait(), { passive: true, capture: true })
|
|
61
|
+
document.addEventListener('visibilitychange', () => document.visibilityState === 'visible' && wait())
|
|
62
|
+
window.addEventListener('gallery-saver-changed', read)
|
|
63
|
+
auth.on('profile', () => {
|
|
64
|
+
signedIn = true
|
|
65
|
+
read()
|
|
66
|
+
})
|
|
67
|
+
auth.on('signout', () => {
|
|
68
|
+
signedIn = false
|
|
69
|
+
clearTimeout(timer)
|
|
70
|
+
showing?.close()
|
|
71
|
+
})
|
|
72
|
+
}
|
package/dist-client/bootstrap.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
1
2
|
import { navigate, store } from '@operato/shell';
|
|
2
3
|
import { ADD_USER_MENU } from '@things-factory/apptool-base/client';
|
|
4
|
+
import { ADD_SETTING } from '@things-factory/setting-base/dist-client';
|
|
5
|
+
import './components/gallery-saver-setting-let.js';
|
|
6
|
+
import { watchForSaver } from './utils/gallery-saver.js';
|
|
3
7
|
/* One's own gallery is in the menu the header's avatar opens, right after the profile (ADR-0165 decision 6). */
|
|
4
8
|
export default function bootstrap() {
|
|
5
9
|
store.dispatch({
|
|
@@ -12,5 +16,11 @@ export default function bootstrap() {
|
|
|
12
16
|
action: () => navigate('gallery-page?tab=mine')
|
|
13
17
|
}
|
|
14
18
|
});
|
|
19
|
+
/* The screen saver is the device's, so it is set among one's own settings (ADR-0165 decision 9). */
|
|
20
|
+
store.dispatch({
|
|
21
|
+
type: ADD_SETTING,
|
|
22
|
+
setting: { seq: 30, template: html `<gallery-saver-setting-let></gallery-saver-setting-let>` }
|
|
23
|
+
});
|
|
24
|
+
watchForSaver();
|
|
15
25
|
}
|
|
16
26
|
//# sourceMappingURL=bootstrap.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../client/bootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAA;
|
|
1
|
+
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../client/bootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAA;AAE1B,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAA;AACnE,OAAO,EAAE,WAAW,EAAE,MAAM,0CAA0C,CAAA;AAEtE,OAAO,2CAA2C,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAExD,gHAAgH;AAChH,MAAM,CAAC,OAAO,UAAU,SAAS;IAC/B,KAAK,CAAC,QAAQ,CAAC;QACb,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE;YACJ,IAAI,EAAE,YAAY;YAClB,IAAI,EAAE,eAAe;YACrB,KAAK,EAAE,0BAA0B;YACjC,KAAK,EAAE,EAAE;YACT,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,uBAAuB,CAAC;SAChD;KACF,CAAC,CAAA;IACF,oGAAoG;IACpG,KAAK,CAAC,QAAQ,CAAC;QACb,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAA,yDAAyD,EAAE;KAC9F,CAAC,CAAA;IACF,aAAa,EAAE,CAAA;AACjB,CAAC","sourcesContent":["import { html } from 'lit'\n\nimport { navigate, store } from '@operato/shell'\nimport { ADD_USER_MENU } from '@things-factory/apptool-base/client'\nimport { ADD_SETTING } from '@things-factory/setting-base/dist-client'\n\nimport './components/gallery-saver-setting-let.js'\nimport { watchForSaver } from './utils/gallery-saver.js'\n\n/* One's own gallery is in the menu the header's avatar opens, right after the profile (ADR-0165 decision 6). */\nexport default function bootstrap() {\n store.dispatch({\n type: ADD_USER_MENU,\n item: {\n name: 'my-gallery',\n icon: 'photo_library',\n label: 'gallery.title.my-gallery',\n order: 12,\n action: () => navigate('gallery-page?tab=mine')\n }\n })\n /* The screen saver is the device's, so it is set among one's own settings (ADR-0165 decision 9). */\n store.dispatch({\n type: ADD_SETTING,\n setting: { seq: 30, template: html`<gallery-saver-setting-let></gallery-saver-setting-let>` }\n })\n watchForSaver()\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import '@material/web/radio/radio.js';
|
|
2
|
+
import { LitElement } from 'lit';
|
|
3
|
+
declare const GallerySaverSettingLet_base: (new (...args: any[]) => LitElement) & typeof LitElement;
|
|
4
|
+
export declare class GallerySaverSettingLet extends GallerySaverSettingLet_base {
|
|
5
|
+
static styles: import("lit").CSSResult;
|
|
6
|
+
private minutes;
|
|
7
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
8
|
+
firstUpdated(): Promise<void>;
|
|
9
|
+
private choose;
|
|
10
|
+
}
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
/*
|
|
3
|
+
* Settings › the gallery as the screen saver on this device (ADR-0165 decision 9): off, or after a few quiet minutes.
|
|
4
|
+
*/
|
|
5
|
+
import '@material/web/radio/radio.js';
|
|
6
|
+
import { css, html, LitElement } from 'lit';
|
|
7
|
+
import { customElement, state } from 'lit/decorators.js';
|
|
8
|
+
import { i18next, localize } from '@operato/i18n';
|
|
9
|
+
import { notify } from '@operato/layout';
|
|
10
|
+
import { SAVER_MINUTES, saverMinutes, setSaverMinutes } from '../utils/gallery-saver.js';
|
|
11
|
+
import { reasonOf, t } from '../utils/gallery-words.js';
|
|
12
|
+
let GallerySaverSettingLet = class GallerySaverSettingLet extends localize(i18next)(LitElement) {
|
|
13
|
+
constructor() {
|
|
14
|
+
super(...arguments);
|
|
15
|
+
this.minutes = 0;
|
|
16
|
+
}
|
|
17
|
+
static { this.styles = css `
|
|
18
|
+
div[slot='content'] {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
gap: var(--spacing-medium, 8px);
|
|
22
|
+
}
|
|
23
|
+
.choices {
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-wrap: wrap;
|
|
26
|
+
gap: var(--spacing-small, 4px) var(--spacing-large, 12px);
|
|
27
|
+
}
|
|
28
|
+
label {
|
|
29
|
+
display: flex;
|
|
30
|
+
gap: 6px;
|
|
31
|
+
align-items: center;
|
|
32
|
+
font: var(--label-font);
|
|
33
|
+
color: var(--md-sys-color-on-surface);
|
|
34
|
+
}
|
|
35
|
+
.note {
|
|
36
|
+
margin: 0;
|
|
37
|
+
font-size: var(--ui-t-small, 12px);
|
|
38
|
+
color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
|
|
39
|
+
}
|
|
40
|
+
`; }
|
|
41
|
+
render() {
|
|
42
|
+
return html `<setting-let>
|
|
43
|
+
<span slot="title">${t('gallery.title.saver')}</span>
|
|
44
|
+
<div slot="content">
|
|
45
|
+
<div class="choices" role="radiogroup">
|
|
46
|
+
${SAVER_MINUTES.map(minutes => html `<label>
|
|
47
|
+
<md-radio
|
|
48
|
+
name="gallery-saver"
|
|
49
|
+
value=${String(minutes)}
|
|
50
|
+
?checked=${this.minutes === minutes}
|
|
51
|
+
@change=${() => this.choose(minutes)}
|
|
52
|
+
></md-radio>
|
|
53
|
+
${minutes ? t('gallery.text.saver-after', { minutes }) : t('gallery.text.saver-off')}
|
|
54
|
+
</label>`)}
|
|
55
|
+
</div>
|
|
56
|
+
<p class="note">${t('gallery.text.saver-note')}</p>
|
|
57
|
+
</div>
|
|
58
|
+
</setting-let>`;
|
|
59
|
+
}
|
|
60
|
+
async firstUpdated() {
|
|
61
|
+
this.minutes = await saverMinutes();
|
|
62
|
+
}
|
|
63
|
+
async choose(minutes) {
|
|
64
|
+
try {
|
|
65
|
+
await setSaverMinutes(minutes);
|
|
66
|
+
this.minutes = minutes;
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
notify({ level: 'error', message: reasonOf(error) });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
__decorate([
|
|
74
|
+
state(),
|
|
75
|
+
__metadata("design:type", Object)
|
|
76
|
+
], GallerySaverSettingLet.prototype, "minutes", void 0);
|
|
77
|
+
GallerySaverSettingLet = __decorate([
|
|
78
|
+
customElement('gallery-saver-setting-let')
|
|
79
|
+
], GallerySaverSettingLet);
|
|
80
|
+
export { GallerySaverSettingLet };
|
|
81
|
+
//# sourceMappingURL=gallery-saver-setting-let.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gallery-saver-setting-let.js","sourceRoot":"","sources":["../../client/components/gallery-saver-setting-let.ts"],"names":[],"mappings":";AAAA;;GAEG;AACH,OAAO,8BAA8B,CAAA;AAErC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAExD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAExC,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AACxF,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,2BAA2B,CAAA;AAGhD,IAAM,sBAAsB,GAA5B,MAAM,sBAAuB,SAAQ,QAAQ,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC;IAAlE;;QA0BY,YAAO,GAAG,CAAC,CAAA;IAoC9B,CAAC;aA7DQ,WAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBlB,AAvBY,CAuBZ;IAID,MAAM;QACJ,OAAO,IAAI,CAAA;2BACY,CAAC,CAAC,qBAAqB,CAAC;;;YAGvC,aAAa,CAAC,GAAG,CACjB,OAAO,CAAC,EAAE,CAAC,IAAI,CAAA;;;wBAGH,MAAM,CAAC,OAAO,CAAC;2BACZ,IAAI,CAAC,OAAO,KAAK,OAAO;0BACzB,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;;gBAEpC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,0BAA0B,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,wBAAwB,CAAC;qBAC7E,CACV;;0BAEe,CAAC,CAAC,yBAAyB,CAAC;;mBAEnC,CAAA;IACjB,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,IAAI,CAAC,OAAO,GAAG,MAAM,YAAY,EAAE,CAAA;IACrC,CAAC;IAEO,KAAK,CAAC,MAAM,CAAC,OAAe;QAClC,IAAI,CAAC;YACH,MAAM,eAAe,CAAC,OAAO,CAAC,CAAA;YAC9B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACxB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QACtD,CAAC;IACH,CAAC;;AAnCgB;IAAhB,KAAK,EAAE;;uDAAoB;AA1BjB,sBAAsB;IADlC,aAAa,CAAC,2BAA2B,CAAC;GAC9B,sBAAsB,CA8DlC","sourcesContent":["/*\n * Settings › the gallery as the screen saver on this device (ADR-0165 decision 9): off, or after a few quiet minutes.\n */\nimport '@material/web/radio/radio.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { notify } from '@operato/layout'\n\nimport { SAVER_MINUTES, saverMinutes, setSaverMinutes } from '../utils/gallery-saver.js'\nimport { reasonOf, t } from '../utils/gallery-words.js'\n\n@customElement('gallery-saver-setting-let')\nexport class GallerySaverSettingLet extends localize(i18next)(LitElement) {\n static styles = css`\n div[slot='content'] {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n }\n .choices {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-small, 4px) var(--spacing-large, 12px);\n }\n label {\n display: flex;\n gap: 6px;\n align-items: center;\n font: var(--label-font);\n color: var(--md-sys-color-on-surface);\n }\n .note {\n margin: 0;\n font-size: var(--ui-t-small, 12px);\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n `\n\n @state() private minutes = 0\n\n render() {\n return html`<setting-let>\n <span slot=\"title\">${t('gallery.title.saver')}</span>\n <div slot=\"content\">\n <div class=\"choices\" role=\"radiogroup\">\n ${SAVER_MINUTES.map(\n minutes => html`<label>\n <md-radio\n name=\"gallery-saver\"\n value=${String(minutes)}\n ?checked=${this.minutes === minutes}\n @change=${() => this.choose(minutes)}\n ></md-radio>\n ${minutes ? t('gallery.text.saver-after', { minutes }) : t('gallery.text.saver-off')}\n </label>`\n )}\n </div>\n <p class=\"note\">${t('gallery.text.saver-note')}</p>\n </div>\n </setting-let>`\n }\n\n async firstUpdated() {\n this.minutes = await saverMinutes()\n }\n\n private async choose(minutes: number) {\n try {\n await setSaverMinutes(minutes)\n this.minutes = minutes\n } catch (error) {\n notify({ level: 'error', message: reasonOf(error) })\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import '@material/web/icon/icon.js';
|
|
2
|
+
import { LitElement, nothing } from 'lit';
|
|
3
|
+
import { type Filter } from '../utils/gallery-api.js';
|
|
4
|
+
export type ShowOptions = {
|
|
5
|
+
/** The posts the show walks through - the gallery's narrowing where it was opened. */
|
|
6
|
+
filter: Filter;
|
|
7
|
+
/** Where it starts: a post and the photo in it. Without it, the first photo. */
|
|
8
|
+
start?: {
|
|
9
|
+
postId: string;
|
|
10
|
+
index: number;
|
|
11
|
+
};
|
|
12
|
+
/** Plays at once. */
|
|
13
|
+
playing?: boolean;
|
|
14
|
+
/** The screen saver: plays in a random order, and closes at anything the person does. */
|
|
15
|
+
saver?: boolean;
|
|
16
|
+
};
|
|
17
|
+
declare const GalleryShow_base: (new (...args: any[]) => LitElement) & typeof LitElement;
|
|
18
|
+
export declare class GalleryShow extends GalleryShow_base {
|
|
19
|
+
static styles: import("lit").CSSResult;
|
|
20
|
+
/** Opens a show over the whole window, and the whole screen when the browser lets it. */
|
|
21
|
+
static open(options: ShowOptions): GalleryShow;
|
|
22
|
+
saver: boolean;
|
|
23
|
+
options: ShowOptions;
|
|
24
|
+
private frames;
|
|
25
|
+
private at;
|
|
26
|
+
private playing;
|
|
27
|
+
private loaded;
|
|
28
|
+
private timer?;
|
|
29
|
+
private wholeScreen;
|
|
30
|
+
private disposed;
|
|
31
|
+
render(): typeof nothing | import("lit-html").TemplateResult<1>;
|
|
32
|
+
private renderBar;
|
|
33
|
+
connectedCallback(): void;
|
|
34
|
+
disconnectedCallback(): void;
|
|
35
|
+
private load;
|
|
36
|
+
private play;
|
|
37
|
+
/** The next photo after the dwell, asked ahead so it is there when it turns. */
|
|
38
|
+
private schedule;
|
|
39
|
+
private step;
|
|
40
|
+
private onKey;
|
|
41
|
+
/** Leaving the whole screen (Esc there is the browser's) leaves the show too. */
|
|
42
|
+
private onFullscreen;
|
|
43
|
+
private onWake;
|
|
44
|
+
close(): void;
|
|
45
|
+
}
|
|
46
|
+
export {};
|
|
@@ -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"]}
|
|
@@ -24,6 +24,8 @@ export declare class GalleryUploader extends GalleryUploader_base {
|
|
|
24
24
|
private tagRefusal;
|
|
25
25
|
private suggestions;
|
|
26
26
|
private chosenSpace;
|
|
27
|
+
/** A post of the own gallery is the person's alone until shared (decision 2) - off when posting. */
|
|
28
|
+
private shared;
|
|
27
29
|
private chosenEvent;
|
|
28
30
|
private takenOn;
|
|
29
31
|
private choices;
|