@netless/window-manager 1.0.14 → 1.0.15

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,7 @@ 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
20
  import "video.js/dist/video-js.css";
21
21
  import "./style.css";
22
22
  import "@netless/telebox-insider/dist/style.css";
@@ -62,7 +62,6 @@ import type Emittery from "emittery";
62
62
  import type { PageController, AddPageParams, PageState } from "./Page";
63
63
  import { boxEmitter } from "./BoxEmitter";
64
64
  import { IframeBridge } from "./View/IframeBridge";
65
- import { setOptions } from "@netless/app-media-player";
66
65
  import type { ExtendPluginInstance } from "./ExtendPluginManager";
67
66
  import { ExtendPluginManager } from "./ExtendPluginManager";
68
67
  import { getExtendClass } from "./Utils/extendClass";
@@ -90,6 +89,54 @@ export type AddAppOptions = {
90
89
 
91
90
  export type setAppOptions = AddAppOptions & { appOptions?: any };
92
91
 
92
+ export type DocsEvent =
93
+ | "prevPage"
94
+ | "nextPage"
95
+ | "prevStep"
96
+ | "nextStep"
97
+ | "jumpToPage"
98
+ | "scalePage";
99
+
100
+ export type DocsEventOptions = {
101
+ /** If provided, will dispatch to the specific app. Default to the focused app. */
102
+ appId?: string;
103
+ /** Used by `jumpToPage` event, range from 1 to total pages count. */
104
+ page?: number;
105
+ /** Used by `scalePage` event. Range from 1 to 4, decimals allowed. `1` means default fitted size. */
106
+ scale?: number;
107
+ };
108
+
109
+ type SlideDocsAppResult = {
110
+ prevPage: () => boolean;
111
+ nextPage: () => boolean;
112
+ prevStep: () => boolean;
113
+ nextStep: () => boolean;
114
+ jumpToPage: (page: number) => boolean;
115
+ scaleView: (scale: number) => void;
116
+ };
117
+
118
+ type PresentationDocsAppResult = {
119
+ prevPage: () => boolean;
120
+ nextPage: () => boolean;
121
+ jumpPage: (index: number) => boolean;
122
+ moveCamera: (camera: { centerX: number; centerY: number; scale: number }) => void;
123
+ getOriginScale: () => number;
124
+ };
125
+
126
+ const SlideAppKind = "Slide";
127
+ const PresentationAppKind = BuiltinApps.Presentation;
128
+ const MinDocsPageScale = 1;
129
+ const MaxDocsPageScale = 4;
130
+
131
+ function isValidDocsPageScale(scale: unknown): scale is number {
132
+ return (
133
+ typeof scale === "number" &&
134
+ Number.isFinite(scale) &&
135
+ scale >= MinDocsPageScale &&
136
+ scale <= MaxDocsPageScale
137
+ );
138
+ }
139
+
93
140
  export type AddAppParams<TAttributes = any> = {
94
141
  kind: string;
95
142
  // app 地址(本地 app 不需要传)
@@ -255,7 +302,7 @@ export class WindowManager
255
302
  super(context);
256
303
  WindowManager.displayer = context.displayer;
257
304
  (window as any).NETLESS_DEPS = __APP_DEPENDENCIES__;
258
- this.emitter.on('mainViewScenePathChange', this.onMainViewScenePathChangeHandler)
305
+ this.emitter.on("mainViewScenePathChange", this.onMainViewScenePathChangeHandler);
259
306
  }
260
307
 
261
308
  public static onCreate(manager: WindowManager) {
@@ -289,7 +336,11 @@ export class WindowManager
289
336
  manager = await this.initManager(room);
290
337
  if (manager) {
291
338
  manager._roomLogger = (room as unknown as { logger: Logger }).logger;
292
- manager.attributesDeboundceLog = new ArgusLog(manager._roomLogger, "attributes", 300);
339
+ manager.attributesDeboundceLog = new ArgusLog(
340
+ manager._roomLogger,
341
+ "attributes",
342
+ 300
343
+ );
293
344
  if (WindowManager.registered.size > 0) {
294
345
  manager._roomLogger.info(
295
346
  `[WindowManager] registered apps: ${JSON.stringify(
@@ -304,9 +355,6 @@ export class WindowManager
304
355
  }
305
356
 
306
357
  this.debug = Boolean(debug);
307
- if (this.debug) {
308
- setOptions({ verbose: true });
309
- }
310
358
  if (manager?._roomLogger) {
311
359
  manager._roomLogger.info(
312
360
  `[WindowManager] Already insert room version: ${manager.version}`
@@ -390,27 +438,43 @@ export class WindowManager
390
438
  console.warn("[WindowManager]: indexedDB open failed");
391
439
  console.log(error);
392
440
  }
393
- manager.emitter.on('mainViewScenePathChange', manager.onMainViewScenePathChangeHandler)
441
+ manager.emitter.on("mainViewScenePathChange", manager.onMainViewScenePathChangeHandler);
394
442
  return manager;
395
443
  }
396
444
 
397
445
  public onMainViewScenePathChangeHandler = (scenePath: string) => {
398
446
  const mainViewElement = this.mainView.divElement;
399
447
  if (mainViewElement) {
400
- const backgroundImage = mainViewElement.querySelector('.background img');
448
+ const backgroundImage = mainViewElement.querySelector(".background img");
401
449
  if (backgroundImage) {
402
450
  const backgroundImageRect = backgroundImage?.getBoundingClientRect();
403
451
  const backgroundImageCSS = window.getComputedStyle(backgroundImage);
404
- const backgroundImageVisible = backgroundImageRect?.width > 0 && backgroundImageRect?.height > 0 && backgroundImageCSS.display !== 'none';
452
+ const backgroundImageVisible =
453
+ backgroundImageRect?.width > 0 &&
454
+ backgroundImageRect?.height > 0 &&
455
+ backgroundImageCSS.display !== "none";
405
456
  const camera = this.mainView.camera;
406
- console.log("[window-manager] backgroundImageVisible:" + backgroundImageVisible + " camera:" + JSON.stringify(camera));
457
+ console.log(
458
+ "[window-manager] backgroundImageVisible:" +
459
+ backgroundImageVisible +
460
+ " camera:" +
461
+ JSON.stringify(camera)
462
+ );
407
463
  return;
408
464
  }
409
- console.log("[window-manager] onMainViewScenePathChange scenePath:" + scenePath + ' backgroundImageVisible is not found');
465
+ console.log(
466
+ "[window-manager] onMainViewScenePathChange scenePath:" +
467
+ scenePath +
468
+ " backgroundImageVisible is not found"
469
+ );
410
470
  return;
411
471
  }
412
- console.log("[window-manager] onMainViewScenePathChange scenePath:" + scenePath + ' mainViewElement is not found');
413
- }
472
+ console.log(
473
+ "[window-manager] onMainViewScenePathChange scenePath:" +
474
+ scenePath +
475
+ " mainViewElement is not found"
476
+ );
477
+ };
414
478
 
415
479
  private static initManager(room: Room): Promise<WindowManager | undefined> {
416
480
  return createInvisiblePlugin(room);
@@ -1007,6 +1071,172 @@ export class WindowManager
1007
1071
  return this.appManager?.appProxies.get(appId);
1008
1072
  }
1009
1073
 
1074
+ /**
1075
+ * Send specific command to DocsViewer / Presentation / Slide app.
1076
+ *
1077
+ * Static docs and Presentation do not have animation steps, so `prevStep` / `nextStep`
1078
+ * are treated as `prevPage` / `nextPage`.
1079
+ */
1080
+ public dispatchDocsEvent(event: DocsEvent, options: DocsEventOptions = {}): boolean {
1081
+ const appId = options.appId || this.focused;
1082
+ if (!appId) {
1083
+ console.warn("not found " + (options.appId || "focused app"));
1084
+ return false;
1085
+ }
1086
+
1087
+ const app = this.queryOne(appId);
1088
+ if (!app) {
1089
+ console.warn("not found app with id " + appId);
1090
+ return false;
1091
+ }
1092
+
1093
+ const isDocsViewerApp =
1094
+ appId.startsWith(`${BuiltinApps.DocsViewer}-`) || app.kind === BuiltinApps.DocsViewer;
1095
+ const isPresentationApp =
1096
+ appId.startsWith(`${PresentationAppKind}-`) || app.kind === PresentationAppKind;
1097
+ const isSlideApp = appId.startsWith(`${SlideAppKind}-`) || app.kind === SlideAppKind;
1098
+ let appKind = app.kind;
1099
+ if (isDocsViewerApp) {
1100
+ appKind = BuiltinApps.DocsViewer;
1101
+ } else if (isPresentationApp) {
1102
+ appKind = PresentationAppKind;
1103
+ } else if (isSlideApp) {
1104
+ appKind = SlideAppKind;
1105
+ }
1106
+
1107
+ if (!WindowManager.registered.has(appKind)) {
1108
+ console.warn("not registered app kind " + appKind);
1109
+ return false;
1110
+ }
1111
+
1112
+ let page: number | undefined, input: HTMLInputElement | null, scale: number | undefined;
1113
+
1114
+ if (isDocsViewerApp) {
1115
+ const dom = app.box?.$footer;
1116
+ if (!dom) {
1117
+ console.warn("not found app with id " + appId);
1118
+ return false;
1119
+ }
1120
+
1121
+ const click = (el: Element | null) => {
1122
+ el && el.dispatchEvent(new MouseEvent("click"));
1123
+ };
1124
+
1125
+ switch (event) {
1126
+ case "prevPage":
1127
+ case "prevStep":
1128
+ click(dom.querySelector('button[class$="btn-page-back"]'));
1129
+ break;
1130
+ case "nextPage":
1131
+ case "nextStep":
1132
+ click(dom.querySelector('button[class$="btn-page-next"]'));
1133
+ break;
1134
+ case "jumpToPage":
1135
+ page = options.page;
1136
+ input = dom.querySelector('input[class$="page-number-input"]');
1137
+ if (!input || typeof page !== "number") {
1138
+ console.warn("failed to jump" + (page ? " to page " + page : ""));
1139
+ return false;
1140
+ }
1141
+ input.value = "" + page;
1142
+ input.dispatchEvent(new InputEvent("change"));
1143
+ break;
1144
+ case "scalePage":
1145
+ console.warn("not supported event " + event + " for app kind " + appKind);
1146
+ return false;
1147
+ default:
1148
+ console.warn("unknown event " + event);
1149
+ return false;
1150
+ }
1151
+
1152
+ return true;
1153
+ }
1154
+
1155
+ if (isPresentationApp) {
1156
+ const controller = app.appResult as PresentationDocsAppResult | undefined;
1157
+ if (!controller) {
1158
+ console.warn("not found app with id " + appId);
1159
+ return false;
1160
+ }
1161
+
1162
+ switch (event) {
1163
+ case "prevPage":
1164
+ case "prevStep":
1165
+ return controller.prevPage();
1166
+ case "nextPage":
1167
+ case "nextStep":
1168
+ return controller.nextPage();
1169
+ case "jumpToPage":
1170
+ page = options.page;
1171
+ if (typeof page !== "number") {
1172
+ console.warn("failed to jump" + (page ? " to page " + page : ""));
1173
+ return false;
1174
+ }
1175
+ return controller.jumpPage(page - 1);
1176
+ case "scalePage":
1177
+ scale = options.scale;
1178
+ if (!isValidDocsPageScale(scale)) {
1179
+ console.warn("failed to scale, scale should be a number from 1 to 4");
1180
+ return false;
1181
+ }
1182
+ try {
1183
+ controller.moveCamera({
1184
+ centerX: 0,
1185
+ centerY: 0,
1186
+ scale: controller.getOriginScale() * scale,
1187
+ });
1188
+ return true;
1189
+ } catch (error) {
1190
+ console.warn(error);
1191
+ return false;
1192
+ }
1193
+ default:
1194
+ console.warn("unknown event " + event);
1195
+ return false;
1196
+ }
1197
+ }
1198
+
1199
+ if (isSlideApp) {
1200
+ const controller = app.appResult as SlideDocsAppResult | undefined;
1201
+ if (!controller) {
1202
+ console.warn("not found app with id " + appId);
1203
+ return false;
1204
+ }
1205
+
1206
+ switch (event) {
1207
+ case "prevPage":
1208
+ return controller.prevPage();
1209
+ case "nextPage":
1210
+ return controller.nextPage();
1211
+ case "prevStep":
1212
+ return controller.prevStep();
1213
+ case "nextStep":
1214
+ return controller.nextStep();
1215
+ case "jumpToPage":
1216
+ page = options.page;
1217
+ if (typeof page !== "number") {
1218
+ console.warn("failed to jump" + (page ? " to page " + page : ""));
1219
+ return false;
1220
+ }
1221
+ return controller.jumpToPage(page);
1222
+ case "scalePage":
1223
+ scale = options.scale;
1224
+ if (!isValidDocsPageScale(scale)) {
1225
+ console.warn("failed to scale, scale should be a number from 1 to 4");
1226
+ return false;
1227
+ }
1228
+ controller.scaleView(scale);
1229
+ return true;
1230
+ default:
1231
+ console.warn("unknown event " + event);
1232
+ return false;
1233
+ }
1234
+ }
1235
+
1236
+ console.warn("not supported app kind " + app.kind);
1237
+ return false;
1238
+ }
1239
+
1010
1240
  /**
1011
1241
  * 关闭 APP
1012
1242
  */
@@ -1081,7 +1311,7 @@ export class WindowManager
1081
1311
  WindowManager.playground.parentNode?.removeChild(WindowManager.playground);
1082
1312
  }
1083
1313
  WindowManager.params = undefined;
1084
- this.emitter.off('mainViewScenePathChange', this.onMainViewScenePathChangeHandler);
1314
+ this.emitter.off("mainViewScenePathChange", this.onMainViewScenePathChangeHandler);
1085
1315
  this._iframeBridge?.destroy();
1086
1316
  this._iframeBridge = undefined;
1087
1317
  log("Destroyed");
@@ -1113,7 +1343,10 @@ export class WindowManager
1113
1343
  if (this.canOperate) {
1114
1344
  this.setAttributes(attributes);
1115
1345
  if (this.attributesDeboundceLog) {
1116
- this.attributesDeboundceLog.logDebouncedShallowMerge("safeSetAttributes", attributes);
1346
+ this.attributesDeboundceLog.logDebouncedShallowMerge(
1347
+ "safeSetAttributes",
1348
+ attributes
1349
+ );
1117
1350
  }
1118
1351
  }
1119
1352
  }
@@ -1134,7 +1367,10 @@ export class WindowManager
1134
1367
  public cleanCurrentScene(): void {
1135
1368
  log("clean current scene");
1136
1369
  this.focusedView?.cleanCurrentScene();
1137
- this.Logger && this.Logger.info(`[WindowManager]: cleanCurrentScene ${this.focusedView?.focusScenePath}`);
1370
+ this.Logger &&
1371
+ this.Logger.info(
1372
+ `[WindowManager]: cleanCurrentScene ${this.focusedView?.focusScenePath}`
1373
+ );
1138
1374
  }
1139
1375
 
1140
1376
  public redo(): number {
@@ -1147,7 +1383,8 @@ export class WindowManager
1147
1383
 
1148
1384
  public delete(): void {
1149
1385
  this.focusedView?.delete();
1150
- this.Logger && this.Logger.info(`[WindowManager]: delete ${this.focusedView?.focusScenePath}`);
1386
+ this.Logger &&
1387
+ this.Logger.info(`[WindowManager]: delete ${this.focusedView?.focusScenePath}`);
1151
1388
  }
1152
1389
 
1153
1390
  public copy(): void {