@netless/app-presentation 0.1.1 → 0.1.3

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/src/preload.ts CHANGED
@@ -1,68 +1,169 @@
1
1
  import type { IDisposable } from "@wopjs/disposable";
2
2
  import type { PresentationPage } from "./presentation";
3
3
 
4
- export class Preload implements IDisposable {
5
- readonly links: HTMLLinkElement[]
6
-
7
- head = 0
8
- tail = 0
9
- timer = 0
10
- count = 0
4
+ export enum ELoadState {
5
+ unloaded,
6
+ loading,
7
+ loaded,
8
+ error
9
+ }
11
10
 
12
- get done(): boolean {
13
- return this.head == 0 && this.tail == this.pages.length
14
- }
11
+ export interface IPreloadMapValue {
12
+ src: string
13
+ state: ELoadState
14
+ }
15
15
 
16
+ export class Preload implements IDisposable {
17
+ static maxLinks: number = 5;
18
+ readonly loadingLinks: Map<number, HTMLLinkElement> = new Map();
19
+ readonly preloadMap = new Map<number, IPreloadMapValue>();
20
+ readonly waitingLoadSet: Set<number> = new Set();
21
+ readonly waitinglinks: number[] = [];
22
+ touchIndex: number = 0;
23
+ preloadSize: number = 0;
16
24
  constructor(readonly pages: PresentationPage[]) {
17
- this.links = new Array(pages.length)
18
- this.touch(0)
25
+ this.preloadMap = new Map(pages.map((e,index) => [index, {
26
+ src: e.src,
27
+ state: ELoadState.unloaded
28
+ }]));
29
+ this.waitingLoadSet = new Set(pages.map((e,index) => index));
30
+ this.preloadSize = this.preloadMap.size;
31
+ this.touch(0);
19
32
  }
20
-
21
- touch(index: number) {
22
- if (this.done) return
23
- this.head = this.tail = index
24
- this.timer ||= setTimeout(this.handler.bind(this))
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;
25
70
  }
26
-
27
- handler() {
28
- const { links } = this
29
-
30
- while (this.head > 0 && links[this.head]) this.head--
31
- if (!links[this.head]) {
32
- const link = document.createElement('link')
33
- links[this.head] = link
34
- link.rel = 'preload'
35
- link.as = 'image'
36
- link.href = this.pages[this.head].src
37
- link.dataset.order = this.count + ''
38
- document.head.appendChild(link)
39
- this.count++
71
+ private autoTouch(){
72
+ this.handler();
73
+ if (this.waitingLoadSet.size === 0) {
74
+ return;
40
75
  }
41
-
42
- while (links[this.tail]) this.tail++
43
- if (this.tail < this.pages.length && !links[this.tail]) {
44
- const link = document.createElement('link')
45
- links[this.tail] = link
46
- link.rel = 'preload'
47
- link.as = 'image'
48
- link.href = this.pages[this.tail].src
49
- link.dataset.order = this.count + ''
50
- document.head.appendChild(link)
51
- this.count++
76
+ let nextTouchDelay = 1000;
77
+ this.touchIndex = (this.touchIndex + 1) % this.preloadSize;
78
+ const value = this.preloadMap.get(this.touchIndex);
79
+ if (value) {
80
+ switch (value.state) {
81
+ case ELoadState.unloaded:
82
+ case ELoadState.error:
83
+ if (!this.waitinglinks.includes(this.touchIndex)) {
84
+ this.waitinglinks.push(this.touchIndex);
85
+ this.waitingLoadSet.delete(this.touchIndex);
86
+ }
87
+ nextTouchDelay = 0;
88
+ break;
89
+ }
52
90
  }
53
-
54
- this.timer = setTimeout(this.handler.bind(this), 2_000)
55
- this.count++
56
-
57
- if (this.count >= 10) for (const link of this.links)
58
- if (link && link.dataset.order && +link.dataset.order < this.count - 10)
59
- document.head.contains(link) && document.head.removeChild(link)
91
+ this.requestAsyncCallBack(()=>{
92
+ this.autoTouch();
93
+ }, nextTouchDelay)
60
94
  }
61
95
 
96
+ private createLink(index: number, resolve?:(state:ELoadState)=>void) {
97
+ const value = this.preloadMap.get(index);
98
+ if (value) {
99
+ const link = document.createElement('link');
100
+ link.rel = 'preload';
101
+ link.as = 'image';
102
+ link.href = value.src;
103
+ link.dataset.order = index + '';
104
+ document.head.appendChild(link);
105
+ link.onload = () => {
106
+ value.state = ELoadState.loaded;
107
+ this.preloadMap.set(index, value);
108
+ document.head.contains(link) && document.head.removeChild(link);
109
+ this.loadingLinks.delete(index);
110
+ resolve && resolve(ELoadState.loaded);
111
+ this.autoTouch();
112
+ }
113
+ link.onerror = () => {
114
+ value.state = ELoadState.error;
115
+ this.preloadMap.set(index, value);
116
+ if (!this.waitinglinks.includes(index)) {
117
+ this.waitinglinks.push(index);
118
+ this.waitingLoadSet.delete(this.touchIndex);
119
+ }
120
+ document.head.contains(link) && document.head.removeChild(link);
121
+ this.loadingLinks.delete(index);
122
+ resolve && resolve(ELoadState.error);
123
+ this.autoTouch();
124
+ }
125
+ this.loadingLinks.set(index, link);
126
+ value.state = ELoadState.loading;
127
+ this.preloadMap.set(index, value);
128
+ }
129
+ }
130
+ private handler() {
131
+ if(!this.waitinglinks.length) {
132
+ return;
133
+ }
134
+ let i = this.loadingLinks.size;
135
+ while (i < Preload.maxLinks) {
136
+ const index = this.waitinglinks.shift();
137
+ if (index !== undefined) {
138
+ this.createLink(index);
139
+ }
140
+ i++;
141
+ }
142
+ }
143
+ private async requestAsyncCallBack (callBack:()=>void, timeout:number):Promise<void> {
144
+ await new Promise(function(resolve) {
145
+ if ((window as any).requestIdleCallback) {
146
+ requestIdleCallback(()=>{
147
+ resolve(1);
148
+ },{timeout})
149
+ } else {
150
+ setTimeout(()=>{
151
+ resolve(2);
152
+ }, timeout)
153
+ }
154
+ });
155
+ callBack();
156
+ }
157
+ private destroyLink(index: number, link: HTMLLinkElement) {
158
+ document.head.contains(link) && document.head.removeChild(link)
159
+ this.loadingLinks.delete(index)
160
+ }
62
161
  dispose() {
63
- clearTimeout(this.timer)
64
- for (const link of this.links)
65
- link && document.head.contains(link) && document.head.removeChild(link)
66
- this.links.length = 0
162
+ for (const [index,link] of this.loadingLinks) {
163
+ this.destroyLink(index, link);
164
+ }
165
+ this.preloadMap.clear();
166
+ this.waitingLoadSet.clear();
167
+ this.waitinglinks.length = 0;
67
168
  }
68
169
  }
@@ -73,6 +73,7 @@ export class Presentation implements IDisposable<void> {
73
73
  showPreview = false
74
74
  pageIndex = 0
75
75
  previewLazyload: ILazyLoadInstance | null = null
76
+ timer: number | null = null
76
77
 
77
78
  constructor(config: PresentationConfig) {
78
79
  this.pages = config.pages
@@ -285,15 +286,22 @@ export class Presentation implements IDisposable<void> {
285
286
  }
286
287
 
287
288
  updateImage() {
288
- const page = this.page()
289
- if (!page) {
290
- this.image.src = ''
291
- return
289
+ if (this.timer) {
290
+ clearTimeout(this.timer)
292
291
  }
293
- this.image.width = page.width
294
- this.image.height = page.height
295
- this.image.src = page.src
296
- this.preload.touch(this.pageIndex)
292
+ this.timer = setTimeout(() => {
293
+ this.timer = null;
294
+ this.preload.touch(this.pageIndex).then(() => {
295
+ const page = this.page()
296
+ if (!page) {
297
+ this.image.src = ''
298
+ return
299
+ }
300
+ this.image.width = page.width
301
+ this.image.height = page.height
302
+ this.image.src = page.src
303
+ })
304
+ }, 200)
297
305
  }
298
306
 
299
307
  /** Returns a modified URL that points to the thumbnail image. */