@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/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
- readonly loadingLinks: Map<number, HTMLLinkElement> = new Map();
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
- readonly waitingLoadSet: Set<number> = new Set();
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
- private autoTouch(){
72
- this.handler();
73
- if (this.waitingLoadSet.size === 0) {
74
- return;
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
- 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;
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, resolve?:(state:ELoadState)=>void) {
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
- 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();
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
- 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);
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, link);
126
- value.state = ELoadState.loading;
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
- 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);
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
- i++;
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
- for (const [index,link] of this.loadingLinks) {
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
  }
@@ -11,7 +11,7 @@ export interface PresentationPage {
11
11
  width: number;
12
12
  height: number;
13
13
  thumbnail?: string | undefined;
14
- name?: string | undefined;
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).then(() => {
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
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
+ }