@fusion.dev/helix-headless 0.203.0 → 0.204.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/api.json +1 -1
- package/custom-elements.json +586 -33
- package/dist/element-events.generated.js +7 -1
- package/dist/log/buffer.d.ts +52 -4
- package/dist/log/buffer.js +88 -4
- package/dist/log/index.d.ts +2 -1
- package/dist/log/index.js +1 -0
- package/dist/log/line/log-line.d.ts +19 -5
- package/dist/log/line/log-line.js +31 -9
- package/dist/log/parsers.d.ts +36 -0
- package/dist/log/parsers.js +101 -0
- package/dist/log/root/log-root.d.ts +114 -6
- package/dist/log/root/log-root.js +243 -17
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +126 -18
- package/types/vue.d.ts +29 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { HelixElement } from '@fusion.dev/helix-core';
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
import { type LogGroup, type LogLine, type LogParser } from '../buffer.js';
|
|
4
|
-
import {
|
|
4
|
+
import type { LogTimes } from '../line/log-line.js';
|
|
5
5
|
/** Text a log reads as it arrives: a stream, or anything `for await` reads. */
|
|
6
6
|
export type LogSource = AsyncIterable<string | Uint8Array> | ReadableStream<string | Uint8Array>;
|
|
7
7
|
/** Where `scrollToLine` puts the line, and how it gets there. */
|
|
@@ -34,8 +34,15 @@ declare const HelixHeadlessLog_base: (new (...args: any[]) => import("@fusion.de
|
|
|
34
34
|
* starts the line over, so a progress bar stays one line, and the line still
|
|
35
35
|
* being written is shown as it stands. ANSI colors and weights are drawn, in
|
|
36
36
|
* the sixteen colors the styled tier takes from your theme, and every other
|
|
37
|
-
* escape code is dropped.
|
|
38
|
-
*
|
|
37
|
+
* escape code is dropped.
|
|
38
|
+
*
|
|
39
|
+
* **Read a CI log's own markers with `format`.** `github-actions` reads the
|
|
40
|
+
* workflow commands a step writes, `::group::` and `::error::` among them,
|
|
41
|
+
* and the times at the start of a downloaded log. `vercel` reads a Vercel
|
|
42
|
+
* build log's phases and its warnings. Each step opens as it starts, folds
|
|
43
|
+
* once it passes, stays open where it failed, and is timed from its lines.
|
|
44
|
+
* For any other log, give `parse` a function that reads each finished line:
|
|
45
|
+
* it returns a level, a group, or `open` and `close` to start and end a step.
|
|
39
46
|
*
|
|
40
47
|
* **Steps fold.** Lines that name a group in `groups` stand under its heading,
|
|
41
48
|
* which shows its status and how long it took and opens and closes the lines.
|
|
@@ -50,6 +57,18 @@ declare const HelixHeadlessLog_base: (new (...args: any[]) => import("@fusion.de
|
|
|
50
57
|
* to one. `timestamps` shows each line's `at`, on the clock or as the time
|
|
51
58
|
* into the run. `mark-lines` marks lines, such as `12, 40-44`.
|
|
52
59
|
*
|
|
60
|
+
* **`selectable` lets a reader select lines**, as a code host does: pressing a
|
|
61
|
+
* number selects its line, and pressing another with Shift selects every line
|
|
62
|
+
* between them. `selected-lines` holds the selection, such as `12-20`, and
|
|
63
|
+
* `helix-select` asks before it changes. With a `line-href` that is a
|
|
64
|
+
* fragment, the address follows the selection, `#L12` or `#L12-L20`, and a
|
|
65
|
+
* page opened at one selects it and scrolls to it.
|
|
66
|
+
*
|
|
67
|
+
* **The numbers are one tab stop.** Where they are links or buttons, Tab
|
|
68
|
+
* reaches the one a reader last used, or the first selected, and the arrow
|
|
69
|
+
* keys move between them, with Home and End for the first and the last. With
|
|
70
|
+
* Shift on a `selectable` log, the selection follows.
|
|
71
|
+
*
|
|
53
72
|
* **What a screen reader hears.** The log is a `log` and stays quiet as lines
|
|
54
73
|
* arrive, since reading a build aloud line by line helps nobody. A separate
|
|
55
74
|
* status region says each line at `error` once it arrives, each step once it
|
|
@@ -75,8 +94,10 @@ declare const HelixHeadlessLog_base: (new (...args: any[]) => import("@fusion.de
|
|
|
75
94
|
* @slot toolbar - Controls above the log, such as a search field or a button that wraps lines.
|
|
76
95
|
*
|
|
77
96
|
* @event {HelixBeforePinEvent} helix-before-pin - Fired when the reader leaves the newest line by scrolling, or comes back to it. Cancelable.
|
|
78
|
-
* @event {HelixExpandEvent} helix-expand - Fired after a reader opens or closes a
|
|
97
|
+
* @event {HelixExpandEvent} helix-expand - Fired after a reader opens or closes a step drawn from `groups` or opened by a parser, with the `id` of every open group in `detail.value`, separated by commas.
|
|
79
98
|
* @event {HelixPinEvent} helix-pin - Fired after the log follows the newest line or lets go of it, with `true` or `false` in `detail.value`.
|
|
99
|
+
* @event {HelixSelectEvent} helix-select - Fired when a reader presses a number to change the selection, with the lines asked for in `detail.value`. Cancelable.
|
|
100
|
+
* @event {HelixSelectedEvent} helix-selected - Fired after the selection changes, with the lines now selected in `detail.value`, such as `12-20`.
|
|
80
101
|
*
|
|
81
102
|
* @csspart after - The space the rows below the drawn ones would take. Only for lines drawn from data.
|
|
82
103
|
* @csspart before - The space the rows above the drawn ones would take. Only for lines drawn from data.
|
|
@@ -105,6 +126,7 @@ declare const HelixHeadlessLog_base: (new (...args: any[]) => import("@fusion.de
|
|
|
105
126
|
* @csspart line-run-italic - A run drawn italic.
|
|
106
127
|
* @csspart line-run-strike - A run drawn struck through.
|
|
107
128
|
* @csspart line-run-underline - A run drawn underlined.
|
|
129
|
+
* @csspart line-selected - The row of a line drawn from data that is selected.
|
|
108
130
|
* @csspart line-text - The text of a line drawn from data.
|
|
109
131
|
* @csspart line-time - The time of a line drawn from data.
|
|
110
132
|
* @csspart line-warn - The row of a line drawn from data at `warn`.
|
|
@@ -177,6 +199,14 @@ export declare class HelixHeadlessLog extends HelixHeadlessLog_base {
|
|
|
177
199
|
errorLabel: string;
|
|
178
200
|
/** The log's name, read by screen readers, where `label` is empty. */
|
|
179
201
|
fallbackLabel: string;
|
|
202
|
+
/**
|
|
203
|
+
* The format written text is in, read with a parser Helix ships:
|
|
204
|
+
* `github-actions` reads a GitHub Actions log's workflow commands into steps
|
|
205
|
+
* and levels, and `vercel` a Vercel build log's phases and warnings. Absent
|
|
206
|
+
* reads none. `parse` wins over it.
|
|
207
|
+
*/
|
|
208
|
+
get format(): 'github-actions' | 'vercel' | undefined;
|
|
209
|
+
set format(next: 'github-actions' | 'vercel' | undefined);
|
|
180
210
|
/**
|
|
181
211
|
* What a screen reader hears when a step finishes or fails. `{heading}` is
|
|
182
212
|
* the step's heading and `{status}` is `done` or `failed`.
|
|
@@ -224,8 +254,8 @@ export declare class HelixHeadlessLog extends HelixHeadlessLog_base {
|
|
|
224
254
|
matchesLabel: string;
|
|
225
255
|
/**
|
|
226
256
|
* Reads each finished line of written text: return the fields to set, such
|
|
227
|
-
* as `level
|
|
228
|
-
* taken as they are.
|
|
257
|
+
* as `level`, `open` or `close` to start or end a step, or `null` to drop
|
|
258
|
+
* the line. Lines in `lines` are taken as they are. Wins over `format`.
|
|
229
259
|
*/
|
|
230
260
|
get parse(): LogParser | undefined;
|
|
231
261
|
set parse(next: LogParser | undefined);
|
|
@@ -243,6 +273,17 @@ export declare class HelixHeadlessLog extends HelixHeadlessLog_base {
|
|
|
243
273
|
query: string;
|
|
244
274
|
/** The words on the button that returns to the newest line, where `resume` is empty. */
|
|
245
275
|
resumeLabel: string;
|
|
276
|
+
/**
|
|
277
|
+
* Whether pressing a line's number selects the line, and pressing another
|
|
278
|
+
* with Shift selects every line between them. Each number is then a
|
|
279
|
+
* button, or a link with `line-href`, and the arrow keys move between them.
|
|
280
|
+
*/
|
|
281
|
+
selectable: boolean;
|
|
282
|
+
/**
|
|
283
|
+
* The lines selected, as a number or a range such as `12-20`. Empty selects
|
|
284
|
+
* none. A reader's press asks first: `helix-select` is cancelable.
|
|
285
|
+
*/
|
|
286
|
+
selectedLines: string;
|
|
246
287
|
/**
|
|
247
288
|
* Text to read as it arrives, to the end: a `ReadableStream` of text or
|
|
248
289
|
* bytes, such as a `fetch` response's body, or an async iterable. Bytes are
|
|
@@ -261,6 +302,14 @@ export declare class HelixHeadlessLog extends HelixHeadlessLog_base {
|
|
|
261
302
|
/** Whether a line longer than the box wraps onto the next row. Otherwise the log scrolls sideways. */
|
|
262
303
|
wrap: boolean;
|
|
263
304
|
private pinnedState;
|
|
305
|
+
private formatValue;
|
|
306
|
+
private parseValue;
|
|
307
|
+
/** The number that is the log's one tab stop among its numbers, or 0 before anyone moved. */
|
|
308
|
+
private stop;
|
|
309
|
+
/** The line a Shift press measures a range from. */
|
|
310
|
+
private selectionAnchor;
|
|
311
|
+
/** The steps the last update saw, which a page's `open` is compared with. */
|
|
312
|
+
private lastGroups;
|
|
264
313
|
private doneState;
|
|
265
314
|
/** Whether a source is being read, which the log says with `aria-busy`. */
|
|
266
315
|
private reading;
|
|
@@ -301,6 +350,16 @@ export declare class HelixHeadlessLog extends HelixHeadlessLog_base {
|
|
|
301
350
|
private readonly virtualize;
|
|
302
351
|
/** Whether the lines are data rather than children. */
|
|
303
352
|
private get fed();
|
|
353
|
+
/** The parser `format` names. */
|
|
354
|
+
private get formatParser();
|
|
355
|
+
/** The steps from `groups`, then the steps a parser opened. */
|
|
356
|
+
private get allGroups();
|
|
357
|
+
/** Whether the numbers take focus: links with `line-href`, or buttons when `selectable`. */
|
|
358
|
+
private get interactive();
|
|
359
|
+
/** The selection as its first and last line, or undefined where there is none. */
|
|
360
|
+
private get selection();
|
|
361
|
+
/** Whether line `number` is selected. */
|
|
362
|
+
private selected;
|
|
304
363
|
/** The element that scrolls, once there is one. */
|
|
305
364
|
private get log();
|
|
306
365
|
/**
|
|
@@ -327,6 +386,55 @@ export declare class HelixHeadlessLog extends HelixHeadlessLog_base {
|
|
|
327
386
|
* hides.
|
|
328
387
|
*/
|
|
329
388
|
scrollToLine(number: number, options?: LogScrollOptions): Promise<boolean>;
|
|
389
|
+
/**
|
|
390
|
+
* Asks to select `value`, a line or a range, and applies it unless refused,
|
|
391
|
+
* then says so and, with a `line-href` that is a fragment such as `#L{n}`,
|
|
392
|
+
* puts the selection in the address.
|
|
393
|
+
*/
|
|
394
|
+
private requestSelect;
|
|
395
|
+
/**
|
|
396
|
+
* What the address holds for a selection, as `line-href` spells a line:
|
|
397
|
+
* `#L12`, and `#L12-L20` for a range. Undefined where `line-href` is not a
|
|
398
|
+
* fragment, since a link elsewhere is the page's to follow.
|
|
399
|
+
*/
|
|
400
|
+
private address;
|
|
401
|
+
/**
|
|
402
|
+
* Puts a selection in the address without a new history entry, so the back
|
|
403
|
+
* button leaves the page rather than walking through every line pressed.
|
|
404
|
+
*/
|
|
405
|
+
private writeAddress;
|
|
406
|
+
/**
|
|
407
|
+
* Reads a line or a range out of the address, as `line-href` spells one,
|
|
408
|
+
* selects it where the log is `selectable`, and scrolls to it.
|
|
409
|
+
*/
|
|
410
|
+
private readonly readAddress;
|
|
411
|
+
/**
|
|
412
|
+
* The numbers of the lines a reader can move between, in order: what is
|
|
413
|
+
* drawn from data, or the lines written as children that no query hides
|
|
414
|
+
* and no shut group folds away.
|
|
415
|
+
*/
|
|
416
|
+
private reachable;
|
|
417
|
+
/**
|
|
418
|
+
* The number that is the tab stop among `numbers`: the one a reader last
|
|
419
|
+
* moved to, else the first selected, else the first.
|
|
420
|
+
*/
|
|
421
|
+
private stopAmong;
|
|
422
|
+
/** Moves focus to line `number`'s number, drawing it first where it is windowed. */
|
|
423
|
+
private focusLine;
|
|
424
|
+
/** The line a number belongs to, out of an event's path, and its number. */
|
|
425
|
+
private numberFrom;
|
|
426
|
+
/**
|
|
427
|
+
* A press on a number selects its line, or with Shift every line from the
|
|
428
|
+
* last one pressed. A link's own navigation is held back while it selects,
|
|
429
|
+
* since the selection writes the address itself.
|
|
430
|
+
*/
|
|
431
|
+
private readonly handleClick;
|
|
432
|
+
/**
|
|
433
|
+
* The arrow keys move between numbers, and Home and End go to the first and
|
|
434
|
+
* the last, from a number with focus. With Shift on a `selectable` log, the
|
|
435
|
+
* selection follows.
|
|
436
|
+
*/
|
|
437
|
+
private readonly handleNumberKey;
|
|
330
438
|
/** Whether a group drawn from data is open: as the reader left it, or as `groups` seeded it. */
|
|
331
439
|
private isOpen;
|
|
332
440
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
var HelixHeadlessLog_1;
|
|
2
2
|
import { __decorate } from "tslib";
|
|
3
|
-
import { customElement, drawIcon, formatWords, HelixBeforePinEvent, HelixElement, HelixExpandEvent, HelixPinEvent, hostAria, MixinCollection, onUnlessFalse, requestChange, SCROLLER_RULE, VIRTUAL_RULE, VirtualizeController, whenHydrated, words, } from '@fusion.dev/helix-core';
|
|
3
|
+
import { customElement, drawIcon, formatWords, HelixBeforePinEvent, HelixElement, HelixExpandEvent, HelixPinEvent, HelixSelectEvent, HelixSelectedEvent, hostAria, MixinCollection, onUnlessFalse, requestChange, SCROLLER_RULE, VIRTUAL_RULE, VirtualizeController, whenHydrated, words, } from '@fusion.dev/helix-core';
|
|
4
4
|
import { arrowDown } from '@fusion.dev/helix-icons';
|
|
5
5
|
import { nothing } from 'lit';
|
|
6
6
|
import { property, state } from 'lit/decorators.js';
|
|
@@ -9,8 +9,8 @@ import { repeat } from 'lit/directives/repeat.js';
|
|
|
9
9
|
import { html } from 'lit/static-html.js';
|
|
10
10
|
import { lineRuns } from '../../code-block/lines.js';
|
|
11
11
|
import { stripAnsi } from '../ansi.js';
|
|
12
|
-
import { LogBuffer, } from '../buffer.js';
|
|
13
|
-
import {
|
|
12
|
+
import { LogBuffer, timeOf, } from '../buffer.js';
|
|
13
|
+
import { LOG_FORMATS } from '../parsers.js';
|
|
14
14
|
import { logRows } from '../rows.js';
|
|
15
15
|
const STATUS_NAMES = {
|
|
16
16
|
done: 'summary: group-summary-done',
|
|
@@ -41,9 +41,16 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
41
41
|
this.matchesLabel = '{count, plural, =0 {No lines match} one {# line matches} other {# lines match}}';
|
|
42
42
|
this.query = '';
|
|
43
43
|
this.resumeLabel = 'Jump to latest';
|
|
44
|
+
this.selectable = false;
|
|
45
|
+
this.selectedLines = '';
|
|
44
46
|
this.threshold = 48;
|
|
45
47
|
this.wrap = false;
|
|
46
48
|
this.pinnedState = undefined;
|
|
49
|
+
this.formatValue = undefined;
|
|
50
|
+
this.parseValue = undefined;
|
|
51
|
+
this.stop = 0;
|
|
52
|
+
this.selectionAnchor = undefined;
|
|
53
|
+
this.lastGroups = [];
|
|
47
54
|
this.doneState = undefined;
|
|
48
55
|
this.reading = false;
|
|
49
56
|
this.announcement = '';
|
|
@@ -72,6 +79,78 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
72
79
|
measured: () => this.wrap,
|
|
73
80
|
rows: () => (this.fed ? this.drawn() : []),
|
|
74
81
|
});
|
|
82
|
+
this.readAddress = () => {
|
|
83
|
+
if (!this.lineHref.startsWith('#') || !this.lineHref.includes('{n}')) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const one = this.lineHref
|
|
87
|
+
.split('{n}')
|
|
88
|
+
.map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
89
|
+
.join('(\\d+)');
|
|
90
|
+
const found = new RegExp(`^${one}(?:-${one.replace(/^#/, '')})?$`).exec(location.hash);
|
|
91
|
+
if (!found) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const first = Number(found[1]);
|
|
95
|
+
const last = Number(found[2] ?? found[1]);
|
|
96
|
+
if (this.selectable) {
|
|
97
|
+
this.selectedLines =
|
|
98
|
+
first === last ? String(first) : `${Math.min(first, last)}-${Math.max(first, last)}`;
|
|
99
|
+
}
|
|
100
|
+
this.selectionAnchor = first;
|
|
101
|
+
this.stop = Math.min(first, last);
|
|
102
|
+
void this.updateComplete.then(() => requestAnimationFrame(() => {
|
|
103
|
+
void this.scrollToLine(Math.min(first, last));
|
|
104
|
+
}));
|
|
105
|
+
};
|
|
106
|
+
this.handleClick = (event) => {
|
|
107
|
+
const number = this.numberFrom(event.composedPath());
|
|
108
|
+
if (number === undefined) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
this.stop = number;
|
|
112
|
+
if (!this.selectable) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
const anchor = this.selectionAnchor;
|
|
117
|
+
if (event.shiftKey && anchor !== undefined) {
|
|
118
|
+
this.requestSelect(`${Math.min(anchor, number)}-${Math.max(anchor, number)}`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
this.selectionAnchor = number;
|
|
122
|
+
this.requestSelect(String(number));
|
|
123
|
+
};
|
|
124
|
+
this.handleNumberKey = (event) => {
|
|
125
|
+
if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const from = this.numberFrom(event.composedPath());
|
|
129
|
+
if (from === undefined) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const numbers = this.reachable();
|
|
133
|
+
const at = numbers.indexOf(from);
|
|
134
|
+
const to = event.key === 'ArrowDown'
|
|
135
|
+
? numbers[Math.min(numbers.length - 1, at + 1)]
|
|
136
|
+
: event.key === 'ArrowUp'
|
|
137
|
+
? numbers[Math.max(0, at - 1)]
|
|
138
|
+
: event.key === 'Home'
|
|
139
|
+
? numbers[0]
|
|
140
|
+
: event.key === 'End'
|
|
141
|
+
? numbers.at(-1)
|
|
142
|
+
: undefined;
|
|
143
|
+
if (to === undefined) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
event.preventDefault();
|
|
147
|
+
if (event.shiftKey && this.selectable) {
|
|
148
|
+
const anchor = this.selectionAnchor ?? from;
|
|
149
|
+
this.selectionAnchor = anchor;
|
|
150
|
+
this.requestSelect(anchor === to ? String(to) : `${Math.min(anchor, to)}-${Math.max(anchor, to)}`);
|
|
151
|
+
}
|
|
152
|
+
void this.focusLine(to);
|
|
153
|
+
};
|
|
75
154
|
this.handleResize = () => {
|
|
76
155
|
this.markScroller();
|
|
77
156
|
if (this.pinned) {
|
|
@@ -103,7 +182,7 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
103
182
|
}
|
|
104
183
|
this.opened.set(id, group.open);
|
|
105
184
|
this.requestUpdate();
|
|
106
|
-
const open = this.
|
|
185
|
+
const open = this.allGroups.filter((each) => this.isOpen(each.id)).map((each) => each.id);
|
|
107
186
|
this.dispatchEvent(new HelixExpandEvent({ value: open.join(',') }));
|
|
108
187
|
};
|
|
109
188
|
}
|
|
@@ -119,6 +198,15 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
119
198
|
this.doneState = next;
|
|
120
199
|
this.requestUpdate('done', previous);
|
|
121
200
|
}
|
|
201
|
+
get format() {
|
|
202
|
+
return this.formatValue;
|
|
203
|
+
}
|
|
204
|
+
set format(next) {
|
|
205
|
+
const previous = this.formatValue;
|
|
206
|
+
this.formatValue = next;
|
|
207
|
+
this.buffer.parse = this.parseValue ?? this.formatParser;
|
|
208
|
+
this.requestUpdate('format', previous);
|
|
209
|
+
}
|
|
122
210
|
get lines() {
|
|
123
211
|
return this.buffer.lines;
|
|
124
212
|
}
|
|
@@ -129,11 +217,12 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
129
217
|
this.requestUpdate('lines', previous);
|
|
130
218
|
}
|
|
131
219
|
get parse() {
|
|
132
|
-
return this.
|
|
220
|
+
return this.parseValue;
|
|
133
221
|
}
|
|
134
222
|
set parse(next) {
|
|
135
|
-
const previous = this.
|
|
136
|
-
this.
|
|
223
|
+
const previous = this.parseValue;
|
|
224
|
+
this.parseValue = next;
|
|
225
|
+
this.buffer.parse = next ?? this.formatParser;
|
|
137
226
|
this.requestUpdate('parse', previous);
|
|
138
227
|
}
|
|
139
228
|
get pinned() {
|
|
@@ -161,6 +250,28 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
161
250
|
this.groups.length > 0 ||
|
|
162
251
|
this.sourceValue !== null);
|
|
163
252
|
}
|
|
253
|
+
get formatParser() {
|
|
254
|
+
return this.formatValue === undefined ? undefined : LOG_FORMATS[this.formatValue];
|
|
255
|
+
}
|
|
256
|
+
get allGroups() {
|
|
257
|
+
return this.buffer.steps.length === 0 ? this.groups : [...this.groups, ...this.buffer.steps];
|
|
258
|
+
}
|
|
259
|
+
get interactive() {
|
|
260
|
+
return this.lineNumbers && (this.selectable || this.lineHref !== '');
|
|
261
|
+
}
|
|
262
|
+
get selection() {
|
|
263
|
+
const found = /^\s*(\d+)(?:\s*-\s*(\d+))?\s*$/.exec(this.selectedLines);
|
|
264
|
+
if (!found) {
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
const first = Number(found[1]);
|
|
268
|
+
const last = Number(found[2] ?? found[1]);
|
|
269
|
+
return [Math.min(first, last), Math.max(first, last)];
|
|
270
|
+
}
|
|
271
|
+
selected(number) {
|
|
272
|
+
const range = this.selection;
|
|
273
|
+
return range !== undefined && number >= range[0] && number <= range[1];
|
|
274
|
+
}
|
|
164
275
|
get log() {
|
|
165
276
|
return this.partNamed('log');
|
|
166
277
|
}
|
|
@@ -246,18 +357,104 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
246
357
|
}
|
|
247
358
|
return true;
|
|
248
359
|
}
|
|
360
|
+
requestSelect(value) {
|
|
361
|
+
if (value === this.selectedLines) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
requestChange({
|
|
365
|
+
apply: (next) => {
|
|
366
|
+
this.selectedLines = next;
|
|
367
|
+
this.dispatchEvent(new HelixSelectedEvent({ value: next }));
|
|
368
|
+
this.writeAddress(next);
|
|
369
|
+
},
|
|
370
|
+
event: (next) => new HelixSelectEvent({ value: next }),
|
|
371
|
+
host: this,
|
|
372
|
+
next: value,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
address(value) {
|
|
376
|
+
if (!this.lineHref.startsWith('#') || !this.lineHref.includes('{n}')) {
|
|
377
|
+
return undefined;
|
|
378
|
+
}
|
|
379
|
+
const found = /^(\d+)(?:-(\d+))?$/.exec(value);
|
|
380
|
+
if (!found) {
|
|
381
|
+
return undefined;
|
|
382
|
+
}
|
|
383
|
+
const first = this.lineHref.replaceAll('{n}', found[1] ?? '');
|
|
384
|
+
return found[2] === undefined
|
|
385
|
+
? first
|
|
386
|
+
: `${first}-${this.lineHref.replaceAll('{n}', found[2]).slice(1)}`;
|
|
387
|
+
}
|
|
388
|
+
writeAddress(value) {
|
|
389
|
+
const hash = this.address(value);
|
|
390
|
+
if (hash !== undefined && typeof history !== 'undefined') {
|
|
391
|
+
history.replaceState(history.state, '', hash);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
reachable() {
|
|
395
|
+
if (this.fed) {
|
|
396
|
+
return this.view.rows.flatMap((row) => (row.kind === 'line' ? [row.number] : []));
|
|
397
|
+
}
|
|
398
|
+
return this.childLines.flatMap((line, at) => !line.logFiltered && this.childGroups.every((group) => group.open || !group.contains(line))
|
|
399
|
+
? [at + 1]
|
|
400
|
+
: []);
|
|
401
|
+
}
|
|
402
|
+
stopAmong(numbers) {
|
|
403
|
+
if (numbers.includes(this.stop)) {
|
|
404
|
+
return this.stop;
|
|
405
|
+
}
|
|
406
|
+
const range = this.selection;
|
|
407
|
+
const first = range
|
|
408
|
+
? numbers.find((number) => number >= range[0] && number <= range[1])
|
|
409
|
+
: undefined;
|
|
410
|
+
return first ?? numbers[0] ?? 0;
|
|
411
|
+
}
|
|
412
|
+
async focusLine(number) {
|
|
413
|
+
this.stop = number;
|
|
414
|
+
if (this.fed) {
|
|
415
|
+
const index = this.view.rows.findIndex((row) => row.kind === 'line' && row.number === number);
|
|
416
|
+
if (index < 0) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (this.pinned) {
|
|
420
|
+
this.requestPinned(false);
|
|
421
|
+
}
|
|
422
|
+
this.virtualize.reveal(index);
|
|
423
|
+
this.virtualize.measure();
|
|
424
|
+
this.requestUpdate();
|
|
425
|
+
await this.updateComplete;
|
|
426
|
+
const line = [
|
|
427
|
+
...(this.renderRoot?.querySelectorAll?.('[part="row"] > *') ?? []),
|
|
428
|
+
].find((each) => each.logNumber === number);
|
|
429
|
+
await line?.updateComplete;
|
|
430
|
+
line?.focusNumber();
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
await this.updateComplete;
|
|
434
|
+
const line = this.childLines[number - 1];
|
|
435
|
+
await line?.updateComplete;
|
|
436
|
+
line?.focusNumber();
|
|
437
|
+
}
|
|
438
|
+
numberFrom(path) {
|
|
439
|
+
const onNumber = path.some((target) => target instanceof Element && target.getAttribute('part') === 'number');
|
|
440
|
+
if (!onNumber) {
|
|
441
|
+
return undefined;
|
|
442
|
+
}
|
|
443
|
+
const line = path.find((target) => typeof target === 'object' && target !== null && 'isLogLine' in target);
|
|
444
|
+
return line && line.logNumber > 0 ? line.logNumber : undefined;
|
|
445
|
+
}
|
|
249
446
|
isOpen(id) {
|
|
250
447
|
const known = this.opened.get(id);
|
|
251
448
|
if (known !== undefined) {
|
|
252
449
|
return known;
|
|
253
450
|
}
|
|
254
|
-
const seeded = this.
|
|
451
|
+
const seeded = this.allGroups.find((group) => group.id === id)?.open ?? false;
|
|
255
452
|
this.opened.set(id, seeded);
|
|
256
453
|
return seeded;
|
|
257
454
|
}
|
|
258
455
|
reopen(previous) {
|
|
259
456
|
const before = new Map((previous ?? []).map((group) => [group.id, group.open]));
|
|
260
|
-
for (const group of this.
|
|
457
|
+
for (const group of this.allGroups) {
|
|
261
458
|
if (group.open !== undefined && before.has(group.id) && before.get(group.id) !== group.open) {
|
|
262
459
|
this.opened.set(group.id, group.open);
|
|
263
460
|
}
|
|
@@ -319,6 +516,7 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
319
516
|
const marks = lineRuns(this.markLines);
|
|
320
517
|
const needle = this.query.trim().toLowerCase();
|
|
321
518
|
const start = this.childLines.map((line) => timeOf(line.at)).find((time) => time !== undefined);
|
|
519
|
+
const stop = this.interactive ? this.stopAmong(this.reachable()) : 0;
|
|
322
520
|
this.childLines.forEach((line, at) => {
|
|
323
521
|
const filtered = needle !== '' && !(line.textContent ?? '').toLowerCase().includes(needle);
|
|
324
522
|
whenHydrated(line, () => {
|
|
@@ -329,6 +527,9 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
329
527
|
line.logStart = start;
|
|
330
528
|
line.logMarked = inRuns(marks, at + 1);
|
|
331
529
|
line.logFiltered = filtered;
|
|
530
|
+
line.logSelectable = this.selectable;
|
|
531
|
+
line.logSelected = this.selected(at + 1);
|
|
532
|
+
line.logStop = at + 1 === stop;
|
|
332
533
|
});
|
|
333
534
|
if (!filtered && needle !== '') {
|
|
334
535
|
for (const group of this.childGroups) {
|
|
@@ -356,7 +557,7 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
356
557
|
listen() {
|
|
357
558
|
const groups = new Map();
|
|
358
559
|
if (this.fed) {
|
|
359
|
-
for (const group of this.
|
|
560
|
+
for (const group of this.allGroups) {
|
|
360
561
|
groups.set(group.id, { heading: group.heading, status: group.status });
|
|
361
562
|
}
|
|
362
563
|
}
|
|
@@ -483,16 +684,23 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
483
684
|
childList: true,
|
|
484
685
|
subtree: true,
|
|
485
686
|
});
|
|
687
|
+
this.addEventListener('click', this.handleClick);
|
|
688
|
+
this.addEventListener('keydown', this.handleNumberKey);
|
|
689
|
+
window.addEventListener('hashchange', this.readAddress);
|
|
486
690
|
this.opening = true;
|
|
487
691
|
requestAnimationFrame(() => {
|
|
488
692
|
this.opening = false;
|
|
489
693
|
});
|
|
694
|
+
this.readAddress();
|
|
490
695
|
if (this.sourceValue !== null && !this.reading) {
|
|
491
696
|
void this.readSource(this.sourceValue);
|
|
492
697
|
}
|
|
493
698
|
}
|
|
494
699
|
disconnectedCallback() {
|
|
495
700
|
super.disconnectedCallback();
|
|
701
|
+
this.removeEventListener('click', this.handleClick);
|
|
702
|
+
this.removeEventListener('keydown', this.handleNumberKey);
|
|
703
|
+
window.removeEventListener('hashchange', this.readAddress);
|
|
496
704
|
this.watcher?.disconnect();
|
|
497
705
|
this.watcher = undefined;
|
|
498
706
|
this.grower?.disconnect();
|
|
@@ -505,9 +713,8 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
505
713
|
willUpdate(changed) {
|
|
506
714
|
super.willUpdate(changed);
|
|
507
715
|
hostAria(this.internals, { label: this.label || this.fallbackLabel, role: 'region' });
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
}
|
|
716
|
+
this.reopen(this.lastGroups);
|
|
717
|
+
this.lastGroups = this.allGroups;
|
|
511
718
|
if (changed.has('limit')) {
|
|
512
719
|
this.buffer.limit = Math.max(0, Math.floor(this.limit || 0));
|
|
513
720
|
this.buffer.write('');
|
|
@@ -532,7 +739,7 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
532
739
|
if (this.fed) {
|
|
533
740
|
this.view = logRows({
|
|
534
741
|
current: this.buffer.current(),
|
|
535
|
-
groups: this.
|
|
742
|
+
groups: this.allGroups,
|
|
536
743
|
lines: this.buffer.lines,
|
|
537
744
|
open: (id) => this.isOpen(id),
|
|
538
745
|
query: this.query,
|
|
@@ -570,7 +777,7 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
570
777
|
this.markScroller();
|
|
571
778
|
this.announce();
|
|
572
779
|
}
|
|
573
|
-
renderRow(row, marks, start) {
|
|
780
|
+
renderRow(row, marks, start, stop) {
|
|
574
781
|
if (row.kind === 'group') {
|
|
575
782
|
const group = this.tag('log-group');
|
|
576
783
|
const { duration, heading, id, status } = row.group;
|
|
@@ -589,7 +796,7 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
589
796
|
const line = this.tag('log-line');
|
|
590
797
|
const { at, level } = row.line;
|
|
591
798
|
return html `<${line}
|
|
592
|
-
exportparts="line, line-debug, line-error, line-marked, line-warn, match: line-match,
|
|
799
|
+
exportparts="line, line-debug, line-error, line-marked, line-selected, line-warn, match: line-match,
|
|
593
800
|
number: line-number, run: line-run, run-bold: line-run-bold, run-dim: line-run-dim,
|
|
594
801
|
run-italic: line-run-italic, run-strike: line-run-strike, run-underline: line-run-underline,
|
|
595
802
|
text: line-text, time: line-time"
|
|
@@ -601,6 +808,9 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
601
808
|
.logNumber=${row.number}
|
|
602
809
|
.logNumbers=${this.lineNumbers}
|
|
603
810
|
.logQuery=${this.query}
|
|
811
|
+
.logSelectable=${this.selectable}
|
|
812
|
+
.logSelected=${this.selected(row.number)}
|
|
813
|
+
.logStop=${row.number === stop}
|
|
604
814
|
.logStart=${start}
|
|
605
815
|
.logTimes=${this.timestamps ?? ''}
|
|
606
816
|
.runs=${this.buffer.runsOf(row.line)}
|
|
@@ -628,8 +838,12 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
|
|
|
628
838
|
const { after, before, first, last } = this.virtualize.window;
|
|
629
839
|
const marks = lineRuns(this.markLines);
|
|
630
840
|
const start = this.timestamps === 'relative' ? this.firstTime : undefined;
|
|
841
|
+
const drawn = this.view.rows
|
|
842
|
+
.slice(first, last)
|
|
843
|
+
.flatMap((row) => (row.kind === 'line' ? [row.number] : []));
|
|
844
|
+
const stop = this.interactive ? this.stopAmong(drawn) : 0;
|
|
631
845
|
return html `<div part="before" role="presentation" style="block-size: ${before}px; flex: none"></div>
|
|
632
|
-
${repeat(this.view.rows.slice(first, last), (row) => (row.kind === 'group' ? `group:${row.group.id}` : `line:${row.number}`), (row, offset) => html `<div part="row" data-index=${first + offset}>${this.renderRow(row, marks, start)}</div>`)}
|
|
846
|
+
${repeat(this.view.rows.slice(first, last), (row) => (row.kind === 'group' ? `group:${row.group.id}` : `line:${row.number}`), (row, offset) => html `<div part="row" data-index=${first + offset}>${this.renderRow(row, marks, start, stop)}</div>`)}
|
|
633
847
|
<div part="after" role="presentation" style="block-size: ${after}px; flex: none"></div>`;
|
|
634
848
|
}
|
|
635
849
|
renderResume() {
|
|
@@ -693,6 +907,9 @@ __decorate([
|
|
|
693
907
|
words,
|
|
694
908
|
property({ attribute: 'fallback-label' })
|
|
695
909
|
], HelixHeadlessLog.prototype, "fallbackLabel", void 0);
|
|
910
|
+
__decorate([
|
|
911
|
+
property()
|
|
912
|
+
], HelixHeadlessLog.prototype, "format", null);
|
|
696
913
|
__decorate([
|
|
697
914
|
words,
|
|
698
915
|
property({ attribute: 'group-label' })
|
|
@@ -739,6 +956,12 @@ __decorate([
|
|
|
739
956
|
words,
|
|
740
957
|
property({ attribute: 'resume-label' })
|
|
741
958
|
], HelixHeadlessLog.prototype, "resumeLabel", void 0);
|
|
959
|
+
__decorate([
|
|
960
|
+
property({ type: Boolean })
|
|
961
|
+
], HelixHeadlessLog.prototype, "selectable", void 0);
|
|
962
|
+
__decorate([
|
|
963
|
+
property({ attribute: 'selected-lines' })
|
|
964
|
+
], HelixHeadlessLog.prototype, "selectedLines", void 0);
|
|
742
965
|
__decorate([
|
|
743
966
|
property({ attribute: false })
|
|
744
967
|
], HelixHeadlessLog.prototype, "source", null);
|
|
@@ -754,6 +977,9 @@ __decorate([
|
|
|
754
977
|
__decorate([
|
|
755
978
|
state()
|
|
756
979
|
], HelixHeadlessLog.prototype, "pinnedState", void 0);
|
|
980
|
+
__decorate([
|
|
981
|
+
state()
|
|
982
|
+
], HelixHeadlessLog.prototype, "stop", void 0);
|
|
757
983
|
__decorate([
|
|
758
984
|
state()
|
|
759
985
|
], HelixHeadlessLog.prototype, "doneState", void 0);
|