@netless/fastboard 0.1.0 → 0.2.2
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.js +3 -397
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -389
- package/dist/index.mjs.map +1 -1
- package/package.json +18 -15
- package/src/index.ts +2 -80
- package/src/base.ts +0 -55
- package/src/core.ts +0 -307
- package/src/emitter.ts +0 -21
- package/src/register-apps.ts +0 -31
- package/src/utils.ts +0 -74
- package/src/value.ts +0 -74
package/src/core.ts
DELETED
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AnimationMode,
|
|
3
|
-
ApplianceNames,
|
|
4
|
-
Camera,
|
|
5
|
-
Color,
|
|
6
|
-
ConversionResponse,
|
|
7
|
-
MemberState,
|
|
8
|
-
Rectangle,
|
|
9
|
-
RoomState,
|
|
10
|
-
SceneDefinition,
|
|
11
|
-
ShapeType,
|
|
12
|
-
} from "white-web-sdk";
|
|
13
|
-
|
|
14
|
-
import { BuiltinApps } from "@netless/window-manager";
|
|
15
|
-
import { FastboardAppBase } from "./base";
|
|
16
|
-
import { convertedFileToScene, genUID, getImageSize, makeSlideParams } from "./utils";
|
|
17
|
-
|
|
18
|
-
export interface InsertDocsStatic {
|
|
19
|
-
readonly fileType: "pdf" | "ppt";
|
|
20
|
-
readonly scenePath: string;
|
|
21
|
-
readonly scenes: SceneDefinition[];
|
|
22
|
-
readonly title?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface InsertDocsDynamic {
|
|
26
|
-
readonly fileType: "pptx";
|
|
27
|
-
readonly scenePath: string;
|
|
28
|
-
readonly taskId: string;
|
|
29
|
-
readonly title?: string;
|
|
30
|
-
readonly url?: string;
|
|
31
|
-
/** @example [{ name: '1' }, { name: '2' }, { name: '3' }] */
|
|
32
|
-
readonly scenes?: SceneDefinition[];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface InsertMediaParams {
|
|
36
|
-
title: string;
|
|
37
|
-
src: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export type InsertDocsParams = InsertDocsStatic | InsertDocsDynamic;
|
|
41
|
-
|
|
42
|
-
export type SetMemberStateFn = (partialMemberState: Partial<MemberState>) => void;
|
|
43
|
-
|
|
44
|
-
export type RoomStateChanged = (diff: Partial<RoomState>) => void;
|
|
45
|
-
|
|
46
|
-
export class FastboardApp extends FastboardAppBase {
|
|
47
|
-
/**
|
|
48
|
-
* Render this app to some DOM.
|
|
49
|
-
*/
|
|
50
|
-
bindContainer(container: HTMLElement) {
|
|
51
|
-
this._assertNotDestroyed();
|
|
52
|
-
this.manager.bindContainer(container);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Move window-manager's collector to some place.
|
|
57
|
-
*/
|
|
58
|
-
bindCollector(container: HTMLElement) {
|
|
59
|
-
this._assertNotDestroyed();
|
|
60
|
-
this.manager.bindCollectorContainer(container);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Is current room writable?
|
|
65
|
-
*/
|
|
66
|
-
readonly writable = this.createValue(
|
|
67
|
-
this.room.isWritable,
|
|
68
|
-
set => this._addRoomListener("onEnableWriteNowChanged", () => set(this.room.isWritable)),
|
|
69
|
-
this.room.setWritable.bind(this.room)
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Current window-manager's windows' state (is it maximized?).
|
|
74
|
-
*/
|
|
75
|
-
readonly boxState = this.createValue(this.manager.boxState, set =>
|
|
76
|
-
this._addManagerListener("boxStateChange", set)
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Current window-manager's focused app's id.
|
|
81
|
-
* @example "HelloWorld-1A2b3C4d"
|
|
82
|
-
*/
|
|
83
|
-
readonly focusedApp = this.createValue(this.manager.focused, set =>
|
|
84
|
-
this._addManagerListener("focusedChange", set)
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* How many times can I call `app.redo()`?
|
|
89
|
-
*/
|
|
90
|
-
readonly canRedoSteps = this.createValue(this.manager.mainView.canRedoSteps, set =>
|
|
91
|
-
this._addMainViewListener("onCanRedoStepsUpdate", set)
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* How many times can I call `app.undo()`?
|
|
96
|
-
*/
|
|
97
|
-
readonly canUndoSteps = this.createValue(this.manager.mainView.canUndoSteps, set =>
|
|
98
|
-
this._addMainViewListener("onCanUndoStepsUpdate", set)
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Current camera information of main view.
|
|
103
|
-
*/
|
|
104
|
-
readonly camera = this.createValue(
|
|
105
|
-
this.manager.mainView.camera,
|
|
106
|
-
set => this._addMainViewListener("onCameraUpdated", set),
|
|
107
|
-
this.manager.moveCamera.bind(this.manager)
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Current tool's info, like "is using pencil?", "what color?".
|
|
112
|
-
*/
|
|
113
|
-
readonly memberState = this.createValue<MemberState, SetMemberStateFn>(
|
|
114
|
-
this.room.state.memberState,
|
|
115
|
-
set => this._addRoomListener<RoomStateChanged>("onRoomStateChanged", ({ memberState: m }) => m && set(m)),
|
|
116
|
-
this.manager.mainView.setMemberState.bind(this.manager.mainView)
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Undo a step on main view.
|
|
121
|
-
*/
|
|
122
|
-
undo() {
|
|
123
|
-
this._assertNotDestroyed();
|
|
124
|
-
this.manager.mainView.undo();
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Redo a step on main view.
|
|
129
|
-
*/
|
|
130
|
-
redo() {
|
|
131
|
-
this._assertNotDestroyed();
|
|
132
|
-
this.manager.mainView.redo();
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Move current main view's camera position.
|
|
137
|
-
*/
|
|
138
|
-
moveCamera(camera: Partial<Camera> & { animationMode?: AnimationMode | undefined }) {
|
|
139
|
-
this._assertNotDestroyed();
|
|
140
|
-
this.manager.moveCamera(camera);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Move current main view's camera to include a rectangle.
|
|
145
|
-
*/
|
|
146
|
-
moveCameraToContain(rectangle: Rectangle & { animationMode?: AnimationMode }) {
|
|
147
|
-
this._assertNotDestroyed();
|
|
148
|
-
this.manager.moveCameraToContain(rectangle);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Delete all things on the main view.
|
|
153
|
-
*/
|
|
154
|
-
cleanCurrentScene() {
|
|
155
|
-
this._assertNotDestroyed();
|
|
156
|
-
this.manager.mainView.cleanCurrentScene();
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Set current tool, like "pencil".
|
|
161
|
-
*/
|
|
162
|
-
setAppliance(appliance: ApplianceNames, shape?: ShapeType) {
|
|
163
|
-
this._assertNotDestroyed();
|
|
164
|
-
this.manager.mainView.setMemberState({ currentApplianceName: appliance, shapeType: shape });
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
setStrokeWidth(strokeWidth: number) {
|
|
168
|
-
this._assertNotDestroyed();
|
|
169
|
-
this.manager.mainView.setMemberState({ strokeWidth });
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
setStrokeColor(strokeColor: Color) {
|
|
173
|
-
this._assertNotDestroyed();
|
|
174
|
-
this.manager.mainView.setMemberState({ strokeColor });
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Insert an image to the main view.
|
|
179
|
-
*/
|
|
180
|
-
async insertImage(url: string) {
|
|
181
|
-
this._assertNotDestroyed();
|
|
182
|
-
await this.manager.switchMainViewToWriter();
|
|
183
|
-
|
|
184
|
-
const { divElement } = this.manager.mainView;
|
|
185
|
-
const containerSize = {
|
|
186
|
-
width: divElement?.scrollWidth || window.innerWidth,
|
|
187
|
-
height: divElement?.scrollHeight || window.innerHeight,
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
// 1. shrink the image a little to fit container **width**
|
|
191
|
-
const maxWidth = containerSize.width * 0.8;
|
|
192
|
-
let { width, height } = await getImageSize(url, containerSize);
|
|
193
|
-
const scale = Math.min(maxWidth / width, 1);
|
|
194
|
-
const uuid = genUID();
|
|
195
|
-
const { centerX, centerY } = this.manager.camera;
|
|
196
|
-
width *= scale;
|
|
197
|
-
height *= scale;
|
|
198
|
-
this.manager.mainView.insertImage({ uuid, centerX, centerY, width, height, locked: false });
|
|
199
|
-
this.manager.mainView.completeImageUpload(uuid, url);
|
|
200
|
-
|
|
201
|
-
// 2. move camera to fit image **height**
|
|
202
|
-
width /= 0.8;
|
|
203
|
-
height /= 0.8;
|
|
204
|
-
const originX = centerX - width / 2;
|
|
205
|
-
const originY = centerY - height / 2;
|
|
206
|
-
this.manager.moveCameraToContain({ originX, originY, width, height });
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Insert PDF/PPTX from conversion result.
|
|
211
|
-
* @param status https://developer.netless.link/server-en/home/server-conversion#get-query-task-conversion-progress
|
|
212
|
-
*/
|
|
213
|
-
insertDocs(filename: string, status: ConversionResponse): Promise<string | undefined>;
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Manual way.
|
|
217
|
-
* @example
|
|
218
|
-
* app.insertDocs({
|
|
219
|
-
* fileType: 'pptx',
|
|
220
|
-
* scenePath: `/pptx/${conversion.taskId}`,
|
|
221
|
-
* taskId: conversion.taskId,
|
|
222
|
-
* title: 'Title',
|
|
223
|
-
* })
|
|
224
|
-
*/
|
|
225
|
-
insertDocs(params: InsertDocsParams): Promise<string | undefined>;
|
|
226
|
-
|
|
227
|
-
insertDocs(arg1: string | InsertDocsParams, arg2?: ConversionResponse) {
|
|
228
|
-
if (typeof arg1 === "object" && "fileType" in arg1) {
|
|
229
|
-
return this._insertDocsImpl(arg1);
|
|
230
|
-
} else if (arg2 && arg2.status !== "Finished") {
|
|
231
|
-
throw new Error("[FastboardApp] Can not insert a converting doc.");
|
|
232
|
-
} else if (arg2 && arg2.progress) {
|
|
233
|
-
const scenes: SceneDefinition[] = arg2.progress.convertedFileList.map(convertedFileToScene);
|
|
234
|
-
const scenePath = `/${arg2.uuid}/${genUID()}`;
|
|
235
|
-
const { emptyScenes, taskId, url } = makeSlideParams(scenes);
|
|
236
|
-
if (taskId && url) {
|
|
237
|
-
const title = arg1;
|
|
238
|
-
return this._insertDocsImpl({ fileType: "pptx", scenePath, taskId, title, url, scenes: emptyScenes });
|
|
239
|
-
} else {
|
|
240
|
-
return this._insertDocsImpl({ fileType: "pdf", scenePath, scenes, title: arg1 });
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
private _insertDocsImpl({ fileType, scenePath, title, scenes, ...attributes }: InsertDocsParams) {
|
|
246
|
-
this._assertNotDestroyed();
|
|
247
|
-
switch (fileType) {
|
|
248
|
-
case "pdf":
|
|
249
|
-
case "ppt":
|
|
250
|
-
return this.manager.addApp({
|
|
251
|
-
kind: "DocsViewer",
|
|
252
|
-
options: { scenePath, title, scenes },
|
|
253
|
-
});
|
|
254
|
-
case "pptx":
|
|
255
|
-
return this.manager.addApp({
|
|
256
|
-
kind: "Slide",
|
|
257
|
-
options: { scenePath, title, scenes },
|
|
258
|
-
attributes,
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Insert the Monaco Code Editor app.
|
|
265
|
-
*/
|
|
266
|
-
insertCodeEditor() {
|
|
267
|
-
this._assertNotDestroyed();
|
|
268
|
-
return this.manager.addApp({
|
|
269
|
-
kind: "Monaco",
|
|
270
|
-
options: { title: "Code Editor" },
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Insert the Countdown app.
|
|
276
|
-
*/
|
|
277
|
-
insertCountdown() {
|
|
278
|
-
this._assertNotDestroyed();
|
|
279
|
-
return this.manager.addApp({
|
|
280
|
-
kind: "Countdown",
|
|
281
|
-
options: { title: "Countdown" },
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Insert the Media Player app.
|
|
287
|
-
*/
|
|
288
|
-
insertMedia({ title, src }: InsertMediaParams) {
|
|
289
|
-
this._assertNotDestroyed();
|
|
290
|
-
return this.manager.addApp({
|
|
291
|
-
kind: BuiltinApps.MediaPlayer,
|
|
292
|
-
options: { title },
|
|
293
|
-
attributes: { src },
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Insert the GeoGebra app.
|
|
299
|
-
*/
|
|
300
|
-
insertGeoGebra() {
|
|
301
|
-
this._assertNotDestroyed();
|
|
302
|
-
return this.manager.addApp({
|
|
303
|
-
kind: "GeoGebra",
|
|
304
|
-
options: { title: "GeoGebra" },
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
}
|
package/src/emitter.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export type FastboardListener<T> = (event: T) => void;
|
|
2
|
-
|
|
3
|
-
export class FastboardEmitter<T> {
|
|
4
|
-
listeners = new Set<FastboardListener<T>>();
|
|
5
|
-
|
|
6
|
-
get length(): number {
|
|
7
|
-
return this.listeners.size;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
dispatch(message: T) {
|
|
11
|
-
this.listeners.forEach(callback => callback(message));
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
addListener(listener: FastboardListener<T>) {
|
|
15
|
-
this.listeners.add(listener);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
removeListener(listener: FastboardListener<T>) {
|
|
19
|
-
this.listeners.delete(listener);
|
|
20
|
-
}
|
|
21
|
-
}
|
package/src/register-apps.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { WindowManager } from "@netless/window-manager";
|
|
2
|
-
|
|
3
|
-
WindowManager.register({
|
|
4
|
-
kind: "Slide",
|
|
5
|
-
appOptions: {
|
|
6
|
-
// turn on to show debug controller
|
|
7
|
-
debug: false,
|
|
8
|
-
},
|
|
9
|
-
src: async () => {
|
|
10
|
-
const app = await import("@netless/app-slide");
|
|
11
|
-
return app.default ?? app;
|
|
12
|
-
},
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
WindowManager.register({
|
|
16
|
-
kind: "Monaco",
|
|
17
|
-
src: "https://cdn.jsdelivr.net/npm/@netless/app-monaco@latest/dist/main.iife.js",
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
WindowManager.register({
|
|
21
|
-
kind: "Countdown",
|
|
22
|
-
src: "https://cdn.jsdelivr.net/npm/@netless/app-countdown@latest/dist/main.iife.js",
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
WindowManager.register({
|
|
26
|
-
kind: "GeoGebra",
|
|
27
|
-
src: "https://cdn.jsdelivr.net/npm/@netless/app-monaco@geogebra/dist/main.iife.js",
|
|
28
|
-
appOptions: {
|
|
29
|
-
HTML5Codebase: "https://flat-storage-cn-hz.whiteboard.agora.io/GeoGebra/HTML5/5.0/web3d",
|
|
30
|
-
},
|
|
31
|
-
});
|
package/src/utils.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import type { ConvertedFile, JoinRoomParams, SceneDefinition, Size } from "white-web-sdk";
|
|
2
|
-
import { WindowManager } from "@netless/window-manager";
|
|
3
|
-
|
|
4
|
-
export function noop() {
|
|
5
|
-
/* noop */
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function getImageSize(url: string, fallback: Size) {
|
|
9
|
-
return new Promise<Size>(resolve => {
|
|
10
|
-
const img = new Image();
|
|
11
|
-
img.onload = () => resolve(img);
|
|
12
|
-
img.onerror = () => resolve(fallback);
|
|
13
|
-
img.src = url;
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function makeSlideParams(scenes: SceneDefinition[]) {
|
|
18
|
-
const emptyScenes: SceneDefinition[] = [];
|
|
19
|
-
let taskId = "";
|
|
20
|
-
let url = "";
|
|
21
|
-
|
|
22
|
-
// e.g. "ppt(x)://cdn/prefix/dynamicConvert/{taskId}/1.slide"
|
|
23
|
-
const pptSrcRE = /^pptx?(?<prefix>:\/\/\S+?dynamicConvert)\/(?<taskId>\w+)\//;
|
|
24
|
-
|
|
25
|
-
for (const { name, ppt } of scenes) {
|
|
26
|
-
// make sure scenesWithoutPPT.length === scenes.length
|
|
27
|
-
emptyScenes.push({ name });
|
|
28
|
-
|
|
29
|
-
if (!ppt || !ppt.src.startsWith("ppt")) {
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
const match = pptSrcRE.exec(ppt.src);
|
|
33
|
-
if (!match || !match.groups) {
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
taskId = match.groups.taskId;
|
|
37
|
-
url = "https" + match.groups.prefix;
|
|
38
|
-
break;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return { emptyScenes, taskId, url };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function convertedFileToScene(f: ConvertedFile, i: number) {
|
|
45
|
-
return {
|
|
46
|
-
name: String(i + 1),
|
|
47
|
-
ppt: {
|
|
48
|
-
src: f.conversionFileUrl,
|
|
49
|
-
width: f.width,
|
|
50
|
-
height: f.height,
|
|
51
|
-
previewURL: f.preview,
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function ensureWindowManager(joinRoom: JoinRoomParams) {
|
|
57
|
-
if (!joinRoom.invisiblePlugins || !joinRoom.invisiblePlugins.includes(WindowManager)) {
|
|
58
|
-
joinRoom.invisiblePlugins = [...(joinRoom.invisiblePlugins || []), WindowManager];
|
|
59
|
-
}
|
|
60
|
-
return joinRoom;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Copy from https://github.com/crimx/side-effect-manager/blob/main/src/gen-uid.ts
|
|
64
|
-
const SOUP = "!#%()*+,-./:;=?@[]^_`{|}~" + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
65
|
-
const SOUP_LEN = SOUP.length;
|
|
66
|
-
const ID_LEN = 20;
|
|
67
|
-
const reusedIdCarrier = /* @__PURE__ */ Array(ID_LEN);
|
|
68
|
-
|
|
69
|
-
export function genUID() {
|
|
70
|
-
for (let i = 0; i < ID_LEN; i++) {
|
|
71
|
-
reusedIdCarrier[i] = SOUP.charAt(Math.random() * SOUP_LEN);
|
|
72
|
-
}
|
|
73
|
-
return reusedIdCarrier.join("");
|
|
74
|
-
}
|
package/src/value.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { FastboardEmitter } from "./emitter";
|
|
2
|
-
import { noop } from "./utils";
|
|
3
|
-
|
|
4
|
-
export type FastboardDisposer = () => void;
|
|
5
|
-
|
|
6
|
-
export interface FastboardReadable<T> {
|
|
7
|
-
readonly value: T;
|
|
8
|
-
subscribe(callback: (value: T) => void): FastboardDisposer;
|
|
9
|
-
reaction(callback: (value: T) => void): FastboardDisposer;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface FastboardWritable<T, SetFn = (value: T) => void> extends FastboardReadable<T> {
|
|
13
|
-
setValue: SetFn;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface FastboardInternalValue<T> extends FastboardWritable<T> {
|
|
17
|
-
dispose: FastboardDisposer;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Create a readonly, reactive value.
|
|
22
|
-
* @example
|
|
23
|
-
* createValue(manager.getMainViewSceneIndex(), (set) => {
|
|
24
|
-
* manager.emitter.on("mainViewSceneIndexChanged", set)
|
|
25
|
-
* return () => manager.emitter.off("mainViewSceneIndexChanged", set)
|
|
26
|
-
* })
|
|
27
|
-
*/
|
|
28
|
-
export function createValue<T>(
|
|
29
|
-
value: T,
|
|
30
|
-
effect: (set: (value: T) => void) => FastboardDisposer | void
|
|
31
|
-
): FastboardReadable<T>;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Create a writable, reactive value.
|
|
35
|
-
* @example
|
|
36
|
-
* createValue(manager.getMainViewSceneIndex(), (set) => {
|
|
37
|
-
* manager.emitter.on("mainViewSceneIndexChanged", set)
|
|
38
|
-
* return () => manager.emitter.off("mainViewSceneIndexChanged", set)
|
|
39
|
-
* }, (newValue) => {
|
|
40
|
-
* manager.setMainViewSceneIndex(newValue)
|
|
41
|
-
* })
|
|
42
|
-
*/
|
|
43
|
-
export function createValue<T, SetFn = (value: T) => void>(
|
|
44
|
-
value: T,
|
|
45
|
-
effect: (set: (value: T) => void) => FastboardDisposer | void,
|
|
46
|
-
set: (value: T) => void
|
|
47
|
-
): FastboardWritable<T, SetFn>;
|
|
48
|
-
|
|
49
|
-
export function createValue<T>(
|
|
50
|
-
value: T,
|
|
51
|
-
effect: (set: (value: T) => void) => FastboardDisposer | void,
|
|
52
|
-
setValue: (value: T) => void = noop
|
|
53
|
-
): FastboardInternalValue<T> {
|
|
54
|
-
const emitter = new FastboardEmitter<T>();
|
|
55
|
-
|
|
56
|
-
function set(newValue: T) {
|
|
57
|
-
emitter.dispatch((value = newValue));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const dispose = effect(set) || noop;
|
|
61
|
-
|
|
62
|
-
function subscribe(callback: (value: T) => void) {
|
|
63
|
-
emitter.addListener(callback);
|
|
64
|
-
callback(value);
|
|
65
|
-
return () => emitter.removeListener(callback);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function reaction(callback: (value: T) => void) {
|
|
69
|
-
emitter.addListener(callback);
|
|
70
|
-
return () => emitter.removeListener(callback);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return { value, subscribe, reaction, setValue, dispose };
|
|
74
|
-
}
|