@netless/app-presentation 0.1.7 → 0.1.8-beta.0
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/package.json +1 -1
- package/src/app-presentation.ts +115 -42
- package/src/preload.ts +83 -108
- package/src/presentation.ts +10 -11
- package/src/store.ts +103 -0
package/package.json
CHANGED
package/src/app-presentation.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import type { AnimationMode, AppContext, NetlessApp, ReadonlyTeleBox, Room, SceneDefinition,
|
|
1
|
+
import type { AnimationMode, AppContext, AppPayload, NetlessApp, PublicEvent, ReadonlyTeleBox, Room, SceneDefinition, View, WindowManager } from "@netless/window-manager"
|
|
2
2
|
|
|
3
3
|
import { disposableStore } from '@wopjs/disposable'
|
|
4
4
|
import { listen } from '@wopjs/dom'
|
|
5
5
|
|
|
6
6
|
import styles from './style.scss?inline'
|
|
7
7
|
import { Presentation, type PresentationConfig, type PresentationPage } from "./presentation";
|
|
8
|
+
import { readable, type Readable } from "./store";
|
|
8
9
|
|
|
9
10
|
export type Logger = (...data: any[]) => void
|
|
10
11
|
|
|
12
|
+
const emptySceneName = '$$empty$$'
|
|
13
|
+
|
|
11
14
|
interface Viewport {
|
|
12
15
|
readonly x: number;
|
|
13
16
|
readonly y: number;
|
|
@@ -71,6 +74,18 @@ const createLogger = (room: Room | undefined): Logger => {
|
|
|
71
74
|
return (...args) => console.log(...args)
|
|
72
75
|
}
|
|
73
76
|
}
|
|
77
|
+
``
|
|
78
|
+
const scenesEqual = (scenes1?: SceneDefinition[], scenes2?: SceneDefinition[]): boolean => {
|
|
79
|
+
if (!scenes1 || !scenes2) {return false}
|
|
80
|
+
if (scenes1.length !== scenes2.length) return false;
|
|
81
|
+
return scenes1.every((scene, index) => {
|
|
82
|
+
const scene2 = scenes2[index];
|
|
83
|
+
return scene.name === scene2.name &&
|
|
84
|
+
scene.ppt?.width === scene2.ppt?.width &&
|
|
85
|
+
scene.ppt?.height === scene2.ppt?.height &&
|
|
86
|
+
scene.ppt?.src === scene2.ppt?.src;
|
|
87
|
+
});
|
|
88
|
+
};
|
|
74
89
|
|
|
75
90
|
export const NetlessAppPresentation: NetlessApp<{}, {}, PresentationAppOptions, PresentationController> = {
|
|
76
91
|
kind: "Presentation",
|
|
@@ -99,45 +114,107 @@ export const NetlessAppPresentation: NetlessApp<{}, {}, PresentationAppOptions,
|
|
|
99
114
|
const log = options.log || createLogger(context.getRoom())
|
|
100
115
|
log(`[Presentation] new ${context.appId}`)
|
|
101
116
|
|
|
117
|
+
const dispose = disposableStore()
|
|
118
|
+
dispose.add(() => log(`[Presentation] dispose ${context.appId}`))
|
|
119
|
+
|
|
120
|
+
const view$$ = context.createStorage('view', { uid: "", originX: 0, originY: 0, width: 0, height: 0 })
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
const _addScenePathListener = (
|
|
124
|
+
name: keyof PublicEvent,
|
|
125
|
+
listener: any
|
|
126
|
+
) => {
|
|
127
|
+
const windowManger = (context as any).manager.windowManger as WindowManager;
|
|
128
|
+
windowManger.emitter.on(name, listener)
|
|
129
|
+
return () => windowManger?.emitter.off(name, listener)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const getPageIndex = (view: View) => {
|
|
133
|
+
const focusScenePath = view.focusScenePath;
|
|
134
|
+
const name = focusScenePath?.split('/').pop();
|
|
135
|
+
let _pageIndex = pages.findIndex((page, index) => {
|
|
136
|
+
const n = page.name ?? String(index + 1);
|
|
137
|
+
return n === name;
|
|
138
|
+
});
|
|
139
|
+
if (_pageIndex === -1) {
|
|
140
|
+
_pageIndex = 0;
|
|
141
|
+
}
|
|
142
|
+
return _pageIndex;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let pageIndex = getPageIndex(view);
|
|
146
|
+
const pageIndex$ = readable<number>(pageIndex, set => {
|
|
147
|
+
set(pageIndex);
|
|
148
|
+
return _addScenePathListener("onAppScenePathChange", (payload: AppPayload)=>{
|
|
149
|
+
const { appId } = payload;
|
|
150
|
+
if (appId === context.appId) {
|
|
151
|
+
const _pageIndex = getPageIndex(payload.view);
|
|
152
|
+
set(_pageIndex)
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
dispose.add(() => {
|
|
158
|
+
pageIndex$.dispose();
|
|
159
|
+
})
|
|
160
|
+
|
|
102
161
|
// Prepare scenes.
|
|
103
162
|
// Caution: some user may insert a 500-page PDF.
|
|
104
163
|
if (context.isAddApp) {
|
|
105
164
|
if (pages.length > 100)
|
|
106
165
|
console.warn(`[Presentation]: too many pages (${pages.length}), may cause performance issues`)
|
|
107
166
|
|
|
108
|
-
|
|
167
|
+
let redirectResolve: ((bol:boolean) => void) | undefined = undefined;
|
|
168
|
+
const room = context.getRoom();
|
|
109
169
|
if (room && room.isWritable) {
|
|
110
170
|
const scenes = room.entireScenes()[scenePath];
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
room.removeScenes(`${scenePath}/${name}`)
|
|
120
|
-
}
|
|
121
|
-
room.putScenes(scenePath, pages.map((p, index) => ({
|
|
171
|
+
if (pageIndex$.value < 0 || pageIndex$.value >= pages.length) {
|
|
172
|
+
throw new Error(`[Presentation] Invalid page index: ${pageIndex$.value}, scenes length: ${pages.length}`);
|
|
173
|
+
}
|
|
174
|
+
new Promise((resolve) => {
|
|
175
|
+
const {name, ppt} = scenes[pageIndex$.value];
|
|
176
|
+
redirectResolve = resolve;
|
|
177
|
+
const _scenes = pages.map((p, index) => ({
|
|
122
178
|
name: p.name ?? String(index + 1),
|
|
123
179
|
ppt: { width: p.width, height: p.height, src: p.src }
|
|
124
|
-
}))
|
|
125
|
-
|
|
180
|
+
}))
|
|
181
|
+
|
|
182
|
+
if (!scenesEqual(scenes, _scenes)) {
|
|
183
|
+
room.removeScenes(scenePath)
|
|
184
|
+
room.putScenes(scenePath, _scenes)
|
|
185
|
+
}
|
|
186
|
+
if(name === _scenes[pageIndex$.value].name && !ppt){
|
|
187
|
+
context.addPage({ scene: { name: emptySceneName } }).then(() => {
|
|
188
|
+
log(`[Presentation] setup setScenePath ${scenePath}/${emptySceneName}`);
|
|
189
|
+
context.setScenePath(`${scenePath}/${emptySceneName}`).then(()=>{
|
|
190
|
+
redirectResolve && redirectResolve(true);
|
|
191
|
+
})
|
|
192
|
+
});
|
|
193
|
+
} else {
|
|
194
|
+
redirectResolve && redirectResolve(false)
|
|
195
|
+
}
|
|
196
|
+
}).then(async(bol)=>{
|
|
197
|
+
await syncPage(pageIndex$.value, (room as any).logger);
|
|
198
|
+
if (bol) {
|
|
199
|
+
log(`[Presentation] setup removeScenes ${scenePath}/${emptySceneName}`);
|
|
200
|
+
room.removeScenes(`${scenePath}/${emptySceneName}`);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
126
203
|
}
|
|
127
204
|
}
|
|
128
205
|
|
|
129
|
-
|
|
130
|
-
dispose.add(() => log(`[Presentation] dispose ${context.appId}`))
|
|
206
|
+
// let lastIndex = -1
|
|
131
207
|
|
|
132
|
-
const
|
|
133
|
-
const view$$ = context.createStorage('view', { uid: "", originX: 0, originY: 0, width: 0, height: 0 })
|
|
208
|
+
const me = context.getRoom()?.uid || context.getDisplayer().observerId + ''
|
|
134
209
|
|
|
135
|
-
let
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
210
|
+
let throttleSyncView = 0
|
|
211
|
+
|
|
212
|
+
const syncPage = async (index: number, logger?: any) => {
|
|
213
|
+
// if (lastIndex !== index) {
|
|
214
|
+
// lastIndex = index
|
|
215
|
+
// console.log('dispatchAppEvent===>pageStateChange', index);
|
|
216
|
+
// context.dispatchAppEvent('pageStateChange', { index, length: pages.length })
|
|
217
|
+
// }
|
|
141
218
|
|
|
142
219
|
if (!context.getIsWritable()) return
|
|
143
220
|
|
|
@@ -153,6 +230,10 @@ export const NetlessAppPresentation: NetlessApp<{}, {}, PresentationAppOptions,
|
|
|
153
230
|
await context.addPage({ scene: { name, ppt: { width: p.width, height: p.height, src: p.src } } })
|
|
154
231
|
}
|
|
155
232
|
|
|
233
|
+
if (logger) {
|
|
234
|
+
logger.info(`[Presentation] syncPage ${scenePath}/${name}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
156
237
|
// Switch to that page.
|
|
157
238
|
await context.setScenePath(`${scenePath}/${name}`)
|
|
158
239
|
}
|
|
@@ -178,16 +259,16 @@ export const NetlessAppPresentation: NetlessApp<{}, {}, PresentationAppOptions,
|
|
|
178
259
|
const name = p.name ?? String(index + 1);
|
|
179
260
|
|
|
180
261
|
if (!scenes.some(scene => scene.name === name)) {
|
|
181
|
-
context.addPage({ scene: { name } })
|
|
262
|
+
context.addPage({ scene: { name, ppt: { width: p.width, height: p.height, src: p.src } } })
|
|
182
263
|
}
|
|
183
264
|
|
|
184
|
-
|
|
265
|
+
syncPage(index);
|
|
185
266
|
return true
|
|
186
267
|
}
|
|
187
268
|
|
|
188
|
-
const prevPage = () => jumpPage(
|
|
189
|
-
const nextPage = () => jumpPage(
|
|
190
|
-
const pageState = () => ({ index:
|
|
269
|
+
const prevPage = () => jumpPage(pageIndex$.value - 1)
|
|
270
|
+
const nextPage = () => jumpPage(pageIndex$.value + 1)
|
|
271
|
+
const pageState = () => ({ index: pageIndex$.value, length: pages.length })
|
|
191
272
|
|
|
192
273
|
const scaleDocsToFit = () => {
|
|
193
274
|
const { width, height } = app.page() || {}
|
|
@@ -207,10 +288,6 @@ export const NetlessAppPresentation: NetlessApp<{}, {}, PresentationAppOptions,
|
|
|
207
288
|
syncViewFromRemote(true)
|
|
208
289
|
}
|
|
209
290
|
}
|
|
210
|
-
|
|
211
|
-
const me = context.getRoom()?.uid || context.getDisplayer().observerId + ''
|
|
212
|
-
|
|
213
|
-
let throttleSyncView = 0
|
|
214
291
|
const syncView = () => {
|
|
215
292
|
if (throttleSyncView > 0) return
|
|
216
293
|
const { width, height } = app.page() || {}
|
|
@@ -243,12 +320,10 @@ export const NetlessAppPresentation: NetlessApp<{}, {}, PresentationAppOptions,
|
|
|
243
320
|
}
|
|
244
321
|
}
|
|
245
322
|
|
|
246
|
-
syncPage(page$$.state.index)
|
|
247
|
-
dispose.add(page$$.addStateChangedListener(() => syncPage(page$$.state.index)))
|
|
248
323
|
dispose.add(view$$.addStateChangedListener(() => syncViewFromRemote(false, true)))
|
|
249
324
|
|
|
250
325
|
const box = context.getBox()
|
|
251
|
-
const app = dispose.add(createPresentation(box, pages, jumpPage,
|
|
326
|
+
const app = dispose.add(createPresentation(box, pages, jumpPage, pageIndex$, options.thumbnail))
|
|
252
327
|
app.contentDOM.dataset.appPresentationVersion = __VERSION__
|
|
253
328
|
app.scaleDocsToFit = scaleDocsToFit
|
|
254
329
|
app.log = log
|
|
@@ -444,6 +519,7 @@ class AppPresentation extends Presentation {
|
|
|
444
519
|
|
|
445
520
|
override updateImage() {
|
|
446
521
|
// Do nothing, the image was set in the whiteboard scene.
|
|
522
|
+
super.updateImage();
|
|
447
523
|
}
|
|
448
524
|
|
|
449
525
|
override onNewPageIndex(index: number, origin: "navigation" | "keydown" | "input" | "preview") {
|
|
@@ -465,7 +541,7 @@ function createPresentation(
|
|
|
465
541
|
box: ReadonlyTeleBox,
|
|
466
542
|
pages: PresentationPage[],
|
|
467
543
|
jumpPage: (index: number) => void,
|
|
468
|
-
|
|
544
|
+
pageIndex$: Readable<number>,
|
|
469
545
|
thumbnail?: (src: string) => string,
|
|
470
546
|
): AppPresentation {
|
|
471
547
|
box.mountStyles(styles)
|
|
@@ -475,10 +551,7 @@ function createPresentation(
|
|
|
475
551
|
box.mountContent(app.contentDOM)
|
|
476
552
|
box.mountFooter(app.footerDOM)
|
|
477
553
|
|
|
478
|
-
app.setPageIndex(
|
|
479
|
-
app.dispose.add(page$$.addStateChangedListener(() => {
|
|
480
|
-
app.setPageIndex(page$$.state.index)
|
|
481
|
-
}))
|
|
554
|
+
app.dispose.add(pageIndex$.subscribe(pageIndex => { app.setPageIndex(pageIndex) }))
|
|
482
555
|
|
|
483
556
|
return app
|
|
484
557
|
}
|
package/src/preload.ts
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import type { IDisposable } from "@wopjs/disposable";
|
|
2
2
|
import type { PresentationPage } from "./presentation";
|
|
3
3
|
|
|
4
|
+
export type Subscriber<T> = (value: T) => void;
|
|
5
|
+
export type Unsubscriber = () => void;
|
|
6
|
+
export type Updater<T> = (value: T) => T;
|
|
7
|
+
|
|
8
|
+
export interface PageIndex<T> {
|
|
9
|
+
readonly value: T;
|
|
10
|
+
subscribe(this: void, run: Subscriber<T>): Unsubscriber;
|
|
11
|
+
reaction(this: void, run: Subscriber<T>): Unsubscriber;
|
|
12
|
+
dispose(value?: T): void;
|
|
13
|
+
}
|
|
14
|
+
|
|
4
15
|
export enum ELoadState {
|
|
5
16
|
unloaded,
|
|
6
|
-
loading,
|
|
7
17
|
loaded,
|
|
8
18
|
error
|
|
9
19
|
}
|
|
@@ -15,10 +25,14 @@ export interface IPreloadMapValue {
|
|
|
15
25
|
|
|
16
26
|
export class Preload implements IDisposable {
|
|
17
27
|
static maxLinks: number = 5;
|
|
18
|
-
|
|
28
|
+
// 正在加载的链接
|
|
29
|
+
readonly loadingLinks: Map<number, {
|
|
30
|
+
link: HTMLLinkElement,
|
|
31
|
+
isForce: boolean
|
|
32
|
+
}> = new Map();
|
|
33
|
+
// 需要预加载的链接集合
|
|
19
34
|
readonly preloadMap = new Map<number, IPreloadMapValue>();
|
|
20
|
-
|
|
21
|
-
readonly waitinglinks: number[] = [];
|
|
35
|
+
// 当前触摸的索引
|
|
22
36
|
touchIndex: number = 0;
|
|
23
37
|
preloadSize: number = 0;
|
|
24
38
|
constructor(readonly pages: PresentationPage[]) {
|
|
@@ -26,118 +40,87 @@ export class Preload implements IDisposable {
|
|
|
26
40
|
src: e.src,
|
|
27
41
|
state: ELoadState.unloaded
|
|
28
42
|
}]));
|
|
29
|
-
this.waitingLoadSet = new Set(pages.map((e,index) => index));
|
|
30
43
|
this.preloadSize = this.preloadMap.size;
|
|
31
|
-
this.touch(0);
|
|
32
|
-
}
|
|
33
|
-
async touch(index: number): Promise<ELoadState> {
|
|
34
|
-
this.touchIndex = index;
|
|
35
|
-
const value = this.preloadMap.get(index);
|
|
36
|
-
let state = ELoadState.unloaded;
|
|
37
|
-
if (value) {
|
|
38
|
-
state = value.state;
|
|
39
|
-
if (value.state === ELoadState.unloaded) {
|
|
40
|
-
if (this.loadingLinks.size) {
|
|
41
|
-
for (const [i,link] of this.loadingLinks) {
|
|
42
|
-
this.destroyLink(i, link);
|
|
43
|
-
this.waitingLoadSet.add(i);
|
|
44
|
-
const v = this.preloadMap.get(i);
|
|
45
|
-
if (v) {
|
|
46
|
-
v.state = ELoadState.unloaded;
|
|
47
|
-
this.preloadMap.set(i, v);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
for (const i of this.waitinglinks) {
|
|
51
|
-
this.waitingLoadSet.add(i);
|
|
52
|
-
}
|
|
53
|
-
this.waitinglinks.length = 0;
|
|
54
|
-
}
|
|
55
|
-
if (this.waitinglinks.includes(index)) {
|
|
56
|
-
this.waitinglinks.splice(this.waitinglinks.indexOf(index), 1);
|
|
57
|
-
}
|
|
58
|
-
state = await new Promise<ELoadState>(resolve => {
|
|
59
|
-
this.waitingLoadSet.delete(index);
|
|
60
|
-
this.createLink(index, resolve);
|
|
61
|
-
})
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
if (this.waitingLoadSet.size) {
|
|
65
|
-
this.requestAsyncCallBack(()=>{
|
|
66
|
-
this.autoTouch();
|
|
67
|
-
}, 1000)
|
|
68
|
-
}
|
|
69
|
-
return state;
|
|
44
|
+
this.touch(0, true);
|
|
70
45
|
}
|
|
71
|
-
|
|
72
|
-
this.
|
|
73
|
-
|
|
74
|
-
|
|
46
|
+
touch(index: number, force: boolean = false) {
|
|
47
|
+
if (index >= this.preloadSize) {
|
|
48
|
+
this.touchIndex = 0;
|
|
49
|
+
} else {
|
|
50
|
+
this.touchIndex = index;
|
|
75
51
|
}
|
|
76
|
-
let nextTouchDelay = 1000;
|
|
77
|
-
this.touchIndex = (this.touchIndex + 1) % this.preloadSize;
|
|
78
52
|
const value = this.preloadMap.get(this.touchIndex);
|
|
79
|
-
if (value) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
nextTouchDelay = 0;
|
|
88
|
-
break;
|
|
53
|
+
if (value && value.state === ELoadState.unloaded) {
|
|
54
|
+
this.createLink(this.touchIndex, force);
|
|
55
|
+
}
|
|
56
|
+
if (this.loadingLinks.size < Preload.maxLinks) {
|
|
57
|
+
const willLoad = [...this.preloadMap.entries()].find(([i, e]) => i > this.touchIndex && e.state !== ELoadState.loaded);
|
|
58
|
+
if (willLoad) {
|
|
59
|
+
this.requestAsyncCallBack(()=>{this.touch(willLoad[0], false)}, 100);
|
|
89
60
|
}
|
|
90
61
|
}
|
|
91
|
-
this.requestAsyncCallBack(()=>{
|
|
92
|
-
this.autoTouch();
|
|
93
|
-
}, nextTouchDelay)
|
|
94
62
|
}
|
|
95
63
|
|
|
96
|
-
private createLink(index: number,
|
|
64
|
+
private createLink(index: number, force: boolean = false):ELoadState {
|
|
97
65
|
const value = this.preloadMap.get(index);
|
|
66
|
+
if (force) {
|
|
67
|
+
this.destroySomeLink(index);
|
|
68
|
+
}
|
|
98
69
|
if (value) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
70
|
+
if (value.state === ELoadState.loaded) {
|
|
71
|
+
return ELoadState.loaded;
|
|
72
|
+
}
|
|
73
|
+
const curlink = this.loadingLinks.get(index);
|
|
74
|
+
if (curlink) {
|
|
75
|
+
if (curlink.isForce !== force) {
|
|
76
|
+
curlink.isForce = force;
|
|
77
|
+
this.loadingLinks.set(index, curlink);
|
|
78
|
+
}
|
|
79
|
+
return ELoadState.unloaded;
|
|
80
|
+
}
|
|
81
|
+
if (this.loadingLinks.size > Preload.maxLinks) {
|
|
82
|
+
return ELoadState.unloaded;
|
|
83
|
+
}
|
|
84
|
+
const linkDom = document.createElement('link');
|
|
85
|
+
linkDom.rel = 'preload';
|
|
86
|
+
linkDom.as = 'image';
|
|
87
|
+
linkDom.href = value.src;
|
|
88
|
+
linkDom.dataset.order = index + '';
|
|
89
|
+
document.head.appendChild(linkDom);
|
|
90
|
+
linkDom.onload = () => {
|
|
91
|
+
const value = this.preloadMap.get(index);
|
|
92
|
+
if (value) {
|
|
93
|
+
value.state = ELoadState.loaded;
|
|
94
|
+
this.preloadMap.set(index, value);
|
|
95
|
+
document.head.contains(linkDom) && document.head.removeChild(linkDom);
|
|
96
|
+
this.loadingLinks.delete(index);
|
|
97
|
+
this.touch(this.touchIndex + 1, false);
|
|
98
|
+
}
|
|
112
99
|
}
|
|
113
|
-
|
|
114
|
-
value
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
this.waitinglinks.push(index);
|
|
118
|
-
this.waitingLoadSet.delete(this.touchIndex);
|
|
100
|
+
linkDom.onerror = () => {
|
|
101
|
+
const value = this.preloadMap.get(index);
|
|
102
|
+
if (value?.src) {
|
|
103
|
+
linkDom.href = value.src;
|
|
119
104
|
}
|
|
120
|
-
document.head.contains(link) && document.head.removeChild(link);
|
|
121
|
-
this.loadingLinks.delete(index);
|
|
122
|
-
resolve && resolve(ELoadState.error);
|
|
123
|
-
this.autoTouch();
|
|
124
105
|
}
|
|
125
|
-
this.loadingLinks.set(index,
|
|
126
|
-
|
|
106
|
+
this.loadingLinks.set(index, {
|
|
107
|
+
link: linkDom,
|
|
108
|
+
isForce: force
|
|
109
|
+
});
|
|
110
|
+
value.state = ELoadState.unloaded;
|
|
127
111
|
this.preloadMap.set(index, value);
|
|
112
|
+
return ELoadState.unloaded;
|
|
128
113
|
}
|
|
114
|
+
return ELoadState.error;
|
|
129
115
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
while (i < Preload.maxLinks) {
|
|
136
|
-
const index = this.waitinglinks.shift();
|
|
137
|
-
if (index !== undefined) {
|
|
138
|
-
this.createLink(index);
|
|
116
|
+
|
|
117
|
+
private destroySomeLink(excludeIndex?: number) {
|
|
118
|
+
for (const [index, { link }] of this.loadingLinks) {
|
|
119
|
+
if (excludeIndex !== undefined && index === excludeIndex) {
|
|
120
|
+
continue;
|
|
139
121
|
}
|
|
140
|
-
|
|
122
|
+
document.head.contains(link) && document.head.removeChild(link);
|
|
123
|
+
this.loadingLinks.delete(index);
|
|
141
124
|
}
|
|
142
125
|
}
|
|
143
126
|
private async requestAsyncCallBack (callBack:()=>void, timeout:number):Promise<void> {
|
|
@@ -154,16 +137,8 @@ export class Preload implements IDisposable {
|
|
|
154
137
|
});
|
|
155
138
|
callBack();
|
|
156
139
|
}
|
|
157
|
-
private destroyLink(index: number, link: HTMLLinkElement) {
|
|
158
|
-
document.head.contains(link) && document.head.removeChild(link)
|
|
159
|
-
this.loadingLinks.delete(index)
|
|
160
|
-
}
|
|
161
140
|
dispose() {
|
|
162
|
-
|
|
163
|
-
this.destroyLink(index, link);
|
|
164
|
-
}
|
|
141
|
+
this.destroySomeLink();
|
|
165
142
|
this.preloadMap.clear();
|
|
166
|
-
this.waitingLoadSet.clear();
|
|
167
|
-
this.waitinglinks.length = 0;
|
|
168
143
|
}
|
|
169
144
|
}
|
package/src/presentation.ts
CHANGED
|
@@ -11,7 +11,7 @@ export interface PresentationPage {
|
|
|
11
11
|
width: number;
|
|
12
12
|
height: number;
|
|
13
13
|
thumbnail?: string | undefined;
|
|
14
|
-
name?: string
|
|
14
|
+
name?: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export interface PresentationConfig {
|
|
@@ -298,16 +298,15 @@ export class Presentation implements IDisposable<void> {
|
|
|
298
298
|
}
|
|
299
299
|
this.timer = setTimeout(() => {
|
|
300
300
|
this.timer = null;
|
|
301
|
-
this.preload.touch(this.pageIndex)
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
})
|
|
301
|
+
this.preload.touch(this.pageIndex, true);
|
|
302
|
+
// const page = this.page()
|
|
303
|
+
// if (!page) {
|
|
304
|
+
// this.image.src = ''
|
|
305
|
+
// return
|
|
306
|
+
// }
|
|
307
|
+
// this.image.width = page.width
|
|
308
|
+
// this.image.height = page.height
|
|
309
|
+
// this.image.src = page.src
|
|
311
310
|
}, 200)
|
|
312
311
|
}
|
|
313
312
|
|
package/src/store.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// This is a simple mimic of svelte/store.
|
|
2
|
+
export type Subscriber<T> = (value: T) => void;
|
|
3
|
+
export type Unsubscriber = () => void;
|
|
4
|
+
export type Updater<T> = (value: T) => T;
|
|
5
|
+
export type StartStopNotifier<T> = (set: Subscriber<T>) => Unsubscriber | void;
|
|
6
|
+
|
|
7
|
+
export interface Readable<T> {
|
|
8
|
+
readonly value: T;
|
|
9
|
+
subscribe(this: void, run: Subscriber<T>): Unsubscriber;
|
|
10
|
+
reaction(this: void, run: Subscriber<T>): Unsubscriber;
|
|
11
|
+
dispose(value?: T): void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface Writable<T> extends Readable<T> {
|
|
15
|
+
set(this: void, value: T): void;
|
|
16
|
+
update(this: void, updater: Updater<T>): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
20
|
+
function noop() {}
|
|
21
|
+
|
|
22
|
+
function safe_not_equal(a: unknown, b: unknown) {
|
|
23
|
+
return a != a ? b == b : a !== b || (a && typeof a === "object") || typeof a === "function";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function readable<T>(value: T, start: StartStopNotifier<T> = noop): Readable<T> {
|
|
27
|
+
let stop: Unsubscriber | undefined;
|
|
28
|
+
const subscribers = new Set<Subscriber<T>>();
|
|
29
|
+
function set(new_value: T) {
|
|
30
|
+
if (safe_not_equal(value, new_value)) {
|
|
31
|
+
value = new_value;
|
|
32
|
+
if (stop) {
|
|
33
|
+
for (const run of subscribers) {
|
|
34
|
+
run(value);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function subscribe(run: Subscriber<T>) {
|
|
40
|
+
subscribers.add(run);
|
|
41
|
+
if (subscribers.size === 1) {
|
|
42
|
+
stop = start(set) || noop;
|
|
43
|
+
}
|
|
44
|
+
run(value);
|
|
45
|
+
return () => {
|
|
46
|
+
subscribers.delete(run);
|
|
47
|
+
if (subscribers.size === 0) {
|
|
48
|
+
stop && stop();
|
|
49
|
+
stop = undefined;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function reaction(run: Subscriber<T>) {
|
|
54
|
+
subscribers.add(run);
|
|
55
|
+
if (subscribers.size === 1) {
|
|
56
|
+
stop = start(set) || noop;
|
|
57
|
+
}
|
|
58
|
+
return () => {
|
|
59
|
+
subscribers.delete(run);
|
|
60
|
+
if (subscribers.size === 0) {
|
|
61
|
+
stop && stop();
|
|
62
|
+
stop = undefined;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function dispose(new_value?: T) {
|
|
67
|
+
if (new_value !== undefined) {
|
|
68
|
+
set(new_value);
|
|
69
|
+
}
|
|
70
|
+
subscribers.clear();
|
|
71
|
+
stop && stop();
|
|
72
|
+
stop = undefined;
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
get value() {
|
|
76
|
+
if (subscribers.size === 0) {
|
|
77
|
+
stop = start(set) || noop;
|
|
78
|
+
stop();
|
|
79
|
+
stop = undefined;
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
},
|
|
83
|
+
subscribe,
|
|
84
|
+
reaction,
|
|
85
|
+
dispose,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function writable<T>(value: T, start: StartStopNotifier<T> = noop, set: Subscriber<T>): Writable<T> {
|
|
90
|
+
const internal = readable(value, start);
|
|
91
|
+
return {
|
|
92
|
+
get value() {
|
|
93
|
+
return internal.value;
|
|
94
|
+
},
|
|
95
|
+
subscribe: internal.subscribe,
|
|
96
|
+
reaction: internal.reaction,
|
|
97
|
+
set,
|
|
98
|
+
update(fn: Updater<T>) {
|
|
99
|
+
set(fn(internal.value));
|
|
100
|
+
},
|
|
101
|
+
dispose: internal.dispose,
|
|
102
|
+
};
|
|
103
|
+
}
|