@fluidframework/debugger 2.0.0-internal.3.0.0 → 2.0.0-internal.3.1.0
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/.eslintrc.js +9 -12
- package/README.md +33 -33
- package/api-extractor.json +2 -2
- package/dist/fluidDebugger.d.ts.map +1 -1
- package/dist/fluidDebugger.js.map +1 -1
- package/dist/fluidDebuggerController.d.ts.map +1 -1
- package/dist/fluidDebuggerController.js +5 -2
- package/dist/fluidDebuggerController.js.map +1 -1
- package/dist/fluidDebuggerUi.d.ts.map +1 -1
- package/dist/fluidDebuggerUi.js +11 -5
- package/dist/fluidDebuggerUi.js.map +1 -1
- package/dist/messageSchema.js.map +1 -1
- package/dist/sanitize.js.map +1 -1
- package/dist/sanitizer.d.ts.map +1 -1
- package/dist/sanitizer.js +9 -5
- package/dist/sanitizer.js.map +1 -1
- package/lib/fluidDebugger.d.ts.map +1 -1
- package/lib/fluidDebugger.js.map +1 -1
- package/lib/fluidDebuggerController.d.ts.map +1 -1
- package/lib/fluidDebuggerController.js +5 -2
- package/lib/fluidDebuggerController.js.map +1 -1
- package/lib/fluidDebuggerUi.d.ts.map +1 -1
- package/lib/fluidDebuggerUi.js +11 -5
- package/lib/fluidDebuggerUi.js.map +1 -1
- package/lib/messageSchema.js.map +1 -1
- package/lib/sanitize.js.map +1 -1
- package/lib/sanitizer.d.ts.map +1 -1
- package/lib/sanitizer.js +9 -5
- package/lib/sanitizer.js.map +1 -1
- package/package.json +68 -67
- package/prettier.config.cjs +1 -1
- package/src/fluidDebugger.ts +30 -30
- package/src/fluidDebuggerController.ts +348 -322
- package/src/fluidDebuggerUi.ts +306 -281
- package/src/messageSchema.ts +367 -367
- package/src/sanitize.ts +29 -29
- package/src/sanitizer.ts +699 -638
- package/tsconfig.esnext.json +6 -6
- package/tsconfig.json +10 -15
package/src/fluidDebuggerUi.ts
CHANGED
|
@@ -7,98 +7,97 @@ import { assert } from "@fluidframework/common-utils";
|
|
|
7
7
|
import { ISequencedDocumentMessage, IVersion } from "@fluidframework/protocol-definitions";
|
|
8
8
|
|
|
9
9
|
export interface IDebuggerUI {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Version information is provided.
|
|
12
|
+
* Expect updates (information about seq#, timestamp) through updateVersion() calls
|
|
13
|
+
*/
|
|
14
|
+
addVersions(version: IVersion[]): void;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Call when new version is downloaded from storage
|
|
18
|
+
* Expect multiple callbacks.
|
|
19
|
+
*/
|
|
20
|
+
updateVersion(index: number, version: IVersion, seqNumber: number): void;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Called in response to successful onVersionSelection() or onSnapshotFileSelection() call
|
|
24
|
+
* and provides extra information about selection.
|
|
25
|
+
* It expected that UI layer would change its mode as result of this call, i.e. switch to
|
|
26
|
+
* displaying op playback controls (if this is supported)
|
|
27
|
+
* Note: There maybe no call to versionSelected() in response to onSnapshotFileSelection() call
|
|
28
|
+
* if file does not exist, has wrong name of wrong format.
|
|
29
|
+
* @param version - version, file name, or undefined if playing ops.
|
|
30
|
+
*/
|
|
31
|
+
versionSelected(seqNumber: number, version: IVersion | string): void;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Called by controller in response to new ops being downloaded
|
|
35
|
+
* Called with disable = true if there are no (currently) ops to play
|
|
36
|
+
*/
|
|
37
|
+
disableNextOpButton(disable: boolean): void;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Called by controller when new ops arrive (or we are done playing previous batch)
|
|
41
|
+
* Indicates next batch of ops that would be played when UI calls controller's onOpButtonClick()
|
|
42
|
+
* Called with ops=[] when there are no ops to play.
|
|
43
|
+
*/
|
|
44
|
+
updateNextOpText(ops: ISequencedDocumentMessage[]): void;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Called periodically when new versions are downloaded from server
|
|
48
|
+
*/
|
|
49
|
+
updateVersionText(versionsLeft: number): void;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Called periodically to notify about last known op
|
|
53
|
+
* @param lastKnownOp - seq number of last known op. -1 if can't play ops in this mode (load from file)
|
|
54
|
+
* @param stillLoading - true if we did not reach yet the end of the stream
|
|
55
|
+
*/
|
|
56
|
+
updateLastOpText(lastKnownOp: number, stillLoading: boolean): void;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export interface IDebuggerController {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Initialization. UI layers calls into controller to connect the two.
|
|
62
|
+
* @param ui - UI layer
|
|
63
|
+
*/
|
|
64
|
+
connectToUi(ui: IDebuggerUI);
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Called by UI layer when debugger window is closed by user
|
|
68
|
+
* If called before user makes selection of snapshot/file, original
|
|
69
|
+
* document service is returned to loader (instead of debugger service) and normal document load continues.
|
|
70
|
+
*/
|
|
71
|
+
onClose(): void;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* UI Layer notifies about selection of version to continue.
|
|
75
|
+
* On successful load, versionSelected() is called.
|
|
76
|
+
* @param version - Snapshot version to start from.
|
|
77
|
+
*/
|
|
78
|
+
onVersionSelection(version: IVersion): void;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* UI Layer notifies about selection of version to continue.
|
|
82
|
+
* On successful load, versionSelected() is called.
|
|
83
|
+
* @param version - File to load snapshot from
|
|
84
|
+
*/
|
|
85
|
+
onSnapshotFileSelection(file: File): void;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* "next op" button is clicked in the UI
|
|
89
|
+
* @param steps - number of ops to play.
|
|
90
|
+
*/
|
|
91
|
+
onOpButtonClick(steps: number): void;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* "Download ops" option is clicked in the UI. Returns JSON of the full opStream when available.
|
|
95
|
+
* @param anonymize - anonymize the ops json using the sanitization tool
|
|
96
|
+
*/
|
|
97
|
+
onDownloadOpsButtonClick(anonymize: boolean): Promise<string>;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
const debuggerWindowHtml =
|
|
101
|
-
`<Title>Fluid Debugger</Title>
|
|
100
|
+
const debuggerWindowHtml = `<Title>Fluid Debugger</Title>
|
|
102
101
|
<body>
|
|
103
102
|
<h3>Fluid Debugger</h3>
|
|
104
103
|
Please select snapshot or file to start with<br/>
|
|
@@ -116,8 +115,7 @@ Close debugger window to proceed to live document<br/><br/>
|
|
|
116
115
|
<br/><br/><div id='versionText'></div>
|
|
117
116
|
</body>`;
|
|
118
117
|
|
|
119
|
-
const debuggerWindowHtml2 =
|
|
120
|
-
`<Title>Fluid Debugger</Title>
|
|
118
|
+
const debuggerWindowHtml2 = `<Title>Fluid Debugger</Title>
|
|
121
119
|
<body>
|
|
122
120
|
<h3>Fluid Debugger</h3>
|
|
123
121
|
<div id='versionText'></div>
|
|
@@ -135,196 +133,223 @@ Step to move: <input type='number' id='steps' value='1' min='1' style='width:50p
|
|
|
135
133
|
</body>`;
|
|
136
134
|
|
|
137
135
|
export class DebuggerUI {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
136
|
+
public static create(controller: IDebuggerController): DebuggerUI | null {
|
|
137
|
+
if (
|
|
138
|
+
typeof window !== "object" ||
|
|
139
|
+
window === null ||
|
|
140
|
+
typeof window.document !== "object" ||
|
|
141
|
+
window.document == null
|
|
142
|
+
) {
|
|
143
|
+
console.log("Can't create debugger window - not running in browser!");
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const debuggerWindow = window.open(
|
|
148
|
+
"",
|
|
149
|
+
"",
|
|
150
|
+
"width=400,height=400,resizable=yes,location=no,menubar=no,titlebar=no,status=no,toolbar=no",
|
|
151
|
+
);
|
|
152
|
+
if (!debuggerWindow) {
|
|
153
|
+
console.error(
|
|
154
|
+
"Can't create debugger window - please enable pop-up windows in your browser!",
|
|
155
|
+
);
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return new DebuggerUI(controller, debuggerWindow);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private static formatDate(date: number) {
|
|
163
|
+
// Alternative - without timezone
|
|
164
|
+
// new Date().toLocaleString('default', { timeZone: 'UTC'}));
|
|
165
|
+
// new Date().toLocaleString('default', { year: 'numeric', month: 'short',
|
|
166
|
+
// day: 'numeric', hour: '2-digit', minute: 'numeric', second: 'numeric' }));
|
|
167
|
+
return new Date(date).toUTCString();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
protected selector?: HTMLSelectElement;
|
|
171
|
+
protected versionText: HTMLDivElement;
|
|
172
|
+
|
|
173
|
+
protected buttonOps?: HTMLButtonElement;
|
|
174
|
+
protected text1?: HTMLDivElement;
|
|
175
|
+
protected text2?: HTMLDivElement;
|
|
176
|
+
protected text3?: HTMLDivElement;
|
|
177
|
+
protected lastOpText?: HTMLDivElement;
|
|
178
|
+
protected wasVersionSelected = false;
|
|
179
|
+
protected versions: IVersion[] = [];
|
|
180
|
+
|
|
181
|
+
protected documentClosed = false;
|
|
182
|
+
|
|
183
|
+
protected constructor(
|
|
184
|
+
private readonly controller: IDebuggerController,
|
|
185
|
+
private readonly debuggerWindow: Window,
|
|
186
|
+
) {
|
|
187
|
+
const doc = this.debuggerWindow.document;
|
|
188
|
+
doc.write(debuggerWindowHtml);
|
|
189
|
+
|
|
190
|
+
window.addEventListener(
|
|
191
|
+
"beforeunload",
|
|
192
|
+
(e) => {
|
|
193
|
+
this.documentClosed = true;
|
|
194
|
+
this.debuggerWindow.close();
|
|
195
|
+
},
|
|
196
|
+
false,
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
this.debuggerWindow.addEventListener(
|
|
200
|
+
"beforeunload",
|
|
201
|
+
(e) => {
|
|
202
|
+
if (!this.documentClosed) {
|
|
203
|
+
this.controller.onClose();
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
false,
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
this.selector = doc.getElementById("selector") as HTMLSelectElement;
|
|
210
|
+
|
|
211
|
+
const buttonVers = doc.getElementById("buttonVers") as HTMLDivElement;
|
|
212
|
+
buttonVers.onclick = () => {
|
|
213
|
+
const index = this.selector!.selectedIndex;
|
|
214
|
+
controller.onVersionSelection(this.versions[index]);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const fileSnapshot = doc.getElementById("file") as HTMLInputElement;
|
|
218
|
+
fileSnapshot.addEventListener(
|
|
219
|
+
"change",
|
|
220
|
+
() => {
|
|
221
|
+
const files = fileSnapshot.files;
|
|
222
|
+
if (files) {
|
|
223
|
+
controller.onSnapshotFileSelection(files[0]);
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
false,
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
const opDownloadButton = doc.getElementById("downloadOps") as HTMLElement;
|
|
230
|
+
const anonymizeCheckbox = doc.getElementById("anonymize") as HTMLInputElement;
|
|
231
|
+
this.attachDownloadOpsListener(opDownloadButton, anonymizeCheckbox);
|
|
232
|
+
|
|
233
|
+
this.versionText = doc.getElementById("versionText") as HTMLDivElement;
|
|
234
|
+
this.versionText.textContent = "Fetching snapshots, please wait...";
|
|
235
|
+
|
|
236
|
+
controller.connectToUi(this);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private attachDownloadOpsListener(element: HTMLElement, anonymize: HTMLInputElement) {
|
|
240
|
+
element.addEventListener("click", () => {
|
|
241
|
+
this.controller
|
|
242
|
+
.onDownloadOpsButtonClick(anonymize.checked)
|
|
243
|
+
.then((opJson) => {
|
|
244
|
+
this.download("opStream.json", opJson);
|
|
245
|
+
})
|
|
246
|
+
.catch((error) => {
|
|
247
|
+
console.log(`Error downloading ops: ${error}`);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
public addVersions(versions: IVersion[]) {
|
|
253
|
+
if (this.selector) {
|
|
254
|
+
this.versions = versions;
|
|
255
|
+
for (const version of versions) {
|
|
256
|
+
const option = document.createElement("option");
|
|
257
|
+
option.text =
|
|
258
|
+
version.date !== undefined
|
|
259
|
+
? `id = ${version.id}, time = ${version.date}`
|
|
260
|
+
: `id = ${version.id}`;
|
|
261
|
+
this.selector.add(option);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
public updateVersion(index: number, version: IVersion, seqNumber: number) {
|
|
267
|
+
if (this.selector) {
|
|
268
|
+
const option = this.selector[index] as HTMLOptionElement;
|
|
269
|
+
option.text = `${option.text}, seq = ${seqNumber}`;
|
|
270
|
+
this.selector[index] = option;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
public versionSelected(seqNumber: number, version: IVersion | string) {
|
|
275
|
+
const text =
|
|
276
|
+
typeof version === "string"
|
|
277
|
+
? `Playing ${version} file`
|
|
278
|
+
: `Playing from ${version.id}, seq# ${seqNumber}`;
|
|
279
|
+
|
|
280
|
+
this.wasVersionSelected = true;
|
|
281
|
+
this.selector = undefined;
|
|
282
|
+
|
|
283
|
+
const doc = this.debuggerWindow.document;
|
|
284
|
+
doc.open();
|
|
285
|
+
doc.write(debuggerWindowHtml2);
|
|
286
|
+
doc.close();
|
|
287
|
+
|
|
288
|
+
this.lastOpText = doc.getElementById("lastOp") as HTMLDivElement;
|
|
289
|
+
this.text1 = doc.getElementById("text1") as HTMLDivElement;
|
|
290
|
+
this.text2 = doc.getElementById("text2") as HTMLDivElement;
|
|
291
|
+
this.text3 = doc.getElementById("text3") as HTMLDivElement;
|
|
292
|
+
|
|
293
|
+
const steps = doc.getElementById("steps") as HTMLInputElement;
|
|
294
|
+
this.buttonOps = doc.getElementById("buttonOps") as HTMLButtonElement;
|
|
295
|
+
this.buttonOps.disabled = true;
|
|
296
|
+
this.buttonOps.onclick = () => {
|
|
297
|
+
this.controller.onOpButtonClick(Number(steps.value));
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
this.versionText = doc.getElementById("versionText") as HTMLDivElement;
|
|
301
|
+
this.versionText.textContent = text;
|
|
302
|
+
|
|
303
|
+
const opDownloadButton = doc.getElementById("downloadOps") as HTMLElement;
|
|
304
|
+
const anonymizeCheckbox = doc.getElementById("anonymize") as HTMLInputElement;
|
|
305
|
+
this.attachDownloadOpsListener(opDownloadButton, anonymizeCheckbox);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
public disableNextOpButton(disable: boolean) {
|
|
309
|
+
assert(!!this.buttonOps, 0x088 /* "Missing button ops button!" */);
|
|
310
|
+
this.buttonOps.disabled = disable;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
public updateNextOpText(ops: ISequencedDocumentMessage[]) {
|
|
314
|
+
if (ops.length === 0) {
|
|
315
|
+
this.text1!.textContent = "";
|
|
316
|
+
this.text2!.textContent = "";
|
|
317
|
+
this.text3!.textContent = "";
|
|
318
|
+
} else {
|
|
319
|
+
const op = ops[0];
|
|
320
|
+
const seq = op.sequenceNumber;
|
|
321
|
+
const date = DebuggerUI.formatDate(op.timestamp);
|
|
322
|
+
this.text1!.textContent = `Next op seq#: ${seq}`;
|
|
323
|
+
this.text2!.textContent = `Type: ${op.type}`;
|
|
324
|
+
this.text3!.textContent = `${date}`;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
public updateVersionText(versionCount: number) {
|
|
329
|
+
if (!this.wasVersionSelected) {
|
|
330
|
+
const text =
|
|
331
|
+
versionCount === 0 ? "" : `Fetching information about ${versionCount} snapshots...`;
|
|
332
|
+
this.versionText.textContent = text;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
public updateLastOpText(lastKnownOp: number, stillLoading: boolean) {
|
|
337
|
+
const text = stillLoading
|
|
338
|
+
? `Last op (still loading): ${lastKnownOp}`
|
|
339
|
+
: `Document's last op seq#: ${lastKnownOp}`;
|
|
340
|
+
this.lastOpText!.textContent = text;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
private download(filename: string, data: string): void {
|
|
344
|
+
const element = document.createElement("a");
|
|
345
|
+
element.setAttribute("href", `data:text/plain;charset=utf-8,${encodeURIComponent(data)}`);
|
|
346
|
+
element.setAttribute("download", filename);
|
|
347
|
+
|
|
348
|
+
element.style.display = "none";
|
|
349
|
+
document.body.appendChild(element);
|
|
350
|
+
|
|
351
|
+
element.click();
|
|
352
|
+
|
|
353
|
+
document.body.removeChild(element);
|
|
354
|
+
}
|
|
330
355
|
}
|