@tldraw/editor 4.6.0-next.20de11b7e238 → 4.6.0-next.241e87d4700a
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-cjs/index.d.ts +668 -96
- package/dist-cjs/index.js +16 -3
- package/dist-cjs/index.js.map +3 -3
- package/dist-cjs/lib/TldrawEditor.js +55 -12
- package/dist-cjs/lib/TldrawEditor.js.map +3 -3
- package/dist-cjs/lib/components/MenuClickCapture.js +16 -1
- package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
- package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js +3 -3
- package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js.map +2 -2
- package/dist-cjs/lib/config/createTLStore.js +7 -0
- package/dist-cjs/lib/config/createTLStore.js.map +2 -2
- package/dist-cjs/lib/config/defaultAssets.js +36 -0
- package/dist-cjs/lib/config/defaultAssets.js.map +7 -0
- package/dist-cjs/lib/editor/Editor.js +215 -5
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/assets/AssetUtil.js +66 -0
- package/dist-cjs/lib/editor/assets/AssetUtil.js.map +7 -0
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/PerformanceManager/PerformanceApiAdapter.js +80 -0
- package/dist-cjs/lib/editor/managers/PerformanceManager/PerformanceApiAdapter.js.map +7 -0
- package/dist-cjs/lib/editor/managers/PerformanceManager/PerformanceManager.js +466 -0
- package/dist-cjs/lib/editor/managers/PerformanceManager/PerformanceManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/PerformanceManager/perf-types.js +17 -0
- package/dist-cjs/lib/editor/managers/PerformanceManager/perf-types.js.map +7 -0
- package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js +106 -0
- package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js.map +7 -0
- package/dist-cjs/lib/editor/managers/ThemeManager/defaultThemes.js +586 -0
- package/dist-cjs/lib/editor/managers/ThemeManager/defaultThemes.js.map +7 -0
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +6 -4
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js +11 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +1 -1
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js +6 -0
- package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
- package/dist-cjs/lib/editor/tools/StateNode.js +14 -17
- package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
- package/dist-cjs/lib/editor/types/SvgExportContext.js.map +2 -2
- package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
- package/dist-cjs/lib/exports/getSvgJsx.js +12 -7
- package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
- package/dist-cjs/lib/globals/environment.js +18 -1
- package/dist-cjs/lib/globals/environment.js.map +2 -2
- package/dist-cjs/lib/hooks/{useIsDarkMode.js → useColorMode.js} +14 -10
- package/dist-cjs/lib/hooks/useColorMode.js.map +7 -0
- package/dist-cjs/lib/hooks/useCursor.js +3 -7
- package/dist-cjs/lib/hooks/useCursor.js.map +2 -2
- package/dist-cjs/lib/hooks/useDarkMode.js +4 -4
- package/dist-cjs/lib/hooks/useDarkMode.js.map +2 -2
- package/dist-cjs/lib/utils/reparenting.js +2 -1
- package/dist-cjs/lib/utils/reparenting.js.map +2 -2
- package/dist-cjs/lib/utils/richText.js.map +2 -2
- package/dist-cjs/lib/utils/runtime.js +2 -1
- package/dist-cjs/lib/utils/runtime.js.map +2 -2
- package/dist-cjs/lib/utils/sync/hardReset.js +0 -8
- package/dist-cjs/lib/utils/sync/hardReset.js.map +2 -2
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +668 -96
- package/dist-esm/index.mjs +17 -6
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/TldrawEditor.mjs +58 -12
- package/dist-esm/lib/TldrawEditor.mjs.map +3 -3
- package/dist-esm/lib/components/MenuClickCapture.mjs +16 -1
- package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs +3 -3
- package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs.map +2 -2
- package/dist-esm/lib/config/createTLStore.mjs +10 -1
- package/dist-esm/lib/config/createTLStore.mjs.map +2 -2
- package/dist-esm/lib/config/defaultAssets.mjs +16 -0
- package/dist-esm/lib/config/defaultAssets.mjs.map +7 -0
- package/dist-esm/lib/editor/Editor.mjs +215 -5
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/assets/AssetUtil.mjs +46 -0
- package/dist-esm/lib/editor/assets/AssetUtil.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/PerformanceManager/PerformanceApiAdapter.mjs +60 -0
- package/dist-esm/lib/editor/managers/PerformanceManager/PerformanceApiAdapter.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/PerformanceManager/PerformanceManager.mjs +438 -0
- package/dist-esm/lib/editor/managers/PerformanceManager/PerformanceManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/PerformanceManager/perf-types.mjs +1 -0
- package/dist-esm/lib/editor/managers/PerformanceManager/perf-types.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs +88 -0
- package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/ThemeManager/defaultThemes.mjs +568 -0
- package/dist-esm/lib/editor/managers/ThemeManager/defaultThemes.mjs.map +7 -0
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +6 -4
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +11 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +1 -1
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs +6 -0
- package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/StateNode.mjs +14 -17
- package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
- package/dist-esm/lib/editor/types/SvgExportContext.mjs.map +2 -2
- package/dist-esm/lib/exports/getSvgJsx.mjs +12 -10
- package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
- package/dist-esm/lib/globals/environment.mjs +18 -1
- package/dist-esm/lib/globals/environment.mjs.map +2 -2
- package/dist-esm/lib/hooks/useColorMode.mjs +19 -0
- package/dist-esm/lib/hooks/useColorMode.mjs.map +7 -0
- package/dist-esm/lib/hooks/useCursor.mjs +3 -7
- package/dist-esm/lib/hooks/useCursor.mjs.map +2 -2
- package/dist-esm/lib/hooks/useDarkMode.mjs +4 -4
- package/dist-esm/lib/hooks/useDarkMode.mjs.map +2 -2
- package/dist-esm/lib/utils/reparenting.mjs +2 -1
- package/dist-esm/lib/utils/reparenting.mjs.map +2 -2
- package/dist-esm/lib/utils/richText.mjs.map +2 -2
- package/dist-esm/lib/utils/runtime.mjs +2 -1
- package/dist-esm/lib/utils/runtime.mjs.map +2 -2
- package/dist-esm/lib/utils/sync/hardReset.mjs +0 -8
- package/dist-esm/lib/utils/sync/hardReset.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +0 -33
- package/package.json +7 -7
- package/src/index.ts +23 -6
- package/src/lib/TldrawEditor.tsx +90 -13
- package/src/lib/components/MenuClickCapture.tsx +20 -0
- package/src/lib/components/default-components/CanvasShapeIndicators.tsx +3 -3
- package/src/lib/config/createTLStore.ts +22 -1
- package/src/lib/config/defaultAssets.ts +19 -0
- package/src/lib/editor/Editor.ts +301 -27
- package/src/lib/editor/assets/AssetUtil.ts +85 -0
- package/src/lib/editor/managers/FontManager/FontManager.test.ts +9 -2
- package/src/lib/editor/managers/FontManager/FontManager.ts +1 -67
- package/src/lib/editor/managers/PerformanceManager/PerformanceApiAdapter.ts +82 -0
- package/src/lib/editor/managers/PerformanceManager/PerformanceManager.test.ts +522 -0
- package/src/lib/editor/managers/PerformanceManager/PerformanceManager.ts +583 -0
- package/src/lib/editor/managers/PerformanceManager/perf-types.ts +196 -0
- package/src/lib/editor/managers/ThemeManager/ThemeManager.ts +116 -0
- package/src/lib/editor/managers/ThemeManager/defaultThemes.ts +605 -0
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +23 -29
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +5 -3
- package/src/lib/editor/shapes/ShapeUtil.ts +28 -3
- package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +1 -1
- package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +7 -0
- package/src/lib/editor/tools/StateNode.ts +16 -18
- package/src/lib/editor/types/SvgExportContext.tsx +5 -0
- package/src/lib/editor/types/external-content.ts +1 -0
- package/src/lib/exports/getSvgJsx.tsx +21 -15
- package/src/lib/globals/environment.ts +18 -0
- package/src/lib/hooks/{useIsDarkMode.ts → useColorMode.ts} +9 -5
- package/src/lib/hooks/useCursor.ts +3 -7
- package/src/lib/hooks/useDarkMode.ts +4 -4
- package/src/lib/utils/reparenting.ts +6 -2
- package/src/lib/utils/richText.ts +1 -1
- package/src/lib/utils/runtime.ts +3 -1
- package/src/lib/utils/sync/hardReset.ts +0 -8
- package/src/version.ts +3 -3
- package/dist-cjs/lib/hooks/useIsDarkMode.js.map +0 -7
- package/dist-esm/lib/hooks/useIsDarkMode.mjs +0 -15
- package/dist-esm/lib/hooks/useIsDarkMode.mjs.map +0 -7
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
30
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
31
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
32
|
+
if (decorator = decorators[i])
|
|
33
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
34
|
+
if (kind && result) __defProp(target, key, result);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
var PerformanceManager_exports = {};
|
|
38
|
+
__export(PerformanceManager_exports, {
|
|
39
|
+
PerformanceManager: () => PerformanceManager
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(PerformanceManager_exports);
|
|
42
|
+
var import_utils = require("@tldraw/utils");
|
|
43
|
+
var import_eventemitter3 = __toESM(require("eventemitter3"), 1);
|
|
44
|
+
function percentile(sorted, p) {
|
|
45
|
+
const idx = Math.ceil(p * sorted.length) - 1;
|
|
46
|
+
return sorted[Math.max(0, idx)];
|
|
47
|
+
}
|
|
48
|
+
function computeFrameTimeStats(frameTimes) {
|
|
49
|
+
if (frameTimes.length === 0) return { avg: 0, median: 0, p95: 0, p99: 0, min: 0, max: 0 };
|
|
50
|
+
const sorted = [...frameTimes].sort((a, b) => a - b);
|
|
51
|
+
const sum = sorted.reduce((a, b) => a + b, 0);
|
|
52
|
+
return {
|
|
53
|
+
avg: sum / sorted.length,
|
|
54
|
+
median: percentile(sorted, 0.5),
|
|
55
|
+
p95: percentile(sorted, 0.95),
|
|
56
|
+
p99: percentile(sorted, 0.99),
|
|
57
|
+
min: sorted[0],
|
|
58
|
+
max: sorted[sorted.length - 1]
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function toLoafEntry(entry) {
|
|
62
|
+
const e = entry;
|
|
63
|
+
if (typeof e.duration !== "number") return null;
|
|
64
|
+
return {
|
|
65
|
+
startTime: e.startTime,
|
|
66
|
+
duration: e.duration,
|
|
67
|
+
blockingDuration: e.blockingDuration ?? 0,
|
|
68
|
+
scripts: (e.scripts ?? []).map((s) => ({
|
|
69
|
+
sourceURL: s.sourceURL ?? "",
|
|
70
|
+
invoker: s.invoker ?? "",
|
|
71
|
+
duration: s.duration ?? 0
|
|
72
|
+
}))
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
class PerformanceManager {
|
|
76
|
+
/** @internal */
|
|
77
|
+
emitter = new import_eventemitter3.default();
|
|
78
|
+
editor;
|
|
79
|
+
// Active interaction tracking
|
|
80
|
+
activeInteraction = null;
|
|
81
|
+
// Active camera tracking
|
|
82
|
+
activeCamera = null;
|
|
83
|
+
// Lazy listener cleanup functions
|
|
84
|
+
frameCleanup = null;
|
|
85
|
+
shapeCreatedCleanup = null;
|
|
86
|
+
shapeEditedCleanup = null;
|
|
87
|
+
shapeDeletedCleanup = null;
|
|
88
|
+
// LoAF observer
|
|
89
|
+
loafObserver = null;
|
|
90
|
+
constructor(editor) {
|
|
91
|
+
this.editor = editor;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Subscribe to a performance event. Returns an unsubscribe function.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* const unsub = editor.performance.on('interaction-end', (event) => {
|
|
99
|
+
* sendToAnalytics({ name: event.name, fps: event.fps, p95: event.p95FrameTime })
|
|
100
|
+
* })
|
|
101
|
+
* // later: unsub()
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* @public
|
|
105
|
+
*/
|
|
106
|
+
on(event, fn) {
|
|
107
|
+
this.emitter.on(event, fn);
|
|
108
|
+
this._maybeAttachLazyListeners(event);
|
|
109
|
+
return () => {
|
|
110
|
+
this.emitter.off(event, fn);
|
|
111
|
+
this._maybeDetachLazyListeners(event);
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Subscribe to a performance event once. The listener is removed after the first invocation.
|
|
116
|
+
* Returns an unsubscribe function for early removal.
|
|
117
|
+
*
|
|
118
|
+
* @public
|
|
119
|
+
*/
|
|
120
|
+
once(event, fn) {
|
|
121
|
+
const wrapped = (...args) => {
|
|
122
|
+
;
|
|
123
|
+
fn(...args);
|
|
124
|
+
this._maybeDetachLazyListeners(event);
|
|
125
|
+
};
|
|
126
|
+
this.emitter.once(event, wrapped);
|
|
127
|
+
this._maybeAttachLazyListeners(event);
|
|
128
|
+
return () => {
|
|
129
|
+
this.emitter.off(event, wrapped);
|
|
130
|
+
this._maybeDetachLazyListeners(event);
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/** @internal */
|
|
134
|
+
dispose() {
|
|
135
|
+
if (this.activeCamera?.timeout) clearTimeout(this.activeCamera.timeout);
|
|
136
|
+
this.activeInteraction = null;
|
|
137
|
+
this.activeCamera = null;
|
|
138
|
+
this.frameCleanup?.();
|
|
139
|
+
this.frameCleanup = null;
|
|
140
|
+
this.shapeCreatedCleanup?.();
|
|
141
|
+
this.shapeCreatedCleanup = null;
|
|
142
|
+
this.shapeEditedCleanup?.();
|
|
143
|
+
this.shapeEditedCleanup = null;
|
|
144
|
+
this.shapeDeletedCleanup?.();
|
|
145
|
+
this.shapeDeletedCleanup = null;
|
|
146
|
+
this._stopLoafObserver();
|
|
147
|
+
this.emitter.removeAllListeners();
|
|
148
|
+
}
|
|
149
|
+
// --- Internal notification methods ---
|
|
150
|
+
/** @internal */
|
|
151
|
+
_notifyInteractionStart(name, path) {
|
|
152
|
+
if (this.emitter.listenerCount("interaction-start") === 0 && this.emitter.listenerCount("interaction-end") === 0) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (this.activeInteraction) {
|
|
156
|
+
console.warn(
|
|
157
|
+
`[tldraw] New interaction '${name}' started while '${this.activeInteraction.name}' was still active`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
const selectedShapeTypes = {};
|
|
161
|
+
for (const shape of this.editor.getSelectedShapes()) {
|
|
162
|
+
selectedShapeTypes[shape.type] = (selectedShapeTypes[shape.type] || 0) + 1;
|
|
163
|
+
}
|
|
164
|
+
this.activeInteraction = {
|
|
165
|
+
name,
|
|
166
|
+
path,
|
|
167
|
+
startTime: performance.now(),
|
|
168
|
+
frameTimes: [],
|
|
169
|
+
selectedShapeTypes,
|
|
170
|
+
loafEntries: []
|
|
171
|
+
};
|
|
172
|
+
const event = {
|
|
173
|
+
name,
|
|
174
|
+
path,
|
|
175
|
+
timestamp: performance.now()
|
|
176
|
+
};
|
|
177
|
+
this.emitter.emit("interaction-start", event);
|
|
178
|
+
}
|
|
179
|
+
/** @internal */
|
|
180
|
+
_notifyInteractionEnd() {
|
|
181
|
+
const interaction = this.activeInteraction;
|
|
182
|
+
if (!interaction) return;
|
|
183
|
+
this.activeInteraction = null;
|
|
184
|
+
if (this.emitter.listenerCount("interaction-end") === 0) return;
|
|
185
|
+
const duration = performance.now() - interaction.startTime;
|
|
186
|
+
const stats = computeFrameTimeStats(interaction.frameTimes);
|
|
187
|
+
const event = {
|
|
188
|
+
name: interaction.name,
|
|
189
|
+
path: interaction.path,
|
|
190
|
+
duration,
|
|
191
|
+
fps: interaction.frameTimes.length > 0 ? interaction.frameTimes.length / duration * 1e3 : 0,
|
|
192
|
+
frameCount: interaction.frameTimes.length,
|
|
193
|
+
avgFrameTime: stats.avg,
|
|
194
|
+
medianFrameTime: stats.median,
|
|
195
|
+
p95FrameTime: stats.p95,
|
|
196
|
+
p99FrameTime: stats.p99,
|
|
197
|
+
minFrameTime: stats.min,
|
|
198
|
+
maxFrameTime: stats.max,
|
|
199
|
+
frameTimes: interaction.frameTimes,
|
|
200
|
+
shapeCount: this.editor.getCurrentPageShapeIds().size,
|
|
201
|
+
selectedShapeTypes: interaction.selectedShapeTypes,
|
|
202
|
+
longAnimationFrames: interaction.loafEntries.length > 0 ? interaction.loafEntries : void 0,
|
|
203
|
+
zoomLevel: this.editor.getCamera().z,
|
|
204
|
+
timestamp: performance.now()
|
|
205
|
+
};
|
|
206
|
+
this.emitter.emit("interaction-end", event);
|
|
207
|
+
}
|
|
208
|
+
/** @internal */
|
|
209
|
+
_notifyCameraOperation(type) {
|
|
210
|
+
if (this.emitter.listenerCount("camera-start") === 0 && this.emitter.listenerCount("camera-end") === 0) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (this.activeCamera) {
|
|
214
|
+
if (this.activeCamera.timeout) {
|
|
215
|
+
clearTimeout(this.activeCamera.timeout);
|
|
216
|
+
}
|
|
217
|
+
if (this.activeCamera.type !== type) {
|
|
218
|
+
this._endCameraSession();
|
|
219
|
+
this._startCameraSession(type);
|
|
220
|
+
} else {
|
|
221
|
+
this.activeCamera.timeout = this.editor.timers.setTimeout(
|
|
222
|
+
() => this._endCameraSession(),
|
|
223
|
+
50
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
this._startCameraSession(type);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/** @internal */
|
|
231
|
+
_notifyUndoRedo(type, undoDepth, redoDepth) {
|
|
232
|
+
if (this.emitter.listenerCount(type) === 0) return;
|
|
233
|
+
const event = {
|
|
234
|
+
type,
|
|
235
|
+
undoDepth,
|
|
236
|
+
redoDepth
|
|
237
|
+
};
|
|
238
|
+
this.emitter.emit(type, event);
|
|
239
|
+
}
|
|
240
|
+
// --- Private helpers ---
|
|
241
|
+
_startCameraSession(type) {
|
|
242
|
+
this.activeCamera = {
|
|
243
|
+
type,
|
|
244
|
+
startTime: performance.now(),
|
|
245
|
+
frameTimes: [],
|
|
246
|
+
timeout: this.editor.timers.setTimeout(() => this._endCameraSession(), 50),
|
|
247
|
+
loafEntries: []
|
|
248
|
+
};
|
|
249
|
+
if (this.emitter.listenerCount("camera-start") > 0) {
|
|
250
|
+
const event = {
|
|
251
|
+
type,
|
|
252
|
+
timestamp: performance.now()
|
|
253
|
+
};
|
|
254
|
+
this.emitter.emit("camera-start", event);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
_endCameraSession() {
|
|
258
|
+
const camera = this.activeCamera;
|
|
259
|
+
if (!camera) return;
|
|
260
|
+
this.activeCamera = null;
|
|
261
|
+
if (camera.timeout) clearTimeout(camera.timeout);
|
|
262
|
+
if (this.emitter.listenerCount("camera-end") === 0) return;
|
|
263
|
+
const duration = performance.now() - camera.startTime;
|
|
264
|
+
const stats = computeFrameTimeStats(camera.frameTimes);
|
|
265
|
+
const viewportBounds = this.editor.getViewportScreenBounds();
|
|
266
|
+
const totalShapes = this.editor.getCurrentPageShapeIds().size;
|
|
267
|
+
const culledShapeCount = this.editor.getCulledShapes().size;
|
|
268
|
+
const event = {
|
|
269
|
+
type: camera.type,
|
|
270
|
+
duration,
|
|
271
|
+
fps: camera.frameTimes.length > 0 ? camera.frameTimes.length / duration * 1e3 : 0,
|
|
272
|
+
frameCount: camera.frameTimes.length,
|
|
273
|
+
avgFrameTime: stats.avg,
|
|
274
|
+
medianFrameTime: stats.median,
|
|
275
|
+
p95FrameTime: stats.p95,
|
|
276
|
+
p99FrameTime: stats.p99,
|
|
277
|
+
minFrameTime: stats.min,
|
|
278
|
+
maxFrameTime: stats.max,
|
|
279
|
+
frameTimes: camera.frameTimes,
|
|
280
|
+
shapeCount: totalShapes,
|
|
281
|
+
viewportWidth: viewportBounds.w,
|
|
282
|
+
viewportHeight: viewportBounds.h,
|
|
283
|
+
longAnimationFrames: camera.loafEntries.length > 0 ? camera.loafEntries : void 0,
|
|
284
|
+
visibleShapeCount: totalShapes - culledShapeCount,
|
|
285
|
+
culledShapeCount,
|
|
286
|
+
zoomLevel: this.editor.getCamera().z,
|
|
287
|
+
timestamp: performance.now()
|
|
288
|
+
};
|
|
289
|
+
this.emitter.emit("camera-end", event);
|
|
290
|
+
}
|
|
291
|
+
_onFrame(elapsed) {
|
|
292
|
+
if (this.activeInteraction) {
|
|
293
|
+
this.activeInteraction.frameTimes.push(elapsed);
|
|
294
|
+
}
|
|
295
|
+
if (this.activeCamera) {
|
|
296
|
+
this.activeCamera.frameTimes.push(elapsed);
|
|
297
|
+
}
|
|
298
|
+
if (this.emitter.listenerCount("frame") > 0) {
|
|
299
|
+
const totalShapes = this.editor.getCurrentPageShapeIds().size;
|
|
300
|
+
const culledShapes = this.editor.getCulledShapes();
|
|
301
|
+
const culledCount = culledShapes.size;
|
|
302
|
+
const event = {
|
|
303
|
+
elapsed,
|
|
304
|
+
shapeCount: totalShapes,
|
|
305
|
+
culledShapeCount: culledCount,
|
|
306
|
+
visibleShapeCount: totalShapes - culledCount
|
|
307
|
+
};
|
|
308
|
+
this.emitter.emit("frame", event);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
_onShapesCreated(records) {
|
|
312
|
+
if (this.emitter.listenerCount("shapes-created") === 0) return;
|
|
313
|
+
const shapeTypes = {};
|
|
314
|
+
for (const record of records) {
|
|
315
|
+
if (record.typeName === "shape") {
|
|
316
|
+
shapeTypes[record.type] = (shapeTypes[record.type] || 0) + 1;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
const count = Object.values(shapeTypes).reduce((a, b) => a + b, 0);
|
|
320
|
+
if (count === 0) return;
|
|
321
|
+
const event = {
|
|
322
|
+
operation: "create",
|
|
323
|
+
count,
|
|
324
|
+
shapeTypes,
|
|
325
|
+
timestamp: performance.now()
|
|
326
|
+
};
|
|
327
|
+
this.emitter.emit("shapes-created", event);
|
|
328
|
+
}
|
|
329
|
+
_onShapesEdited(records) {
|
|
330
|
+
if (this.emitter.listenerCount("shapes-updated") === 0) return;
|
|
331
|
+
const shapeTypes = {};
|
|
332
|
+
for (const record of records) {
|
|
333
|
+
if (record.typeName === "shape") {
|
|
334
|
+
shapeTypes[record.type] = (shapeTypes[record.type] || 0) + 1;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const count = Object.values(shapeTypes).reduce((a, b) => a + b, 0);
|
|
338
|
+
if (count === 0) return;
|
|
339
|
+
const event = {
|
|
340
|
+
operation: "update",
|
|
341
|
+
count,
|
|
342
|
+
shapeTypes,
|
|
343
|
+
timestamp: performance.now()
|
|
344
|
+
};
|
|
345
|
+
this.emitter.emit("shapes-updated", event);
|
|
346
|
+
}
|
|
347
|
+
_onShapesDeleted(ids) {
|
|
348
|
+
if (this.emitter.listenerCount("shapes-deleted") === 0) return;
|
|
349
|
+
const shapeTypes = {};
|
|
350
|
+
for (const id of ids) {
|
|
351
|
+
const shape = this.editor.getShape(id);
|
|
352
|
+
if (shape) {
|
|
353
|
+
shapeTypes[shape.type] = (shapeTypes[shape.type] || 0) + 1;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const event = {
|
|
357
|
+
operation: "delete",
|
|
358
|
+
count: ids.length,
|
|
359
|
+
shapeTypes,
|
|
360
|
+
timestamp: performance.now()
|
|
361
|
+
};
|
|
362
|
+
this.emitter.emit("shapes-deleted", event);
|
|
363
|
+
}
|
|
364
|
+
// --- LoAF observer ---
|
|
365
|
+
_startLoafObserver() {
|
|
366
|
+
if (typeof PerformanceObserver === "undefined") return;
|
|
367
|
+
try {
|
|
368
|
+
const supported = PerformanceObserver.supportedEntryTypes;
|
|
369
|
+
if (!supported?.includes("long-animation-frame")) return;
|
|
370
|
+
} catch {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
this.loafObserver = new PerformanceObserver((list) => {
|
|
374
|
+
const isInteractionActive = this.activeInteraction !== null;
|
|
375
|
+
const isCameraActive = this.activeCamera !== null;
|
|
376
|
+
if (!isInteractionActive && !isCameraActive) return;
|
|
377
|
+
for (const entry of list.getEntries()) {
|
|
378
|
+
const loaf = toLoafEntry(entry);
|
|
379
|
+
if (!loaf) continue;
|
|
380
|
+
if (isInteractionActive) {
|
|
381
|
+
this.activeInteraction.loafEntries.push(loaf);
|
|
382
|
+
}
|
|
383
|
+
if (isCameraActive) {
|
|
384
|
+
this.activeCamera.loafEntries.push(loaf);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
this.loafObserver.observe({ type: "long-animation-frame", buffered: false });
|
|
389
|
+
}
|
|
390
|
+
_stopLoafObserver() {
|
|
391
|
+
if (this.loafObserver) {
|
|
392
|
+
this.loafObserver.disconnect();
|
|
393
|
+
this.loafObserver = null;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// --- Lazy listener management ---
|
|
397
|
+
_needsFrameListener() {
|
|
398
|
+
return this.emitter.listenerCount("frame") > 0 || this.emitter.listenerCount("interaction-start") > 0 || this.emitter.listenerCount("interaction-end") > 0 || this.emitter.listenerCount("camera-start") > 0 || this.emitter.listenerCount("camera-end") > 0;
|
|
399
|
+
}
|
|
400
|
+
_needsLoafObserver() {
|
|
401
|
+
return this.emitter.listenerCount("interaction-end") > 0 || this.emitter.listenerCount("camera-end") > 0;
|
|
402
|
+
}
|
|
403
|
+
_maybeAttachLazyListeners(event) {
|
|
404
|
+
if (!this.frameCleanup && (event === "frame" || event === "interaction-start" || event === "interaction-end" || event === "camera-start" || event === "camera-end")) {
|
|
405
|
+
if (this._needsFrameListener()) {
|
|
406
|
+
this.editor.on("frame", this._onFrame);
|
|
407
|
+
this.frameCleanup = () => this.editor.off("frame", this._onFrame);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (!this.loafObserver && (event === "interaction-end" || event === "camera-end")) {
|
|
411
|
+
if (this._needsLoafObserver()) {
|
|
412
|
+
this._startLoafObserver();
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (!this.shapeCreatedCleanup && event === "shapes-created") {
|
|
416
|
+
this.editor.on("created-shapes", this._onShapesCreated);
|
|
417
|
+
this.shapeCreatedCleanup = () => this.editor.off("created-shapes", this._onShapesCreated);
|
|
418
|
+
}
|
|
419
|
+
if (!this.shapeEditedCleanup && event === "shapes-updated") {
|
|
420
|
+
this.editor.on("edited-shapes", this._onShapesEdited);
|
|
421
|
+
this.shapeEditedCleanup = () => this.editor.off("edited-shapes", this._onShapesEdited);
|
|
422
|
+
}
|
|
423
|
+
if (!this.shapeDeletedCleanup && event === "shapes-deleted") {
|
|
424
|
+
this.editor.on("deleted-shapes", this._onShapesDeleted);
|
|
425
|
+
this.shapeDeletedCleanup = () => this.editor.off("deleted-shapes", this._onShapesDeleted);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
_maybeDetachLazyListeners(event) {
|
|
429
|
+
if (this.frameCleanup && (event === "frame" || event === "interaction-start" || event === "interaction-end" || event === "camera-start" || event === "camera-end")) {
|
|
430
|
+
if (!this._needsFrameListener()) {
|
|
431
|
+
this.frameCleanup();
|
|
432
|
+
this.frameCleanup = null;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (this.loafObserver && (event === "interaction-end" || event === "camera-end")) {
|
|
436
|
+
if (!this._needsLoafObserver()) {
|
|
437
|
+
this._stopLoafObserver();
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (this.shapeCreatedCleanup && event === "shapes-created" && this.emitter.listenerCount("shapes-created") === 0) {
|
|
441
|
+
this.shapeCreatedCleanup();
|
|
442
|
+
this.shapeCreatedCleanup = null;
|
|
443
|
+
}
|
|
444
|
+
if (this.shapeEditedCleanup && event === "shapes-updated" && this.emitter.listenerCount("shapes-updated") === 0) {
|
|
445
|
+
this.shapeEditedCleanup();
|
|
446
|
+
this.shapeEditedCleanup = null;
|
|
447
|
+
}
|
|
448
|
+
if (this.shapeDeletedCleanup && event === "shapes-deleted" && this.emitter.listenerCount("shapes-deleted") === 0) {
|
|
449
|
+
this.shapeDeletedCleanup();
|
|
450
|
+
this.shapeDeletedCleanup = null;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
__decorateClass([
|
|
455
|
+
import_utils.bind
|
|
456
|
+
], PerformanceManager.prototype, "_onFrame", 1);
|
|
457
|
+
__decorateClass([
|
|
458
|
+
import_utils.bind
|
|
459
|
+
], PerformanceManager.prototype, "_onShapesCreated", 1);
|
|
460
|
+
__decorateClass([
|
|
461
|
+
import_utils.bind
|
|
462
|
+
], PerformanceManager.prototype, "_onShapesEdited", 1);
|
|
463
|
+
__decorateClass([
|
|
464
|
+
import_utils.bind
|
|
465
|
+
], PerformanceManager.prototype, "_onShapesDeleted", 1);
|
|
466
|
+
//# sourceMappingURL=PerformanceManager.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/lib/editor/managers/PerformanceManager/PerformanceManager.ts"],
|
|
4
|
+
"sourcesContent": ["import type { TLRecord, TLShapeId } from '@tldraw/tlschema'\nimport { bind } from '@tldraw/utils'\nimport EventEmitter from 'eventemitter3'\nimport type { Editor } from '../../Editor'\nimport type {\n\tTLCameraEndPerfEvent,\n\tTLCameraStartPerfEvent,\n\tTLFramePerfEvent,\n\tTLInteractionEndPerfEvent,\n\tTLInteractionStartPerfEvent,\n\tTLPerfEventMap,\n\tTLPerfLongAnimationFrame,\n\tTLShapeOperationPerfEvent,\n\tTLUndoRedoPerfEvent,\n} from './perf-types'\n\nfunction percentile(sorted: number[], p: number): number {\n\tconst idx = Math.ceil(p * sorted.length) - 1\n\treturn sorted[Math.max(0, idx)]\n}\n\nfunction computeFrameTimeStats(frameTimes: number[]) {\n\tif (frameTimes.length === 0) return { avg: 0, median: 0, p95: 0, p99: 0, min: 0, max: 0 }\n\tconst sorted = [...frameTimes].sort((a, b) => a - b)\n\tconst sum = sorted.reduce((a, b) => a + b, 0)\n\treturn {\n\t\tavg: sum / sorted.length,\n\t\tmedian: percentile(sorted, 0.5),\n\t\tp95: percentile(sorted, 0.95),\n\t\tp99: percentile(sorted, 0.99),\n\t\tmin: sorted[0],\n\t\tmax: sorted[sorted.length - 1],\n\t}\n}\n\nfunction toLoafEntry(entry: PerformanceEntry): TLPerfLongAnimationFrame | null {\n\t// LoAF entries have these properties but TypeScript doesn't know about them yet\n\tconst e = entry as PerformanceEntry & {\n\t\tblockingDuration?: number\n\t\tscripts?: ReadonlyArray<{\n\t\t\tsourceURL?: string\n\t\t\tinvoker?: string\n\t\t\tduration?: number\n\t\t}>\n\t}\n\tif (typeof e.duration !== 'number') return null\n\treturn {\n\t\tstartTime: e.startTime,\n\t\tduration: e.duration,\n\t\tblockingDuration: e.blockingDuration ?? 0,\n\t\tscripts: (e.scripts ?? []).map((s) => ({\n\t\t\tsourceURL: s.sourceURL ?? '',\n\t\t\tinvoker: s.invoker ?? '',\n\t\t\tduration: s.duration ?? 0,\n\t\t})),\n\t}\n}\n\n/**\n * Manages performance event subscriptions for the editor. Available as `editor.performance`.\n *\n * Listeners are lazy \u2014 internal editor hooks (frame, shape events) are only attached while\n * at least one subscriber exists, so there is zero overhead when unused.\n *\n * @example\n * ```ts\n * const unsub = editor.performance.on('interaction-end', (event) => {\n * console.log(`${event.name}: ${event.fps.toFixed(1)} fps, p95=${event.p95FrameTime.toFixed(1)}ms`)\n * })\n * ```\n *\n * @public\n */\nexport class PerformanceManager {\n\t/** @internal */\n\treadonly emitter = new EventEmitter<TLPerfEventMap>()\n\n\tprivate editor: Editor\n\n\t// Active interaction tracking\n\tprivate activeInteraction: {\n\t\tname: string\n\t\tpath: string\n\t\tstartTime: number\n\t\tframeTimes: number[]\n\t\tselectedShapeTypes: Record<string, number>\n\t\tloafEntries: TLPerfLongAnimationFrame[]\n\t} | null = null\n\n\t// Active camera tracking\n\tprivate activeCamera: {\n\t\ttype: 'panning' | 'zooming'\n\t\tstartTime: number\n\t\tframeTimes: number[]\n\t\ttimeout: number | null\n\t\tloafEntries: TLPerfLongAnimationFrame[]\n\t} | null = null\n\n\t// Lazy listener cleanup functions\n\tprivate frameCleanup: (() => void) | null = null\n\tprivate shapeCreatedCleanup: (() => void) | null = null\n\tprivate shapeEditedCleanup: (() => void) | null = null\n\tprivate shapeDeletedCleanup: (() => void) | null = null\n\n\t// LoAF observer\n\tprivate loafObserver: PerformanceObserver | null = null\n\n\tconstructor(editor: Editor) {\n\t\tthis.editor = editor\n\t}\n\n\t/**\n\t * Subscribe to a performance event. Returns an unsubscribe function.\n\t *\n\t * @example\n\t * ```ts\n\t * const unsub = editor.performance.on('interaction-end', (event) => {\n\t * sendToAnalytics({ name: event.name, fps: event.fps, p95: event.p95FrameTime })\n\t * })\n\t * // later: unsub()\n\t * ```\n\t *\n\t * @public\n\t */\n\ton<K extends keyof TLPerfEventMap>(\n\t\tevent: K,\n\t\tfn: (...args: TLPerfEventMap[K]) => void\n\t): () => void {\n\t\tthis.emitter.on(event, fn as any)\n\t\tthis._maybeAttachLazyListeners(event)\n\t\treturn () => {\n\t\t\tthis.emitter.off(event, fn as any)\n\t\t\tthis._maybeDetachLazyListeners(event)\n\t\t}\n\t}\n\n\t/**\n\t * Subscribe to a performance event once. The listener is removed after the first invocation.\n\t * Returns an unsubscribe function for early removal.\n\t *\n\t * @public\n\t */\n\tonce<K extends keyof TLPerfEventMap>(\n\t\tevent: K,\n\t\tfn: (...args: TLPerfEventMap[K]) => void\n\t): () => void {\n\t\tconst wrapped = (...args: TLPerfEventMap[K]) => {\n\t\t\t;(fn as any)(...args)\n\t\t\tthis._maybeDetachLazyListeners(event)\n\t\t}\n\t\tthis.emitter.once(event, wrapped as any)\n\t\tthis._maybeAttachLazyListeners(event)\n\t\treturn () => {\n\t\t\tthis.emitter.off(event, wrapped as any)\n\t\t\tthis._maybeDetachLazyListeners(event)\n\t\t}\n\t}\n\n\t/** @internal */\n\tdispose() {\n\t\tif (this.activeCamera?.timeout) clearTimeout(this.activeCamera.timeout)\n\t\tthis.activeInteraction = null\n\t\tthis.activeCamera = null\n\t\tthis.frameCleanup?.()\n\t\tthis.frameCleanup = null\n\t\tthis.shapeCreatedCleanup?.()\n\t\tthis.shapeCreatedCleanup = null\n\t\tthis.shapeEditedCleanup?.()\n\t\tthis.shapeEditedCleanup = null\n\t\tthis.shapeDeletedCleanup?.()\n\t\tthis.shapeDeletedCleanup = null\n\t\tthis._stopLoafObserver()\n\t\tthis.emitter.removeAllListeners()\n\t}\n\n\t// --- Internal notification methods ---\n\n\t/** @internal */\n\t_notifyInteractionStart(name: string, path: string) {\n\t\tif (\n\t\t\tthis.emitter.listenerCount('interaction-start') === 0 &&\n\t\t\tthis.emitter.listenerCount('interaction-end') === 0\n\t\t) {\n\t\t\treturn\n\t\t}\n\n\t\tif (this.activeInteraction) {\n\t\t\tconsole.warn(\n\t\t\t\t`[tldraw] New interaction '${name}' started while '${this.activeInteraction.name}' was still active`\n\t\t\t)\n\t\t}\n\n\t\t// Capture selected shape types at start\n\t\tconst selectedShapeTypes: Record<string, number> = {}\n\t\tfor (const shape of this.editor.getSelectedShapes()) {\n\t\t\tselectedShapeTypes[shape.type] = (selectedShapeTypes[shape.type] || 0) + 1\n\t\t}\n\n\t\tthis.activeInteraction = {\n\t\t\tname,\n\t\t\tpath,\n\t\t\tstartTime: performance.now(),\n\t\t\tframeTimes: [],\n\t\t\tselectedShapeTypes,\n\t\t\tloafEntries: [],\n\t\t}\n\n\t\tconst event: TLInteractionStartPerfEvent = {\n\t\t\tname,\n\t\t\tpath,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('interaction-start', event)\n\t}\n\n\t/** @internal */\n\t_notifyInteractionEnd() {\n\t\tconst interaction = this.activeInteraction\n\t\tif (!interaction) return\n\t\tthis.activeInteraction = null\n\n\t\tif (this.emitter.listenerCount('interaction-end') === 0) return\n\n\t\tconst duration = performance.now() - interaction.startTime\n\t\tconst stats = computeFrameTimeStats(interaction.frameTimes)\n\n\t\tconst event: TLInteractionEndPerfEvent = {\n\t\t\tname: interaction.name,\n\t\t\tpath: interaction.path,\n\t\t\tduration,\n\t\t\tfps:\n\t\t\t\tinteraction.frameTimes.length > 0 ? (interaction.frameTimes.length / duration) * 1000 : 0,\n\t\t\tframeCount: interaction.frameTimes.length,\n\t\t\tavgFrameTime: stats.avg,\n\t\t\tmedianFrameTime: stats.median,\n\t\t\tp95FrameTime: stats.p95,\n\t\t\tp99FrameTime: stats.p99,\n\t\t\tminFrameTime: stats.min,\n\t\t\tmaxFrameTime: stats.max,\n\t\t\tframeTimes: interaction.frameTimes,\n\t\t\tshapeCount: this.editor.getCurrentPageShapeIds().size,\n\t\t\tselectedShapeTypes: interaction.selectedShapeTypes,\n\t\t\tlongAnimationFrames: interaction.loafEntries.length > 0 ? interaction.loafEntries : undefined,\n\t\t\tzoomLevel: this.editor.getCamera().z,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('interaction-end', event)\n\t}\n\n\t/** @internal */\n\t_notifyCameraOperation(type: 'panning' | 'zooming') {\n\t\tif (\n\t\t\tthis.emitter.listenerCount('camera-start') === 0 &&\n\t\t\tthis.emitter.listenerCount('camera-end') === 0\n\t\t) {\n\t\t\treturn\n\t\t}\n\n\t\tif (this.activeCamera) {\n\t\t\t// Extend existing camera session\n\t\t\tif (this.activeCamera.timeout) {\n\t\t\t\tclearTimeout(this.activeCamera.timeout)\n\t\t\t}\n\t\t\t// If type changed, end old and start new\n\t\t\tif (this.activeCamera.type !== type) {\n\t\t\t\tthis._endCameraSession()\n\t\t\t\tthis._startCameraSession(type)\n\t\t\t} else {\n\t\t\t\t// Reset timeout\n\t\t\t\tthis.activeCamera.timeout = this.editor.timers.setTimeout(\n\t\t\t\t\t() => this._endCameraSession(),\n\t\t\t\t\t50\n\t\t\t\t)\n\t\t\t}\n\t\t} else {\n\t\t\tthis._startCameraSession(type)\n\t\t}\n\t}\n\n\t/** @internal */\n\t_notifyUndoRedo(type: 'undo' | 'redo', undoDepth: number, redoDepth: number) {\n\t\tif (this.emitter.listenerCount(type) === 0) return\n\n\t\tconst event: TLUndoRedoPerfEvent = {\n\t\t\ttype,\n\t\t\tundoDepth,\n\t\t\tredoDepth,\n\t\t}\n\t\tthis.emitter.emit(type, event)\n\t}\n\n\t// --- Private helpers ---\n\n\tprivate _startCameraSession(type: 'panning' | 'zooming') {\n\t\tthis.activeCamera = {\n\t\t\ttype,\n\t\t\tstartTime: performance.now(),\n\t\t\tframeTimes: [],\n\t\t\ttimeout: this.editor.timers.setTimeout(() => this._endCameraSession(), 50),\n\t\t\tloafEntries: [],\n\t\t}\n\n\t\tif (this.emitter.listenerCount('camera-start') > 0) {\n\t\t\tconst event: TLCameraStartPerfEvent = {\n\t\t\t\ttype,\n\t\t\t\ttimestamp: performance.now(),\n\t\t\t}\n\t\t\tthis.emitter.emit('camera-start', event)\n\t\t}\n\t}\n\n\tprivate _endCameraSession() {\n\t\tconst camera = this.activeCamera\n\t\tif (!camera) return\n\t\tthis.activeCamera = null\n\t\tif (camera.timeout) clearTimeout(camera.timeout)\n\n\t\tif (this.emitter.listenerCount('camera-end') === 0) return\n\n\t\tconst duration = performance.now() - camera.startTime\n\t\tconst stats = computeFrameTimeStats(camera.frameTimes)\n\t\tconst viewportBounds = this.editor.getViewportScreenBounds()\n\t\tconst totalShapes = this.editor.getCurrentPageShapeIds().size\n\t\tconst culledShapeCount = this.editor.getCulledShapes().size\n\n\t\tconst event: TLCameraEndPerfEvent = {\n\t\t\ttype: camera.type,\n\t\t\tduration,\n\t\t\tfps: camera.frameTimes.length > 0 ? (camera.frameTimes.length / duration) * 1000 : 0,\n\t\t\tframeCount: camera.frameTimes.length,\n\t\t\tavgFrameTime: stats.avg,\n\t\t\tmedianFrameTime: stats.median,\n\t\t\tp95FrameTime: stats.p95,\n\t\t\tp99FrameTime: stats.p99,\n\t\t\tminFrameTime: stats.min,\n\t\t\tmaxFrameTime: stats.max,\n\t\t\tframeTimes: camera.frameTimes,\n\t\t\tshapeCount: totalShapes,\n\t\t\tviewportWidth: viewportBounds.w,\n\t\t\tviewportHeight: viewportBounds.h,\n\t\t\tlongAnimationFrames: camera.loafEntries.length > 0 ? camera.loafEntries : undefined,\n\t\t\tvisibleShapeCount: totalShapes - culledShapeCount,\n\t\t\tculledShapeCount,\n\t\t\tzoomLevel: this.editor.getCamera().z,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('camera-end', event)\n\t}\n\n\t@bind\n\tprivate _onFrame(elapsed: number) {\n\t\t// Record frame time for active interaction/camera\n\t\tif (this.activeInteraction) {\n\t\t\tthis.activeInteraction.frameTimes.push(elapsed)\n\t\t}\n\t\tif (this.activeCamera) {\n\t\t\tthis.activeCamera.frameTimes.push(elapsed)\n\t\t}\n\n\t\t// Emit standalone frame event if listeners exist\n\t\tif (this.emitter.listenerCount('frame') > 0) {\n\t\t\tconst totalShapes = this.editor.getCurrentPageShapeIds().size\n\t\t\tconst culledShapes = this.editor.getCulledShapes()\n\t\t\tconst culledCount = culledShapes.size\n\t\t\tconst event: TLFramePerfEvent = {\n\t\t\t\telapsed,\n\t\t\t\tshapeCount: totalShapes,\n\t\t\t\tculledShapeCount: culledCount,\n\t\t\t\tvisibleShapeCount: totalShapes - culledCount,\n\t\t\t}\n\t\t\tthis.emitter.emit('frame', event)\n\t\t}\n\t}\n\n\t@bind\n\tprivate _onShapesCreated(records: TLRecord[]) {\n\t\tif (this.emitter.listenerCount('shapes-created') === 0) return\n\t\tconst shapeTypes: Record<string, number> = {}\n\t\tfor (const record of records) {\n\t\t\tif (record.typeName === 'shape') {\n\t\t\t\tshapeTypes[record.type] = (shapeTypes[record.type] || 0) + 1\n\t\t\t}\n\t\t}\n\t\tconst count = Object.values(shapeTypes).reduce((a, b) => a + b, 0)\n\t\tif (count === 0) return\n\t\tconst event: TLShapeOperationPerfEvent = {\n\t\t\toperation: 'create',\n\t\t\tcount,\n\t\t\tshapeTypes,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('shapes-created', event)\n\t}\n\n\t@bind\n\tprivate _onShapesEdited(records: TLRecord[]) {\n\t\tif (this.emitter.listenerCount('shapes-updated') === 0) return\n\t\tconst shapeTypes: Record<string, number> = {}\n\t\tfor (const record of records) {\n\t\t\tif (record.typeName === 'shape') {\n\t\t\t\tshapeTypes[record.type] = (shapeTypes[record.type] || 0) + 1\n\t\t\t}\n\t\t}\n\t\tconst count = Object.values(shapeTypes).reduce((a, b) => a + b, 0)\n\t\tif (count === 0) return\n\t\tconst event: TLShapeOperationPerfEvent = {\n\t\t\toperation: 'update',\n\t\t\tcount,\n\t\t\tshapeTypes,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('shapes-updated', event)\n\t}\n\n\t@bind\n\tprivate _onShapesDeleted(ids: TLShapeId[]) {\n\t\tif (this.emitter.listenerCount('shapes-deleted') === 0) return\n\t\tconst shapeTypes: Record<string, number> = {}\n\t\tfor (const id of ids) {\n\t\t\t// Works because 'deleted-shapes' fires before store.remove() in Editor.deleteShapes\n\t\t\tconst shape = this.editor.getShape(id)\n\t\t\tif (shape) {\n\t\t\t\tshapeTypes[shape.type] = (shapeTypes[shape.type] || 0) + 1\n\t\t\t}\n\t\t}\n\t\tconst event: TLShapeOperationPerfEvent = {\n\t\t\toperation: 'delete',\n\t\t\tcount: ids.length,\n\t\t\tshapeTypes,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('shapes-deleted', event)\n\t}\n\n\t// --- LoAF observer ---\n\n\tprivate _startLoafObserver() {\n\t\tif (typeof PerformanceObserver === 'undefined') return\n\n\t\ttry {\n\t\t\tconst supported = PerformanceObserver.supportedEntryTypes\n\t\t\tif (!supported?.includes('long-animation-frame')) return\n\t\t} catch {\n\t\t\treturn\n\t\t}\n\n\t\tthis.loafObserver = new PerformanceObserver((list) => {\n\t\t\tconst isInteractionActive = this.activeInteraction !== null\n\t\t\tconst isCameraActive = this.activeCamera !== null\n\n\t\t\tif (!isInteractionActive && !isCameraActive) return\n\n\t\t\tfor (const entry of list.getEntries()) {\n\t\t\t\tconst loaf = toLoafEntry(entry)\n\t\t\t\tif (!loaf) continue\n\n\t\t\t\tif (isInteractionActive) {\n\t\t\t\t\tthis.activeInteraction!.loafEntries.push(loaf)\n\t\t\t\t}\n\t\t\t\tif (isCameraActive) {\n\t\t\t\t\tthis.activeCamera!.loafEntries.push(loaf)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\tthis.loafObserver.observe({ type: 'long-animation-frame', buffered: false })\n\t}\n\n\tprivate _stopLoafObserver() {\n\t\tif (this.loafObserver) {\n\t\t\tthis.loafObserver.disconnect()\n\t\t\tthis.loafObserver = null\n\t\t}\n\t}\n\n\t// --- Lazy listener management ---\n\n\tprivate _needsFrameListener(): boolean {\n\t\treturn (\n\t\t\tthis.emitter.listenerCount('frame') > 0 ||\n\t\t\tthis.emitter.listenerCount('interaction-start') > 0 ||\n\t\t\tthis.emitter.listenerCount('interaction-end') > 0 ||\n\t\t\tthis.emitter.listenerCount('camera-start') > 0 ||\n\t\t\tthis.emitter.listenerCount('camera-end') > 0\n\t\t)\n\t}\n\n\tprivate _needsLoafObserver(): boolean {\n\t\treturn (\n\t\t\tthis.emitter.listenerCount('interaction-end') > 0 ||\n\t\t\tthis.emitter.listenerCount('camera-end') > 0\n\t\t)\n\t}\n\n\tprivate _maybeAttachLazyListeners(event: keyof TLPerfEventMap) {\n\t\t// Frame listener needed for frame event + interaction/camera frame time tracking\n\t\tif (\n\t\t\t!this.frameCleanup &&\n\t\t\t(event === 'frame' ||\n\t\t\t\tevent === 'interaction-start' ||\n\t\t\t\tevent === 'interaction-end' ||\n\t\t\t\tevent === 'camera-start' ||\n\t\t\t\tevent === 'camera-end')\n\t\t) {\n\t\t\tif (this._needsFrameListener()) {\n\t\t\t\tthis.editor.on('frame', this._onFrame)\n\t\t\t\tthis.frameCleanup = () => this.editor.off('frame', this._onFrame)\n\t\t\t}\n\t\t}\n\n\t\t// LoAF observer needed when interaction-end or camera-end listeners exist\n\t\tif (!this.loafObserver && (event === 'interaction-end' || event === 'camera-end')) {\n\t\t\tif (this._needsLoafObserver()) {\n\t\t\t\tthis._startLoafObserver()\n\t\t\t}\n\t\t}\n\n\t\tif (!this.shapeCreatedCleanup && event === 'shapes-created') {\n\t\t\tthis.editor.on('created-shapes', this._onShapesCreated)\n\t\t\tthis.shapeCreatedCleanup = () => this.editor.off('created-shapes', this._onShapesCreated)\n\t\t}\n\n\t\tif (!this.shapeEditedCleanup && event === 'shapes-updated') {\n\t\t\tthis.editor.on('edited-shapes', this._onShapesEdited)\n\t\t\tthis.shapeEditedCleanup = () => this.editor.off('edited-shapes', this._onShapesEdited)\n\t\t}\n\n\t\tif (!this.shapeDeletedCleanup && event === 'shapes-deleted') {\n\t\t\tthis.editor.on('deleted-shapes', this._onShapesDeleted)\n\t\t\tthis.shapeDeletedCleanup = () => this.editor.off('deleted-shapes', this._onShapesDeleted)\n\t\t}\n\t}\n\n\tprivate _maybeDetachLazyListeners(event: keyof TLPerfEventMap) {\n\t\tif (\n\t\t\tthis.frameCleanup &&\n\t\t\t(event === 'frame' ||\n\t\t\t\tevent === 'interaction-start' ||\n\t\t\t\tevent === 'interaction-end' ||\n\t\t\t\tevent === 'camera-start' ||\n\t\t\t\tevent === 'camera-end')\n\t\t) {\n\t\t\tif (!this._needsFrameListener()) {\n\t\t\t\tthis.frameCleanup()\n\t\t\t\tthis.frameCleanup = null\n\t\t\t}\n\t\t}\n\n\t\t// Stop LoAF observer when no longer needed\n\t\tif (this.loafObserver && (event === 'interaction-end' || event === 'camera-end')) {\n\t\t\tif (!this._needsLoafObserver()) {\n\t\t\t\tthis._stopLoafObserver()\n\t\t\t}\n\t\t}\n\n\t\tif (\n\t\t\tthis.shapeCreatedCleanup &&\n\t\t\tevent === 'shapes-created' &&\n\t\t\tthis.emitter.listenerCount('shapes-created') === 0\n\t\t) {\n\t\t\tthis.shapeCreatedCleanup()\n\t\t\tthis.shapeCreatedCleanup = null\n\t\t}\n\n\t\tif (\n\t\t\tthis.shapeEditedCleanup &&\n\t\t\tevent === 'shapes-updated' &&\n\t\t\tthis.emitter.listenerCount('shapes-updated') === 0\n\t\t) {\n\t\t\tthis.shapeEditedCleanup()\n\t\t\tthis.shapeEditedCleanup = null\n\t\t}\n\n\t\tif (\n\t\t\tthis.shapeDeletedCleanup &&\n\t\t\tevent === 'shapes-deleted' &&\n\t\t\tthis.emitter.listenerCount('shapes-deleted') === 0\n\t\t) {\n\t\t\tthis.shapeDeletedCleanup()\n\t\t\tthis.shapeDeletedCleanup = null\n\t\t}\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["EventEmitter"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __copyProps = (to, from, except, desc) => {
|
|
7
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
+
for (let key of __getOwnPropNames(from))
|
|
9
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
+
}
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
|
+
var perf_types_exports = {};
|
|
16
|
+
module.exports = __toCommonJS(perf_types_exports);
|
|
17
|
+
//# sourceMappingURL=perf-types.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/lib/editor/managers/PerformanceManager/perf-types.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Common frame time statistics shared by interaction and camera end events.\n * @public\n */\nexport interface TLPerfFrameTimeStats {\n\t/** Total duration of the session in ms. */\n\tduration: number\n\t/** Average frames per second during the session. */\n\tfps: number\n\t/** Total number of frames recorded. */\n\tframeCount: number\n\t/** Mean frame duration in ms. */\n\tavgFrameTime: number\n\t/** Median (p50) frame duration in ms. */\n\tmedianFrameTime: number\n\t/** 95th percentile frame duration in ms. */\n\tp95FrameTime: number\n\t/** 99th percentile frame duration in ms. */\n\tp99FrameTime: number\n\t/** Shortest frame duration in ms. */\n\tminFrameTime: number\n\t/** Longest frame duration in ms. */\n\tmaxFrameTime: number\n\t/** Raw frame durations for local analysis. Exclude when sending to analytics. */\n\tframeTimes: number[]\n\t/**\n\t * Long animation frames observed during this period (Chromium 123+).\n\t * Only present when the browser supports the Long Animation Frames API and\n\t * at least one long frame was observed.\n\t * Exclude when sending to analytics \u2014 entries are large and contain script URLs.\n\t */\n\tlongAnimationFrames?: TLPerfLongAnimationFrame[]\n}\n\n/**\n * Emitted when an interaction state (e.g. translating, resizing) is entered.\n * @public\n */\nexport interface TLInteractionStartPerfEvent {\n\t/** The state node id (e.g. `'translating'`). */\n\tname: string\n\t/** Full tool path (e.g. `'select.translating'`). */\n\tpath: string\n\t/** `performance.now()` when the interaction started. */\n\ttimestamp: number\n}\n\n/**\n * Emitted when an interaction state is exited, with aggregated frame time stats.\n * @public\n */\nexport interface TLInteractionEndPerfEvent extends TLPerfFrameTimeStats {\n\t/** The state node id (e.g. `'translating'`). */\n\tname: string\n\t/** Full tool path (e.g. `'select.translating'`). */\n\tpath: string\n\t/** Total shapes on the current page. */\n\tshapeCount: number\n\t/** Breakdown of selected shape types at interaction start (e.g. `{ geo: 2, draw: 1 }`). */\n\tselectedShapeTypes: Record<string, number>\n\t/** Camera zoom level (`camera.z`) at interaction end. */\n\tzoomLevel: number\n\t/** `performance.now()` when the interaction ended. */\n\ttimestamp: number\n}\n\n/**\n * Emitted when a camera operation (pan or zoom) begins.\n * @public\n */\nexport interface TLCameraStartPerfEvent {\n\t/** Whether this is a pan or zoom operation. */\n\ttype: 'panning' | 'zooming'\n\t/** `performance.now()` when the camera session started. */\n\ttimestamp: number\n}\n\n/**\n * Emitted when a camera operation ends (after a 50ms debounce), with aggregated frame time stats.\n * @public\n */\nexport interface TLCameraEndPerfEvent extends TLPerfFrameTimeStats {\n\t/** Whether this was a pan or zoom operation. */\n\ttype: 'panning' | 'zooming'\n\t/** Total shapes on the current page. */\n\tshapeCount: number\n\t/** Number of shapes visible (not culled) in the viewport. */\n\tvisibleShapeCount: number\n\t/** Number of shapes culled (off-screen) from rendering. */\n\tculledShapeCount: number\n\t/** Viewport width in screen pixels. */\n\tviewportWidth: number\n\t/** Viewport height in screen pixels. */\n\tviewportHeight: number\n\t/** Camera zoom level (`camera.z`) at session end. */\n\tzoomLevel: number\n\t/** `performance.now()` when the camera session ended. */\n\ttimestamp: number\n}\n\n/**\n * Emitted when shapes are created, updated, or deleted.\n * @public\n */\nexport interface TLShapeOperationPerfEvent {\n\t/** The operation type. */\n\toperation: 'create' | 'update' | 'delete'\n\t/** Number of shapes affected. */\n\tcount: number\n\t/** Breakdown by shape type (e.g. `{ geo: 2, draw: 1 }`). */\n\tshapeTypes: Record<string, number>\n\t/** `performance.now()` when the operation occurred. */\n\ttimestamp: number\n}\n\n/**\n * Emitted every animation frame when at least one `'frame'` listener is registered.\n * @public\n */\nexport interface TLFramePerfEvent {\n\t/** Time since the last frame in ms. */\n\telapsed: number\n\t/** Total shapes on the current page. */\n\tshapeCount: number\n\t/** Number of shapes culled (off-screen) from rendering. */\n\tculledShapeCount: number\n\t/** Number of shapes visible (not culled) in the viewport. */\n\tvisibleShapeCount: number\n}\n\n/**\n * Emitted after an undo or redo operation.\n * @public\n */\nexport interface TLUndoRedoPerfEvent {\n\t/** Whether this was an undo or redo. */\n\ttype: 'undo' | 'redo'\n\t/** Number of undo steps remaining. */\n\tundoDepth: number\n\t/** Number of redo steps remaining. */\n\tredoDepth: number\n}\n\n/**\n * A long animation frame observed by the browser during an interaction.\n * Available only in browsers that support the Long Animation Frames API (Chromium 123+).\n * @public\n */\nexport interface TLPerfLongAnimationFrame {\n\t/** Frame start time (relative to timeOrigin). */\n\tstartTime: number\n\t/** Total frame duration in ms. */\n\tduration: number\n\t/** Time the main thread was blocked in ms. */\n\tblockingDuration: number\n\t/** Scripts that contributed to the long frame. */\n\tscripts: TLPerfLongAnimationFrameScript[]\n}\n\n/** A script attribution entry from a long animation frame. @public */\nexport interface TLPerfLongAnimationFrameScript {\n\t/** The script source URL (may be empty for inline scripts). */\n\tsourceURL: string\n\t/** The function name or invoker description. */\n\tinvoker: string\n\t/** Time spent in this script in ms. */\n\tduration: number\n}\n\n/**\n * Map of all performance event names to their payload types.\n * Used with {@link PerformanceManager.on} and {@link PerformanceManager.once}.\n * @public\n */\nexport interface TLPerfEventMap {\n\t/** An interaction state was entered. */\n\t'interaction-start': [TLInteractionStartPerfEvent]\n\t/** An interaction state was exited, with aggregated frame time stats. */\n\t'interaction-end': [TLInteractionEndPerfEvent]\n\t/** A camera operation (pan/zoom) began. */\n\t'camera-start': [TLCameraStartPerfEvent]\n\t/** A camera operation ended (after debounce), with aggregated frame time stats. */\n\t'camera-end': [TLCameraEndPerfEvent]\n\t/** Shapes were created. */\n\t'shapes-created': [TLShapeOperationPerfEvent]\n\t/** Shapes were updated. */\n\t'shapes-updated': [TLShapeOperationPerfEvent]\n\t/** Shapes were deleted. */\n\t'shapes-deleted': [TLShapeOperationPerfEvent]\n\t/** An animation frame was rendered. Only fires when listeners are registered. */\n\tframe: [TLFramePerfEvent]\n\t/** An undo operation was performed. */\n\tundo: [TLUndoRedoPerfEvent]\n\t/** A redo operation was performed. */\n\tredo: [TLUndoRedoPerfEvent]\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;AAAA;AAAA;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|