@lightworkai.official/debug-capture 0.14.1 → 0.14.2
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/README.md +20 -14
- package/dist/debug-capture.js +8 -3
- package/dist/index.mjs +18 -13
- package/dist/ui/strings.d.ts +3 -0
- package/dist/ui/styles.d.ts +1 -1
- package/package.json +1 -1
- package/src/ui/element.ts +105 -32
- package/src/ui/strings.ts +7 -0
- package/src/ui/styles.ts +5 -0
package/src/ui/element.ts
CHANGED
|
@@ -148,18 +148,76 @@ function createReporterClass(): CustomElementConstructor {
|
|
|
148
148
|
this.returnFocus = active instanceof HTMLElement && active !== document.body ? active : null;
|
|
149
149
|
const t = strings(getConfig().locale);
|
|
150
150
|
/*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
151
|
+
* The owner's order (docs/WIDGET-ASK-PLAN.md, 0.14.2): where the desk
|
|
152
|
+
* offers something to ask, the press asks first — แจ้งปัญหา or คำขอ —
|
|
153
|
+
* because a request has no use for the screen and must not be asked to
|
|
154
|
+
* share it. Read from what is already known, never awaited: the share
|
|
155
|
+
* has to start inside the press that chose แจ้งปัญหา. An offer not read
|
|
156
|
+
* yet is the report as it always was, and its form shows the two cards
|
|
157
|
+
* when the offer lands.
|
|
158
158
|
*/
|
|
159
|
-
|
|
159
|
+
const offered = requestTypesNow();
|
|
160
|
+
if (offered === null) void loadRequestTypes();
|
|
161
|
+
if (reason.type === "manual" && offered?.length) {
|
|
162
|
+
this.renderChooser(t, reason);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
160
165
|
await this.report(reason, t);
|
|
161
166
|
}
|
|
162
167
|
|
|
168
|
+
/** แจ้งปัญหา or คำขอ — the two cards, before anything is asked of the browser. */
|
|
169
|
+
private renderChooser(t: Strings, reason: DebugReason): void {
|
|
170
|
+
const panel = this.shell(t);
|
|
171
|
+
panel.classList.add("narrow");
|
|
172
|
+
panel.setAttribute("aria-label", t.chooseTitle);
|
|
173
|
+
const head = el("div", "head");
|
|
174
|
+
const closeBtn = button("✕", "icon");
|
|
175
|
+
closeBtn.setAttribute("aria-label", t.close);
|
|
176
|
+
closeBtn.addEventListener("click", () => this.close());
|
|
177
|
+
head.append(el("h2", "", t.chooseTitle), closeBtn);
|
|
178
|
+
|
|
179
|
+
const problem = kindButton("problem", BUG, t.kindProblem, t.chooseProblemHint);
|
|
180
|
+
problem.dataset.choose = "problem";
|
|
181
|
+
// Our own dialog leaves the screen before the share starts, or it is in the picture.
|
|
182
|
+
problem.addEventListener("click", () => {
|
|
183
|
+
this.root.replaceChildren();
|
|
184
|
+
void this.report(reason, t);
|
|
185
|
+
});
|
|
186
|
+
const ask = kindButton("request", WORKFLOW, t.kindRequest, t.chooseRequestHint);
|
|
187
|
+
ask.dataset.choose = "request";
|
|
188
|
+
// A request: no share asked for. The page's address still travels with it.
|
|
189
|
+
ask.addEventListener("click", () => {
|
|
190
|
+
this.bundle = buildBundle({ reason, note: "", screenshotDataUrl: null, screenshotMethod: "none", erroredQueries: safeQueries() });
|
|
191
|
+
this.renderForm(t, reason, "request");
|
|
192
|
+
});
|
|
193
|
+
const choices = el("div", "kinds choose");
|
|
194
|
+
choices.append(problem, ask);
|
|
195
|
+
choices.addEventListener("keydown", (e) => {
|
|
196
|
+
if (!ARROWS.includes(e.key)) return;
|
|
197
|
+
e.preventDefault();
|
|
198
|
+
(this.root.activeElement === problem ? ask : problem).focus();
|
|
199
|
+
});
|
|
200
|
+
panel.append(head, choices);
|
|
201
|
+
problem.focus();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* คำขอ → แจ้งปัญหา on a request that began with no screen: the share is
|
|
206
|
+
* asked for now, inside this press, with our dialog off the screen. What
|
|
207
|
+
* was typed crosses over; a share refused goes back to the request, as it
|
|
208
|
+
* was.
|
|
209
|
+
*/
|
|
210
|
+
private async switchToReport(t: Strings, reason: DebugReason, carried: Draft): Promise<void> {
|
|
211
|
+
this.root.replaceChildren();
|
|
212
|
+
const shot = await captureScreen();
|
|
213
|
+
if (shot.method === "cancelled") {
|
|
214
|
+
this.renderForm(t, reason, "request", { ...carried, asking: true });
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
this.bundle = buildBundle({ reason, note: "", screenshotDataUrl: shot.dataUrl, screenshotMethod: shot.method, erroredQueries: safeQueries() });
|
|
218
|
+
this.renderForm(t, reason, "problem", carried);
|
|
219
|
+
}
|
|
220
|
+
|
|
163
221
|
/** A report: the screen first, then the form — see open(). Called inside a click, with nothing awaited before the capture. */
|
|
164
222
|
private async report(reason: DebugReason, t: Strings): Promise<void> {
|
|
165
223
|
/*
|
|
@@ -186,7 +244,7 @@ function createReporterClass(): CustomElementConstructor {
|
|
|
186
244
|
screenshotMethod: shot.method,
|
|
187
245
|
erroredQueries: safeQueries(),
|
|
188
246
|
});
|
|
189
|
-
this.renderForm(t, reason);
|
|
247
|
+
this.renderForm(t, reason, "problem");
|
|
190
248
|
}
|
|
191
249
|
|
|
192
250
|
/** Closed unsent, what was typed is kept for the next press; sent, it is gone. */
|
|
@@ -236,11 +294,13 @@ function createReporterClass(): CustomElementConstructor {
|
|
|
236
294
|
* leaves the page's logs behind — they are a problem's evidence, not an
|
|
237
295
|
* ask's — and its picture can be taken off.
|
|
238
296
|
*/
|
|
239
|
-
private renderForm(t: Strings, reason: DebugReason): void {
|
|
297
|
+
private renderForm(t: Strings, reason: DebugReason, start: "problem" | "request", carried?: Draft): void {
|
|
240
298
|
const bundle = this.bundle;
|
|
241
299
|
if (!bundle) return;
|
|
242
300
|
const panel = this.shell(t);
|
|
243
|
-
let mode: "problem" | "request" =
|
|
301
|
+
let mode: "problem" | "request" = start;
|
|
302
|
+
// Was the screen asked for? A request chosen first was not — แจ้งปัญหา asks for it then.
|
|
303
|
+
const screenTaken = start === "problem" || !!bundle.screenshotDataUrl;
|
|
244
304
|
let chosen: RequestTypeOption | null = null;
|
|
245
305
|
|
|
246
306
|
const head = el("div", "head");
|
|
@@ -251,27 +311,24 @@ function createReporterClass(): CustomElementConstructor {
|
|
|
251
311
|
head.append(heading, closeBtn);
|
|
252
312
|
|
|
253
313
|
/*
|
|
254
|
-
* แจ้งปัญหา or คำขอ — two cards
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
314
|
+
* แจ้งปัญหา or คำขอ — the same two cards the press chose from, kept under
|
|
315
|
+
* the heading so the form can be switched any time with nothing typed
|
|
316
|
+
* lost (to แจ้งปัญหา from a request that began with no screen, the
|
|
317
|
+
* share is asked for then — switchToReport). Shown only where the desk
|
|
318
|
+
* offers something to ask.
|
|
259
319
|
*/
|
|
260
320
|
const kinds = el("div", "kinds");
|
|
261
321
|
kinds.hidden = true;
|
|
262
322
|
kinds.setAttribute("role", "radiogroup");
|
|
263
323
|
kinds.setAttribute("aria-label", t.chooseTitle);
|
|
264
324
|
const kindCard = (kind: "problem" | "request", icon: IconNode, title: string, hint: string) => {
|
|
265
|
-
const card =
|
|
325
|
+
const card = kindButton(kind, icon, title, hint);
|
|
266
326
|
card.setAttribute("role", "radio");
|
|
267
|
-
card.dataset.kind = kind;
|
|
268
|
-
const top = el("span", "kind-top");
|
|
269
|
-
top.append(svgIcon(icon), el("b", "", title));
|
|
270
|
-
card.append(top, el("span", "kind-hint", hint), svgIcon(CHECK, "tick"));
|
|
271
327
|
card.addEventListener("click", () => setMode(kind));
|
|
272
328
|
return card;
|
|
273
329
|
};
|
|
274
|
-
|
|
330
|
+
// From a request that began with no screen, แจ้งปัญหา will ask for it — said on the card, as the chooser says it.
|
|
331
|
+
const problemCard = kindCard("problem", BUG, t.kindProblem, screenTaken ? t.kindProblemHint : t.chooseProblemHint);
|
|
275
332
|
const requestCard = kindCard("request", WORKFLOW, t.kindRequest, t.kindRequestHint);
|
|
276
333
|
kinds.append(problemCard, requestCard);
|
|
277
334
|
// A radio group's own keys: an arrow moves the choice, and the focus with it.
|
|
@@ -366,16 +423,24 @@ function createReporterClass(): CustomElementConstructor {
|
|
|
366
423
|
logs.v.textContent = asking ? t.requestLeavesWhat : captured;
|
|
367
424
|
shotChoice.hidden = !asking || !bundle.screenshotDataUrl;
|
|
368
425
|
if (markWhat) fillLabel(markWhat, asking ? t.requestAnnotateHint : t.annotateHint);
|
|
369
|
-
for (const node of shot)
|
|
426
|
+
for (const node of shot) {
|
|
427
|
+
node.classList.toggle("dropped", asking && !withShot.checked);
|
|
428
|
+
// A request that began with no screen has no picture to speak of.
|
|
429
|
+
node.hidden = asking && !bundle.screenshotDataUrl;
|
|
430
|
+
}
|
|
370
431
|
submitBtn.textContent = asking ? t.requestSubmit : t.submit;
|
|
371
432
|
ready();
|
|
372
433
|
};
|
|
373
434
|
withShot.addEventListener("change", apply);
|
|
374
|
-
|
|
435
|
+
const setMode = (next: "problem" | "request"): void => {
|
|
375
436
|
if (next === mode) return;
|
|
437
|
+
if (next === "problem" && !screenTaken) {
|
|
438
|
+
void this.switchToReport(t, reason, { title: summary.value, detail: detail.value, asking: false, typeId: chosen?.id ?? null });
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
376
441
|
mode = next;
|
|
377
442
|
apply();
|
|
378
|
-
}
|
|
443
|
+
};
|
|
379
444
|
|
|
380
445
|
/*
|
|
381
446
|
* What the desk offers, as soon as it is known: now, or when the read
|
|
@@ -422,12 +487,11 @@ function createReporterClass(): CustomElementConstructor {
|
|
|
422
487
|
};
|
|
423
488
|
/*
|
|
424
489
|
* What was typed last time and not sent comes back, said so — with a
|
|
425
|
-
* way to clear it.
|
|
426
|
-
* offered again.
|
|
490
|
+
* way to clear it. Carried across a switch, the words are simply there.
|
|
427
491
|
*/
|
|
428
|
-
const restored = draft && (draft.title || draft.detail) ? draft : null;
|
|
492
|
+
const restored = carried ?? (draft && (draft.title || draft.detail) ? draft : null);
|
|
429
493
|
const again = el("p", "draft");
|
|
430
|
-
again.hidden = !restored;
|
|
494
|
+
again.hidden = !restored || !!carried;
|
|
431
495
|
if (restored) {
|
|
432
496
|
summary.value = restored.title;
|
|
433
497
|
detail.value = restored.detail;
|
|
@@ -446,14 +510,13 @@ function createReporterClass(): CustomElementConstructor {
|
|
|
446
510
|
const known = requestTypesNow();
|
|
447
511
|
const offerAndRestore = (types: RequestTypeOption[]) => {
|
|
448
512
|
offer(types);
|
|
449
|
-
|
|
513
|
+
// The kind is the press's choice; a request's type comes back as it was.
|
|
514
|
+
if (mode !== "request" || kinds.hidden || !restored?.typeId) return;
|
|
450
515
|
const was = types.find((x) => x.id === restored.typeId);
|
|
451
516
|
if (was) {
|
|
452
517
|
chosen = was;
|
|
453
518
|
markType();
|
|
454
519
|
}
|
|
455
|
-
mode = "request";
|
|
456
|
-
apply();
|
|
457
520
|
};
|
|
458
521
|
if (known) offerAndRestore(known);
|
|
459
522
|
else if (reason.type === "manual") void loadRequestTypes().then(offerAndRestore);
|
|
@@ -623,6 +686,16 @@ const CLOCK: IconNode = [
|
|
|
623
686
|
];
|
|
624
687
|
const CHECK: IconNode = [["path", { d: "M20 6 9 17l-5-5" }]];
|
|
625
688
|
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
689
|
+
/** A card for แจ้งปัญหา or คำขอ — the chooser's and the form's, one drawing. */
|
|
690
|
+
function kindButton(kind: "problem" | "request", icon: IconNode, title: string, hint: string): HTMLButtonElement {
|
|
691
|
+
const card = button("", "kind");
|
|
692
|
+
card.dataset.kind = kind;
|
|
693
|
+
const top = el("span", "kind-top");
|
|
694
|
+
top.append(svgIcon(icon), el("b", "", title));
|
|
695
|
+
card.append(top, el("span", "kind-hint", hint), svgIcon(CHECK, "tick"));
|
|
696
|
+
return card;
|
|
697
|
+
}
|
|
698
|
+
|
|
626
699
|
function svgIcon(nodes: IconNode, className = "icon"): SVGSVGElement {
|
|
627
700
|
const svg = document.createElementNS(SVG_NS, "svg");
|
|
628
701
|
for (const [k, v] of Object.entries({ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", class: className })) svg.setAttribute(k, v);
|
package/src/ui/strings.ts
CHANGED
|
@@ -74,6 +74,9 @@ export interface Strings {
|
|
|
74
74
|
kindProblemHint: string;
|
|
75
75
|
kindRequest: string;
|
|
76
76
|
kindRequestHint: string;
|
|
77
|
+
/** On the cards before anything is asked of the browser: what each will do. */
|
|
78
|
+
chooseProblemHint: string;
|
|
79
|
+
chooseRequestHint: string;
|
|
77
80
|
requestTitle: string;
|
|
78
81
|
requestTypeLabel: string;
|
|
79
82
|
/** Carries `{time}`. */
|
|
@@ -146,6 +149,8 @@ const th: Strings = {
|
|
|
146
149
|
kindProblemHint: "มีอะไรเสีย หรือใช้งานไม่ได้",
|
|
147
150
|
kindRequest: "คำขอ",
|
|
148
151
|
kindRequestHint: "ขอให้ทีมทำ เช่น ขอสิทธิ์ ขอแก้ข้อมูล",
|
|
152
|
+
chooseProblemHint: "มีอะไรเสีย หรือใช้งานไม่ได้ — แนบภาพหน้าจอ",
|
|
153
|
+
chooseRequestHint: "ขอให้ทีมทำ เช่น ขอสิทธิ์ ขอแก้ข้อมูล — ไม่ต้องแชร์หน้าจอ",
|
|
149
154
|
requestTitle: "ขอให้ทีมดำเนินการ",
|
|
150
155
|
requestTypeLabel: "เรื่องที่ขอ",
|
|
151
156
|
requestTarget: "ทีมตั้งใจให้เสร็จภายใน {time}",
|
|
@@ -213,6 +218,8 @@ const en: Strings = {
|
|
|
213
218
|
kindProblemHint: "Something is broken or not working",
|
|
214
219
|
kindRequest: "Request",
|
|
215
220
|
kindRequestHint: "Ask the team to do something, like access or a data change",
|
|
221
|
+
chooseProblemHint: "Something is broken or not working — with a screenshot",
|
|
222
|
+
chooseRequestHint: "Ask the team to do something, like access or a data change — no screen sharing",
|
|
216
223
|
requestTitle: "Ask the team",
|
|
217
224
|
requestTypeLabel: "What you need",
|
|
218
225
|
requestTarget: "The team aims to finish within {time}",
|
package/src/ui/styles.ts
CHANGED
|
@@ -99,6 +99,11 @@ canvas { width: 100%; height: auto; display: block; border: 1px solid #e2e8f0; b
|
|
|
99
99
|
สร้างเวิร์กโฟลว์ asks it; what is asked for as cards, the way the portal lists it. */
|
|
100
100
|
[hidden] { display: none !important; }
|
|
101
101
|
.kinds { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: -4px; }
|
|
102
|
+
/* Before anything is asked of the browser: the same two cards, a little larger, in a smaller dialog. */
|
|
103
|
+
.panel.narrow { width: min(560px, 100%); }
|
|
104
|
+
.kinds.choose { margin: 0 0 24px; }
|
|
105
|
+
.kinds.choose button.kind { padding: 16px 18px; gap: 6px; }
|
|
106
|
+
.kinds.choose .kind-hint { font-size: 13px; }
|
|
102
107
|
button.kind {
|
|
103
108
|
position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
|
|
104
109
|
padding: 12px 34px 12px 14px; text-align: left; border-radius: 12px;
|