@netless/app-presentation 0.1.5 → 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/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 {
@@ -76,6 +76,8 @@ export class Presentation implements IDisposable<void> {
76
76
  previewLazyload: ILazyLoadInstance | null = null
77
77
  timer: number | null = null
78
78
 
79
+ justDocsViewReadonly = false;
80
+
79
81
  constructor(config: PresentationConfig) {
80
82
  this.pages = config.pages
81
83
  this.preload = new Preload(this.pages)
@@ -100,6 +102,10 @@ export class Presentation implements IDisposable<void> {
100
102
  this.initialize()
101
103
  }
102
104
 
105
+ setDocsViewReadonly(readonly: boolean) {
106
+ this.justDocsViewReadonly = readonly
107
+ }
108
+
103
109
  initialize() {
104
110
  if (this.initialized) return
105
111
  this.initialized = true
@@ -223,7 +229,7 @@ export class Presentation implements IDisposable<void> {
223
229
  pageNumber.appendChild(totalPage)
224
230
 
225
231
  this.dispose.add(listen(window, "keydown", ev => {
226
- if (this.readonly || this.isEditable(ev.target)) return
232
+ if (this.readonly || this.isEditable(ev.target) || this.justDocsViewReadonly) return
227
233
  if ((ev.key == 'ArrowUp' || ev.key == 'ArrowLeft') && this.pageIndex > 0)
228
234
  this.onNewPageIndex(this.pageIndex - 1, 'keydown')
229
235
  else if ((ev.key == 'ArrowDown' || ev.key == 'ArrowRight') && this.pageIndex < this.pages.length - 1)
@@ -292,16 +298,15 @@ export class Presentation implements IDisposable<void> {
292
298
  }
293
299
  this.timer = setTimeout(() => {
294
300
  this.timer = null;
295
- this.preload.touch(this.pageIndex).then(() => {
296
- const page = this.page()
297
- if (!page) {
298
- this.image.src = ''
299
- return
300
- }
301
- this.image.width = page.width
302
- this.image.height = page.height
303
- this.image.src = page.src
304
- })
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
305
310
  }, 200)
306
311
  }
307
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
+ }