@netless/window-manager 1.0.14 → 1.0.16

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/index.ts CHANGED
@@ -16,7 +16,8 @@ import { ArgusLog, log } from "./Utils/log";
16
16
  import { PageStateImpl } from "./PageState";
17
17
  import { ReconnectRefresher } from "./ReconnectRefresher";
18
18
  import { replaceRoomFunction } from "./Utils/RoomHacker";
19
- import { setupBuiltin } from "./BuiltinApps";
19
+ import { BuiltinApps, setupBuiltin } from "./BuiltinApps";
20
+ import type { BuiltinAppOptions } from "./BuiltinApps";
20
21
  import "video.js/dist/video-js.css";
21
22
  import "./style.css";
22
23
  import "@netless/telebox-insider/dist/style.css";
@@ -62,11 +63,11 @@ import type Emittery from "emittery";
62
63
  import type { PageController, AddPageParams, PageState } from "./Page";
63
64
  import { boxEmitter } from "./BoxEmitter";
64
65
  import { IframeBridge } from "./View/IframeBridge";
65
- import { setOptions } from "@netless/app-media-player";
66
66
  import type { ExtendPluginInstance } from "./ExtendPluginManager";
67
67
  import { ExtendPluginManager } from "./ExtendPluginManager";
68
68
  import { getExtendClass } from "./Utils/extendClass";
69
69
  import type { ExtendClass } from "./Utils/extendClass";
70
+ import { resolveAppOptions as mergeAppOptions } from "./Utils/resolveAppOptions";
70
71
 
71
72
  export * from "./utils/extendClass";
72
73
 
@@ -90,6 +91,54 @@ export type AddAppOptions = {
90
91
 
91
92
  export type setAppOptions = AddAppOptions & { appOptions?: any };
92
93
 
94
+ export type DocsEvent =
95
+ | "prevPage"
96
+ | "nextPage"
97
+ | "prevStep"
98
+ | "nextStep"
99
+ | "jumpToPage"
100
+ | "scalePage";
101
+
102
+ export type DocsEventOptions = {
103
+ /** If provided, will dispatch to the specific app. Default to the focused app. */
104
+ appId?: string;
105
+ /** Used by `jumpToPage` event, range from 1 to total pages count. */
106
+ page?: number;
107
+ /** Used by `scalePage` event. Range from 1 to 4, decimals allowed. `1` means default fitted size. */
108
+ scale?: number;
109
+ };
110
+
111
+ type SlideDocsAppResult = {
112
+ prevPage: () => boolean;
113
+ nextPage: () => boolean;
114
+ prevStep: () => boolean;
115
+ nextStep: () => boolean;
116
+ jumpToPage: (page: number) => boolean;
117
+ scaleView: (scale: number) => void;
118
+ };
119
+
120
+ type PresentationDocsAppResult = {
121
+ prevPage: () => boolean;
122
+ nextPage: () => boolean;
123
+ jumpPage: (index: number) => boolean;
124
+ moveCamera: (camera: { centerX: number; centerY: number; scale: number }) => void;
125
+ getOriginScale: () => number;
126
+ };
127
+
128
+ const SlideAppKind = "Slide";
129
+ const PresentationAppKind = BuiltinApps.Presentation;
130
+ const MinDocsPageScale = 1;
131
+ const MaxDocsPageScale = 4;
132
+
133
+ function isValidDocsPageScale(scale: unknown): scale is number {
134
+ return (
135
+ typeof scale === "number" &&
136
+ Number.isFinite(scale) &&
137
+ scale >= MinDocsPageScale &&
138
+ scale <= MaxDocsPageScale
139
+ );
140
+ }
141
+
93
142
  export type AddAppParams<TAttributes = any> = {
94
143
  kind: string;
95
144
  // app 地址(本地 app 不需要传)
@@ -197,6 +246,8 @@ export type MountParams = {
197
246
  supportAppliancePlugin?: boolean;
198
247
  /** 是否使用 boxesStatus 状态管理窗口 */
199
248
  useBoxesStatus?: boolean;
249
+ /** Local Presentation options applied before restoring apps. Not synchronized. */
250
+ builtinAppOptions?: BuiltinAppOptions;
200
251
  };
201
252
 
202
253
  export const reconnectRefresher = new ReconnectRefresher({ emitter: internalEmitter });
@@ -234,6 +285,8 @@ export class WindowManager
234
285
 
235
286
  public _appliancePlugin?: any;
236
287
 
288
+ public builtinAppOptions?: BuiltinAppOptions;
289
+
237
290
  private boxManager?: BoxManager;
238
291
  private static params?: MountParams;
239
292
  static extendClass?: ExtendClass;
@@ -255,7 +308,7 @@ export class WindowManager
255
308
  super(context);
256
309
  WindowManager.displayer = context.displayer;
257
310
  (window as any).NETLESS_DEPS = __APP_DEPENDENCIES__;
258
- this.emitter.on('mainViewScenePathChange', this.onMainViewScenePathChangeHandler)
311
+ this.emitter.on("mainViewScenePathChange", this.onMainViewScenePathChangeHandler);
259
312
  }
260
313
 
261
314
  public static onCreate(manager: WindowManager) {
@@ -289,7 +342,11 @@ export class WindowManager
289
342
  manager = await this.initManager(room);
290
343
  if (manager) {
291
344
  manager._roomLogger = (room as unknown as { logger: Logger }).logger;
292
- manager.attributesDeboundceLog = new ArgusLog(manager._roomLogger, "attributes", 300);
345
+ manager.attributesDeboundceLog = new ArgusLog(
346
+ manager._roomLogger,
347
+ "attributes",
348
+ 300
349
+ );
293
350
  if (WindowManager.registered.size > 0) {
294
351
  manager._roomLogger.info(
295
352
  `[WindowManager] registered apps: ${JSON.stringify(
@@ -304,9 +361,6 @@ export class WindowManager
304
361
  }
305
362
 
306
363
  this.debug = Boolean(debug);
307
- if (this.debug) {
308
- setOptions({ verbose: true });
309
- }
310
364
  if (manager?._roomLogger) {
311
365
  manager._roomLogger.info(
312
366
  `[WindowManager] Already insert room version: ${manager.version}`
@@ -337,6 +391,8 @@ export class WindowManager
337
391
  throw new Error("[WindowManager]: create manager failed");
338
392
  }
339
393
 
394
+ manager.builtinAppOptions = params.builtinAppOptions;
395
+
340
396
  if (containerSizeRatio) {
341
397
  WindowManager.containerSizeRatio = containerSizeRatio;
342
398
  }
@@ -390,27 +446,43 @@ export class WindowManager
390
446
  console.warn("[WindowManager]: indexedDB open failed");
391
447
  console.log(error);
392
448
  }
393
- manager.emitter.on('mainViewScenePathChange', manager.onMainViewScenePathChangeHandler)
449
+ manager.emitter.on("mainViewScenePathChange", manager.onMainViewScenePathChangeHandler);
394
450
  return manager;
395
451
  }
396
452
 
397
453
  public onMainViewScenePathChangeHandler = (scenePath: string) => {
398
454
  const mainViewElement = this.mainView.divElement;
399
455
  if (mainViewElement) {
400
- const backgroundImage = mainViewElement.querySelector('.background img');
456
+ const backgroundImage = mainViewElement.querySelector(".background img");
401
457
  if (backgroundImage) {
402
458
  const backgroundImageRect = backgroundImage?.getBoundingClientRect();
403
459
  const backgroundImageCSS = window.getComputedStyle(backgroundImage);
404
- const backgroundImageVisible = backgroundImageRect?.width > 0 && backgroundImageRect?.height > 0 && backgroundImageCSS.display !== 'none';
460
+ const backgroundImageVisible =
461
+ backgroundImageRect?.width > 0 &&
462
+ backgroundImageRect?.height > 0 &&
463
+ backgroundImageCSS.display !== "none";
405
464
  const camera = this.mainView.camera;
406
- console.log("[window-manager] backgroundImageVisible:" + backgroundImageVisible + " camera:" + JSON.stringify(camera));
465
+ console.log(
466
+ "[window-manager] backgroundImageVisible:" +
467
+ backgroundImageVisible +
468
+ " camera:" +
469
+ JSON.stringify(camera)
470
+ );
407
471
  return;
408
472
  }
409
- console.log("[window-manager] onMainViewScenePathChange scenePath:" + scenePath + ' backgroundImageVisible is not found');
473
+ console.log(
474
+ "[window-manager] onMainViewScenePathChange scenePath:" +
475
+ scenePath +
476
+ " backgroundImageVisible is not found"
477
+ );
410
478
  return;
411
479
  }
412
- console.log("[window-manager] onMainViewScenePathChange scenePath:" + scenePath + ' mainViewElement is not found');
413
- }
480
+ console.log(
481
+ "[window-manager] onMainViewScenePathChange scenePath:" +
482
+ scenePath +
483
+ " mainViewElement is not found"
484
+ );
485
+ };
414
486
 
415
487
  private static initManager(room: Room): Promise<WindowManager | undefined> {
416
488
  return createInvisiblePlugin(room);
@@ -522,6 +594,15 @@ export class WindowManager
522
594
  return appRegister.unregister(kind);
523
595
  }
524
596
 
597
+ public resolveAppOptions(
598
+ kind: string,
599
+ registeredOptions?: any | (() => any)
600
+ ): any | (() => any) {
601
+ const override =
602
+ kind === BuiltinApps.Presentation ? this.builtinAppOptions?.Presentation : undefined;
603
+ return mergeAppOptions(registeredOptions, override);
604
+ }
605
+
525
606
  /**
526
607
  * 创建一个 app 至白板
527
608
  */
@@ -1007,6 +1088,172 @@ export class WindowManager
1007
1088
  return this.appManager?.appProxies.get(appId);
1008
1089
  }
1009
1090
 
1091
+ /**
1092
+ * Send specific command to DocsViewer / Presentation / Slide app.
1093
+ *
1094
+ * Static docs and Presentation do not have animation steps, so `prevStep` / `nextStep`
1095
+ * are treated as `prevPage` / `nextPage`.
1096
+ */
1097
+ public dispatchDocsEvent(event: DocsEvent, options: DocsEventOptions = {}): boolean {
1098
+ const appId = options.appId || this.focused;
1099
+ if (!appId) {
1100
+ console.warn("not found " + (options.appId || "focused app"));
1101
+ return false;
1102
+ }
1103
+
1104
+ const app = this.queryOne(appId);
1105
+ if (!app) {
1106
+ console.warn("not found app with id " + appId);
1107
+ return false;
1108
+ }
1109
+
1110
+ const isDocsViewerApp =
1111
+ appId.startsWith(`${BuiltinApps.DocsViewer}-`) || app.kind === BuiltinApps.DocsViewer;
1112
+ const isPresentationApp =
1113
+ appId.startsWith(`${PresentationAppKind}-`) || app.kind === PresentationAppKind;
1114
+ const isSlideApp = appId.startsWith(`${SlideAppKind}-`) || app.kind === SlideAppKind;
1115
+ let appKind = app.kind;
1116
+ if (isDocsViewerApp) {
1117
+ appKind = BuiltinApps.DocsViewer;
1118
+ } else if (isPresentationApp) {
1119
+ appKind = PresentationAppKind;
1120
+ } else if (isSlideApp) {
1121
+ appKind = SlideAppKind;
1122
+ }
1123
+
1124
+ if (!WindowManager.registered.has(appKind)) {
1125
+ console.warn("not registered app kind " + appKind);
1126
+ return false;
1127
+ }
1128
+
1129
+ let page: number | undefined, input: HTMLInputElement | null, scale: number | undefined;
1130
+
1131
+ if (isDocsViewerApp) {
1132
+ const dom = app.box?.$footer;
1133
+ if (!dom) {
1134
+ console.warn("not found app with id " + appId);
1135
+ return false;
1136
+ }
1137
+
1138
+ const click = (el: Element | null) => {
1139
+ el && el.dispatchEvent(new MouseEvent("click"));
1140
+ };
1141
+
1142
+ switch (event) {
1143
+ case "prevPage":
1144
+ case "prevStep":
1145
+ click(dom.querySelector('button[class$="btn-page-back"]'));
1146
+ break;
1147
+ case "nextPage":
1148
+ case "nextStep":
1149
+ click(dom.querySelector('button[class$="btn-page-next"]'));
1150
+ break;
1151
+ case "jumpToPage":
1152
+ page = options.page;
1153
+ input = dom.querySelector('input[class$="page-number-input"]');
1154
+ if (!input || typeof page !== "number") {
1155
+ console.warn("failed to jump" + (page ? " to page " + page : ""));
1156
+ return false;
1157
+ }
1158
+ input.value = "" + page;
1159
+ input.dispatchEvent(new InputEvent("change"));
1160
+ break;
1161
+ case "scalePage":
1162
+ console.warn("not supported event " + event + " for app kind " + appKind);
1163
+ return false;
1164
+ default:
1165
+ console.warn("unknown event " + event);
1166
+ return false;
1167
+ }
1168
+
1169
+ return true;
1170
+ }
1171
+
1172
+ if (isPresentationApp) {
1173
+ const controller = app.appResult as PresentationDocsAppResult | undefined;
1174
+ if (!controller) {
1175
+ console.warn("not found app with id " + appId);
1176
+ return false;
1177
+ }
1178
+
1179
+ switch (event) {
1180
+ case "prevPage":
1181
+ case "prevStep":
1182
+ return controller.prevPage();
1183
+ case "nextPage":
1184
+ case "nextStep":
1185
+ return controller.nextPage();
1186
+ case "jumpToPage":
1187
+ page = options.page;
1188
+ if (typeof page !== "number") {
1189
+ console.warn("failed to jump" + (page ? " to page " + page : ""));
1190
+ return false;
1191
+ }
1192
+ return controller.jumpPage(page - 1);
1193
+ case "scalePage":
1194
+ scale = options.scale;
1195
+ if (!isValidDocsPageScale(scale)) {
1196
+ console.warn("failed to scale, scale should be a number from 1 to 4");
1197
+ return false;
1198
+ }
1199
+ try {
1200
+ controller.moveCamera({
1201
+ centerX: 0,
1202
+ centerY: 0,
1203
+ scale: controller.getOriginScale() * scale,
1204
+ });
1205
+ return true;
1206
+ } catch (error) {
1207
+ console.warn(error);
1208
+ return false;
1209
+ }
1210
+ default:
1211
+ console.warn("unknown event " + event);
1212
+ return false;
1213
+ }
1214
+ }
1215
+
1216
+ if (isSlideApp) {
1217
+ const controller = app.appResult as SlideDocsAppResult | undefined;
1218
+ if (!controller) {
1219
+ console.warn("not found app with id " + appId);
1220
+ return false;
1221
+ }
1222
+
1223
+ switch (event) {
1224
+ case "prevPage":
1225
+ return controller.prevPage();
1226
+ case "nextPage":
1227
+ return controller.nextPage();
1228
+ case "prevStep":
1229
+ return controller.prevStep();
1230
+ case "nextStep":
1231
+ return controller.nextStep();
1232
+ case "jumpToPage":
1233
+ page = options.page;
1234
+ if (typeof page !== "number") {
1235
+ console.warn("failed to jump" + (page ? " to page " + page : ""));
1236
+ return false;
1237
+ }
1238
+ return controller.jumpToPage(page);
1239
+ case "scalePage":
1240
+ scale = options.scale;
1241
+ if (!isValidDocsPageScale(scale)) {
1242
+ console.warn("failed to scale, scale should be a number from 1 to 4");
1243
+ return false;
1244
+ }
1245
+ controller.scaleView(scale);
1246
+ return true;
1247
+ default:
1248
+ console.warn("unknown event " + event);
1249
+ return false;
1250
+ }
1251
+ }
1252
+
1253
+ console.warn("not supported app kind " + app.kind);
1254
+ return false;
1255
+ }
1256
+
1010
1257
  /**
1011
1258
  * 关闭 APP
1012
1259
  */
@@ -1081,7 +1328,7 @@ export class WindowManager
1081
1328
  WindowManager.playground.parentNode?.removeChild(WindowManager.playground);
1082
1329
  }
1083
1330
  WindowManager.params = undefined;
1084
- this.emitter.off('mainViewScenePathChange', this.onMainViewScenePathChangeHandler);
1331
+ this.emitter.off("mainViewScenePathChange", this.onMainViewScenePathChangeHandler);
1085
1332
  this._iframeBridge?.destroy();
1086
1333
  this._iframeBridge = undefined;
1087
1334
  log("Destroyed");
@@ -1113,7 +1360,10 @@ export class WindowManager
1113
1360
  if (this.canOperate) {
1114
1361
  this.setAttributes(attributes);
1115
1362
  if (this.attributesDeboundceLog) {
1116
- this.attributesDeboundceLog.logDebouncedShallowMerge("safeSetAttributes", attributes);
1363
+ this.attributesDeboundceLog.logDebouncedShallowMerge(
1364
+ "safeSetAttributes",
1365
+ attributes
1366
+ );
1117
1367
  }
1118
1368
  }
1119
1369
  }
@@ -1134,7 +1384,10 @@ export class WindowManager
1134
1384
  public cleanCurrentScene(): void {
1135
1385
  log("clean current scene");
1136
1386
  this.focusedView?.cleanCurrentScene();
1137
- this.Logger && this.Logger.info(`[WindowManager]: cleanCurrentScene ${this.focusedView?.focusScenePath}`);
1387
+ this.Logger &&
1388
+ this.Logger.info(
1389
+ `[WindowManager]: cleanCurrentScene ${this.focusedView?.focusScenePath}`
1390
+ );
1138
1391
  }
1139
1392
 
1140
1393
  public redo(): number {
@@ -1147,7 +1400,8 @@ export class WindowManager
1147
1400
 
1148
1401
  public delete(): void {
1149
1402
  this.focusedView?.delete();
1150
- this.Logger && this.Logger.info(`[WindowManager]: delete ${this.focusedView?.focusScenePath}`);
1403
+ this.Logger &&
1404
+ this.Logger.info(`[WindowManager]: delete ${this.focusedView?.focusScenePath}`);
1151
1405
  }
1152
1406
 
1153
1407
  public copy(): void {
@@ -1257,6 +1511,7 @@ setupBuiltin();
1257
1511
  export * from "./typings";
1258
1512
 
1259
1513
  export { BuiltinApps } from "./BuiltinApps";
1514
+ export type { BuiltinAppOptions } from "./BuiltinApps";
1260
1515
  export type { PublicEvent } from "./callback";
1261
1516
 
1262
1517
  export * from "./ExtendPluginManager";