@lightworkai.official/debug-capture 0.14.2 → 0.14.3
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 +6 -3
- package/dist/debug-capture.js +19 -8
- package/dist/index.mjs +28 -17
- package/dist/ui/strings.d.ts +11 -2
- package/dist/ui/styles.d.ts +1 -1
- package/package.json +1 -1
- package/src/ui/element.ts +84 -38
- package/src/ui/strings.ts +30 -9
- package/src/ui/styles.ts +16 -5
package/src/ui/element.ts
CHANGED
|
@@ -311,41 +311,47 @@ function createReporterClass(): CustomElementConstructor {
|
|
|
311
311
|
head.append(heading, closeBtn);
|
|
312
312
|
|
|
313
313
|
/*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
314
|
+
* The kind was chosen at the press (renderChooser); the form names it in
|
|
315
|
+
* its heading and keeps one quiet line to switch — the owner: "no need
|
|
316
|
+
* to be card style since that select it on the first dialog". Switching
|
|
317
|
+
* keeps what was typed; to แจ้งปัญหา from a request that began with no
|
|
318
|
+
* screen, the share is asked for at that press (switchToReport). Shown
|
|
319
|
+
* only where the desk offers something to ask.
|
|
319
320
|
*/
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
card.addEventListener("click", () => setMode(kind));
|
|
328
|
-
return card;
|
|
329
|
-
};
|
|
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);
|
|
332
|
-
const requestCard = kindCard("request", WORKFLOW, t.kindRequest, t.kindRequestHint);
|
|
333
|
-
kinds.append(problemCard, requestCard);
|
|
334
|
-
// A radio group's own keys: an arrow moves the choice, and the focus with it.
|
|
335
|
-
kinds.addEventListener("keydown", (e) => {
|
|
336
|
-
if (!ARROWS.includes(e.key)) return;
|
|
337
|
-
e.preventDefault();
|
|
338
|
-
setMode(mode === "problem" ? "request" : "problem");
|
|
339
|
-
(mode === "problem" ? problemCard : requestCard).focus();
|
|
340
|
-
});
|
|
321
|
+
const door = el("p", "door");
|
|
322
|
+
door.hidden = true;
|
|
323
|
+
const doorText = el("span");
|
|
324
|
+
const doorBtn = button("", "link");
|
|
325
|
+
doorBtn.dataset.switchKind = "";
|
|
326
|
+
doorBtn.addEventListener("click", () => setMode(mode === "problem" ? "request" : "problem"));
|
|
327
|
+
door.append(doorText, doorBtn);
|
|
341
328
|
|
|
342
|
-
|
|
329
|
+
/*
|
|
330
|
+
* What is asked for: a row each. The one chosen opens up — its words and
|
|
331
|
+
* how long the team means to take, as the portal shows them — and the
|
|
332
|
+
* rest stay one line, so a desk with 10 or 20 types is still a list to
|
|
333
|
+
* read, not a page to scroll. From SEARCH_FROM types there is a search
|
|
334
|
+
* over them, and the list scrolls within its own height.
|
|
335
|
+
*/
|
|
343
336
|
const ask = el("div", "ask");
|
|
344
337
|
ask.hidden = true;
|
|
338
|
+
const typeSearch = document.createElement("input");
|
|
339
|
+
// A search, not a text field: the title stays the dialog's one text input.
|
|
340
|
+
typeSearch.type = "search";
|
|
341
|
+
typeSearch.className = "type-search";
|
|
342
|
+
typeSearch.placeholder = t.requestTypeSearch;
|
|
343
|
+
typeSearch.setAttribute("aria-label", t.requestTypeSearch);
|
|
344
|
+
typeSearch.dataset.requestTypeSearch = "";
|
|
345
|
+
typeSearch.hidden = true;
|
|
345
346
|
const typeList = el("div", "types");
|
|
346
347
|
typeList.setAttribute("role", "radiogroup");
|
|
347
348
|
typeList.setAttribute("aria-label", t.requestTypeLabel);
|
|
348
|
-
|
|
349
|
+
const typeNone = el("p", "status", t.requestTypeNone);
|
|
350
|
+
typeNone.hidden = true;
|
|
351
|
+
// A long list says how many it holds — the rows past its edge are otherwise unseen.
|
|
352
|
+
const typeCount = el("p", "status type-count", "");
|
|
353
|
+
typeCount.hidden = true;
|
|
354
|
+
ask.append(labelFor(t.requestTypeLabel, "", true), typeSearch, typeList, typeNone, typeCount);
|
|
349
355
|
const typeCards: HTMLButtonElement[] = [];
|
|
350
356
|
const markType = () => {
|
|
351
357
|
for (const card of typeCards) {
|
|
@@ -353,6 +359,13 @@ function createReporterClass(): CustomElementConstructor {
|
|
|
353
359
|
card.setAttribute("aria-checked", String(on));
|
|
354
360
|
card.tabIndex = on ? 0 : -1;
|
|
355
361
|
}
|
|
362
|
+
// The chosen one in view inside the list's own scroll, never the dialog's.
|
|
363
|
+
const on = typeCards.find((c) => c.dataset.requestType === chosen?.id);
|
|
364
|
+
if (on && typeList.scrollHeight > typeList.clientHeight) {
|
|
365
|
+
const top = on.offsetTop - typeList.offsetTop;
|
|
366
|
+
if (top < typeList.scrollTop) typeList.scrollTop = top;
|
|
367
|
+
else if (top + on.offsetHeight > typeList.scrollTop + typeList.clientHeight) typeList.scrollTop = top + on.offsetHeight - typeList.clientHeight;
|
|
368
|
+
}
|
|
356
369
|
};
|
|
357
370
|
|
|
358
371
|
const summary = document.createElement("input");
|
|
@@ -408,12 +421,11 @@ function createReporterClass(): CustomElementConstructor {
|
|
|
408
421
|
|
|
409
422
|
const apply = () => {
|
|
410
423
|
const asking = mode === "request";
|
|
411
|
-
heading.textContent =
|
|
424
|
+
heading.textContent = asking ? t.requestTitle : t.title;
|
|
412
425
|
panel.setAttribute("aria-label", asking ? t.requestTitle : t.title);
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
426
|
+
doorText.textContent = `${asking ? t.problemInstead : t.askInstead} `;
|
|
427
|
+
// From a request that began with no screen, แจ้งปัญหา will ask for it — said on the line.
|
|
428
|
+
doorBtn.textContent = asking ? (screenTaken ? t.problemInsteadAction : t.problemInsteadShare) : t.askInsteadAction;
|
|
417
429
|
ask.hidden = !asking;
|
|
418
430
|
fillLabel(summaryLabel, asking ? t.requestSummaryLabel : t.summaryLabel, asking ? "" : t.summaryRequired, true);
|
|
419
431
|
summary.placeholder = asking ? t.requestSummaryPlaceholder : t.summaryPlaceholder;
|
|
@@ -472,17 +484,49 @@ function createReporterClass(): CustomElementConstructor {
|
|
|
472
484
|
typeCards.push(card);
|
|
473
485
|
}
|
|
474
486
|
typeList.replaceChildren(...typeCards);
|
|
487
|
+
// ↑ ↓ among the rows the search leaves.
|
|
475
488
|
typeList.onkeydown = (e) => {
|
|
476
489
|
if (!ARROWS.includes(e.key) || !chosen) return;
|
|
477
490
|
e.preventDefault();
|
|
478
|
-
const
|
|
491
|
+
const shown = types.filter((x, i) => !typeCards[i]!.hidden);
|
|
492
|
+
if (!shown.length) return;
|
|
493
|
+
const at = shown.findIndex((x) => x.id === chosen!.id);
|
|
479
494
|
const step = e.key === "ArrowUp" || e.key === "ArrowLeft" ? -1 : 1;
|
|
480
|
-
chosen =
|
|
495
|
+
chosen = shown[at < 0 ? 0 : (at + step + shown.length) % shown.length]!;
|
|
481
496
|
markType();
|
|
482
497
|
typeCards.find((c) => c.dataset.requestType === chosen!.id)?.focus();
|
|
483
498
|
};
|
|
499
|
+
typeList.classList.toggle("long", types.length >= SEARCH_FROM);
|
|
500
|
+
typeSearch.hidden = types.length < SEARCH_FROM;
|
|
501
|
+
const count = (n: number) => {
|
|
502
|
+
typeCount.hidden = types.length < SEARCH_FROM || n === 0;
|
|
503
|
+
typeCount.textContent = t.requestTypeCount.replace("{n}", String(n));
|
|
504
|
+
};
|
|
505
|
+
count(types.length);
|
|
506
|
+
typeSearch.oninput = () => {
|
|
507
|
+
const q = typeSearch.value.trim().toLowerCase();
|
|
508
|
+
let first: RequestTypeOption | null = null;
|
|
509
|
+
types.forEach((x, i) => {
|
|
510
|
+
const hit = !q || `${x.name} ${x.description}`.toLowerCase().includes(q);
|
|
511
|
+
typeCards[i]!.hidden = !hit;
|
|
512
|
+
if (hit && !first) first = x;
|
|
513
|
+
});
|
|
514
|
+
typeNone.hidden = !!first;
|
|
515
|
+
count(typeCards.filter((c) => !c.hidden).length);
|
|
516
|
+
// The choice follows the search when the one chosen is filtered out.
|
|
517
|
+
if (first && chosen && typeCards[types.indexOf(chosen)]!.hidden) chosen = first;
|
|
518
|
+
markType();
|
|
519
|
+
ready();
|
|
520
|
+
};
|
|
521
|
+
// Enter in the search goes to the chosen row, not the send button.
|
|
522
|
+
typeSearch.onkeydown = (e) => {
|
|
523
|
+
if (e.key === "ArrowDown" || e.key === "Enter") {
|
|
524
|
+
e.preventDefault();
|
|
525
|
+
typeCards.find((c) => c.dataset.requestType === chosen?.id && !c.hidden)?.focus();
|
|
526
|
+
}
|
|
527
|
+
};
|
|
484
528
|
markType();
|
|
485
|
-
|
|
529
|
+
door.hidden = false;
|
|
486
530
|
apply();
|
|
487
531
|
};
|
|
488
532
|
/*
|
|
@@ -511,7 +555,7 @@ function createReporterClass(): CustomElementConstructor {
|
|
|
511
555
|
const offerAndRestore = (types: RequestTypeOption[]) => {
|
|
512
556
|
offer(types);
|
|
513
557
|
// The kind is the press's choice; a request's type comes back as it was.
|
|
514
|
-
if (mode !== "request" ||
|
|
558
|
+
if (mode !== "request" || door.hidden || !restored?.typeId) return;
|
|
515
559
|
const was = types.find((x) => x.id === restored.typeId);
|
|
516
560
|
if (was) {
|
|
517
561
|
chosen = was;
|
|
@@ -522,7 +566,7 @@ function createReporterClass(): CustomElementConstructor {
|
|
|
522
566
|
else if (reason.type === "manual") void loadRequestTypes().then(offerAndRestore);
|
|
523
567
|
this.keep = () => ({ title: summary.value, detail: detail.value, asking: mode === "request", typeId: chosen?.id ?? null });
|
|
524
568
|
|
|
525
|
-
panel.append(head,
|
|
569
|
+
panel.append(head, door, again, ask, summaryLabel, summary, detailLabel, detail, meta, shotChoice, ...shot);
|
|
526
570
|
const foot = el("div", "foot");
|
|
527
571
|
const cancel = button(t.close);
|
|
528
572
|
cancel.addEventListener("click", () => this.close());
|
|
@@ -656,6 +700,8 @@ function hoursWords(t: Strings, h: number): string {
|
|
|
656
700
|
}
|
|
657
701
|
|
|
658
702
|
const ARROWS = ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"];
|
|
703
|
+
/** From this many request types, the list gets a search and scrolls in its own height. */
|
|
704
|
+
const SEARCH_FROM = 7;
|
|
659
705
|
|
|
660
706
|
/*
|
|
661
707
|
* The desk's own icons (lucide, as its สร้างเวิร์กโฟลว์ and the portal draw
|
package/src/ui/strings.ts
CHANGED
|
@@ -65,20 +65,29 @@ export interface Strings {
|
|
|
65
65
|
handoffBlocked: string;
|
|
66
66
|
handoffOpen: string;
|
|
67
67
|
/*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
68
|
+
* ขอให้ทีมดำเนินการ, where the desk opens request types to its reporters
|
|
69
|
+
* (docs/WIDGET-ASK-PLAN.md): the press asks first with two cards; the form
|
|
70
|
+
* keeps one quiet line to switch.
|
|
71
71
|
*/
|
|
72
72
|
chooseTitle: string;
|
|
73
73
|
kindProblem: string;
|
|
74
|
-
kindProblemHint: string;
|
|
75
74
|
kindRequest: string;
|
|
76
|
-
|
|
75
|
+
askInstead: string;
|
|
76
|
+
askInsteadAction: string;
|
|
77
|
+
problemInstead: string;
|
|
78
|
+
problemInsteadAction: string;
|
|
79
|
+
/** The same, from a request that began with no screen: the share will be asked for. */
|
|
80
|
+
problemInsteadShare: string;
|
|
77
81
|
/** On the cards before anything is asked of the browser: what each will do. */
|
|
78
82
|
chooseProblemHint: string;
|
|
79
83
|
chooseRequestHint: string;
|
|
80
84
|
requestTitle: string;
|
|
81
85
|
requestTypeLabel: string;
|
|
86
|
+
/** Over a long list of types: the search, and what it says when nothing matches. */
|
|
87
|
+
requestTypeSearch: string;
|
|
88
|
+
requestTypeNone: string;
|
|
89
|
+
/** Carries `{n}`. */
|
|
90
|
+
requestTypeCount: string;
|
|
82
91
|
/** Carries `{time}`. */
|
|
83
92
|
requestTarget: string;
|
|
84
93
|
requestSummaryLabel: string;
|
|
@@ -146,13 +155,19 @@ const th: Strings = {
|
|
|
146
155
|
handoffOpen: "เปิดในแท็บใหม่",
|
|
147
156
|
chooseTitle: "ต้องการให้ทีมช่วยเรื่องอะไร",
|
|
148
157
|
kindProblem: "แจ้งปัญหา",
|
|
149
|
-
kindProblemHint: "มีอะไรเสีย หรือใช้งานไม่ได้",
|
|
150
158
|
kindRequest: "คำขอ",
|
|
151
|
-
|
|
159
|
+
askInstead: "เป็นคำขอ?",
|
|
160
|
+
askInsteadAction: "เลือกเรื่องที่ขอ",
|
|
161
|
+
problemInstead: "เป็นปัญหาที่ต้องแก้?",
|
|
162
|
+
problemInsteadAction: "แจ้งปัญหาแทน",
|
|
163
|
+
problemInsteadShare: "แจ้งปัญหาแทน (แนบภาพหน้าจอ)",
|
|
152
164
|
chooseProblemHint: "มีอะไรเสีย หรือใช้งานไม่ได้ — แนบภาพหน้าจอ",
|
|
153
165
|
chooseRequestHint: "ขอให้ทีมทำ เช่น ขอสิทธิ์ ขอแก้ข้อมูล — ไม่ต้องแชร์หน้าจอ",
|
|
154
166
|
requestTitle: "ขอให้ทีมดำเนินการ",
|
|
155
167
|
requestTypeLabel: "เรื่องที่ขอ",
|
|
168
|
+
requestTypeSearch: "ค้นหาเรื่องที่ขอ",
|
|
169
|
+
requestTypeNone: "ไม่พบเรื่องที่ขอ — ลองคำอื่น",
|
|
170
|
+
requestTypeCount: "{n} เรื่อง — เลื่อนดูหรือพิมพ์ค้นหา",
|
|
156
171
|
requestTarget: "ทีมตั้งใจให้เสร็จภายใน {time}",
|
|
157
172
|
requestSummaryLabel: "หัวข้อ",
|
|
158
173
|
requestSummaryPlaceholder: "ขออะไร — สั้น ๆ",
|
|
@@ -215,13 +230,19 @@ const en: Strings = {
|
|
|
215
230
|
handoffOpen: "Open in a new tab",
|
|
216
231
|
chooseTitle: "How can the team help?",
|
|
217
232
|
kindProblem: "Report a problem",
|
|
218
|
-
kindProblemHint: "Something is broken or not working",
|
|
219
233
|
kindRequest: "Request",
|
|
220
|
-
|
|
234
|
+
askInstead: "Asking for something?",
|
|
235
|
+
askInsteadAction: "Choose what you need",
|
|
236
|
+
problemInstead: "Something broken?",
|
|
237
|
+
problemInsteadAction: "Report a problem instead",
|
|
238
|
+
problemInsteadShare: "Report a problem instead (with a screenshot)",
|
|
221
239
|
chooseProblemHint: "Something is broken or not working — with a screenshot",
|
|
222
240
|
chooseRequestHint: "Ask the team to do something, like access or a data change — no screen sharing",
|
|
223
241
|
requestTitle: "Ask the team",
|
|
224
242
|
requestTypeLabel: "What you need",
|
|
243
|
+
requestTypeSearch: "Search what you need",
|
|
244
|
+
requestTypeNone: "Nothing matches — try another word",
|
|
245
|
+
requestTypeCount: "{n} to choose from — scroll or search",
|
|
225
246
|
requestTarget: "The team aims to finish within {time}",
|
|
226
247
|
requestSummaryLabel: "Subject",
|
|
227
248
|
requestSummaryPlaceholder: "What do you need — briefly",
|
package/src/ui/styles.ts
CHANGED
|
@@ -34,11 +34,11 @@ h2 { margin: 0; font-size: 20px; font-weight: 700; flex: 1; }
|
|
|
34
34
|
label { display: block; font-size: 14px; font-weight: 600; margin-bottom: 6px; }
|
|
35
35
|
.req { color: #dc2626; }
|
|
36
36
|
.hint { font-weight: 400; color: #64748b; }
|
|
37
|
-
input[type="text"], textarea, select {
|
|
37
|
+
input[type="text"], input[type="search"], textarea, select {
|
|
38
38
|
width: 100%; padding: 10px 12px; font: inherit; font-size: 14px;
|
|
39
39
|
border: 1px solid #cbd5e1; border-radius: 10px; background: #fff; color: inherit;
|
|
40
40
|
}
|
|
41
|
-
input[type="text"]:focus, textarea:focus, select:focus {
|
|
41
|
+
input[type="text"]:focus, input[type="search"]:focus, textarea:focus, select:focus {
|
|
42
42
|
outline: none; border-color: #2563eb; box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.18);
|
|
43
43
|
}
|
|
44
44
|
textarea { min-height: 88px; resize: vertical; }
|
|
@@ -98,8 +98,10 @@ canvas { width: 100%; height: auto; display: block; border: 1px solid #e2e8f0; b
|
|
|
98
98
|
แจ้งปัญหา · คำขอ as two cards under the heading, the way the desk's own
|
|
99
99
|
สร้างเวิร์กโฟลว์ asks it; what is asked for as cards, the way the portal lists it. */
|
|
100
100
|
[hidden] { display: none !important; }
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
/* The press's choice: two cards in a smaller dialog, before anything is asked of the browser. */
|
|
102
|
+
.kinds { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
|
103
|
+
/* In the form, the choice is made: one quiet line under the heading to switch. */
|
|
104
|
+
.door { margin: -8px 0 0; font-size: 13px; color: #64748b; }
|
|
103
105
|
.panel.narrow { width: min(560px, 100%); }
|
|
104
106
|
.kinds.choose { margin: 0 0 24px; }
|
|
105
107
|
.kinds.choose button.kind { padding: 16px 18px; gap: 6px; }
|
|
@@ -120,7 +122,16 @@ button.kind[aria-checked="true"], button.kind[aria-checked="true"]:hover:not(:di
|
|
|
120
122
|
button.kind[aria-checked="true"] .tick { opacity: 1; }
|
|
121
123
|
button.kind:focus-visible, button.type:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.25); }
|
|
122
124
|
button.kind[aria-checked="true"]:focus-visible { box-shadow: inset 0 0 0 1px #2563eb, 0 0 0 3px rgba(37, 99, 235, 0.25); }
|
|
123
|
-
.types { display: grid; gap:
|
|
125
|
+
.types { display: grid; gap: 6px; }
|
|
126
|
+
/* A long list scrolls in its own height, so the title and ส่งคำขอ stay near. */
|
|
127
|
+
.types.long { max-height: 300px; overflow-y: auto; padding: 2px; margin: -2px; overscroll-behavior: contain; }
|
|
128
|
+
.type-search { margin-bottom: 8px; }
|
|
129
|
+
.ask .type-count { margin: 6px 0 0; font-size: 12px; }
|
|
130
|
+
/* The rest are one line: only the chosen one opens up with its words and the team's time. */
|
|
131
|
+
button.type:not([aria-checked="true"]) { padding: 8px 12px; align-items: center; }
|
|
132
|
+
button.type:not([aria-checked="true"]) .type-hint, button.type:not([aria-checked="true"]) .type-due { display: none; }
|
|
133
|
+
button.type:not([aria-checked="true"]) .type-body b { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
134
|
+
button.type:not([aria-checked="true"]) .icon { margin-top: 0; }
|
|
124
135
|
button.type {
|
|
125
136
|
display: flex; align-items: flex-start; gap: 10px; width: 100%;
|
|
126
137
|
padding: 10px 12px; text-align: left; border-radius: 10px;
|