@netless/app-presentation 0.1.7 → 0.1.8-beta.1
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/dist/index.d.ts +9 -11
- package/dist/index.global.js +238 -158
- package/dist/index.js +238 -158
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +238 -158
- package/dist/index.mjs.map +1 -1
- 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/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
|
+
}
|