@netless/app-slide 0.2.106-beta.4 → 0.2.106

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,9 +1,10 @@
1
- import type { ReadonlyTeleBox, RegisterParams } from "@netless/window-manager";
2
- import { log } from "./logger";
1
+ import type { AppContext, ReadonlyTeleBox, RegisterParams } from "@netless/window-manager";
2
+ import { log, logger } from "./logger";
3
3
 
4
4
  export interface FreezableSlide {
5
- freeze: () => void;
6
- unfreeze: () => void;
5
+ freeze: () => void | Promise<void>;
6
+ unfreeze: () => void | Promise<void>;
7
+ context?: AppContext<any, any, any>;
7
8
  }
8
9
 
9
10
  export let useFreezer = false;
@@ -22,54 +23,121 @@ const inspect = (arr: string[]) => {
22
23
  return "[" + arr + "]";
23
24
  };
24
25
 
26
+ const FreezerRetryDelay = 500;
27
+
25
28
  export const apps = {
26
29
  map: new Map<string, FreezableSlide>(),
27
30
  boxes: new Map<string, ReadonlyTeleBox>(),
28
31
  queue: [] as string[],
29
- validateQueue() {
32
+ focusedAppId: undefined as string | undefined,
33
+ runTransition(
34
+ appId: string,
35
+ slide: FreezableSlide,
36
+ action: "freeze" | "unfreeze",
37
+ retried = false
38
+ ) {
39
+ try {
40
+ const result = action === "freeze" ? slide.freeze() : slide.unfreeze();
41
+ void Promise.resolve(result).catch(error =>
42
+ this.onTransitionFailure(appId, slide, action, error, retried)
43
+ );
44
+ } catch (error) {
45
+ void Promise.resolve().then(() =>
46
+ this.onTransitionFailure(appId, slide, action, error, retried)
47
+ );
48
+ }
49
+ },
50
+ onTransitionFailure(
51
+ appId: string,
52
+ slide: FreezableSlide,
53
+ action: "freeze" | "unfreeze",
54
+ error: unknown,
55
+ retried: boolean
56
+ ) {
57
+ logger.warn(`[Slide] freezer: ${action} failed`, appId, error);
58
+ if (this.map.get(appId) !== slide) return;
59
+ // A failed freeze still occupies a cache slot until a later validation succeeds.
60
+ if (action === "freeze" && !this.queue.includes(appId)) this.queue.push(appId);
61
+ if (retried) return;
62
+ if (action === "freeze" ? this.focusedAppId === appId : this.focusedAppId !== appId) return;
63
+ setTimeout(() => {
64
+ if (this.map.get(appId) !== slide) return;
65
+ if (action === "freeze") {
66
+ if (this.focusedAppId !== appId && this.queue.length > FreezerLength) {
67
+ this.validateQueue(true);
68
+ }
69
+ } else if (this.focusedAppId === appId) {
70
+ this.runTransition(appId, slide, action, true);
71
+ }
72
+ }, FreezerRetryDelay);
73
+ },
74
+ validateQueue(retried = false) {
30
75
  // queue = [5, 4, 3, 2, 1]
31
76
  this.queue.sort((a, b) => {
32
77
  const za = this.boxes.get(a)?.zIndex ?? 0;
33
78
  const zb = this.boxes.get(b)?.zIndex ?? 0;
34
79
  return -(za - zb);
35
80
  });
36
- log("[Slide] freezer: validate", inspect(this.queue));
81
+ const validated = [...this.queue];
82
+ const frozen: string[] = [];
83
+ const missing: string[] = [];
37
84
  while (this.queue.length > FreezerLength) {
38
85
  const appId = this.queue.pop() as string;
39
86
  const slide = this.map.get(appId);
40
87
  if (slide) {
41
- log("[Slide] freezer: validate-freeze", appId, inspect(this.queue));
42
- slide.freeze();
88
+ this.runTransition(appId, slide, "freeze", retried);
89
+ frozen.push(appId);
90
+ } else {
91
+ missing.push(appId);
43
92
  }
44
93
  }
94
+ return { validated, frozen, missing };
45
95
  },
46
96
  set(appId: string, slide: FreezableSlide, box: ReadonlyTeleBox) {
47
- log("[Slide] freezer: add", appId, inspect(this.queue));
48
97
  this.map.set(appId, slide);
49
98
  this.boxes.set(appId, box);
50
99
  if (!this.queue.includes(appId)) {
51
100
  this.queue.unshift(appId);
52
101
  }
53
- this.validateQueue();
102
+ const { validated, frozen, missing } = this.validateQueue();
103
+ log(
104
+ "[Slide] freezer: add",
105
+ appId,
106
+ inspect(this.queue),
107
+ "validate",
108
+ inspect(validated),
109
+ ...(frozen.length ? ["freeze-requested", inspect(frozen)] : []),
110
+ ...(missing.length ? ["missing", inspect(missing)] : [])
111
+ );
54
112
  },
55
113
  delete(appId: string) {
56
114
  this.map.delete(appId);
57
115
  this.boxes.delete(appId);
58
116
  this.queue = this.queue.filter(id => id !== appId);
117
+ if (this.focusedAppId === appId) this.focusedAppId = undefined;
59
118
  log("[Slide] freezer: delete", appId, inspect(this.queue));
60
119
  },
61
120
  focus(appId: string) {
121
+ this.focusedAppId = appId;
62
122
  const slide = this.map.get(appId);
63
123
  const index = this.queue.indexOf(appId);
64
124
  if (index > -1) {
65
125
  this.queue.splice(index, 1);
66
126
  }
67
127
  this.queue.unshift(appId);
68
- this.validateQueue();
69
- log("[Slide] freezer: focus", appId, inspect(this.queue));
128
+ const { validated, frozen, missing } = this.validateQueue();
70
129
  if (slide) {
71
- slide.unfreeze();
130
+ this.runTransition(appId, slide, "unfreeze");
72
131
  }
132
+ log(
133
+ slide ? "[Slide] freezer: focus" : "[Slide] freezer: focus-missing",
134
+ appId,
135
+ inspect(this.queue),
136
+ "validate",
137
+ inspect(validated),
138
+ ...(frozen.length ? ["freeze-requested", inspect(frozen)] : []),
139
+ ...(missing.length ? ["missing", inspect(missing)] : [])
140
+ );
73
141
  },
74
142
  };
75
143
 
@@ -1,14 +1,35 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import type { AppContext, Displayer } from "@netless/window-manager";
2
+ import type { AppContext } from "@netless/window-manager";
3
3
  import type { SlideController } from "../SlideController";
4
4
  import { isObj } from "./helpers";
5
5
 
6
6
  type DebugMessage = { slide: boolean | "__instance" | "__debug" };
7
7
 
8
- type RoomLogger = {
8
+ type WindowManagerLogger = {
9
9
  debug: (...args: any) => void;
10
10
  info: (...args: any) => void;
11
11
  warn: (...args: any) => void;
12
+ error: (...args: any) => void;
13
+ };
14
+
15
+ type LogLevel = keyof WindowManagerLogger;
16
+
17
+ const getAppLogger = (context: AppContext<any, any, any>): WindowManagerLogger | undefined => {
18
+ try {
19
+ return context.getWindowManager()?.Logger as WindowManagerLogger | undefined;
20
+ } catch {
21
+ return undefined;
22
+ }
23
+ };
24
+
25
+ const reportAppLog = (appLogger: WindowManagerLogger | null, level: LogLevel, ...args: any[]) => {
26
+ try {
27
+ if (appLogger) appLogger[level](...args);
28
+ else if (level === "info" || level === "debug") console.log(...args);
29
+ else console[level](...args);
30
+ } catch {
31
+ // Diagnostics must not affect App setup or resource cleanup.
32
+ }
12
33
  };
13
34
 
14
35
  class Logger {
@@ -33,7 +54,8 @@ class Logger {
33
54
 
34
55
  public level: "debug" | "verbose" = import.meta.env.DEV ? "verbose" : "debug";
35
56
 
36
- public roomLogger: RoomLogger | null = null;
57
+ /** Legacy field name; the value comes from WindowManager.Logger. */
58
+ public roomLogger: WindowManagerLogger | null = null;
37
59
 
38
60
  constructor(public enable: boolean) {
39
61
  this.initialize();
@@ -48,27 +70,21 @@ class Logger {
48
70
  }
49
71
 
50
72
  log(...args: any[]) {
51
- if (this.roomLogger) {
52
- this.roomLogger.info(...args);
53
- } else if (this.enable) {
54
- console.log(...args);
55
- }
73
+ if (this.roomLogger || this.enable) reportAppLog(this.roomLogger, "info", ...args);
56
74
  }
57
75
 
58
76
  verbose(...args: any[]) {
59
- if (this.roomLogger) {
60
- this.roomLogger.debug(...args);
61
- } else if (this.enable && this.level === "verbose") {
62
- console.log(...args);
77
+ if (this.roomLogger || (this.enable && this.level === "verbose")) {
78
+ reportAppLog(this.roomLogger, "debug", ...args);
63
79
  }
64
80
  }
65
81
 
66
82
  warn(...args: any[]) {
67
- if (this.roomLogger) {
68
- this.roomLogger.warn(...args);
69
- } else {
70
- console.warn(...args);
71
- }
83
+ reportAppLog(this.roomLogger, "warn", ...args);
84
+ }
85
+
86
+ error(...args: any[]) {
87
+ reportAppLog(this.roomLogger, "error", ...args);
72
88
  }
73
89
 
74
90
  /**
@@ -101,6 +117,6 @@ class Logger {
101
117
  export const logger = /** @__PURE__ */ new Logger(import.meta.env.DEV);
102
118
  export const log = /** @__PURE__ */ logger.log.bind(logger);
103
119
  export const verbose = /** @__PURE__ */ logger.verbose.bind(logger);
104
- export const setRoomLogger = (displayer: Displayer) => {
105
- logger.roomLogger = (displayer as any).logger;
120
+ export const setRoomLogger = (context: AppContext<any, any, any>) => {
121
+ logger.roomLogger = getAppLogger(context) ?? null;
106
122
  };