@netless/forge-slide 0.1.1-alpha.8 → 1.0.0-alpha.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.
@@ -1,112 +1,193 @@
1
- import {SlideApplicationOption} from "./SlideApplication";
2
- import EventEmitter from "eventemitter3";
1
+ import { SlideApplicationOption } from './SlideApplication';
2
+ import EventEmitter from 'eventemitter3';
3
+ import { kvStore } from '@netless/forge-room';
3
4
 
4
5
  interface SideBarViewEvents {
5
6
  pageChange: (index: number) => void;
6
7
  }
7
8
 
8
9
  export class SideBarView extends EventEmitter<SideBarViewEvents> {
9
- public readonly root: HTMLDivElement = document.createElement("div");
10
- private isShow: boolean = false;
11
- private itemList: HTMLDivElement[] = [];
12
-
13
- public constructor() {
14
- super();
15
- this.root.style.backgroundColor = '#eee';
16
- this.root.className = "forge-slide-sidebar";
17
- this.root.style.width = "240px";
18
- this.root.style.height = "100%";
19
- this.root.style.position = "absolute";
20
- this.root.style.left = "-240px";
21
- this.root.style.top = "0";
22
- this.root.style.zIndex = "5";
23
- this.root.style.transition = "left 0.3s ease-in-out";
24
- this.root.style.overflow = "auto";
25
- this.root.style.display = "flex";
26
- this.root.style.flexDirection = "column";
27
- this.root.style.justifyContent = "flex-start";
28
- this.root.style.alignItems = "center";
10
+ public readonly root: HTMLDivElement = document.createElement('div');
11
+ private isShow: boolean = false;
12
+ private eventsMap: Map<HTMLDivElement, (() => void)[]> = new Map();
13
+ private taskId: string = '';
14
+ private prefix: string = '';
15
+ private addImagesPool: number[] = [];
16
+ private scheduleId: number = 0;
17
+ private isSchedule = false;
18
+
19
+ public constructor() {
20
+ super();
21
+ this.root.style.backgroundColor = '#eee';
22
+ this.root.className = 'forge-slide-sidebar';
23
+ this.root.style.width = '240px';
24
+ this.root.style.height = '100%';
25
+ this.root.style.position = 'absolute';
26
+ this.root.style.left = '-240px';
27
+ this.root.style.top = '0';
28
+ this.root.style.zIndex = '5';
29
+ this.root.style.transition = 'left 0.3s ease-in-out';
30
+ this.root.style.overflow = 'auto';
31
+ this.root.style.display = 'flex';
32
+ this.root.style.flexDirection = 'column';
33
+ this.root.style.justifyContent = 'flex-start';
34
+ this.root.style.alignItems = 'center';
35
+ this.root.style.userSelect = 'none';
36
+ }
37
+
38
+ get isShowSideBar(): boolean {
39
+ return this.isShow;
40
+ }
41
+
42
+ public async getPreviewImage(imageUrl: string) {
43
+ const image = await fetch(imageUrl);
44
+ const blob = await image.blob();
45
+ return new Promise<PreviewImage>(resolve => {
46
+ const reader = new FileReader();
47
+ reader.onloadend = () => {
48
+ const base64Data = reader.result as string;
49
+ const img = document.createElement('img');
50
+ img.src = base64Data;
51
+ img.onload = () => {
52
+ resolve({
53
+ url: imageUrl,
54
+ src: base64Data,
55
+ width: img.width,
56
+ height: img.height,
57
+ });
58
+ };
59
+ };
60
+ reader.readAsDataURL(blob);
61
+ });
62
+ }
63
+
64
+ private async appendPreviewImage(pageIndex: number) {
65
+ const itemContainer = document.createElement('div');
66
+ itemContainer.style.width = '60%';
67
+ itemContainer.style.display = 'flex';
68
+ itemContainer.style.justifyContent = 'center';
69
+ itemContainer.style.alignItems = 'flex-start';
70
+ itemContainer.style.position = 'relative';
71
+ itemContainer.style.borderRadius = '4px';
72
+ itemContainer.style.transition = 'border-color .3s';
73
+ itemContainer.style.marginBottom = '10px';
74
+
75
+ const onMouseOverHandle = () => {
76
+ itemContainer.style.borderColor = '#ccc';
77
+ };
78
+ const onMouseOutHandle = () => {
79
+ itemContainer.style.borderColor = 'transparent';
80
+ };
81
+ const onClickHandle = () => {
82
+ this.emit('pageChange', pageIndex);
83
+ };
84
+
85
+ itemContainer.addEventListener('click', onClickHandle);
86
+ itemContainer.addEventListener('mouseover', onMouseOverHandle);
87
+ itemContainer.addEventListener('mouseout', onMouseOutHandle);
88
+ this.eventsMap.set(itemContainer, [onClickHandle, onMouseOverHandle, onMouseOutHandle]);
89
+
90
+ const pageIndexContainer = document.createElement('span');
91
+ pageIndexContainer.textContent = `${pageIndex}`;
92
+ pageIndexContainer.style.position = 'absolute';
93
+ pageIndexContainer.style.top = '1px';
94
+ pageIndexContainer.style.left = '-10px';
95
+ pageIndexContainer.style.transform = 'translate(-100%)';
96
+ pageIndexContainer.style.fontSize = '12px';
97
+ pageIndexContainer.style.color = '#5f5f5f';
98
+
99
+ const previewUrl = `${this.prefix}/${this.taskId}/preview/${pageIndex}.png`;
100
+ const preview = document.createElement('img');
101
+ const cachePreview = await kvStore.getItem(previewUrl);
102
+ if (cachePreview) {
103
+ const { src } = JSON.parse(cachePreview);
104
+ preview.src = src;
105
+ } else {
106
+ const previewInfo = await this.getPreviewImage(previewUrl);
107
+ await kvStore.setItem(previewUrl, JSON.stringify(previewInfo));
108
+ const { src } = previewInfo;
109
+ preview.src = src;
29
110
  }
30
111
 
31
- get isShowSideBar(): boolean {
32
- return this.isShow;
33
- }
112
+ preview.style.width = '100%';
113
+ preview.style.display = 'inline-block';
34
114
 
35
- private onMouseOver = (itemContainer: HTMLDivElement) => {
36
- itemContainer.style.borderColor = "#ccc";
37
- }
115
+ itemContainer.appendChild(preview);
116
+ itemContainer.appendChild(pageIndexContainer);
38
117
 
39
- private onMouseOut = (itemContainer: HTMLDivElement) => {
40
- itemContainer.style.borderColor = "transparent";
41
- }
118
+ this.root.appendChild(itemContainer);
119
+ }
42
120
 
43
- private onClickHandle = (index: number) => {
44
- this.emit("pageChange", index);
121
+ private async scheduleGetPreviewImage() {
122
+ if (!this.isSchedule) {
123
+ this.scheduleId = setTimeout(async () => {
124
+ await this.scheduleGetPreviewImage();
125
+ }, 32) as unknown as number;
126
+ return;
45
127
  }
46
-
47
- public initialize(slideCount: number, option: SlideApplicationOption): void {
48
- for (let i = 1; i <= slideCount; i++) {
49
- const itemContainer = document.createElement("div");
50
- this.itemList.push(itemContainer);
51
- itemContainer.style.width = "60%";
52
- itemContainer.style.display = "flex";
53
- itemContainer.style.justifyContent = "center";
54
- itemContainer.style.alignItems = "flex-start";
55
- itemContainer.style.position = "relative";
56
- itemContainer.style.borderRadius = "4px"
57
- itemContainer.style.transition = "border-color .3s";
58
- itemContainer.style.marginBottom = "10px";
59
-
60
- itemContainer.addEventListener("mouseover", () => this.onMouseOver(itemContainer))
61
- itemContainer.addEventListener("mouseout", () => this.onMouseOut(itemContainer))
62
- itemContainer.addEventListener("click", () => this.onClickHandle(i))
63
-
64
- const pageIndex = document.createElement("span");
65
- pageIndex.textContent = `${i}`;
66
- pageIndex.style.position = "absolute";
67
- pageIndex.style.top = "1px";
68
- pageIndex.style.left = "-10px";
69
- pageIndex.style.transform = "translate(-100%)";
70
- pageIndex.style.fontSize = "12px";
71
- pageIndex.style.color = "#5f5f5f";
72
-
73
- const preview = document.createElement("img");
74
- preview.style.width = "100%";
75
- preview.style.display = "inline-block";
76
- preview.src = `${option.prefix}/${option.taskId}/preview/${i}.png`;
77
-
78
- itemContainer.appendChild(preview);
79
- itemContainer.appendChild(pageIndex);
80
-
81
- this.root.appendChild(itemContainer);
82
- }
128
+ const pageIndex = this.addImagesPool.shift();
129
+ if (!pageIndex) {
130
+ clearTimeout(this.scheduleId);
131
+ this.scheduleId = 0;
132
+ return;
83
133
  }
84
-
85
- public hidden() {
86
- if (!this.root) {
87
- return;
88
- }
89
- this.root.style.left = "-240px";
90
- this.root.style.border = "none";
91
- this.root.style.boxShadow = "none";
92
- this.isShow = false;
134
+ await this.appendPreviewImage(pageIndex);
135
+ this.scheduleId = setTimeout(async () => {
136
+ await this.scheduleGetPreviewImage();
137
+ }, 32) as unknown as number;
138
+ }
139
+
140
+ public async startGetPreviewImageSchedule() {
141
+ this.isSchedule = true;
142
+ }
143
+
144
+ public pauseGetPreviewImageSchedule() {
145
+ this.isSchedule = false;
146
+ }
147
+
148
+ public initialize(slideCount: number, option: SlideApplicationOption): void {
149
+ this.taskId = option.taskId;
150
+ this.prefix = option.prefix;
151
+ for (let i = 1; i <= slideCount; i++) {
152
+ this.addImagesPool.push(i);
93
153
  }
154
+ this.scheduleGetPreviewImage();
155
+ }
94
156
 
95
- public show() {
96
- if (!this.root) {
97
- return;
98
- }
99
- this.root.style.left = "0";
100
- this.root.style.border = "1px solid #ccc";
101
- this.root.style.boxShadow = "0 0 10px rgba(0, 0, 0, 0.1)";
102
- this.isShow = true;
157
+ public hidden() {
158
+ if (!this.root || !this.isShow) {
159
+ return;
103
160
  }
104
-
105
- public dispose() {
106
- this.itemList.forEach(item => {
107
- item.removeEventListener("mouseover", () => this.onMouseOver(item))
108
- item.removeEventListener("mouseout", () => this.onMouseOut(item))
109
- })
161
+ this.root.style.left = '-240px';
162
+ this.root.style.border = 'none';
163
+ this.root.style.boxShadow = 'none';
164
+ this.isShow = false;
165
+ }
166
+
167
+ public show() {
168
+ if (!this.root) {
169
+ return;
110
170
  }
171
+ this.root.style.left = '0';
172
+ this.root.style.border = '1px solid #ccc';
173
+ this.root.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.1)';
174
+ this.isShow = true;
175
+ }
176
+
177
+ public dispose() {
178
+ this.removeAllListeners();
179
+ this.eventsMap.forEach((handlers, element) => {
180
+ const [clickEvent, mouseOverEvent, mouseOutEvent] = handlers;
181
+ element.removeEventListener('click', clickEvent);
182
+ element.removeEventListener('mouseover', mouseOverEvent);
183
+ element.removeEventListener('mouseout', mouseOutEvent);
184
+ });
185
+ }
186
+ }
111
187
 
112
- }
188
+ interface PreviewImage {
189
+ url: string;
190
+ src: string;
191
+ width: number;
192
+ height: number;
193
+ }
package/src/Slide.ts CHANGED
@@ -1,6 +1,6 @@
1
- import EventEmitter from "eventemitter3";
2
- import { ApplicationInstanceType } from "@netless/forge-room";
3
- import { ForgeSlidePermissionFlag, ForgeSlidePermissions } from "./ForgeSlidePermession";
1
+ import EventEmitter from 'eventemitter3';
2
+ import { ApplicationInstanceType } from '@netless/forge-room';
3
+ import { ForgeSlidePermissionFlag, ForgeSlidePermissions } from './ForgeSlidePermession';
4
4
 
5
5
  export interface SlideEvents {
6
6
  /**
@@ -39,58 +39,57 @@ export interface SlideEvents {
39
39
 
40
40
  export class SlideForge extends EventEmitter<SlideEvents> implements ApplicationInstanceType {
41
41
 
42
- public readonly view!: HTMLDivElement;
43
- public readonly permissions!: ForgeSlidePermissions;
44
- public readonly footView!: HTMLDivElement;
45
- public readonly sideBarView!: HTMLDivElement;
46
- public readonly whiteboardView!: HTMLDivElement;
47
- /**
42
+ public readonly view!: HTMLDivElement;
43
+ public readonly permissions!: ForgeSlidePermissions;
44
+ public readonly footView!: HTMLDivElement;
45
+ public readonly sideBarView!: HTMLDivElement;
46
+ /**
48
47
  * 当前页面索引, 从 0 开始
49
48
  */
50
- public readonly pageIndex!: number;
51
- /**
49
+ public readonly pageIndex!: number;
50
+ /**
52
51
  * 总页数
53
52
  */
54
- public readonly pageCount!: number;
55
- /**
53
+ public readonly pageCount!: number;
54
+ /**
56
55
  * 切换到参数指定页面, index 从 0 开始
57
56
  * @param {number} index 页面索引
58
57
  */
59
- public goto!: (index: number) => void
60
- /**
58
+ public goto!: (index: number) => void;
59
+ /**
61
60
  * 下一步, 如果已经是本页的最后一步, 则切换到下一页
62
61
  */
63
- public nextStep!: () => void
64
- /**
62
+ public nextStep!: () => void;
63
+ /**
65
64
  * 上一步, 如果已经是本页的第一步, 则切换到上一页
66
65
  */
67
- public prevStep!: () => void
68
- /**
66
+ public prevStep!: () => void;
67
+ /**
69
68
  * 下一页, 如果是最后一页, 则不执行任何操作
70
69
  */
71
- public nextPage!: () => void
72
- /**
70
+ public nextPage!: () => void;
71
+ /**
73
72
  * 上一页, 如果是第一页, 则不执行任何操作
74
73
  */
75
- public prevPage!: () => void
76
- /**
74
+ public prevPage!: () => void;
75
+ /**
77
76
  * 切换侧栏显示状态
78
77
  */
79
- public sideBarToggle!: () => void
78
+ public sideBarToggle!: () => void;
80
79
 
81
- /**
80
+ /**
82
81
  * 获取预览图图片内容, base64 编码
83
82
  * @param {number} index 页面索引
84
83
  */
85
- public imgContent!: (index: number) => Promise<string>;
86
- /**
84
+ public imgContent!: (index: number) => Promise<string>;
85
+ /**
87
86
  * 获取预览图图片链接
88
87
  * @param {number} index 页面索引
89
88
  */
90
- public imgUrl!: (index: number) => Promise<string>;
91
- /**
89
+ public imgUrl!: (index: number) => Promise<string>;
90
+ /**
92
91
  * 获取预览图图片尺寸
93
92
  * @param {number} index 页面索引
94
93
  */
95
- public imgSize!: (index: number) => Promise<{width: number, height: number}>;
94
+ public imgSize!: (index: number) => Promise<{width: number, height: number}>;
96
95
  }