LogViewerModel
class exported by @mwillbanks/tuil-log-viewer.
class
Public class exported by @mwillbanks/tuil-log-viewer.
export class LogViewerModel {
readonly #pipeline: LogPipeline;
readonly #ownsQueryEditor: boolean;
readonly scroll: ScrollAreaState;
readonly queryEditor: EditorSession;
#records: readonly LogRecord[] = [];
#selected = 0;
#query = "";
#theme: LogTheme;
readonly #observers = new Set<() => void>();
readonly #unsubscribeBuffer: () => void;
#revision = 0;
constructor(pipeline: LogPipeline, options: LogViewerModelOptions = {}) {
this.#pipeline = pipeline;
this.#records = pipeline.buffer.records();
this.scroll = new ScrollAreaState({
id: options.id ?? "log-viewer",
viewport: {
width: options.width ?? 100,
height: options.height ?? 20,
},
extent: {
width: options.width ?? 100,
height: this.#records.length,
},
sticky: { bottom: true },
followFocus: true,
});
if (!options.queryEditor) {
throw new Error(
"LogViewerModel requires a queryEditor from the application editor provider registry",
);
}
if (!options.queryEditorOwnership) {
throw new Error(
"LogViewerModel requires explicit borrowed or owned queryEditor ownership",
);
}
this.queryEditor = options.queryEditor;
this.#ownsQueryEditor = options.queryEditorOwnership === "owned";
this.#selected = Math.max(0, this.#records.length - 1);
this.scroll.move("bottom");
this.#theme = createLogTheme(options.theme ?? "comfortable");
this.#unsubscribeBuffer = pipeline.buffer.subscribe(() => {
if (!pipeline.buffer.statistics().paused) this.refresh();
else this.#notify();
});
}
refresh(): void {
this.#records = this.#query
? this.#pipeline.filter(this.#query)
: this.#pipeline.buffer.records();
this.scroll.setExtent({
width: this.scroll.snapshot().extent.width,
height: this.#records.length,
});
this.#selected = Math.max(
0,
Math.min(this.#records.length - 1, this.#selected),
);
this.#notify();
}
setQuery(source: string): readonly string[] {
const compiled = compileLogQuery(source);
this.#query = source;
this.#pipeline.query(source);
this.queryEditor.dispatch({
changes: [
{
range: {
anchor: { line: 0, column: 0 },
head: {
line: 0,
column: this.queryEditor.serialize().length,
},
},
insert: source,
},
],
});
this.refresh();
return Object.freeze(compiled.diagnostics.map((item) => item.message));
}
select(index: number): void {
this.#selected = Math.max(0, Math.min(this.#records.length - 1, index));
this.scroll.scrollIntoView({
x: 0,
y: this.#selected,
width: 1,
height: 1,
});
this.#notify();
}
move(delta: number): void {
this.select(this.#selected + delta);
}
pause(): void {
this.#pipeline.buffer.pause();
}
resume(): void {
this.#pipeline.buffer.resume();
}
setTheme(variant: LogThemeVariant): void {
this.#theme = createLogTheme(variant);
this.#notify();
}
subscribe(observer: () => void): () => void {
this.#observers.add(observer);
return () => this.#observers.delete(observer);
}
revision(): number {
return this.#revision;
}
snapshot(): {
readonly rows: readonly LogRowView[];
readonly selected?: LogRecord;
readonly live: boolean;
readonly theme: LogTheme;
readonly dropped: number;
readonly sampled: number;
readonly rateLimited: number;
readonly total: number;
} {
const viewport = this.scroll.snapshot();
const statistics = this.#pipeline.buffer.statistics();
const start = viewport.position.y;
const end = start + viewport.viewport.height;
return Object.freeze({
rows: Object.freeze(
this.#records
.slice(start, end)
.map((record, offset) =>
LogRow(record, start + offset, start + offset === this.#selected),
),
),
selected: this.#records[this.#selected],
live: !statistics.paused,
theme: this.#theme,
dropped: statistics.dropped,
sampled: statistics.sampled,
rateLimited: statistics.rateLimited,
total: this.#records.length,
});
}
export(format: "jsonl" | "text" = "jsonl"): string {
return this.#pipeline.export(this.#records, format);
}
async copy(
clipboard: EditorClipboardAdapter,
format: "jsonl" | "text" = "text",
): Promise<string> {
const value = this.export(format);
await clipboard.write(value);
return value;
}
dispose(): void {
this.#unsubscribeBuffer();
this.#observers.clear();
if (this.#ownsQueryEditor) this.queryEditor.dispose();
}
#notify(): void {
this.#revision += 1;
for (const observer of this.#observers) observer();
}
}Members
| Member | Type | Required | Description | Related types |
|---|---|---|---|---|
#pipeline | LogPipeline | Yes | The #pipeline member uses the LogPipeline contract. | LogPipeline |
#ownsQueryEditor | boolean | Yes | The #ownsQueryEditor member uses the boolean contract. | — |
scroll | ScrollAreaState | Yes | The scroll member uses the ScrollAreaState contract. | ScrollAreaState |
queryEditor | EditorSession | Yes | The queryEditor member uses the EditorSession contract. | EditorSession |
#records | readonly LogRecord[] | Yes | The #records member uses the readonly LogRecord[] contract. | LogRecord |
#selected | number | Yes | The #selected member uses the number contract. | — |
#query | string | Yes | The #query member uses the string contract. | — |
#theme | LogTheme | Yes | The #theme member uses the LogTheme contract. | LogTheme |
#observers | Set<() => void> | Yes | The #observers member uses the Set<() => void> contract. | — |
#unsubscribeBuffer | () => void | Yes | The #unsubscribeBuffer member uses the () => void contract. | — |
#revision | number | Yes | The #revision member uses the number contract. | — |
__constructor | any | Yes | The __constructor member uses the any contract. | — |
refresh | () => void | Yes | The refresh member uses the () => void contract. | — |
setQuery | (source: string) => readonly string[] | Yes | The setQuery member uses the (source: string) => readonly string[] contract. | — |
select | (index: number) => void | Yes | The select member uses the (index: number) => void contract. | — |
move | (delta: number) => void | Yes | The move member uses the (delta: number) => void contract. | — |
pause | () => void | Yes | The pause member uses the () => void contract. | — |
resume | () => void | Yes | The resume member uses the () => void contract. | — |
setTheme | (variant: LogThemeVariant) => void | Yes | The setTheme member uses the (variant: LogThemeVariant) => void contract. | LogThemeVariant |
subscribe | (observer: () => void) => () => void | Yes | The subscribe member uses the (observer: () => void) => () => void contract. | — |
revision | () => number | Yes | The revision member uses the () => number contract. | — |
snapshot | () => { readonly rows: readonly LogRowView[]; readonly selected?: LogRecord; readonly live: boolean; readonly theme: LogTheme; readonly dropped: number; readonly sampled: number; readonly rateLimited: number; readonly total: number; } | Yes | The snapshot member uses the () => { readonly rows: readonly LogRowView[]; readonly selected?: LogRecord; readonly live: boolean; readonly theme: LogTheme; readonly dropped: number; readonly sampled: number; readonly rateLimited: number; readonly total: number; } contract. | LogRecord, LogRowView, LogTheme |
export | (format?: "jsonl" | "text") => string | Yes | The export member uses the (format?: "jsonl" | "text") => string contract. | — |
copy | (clipboard: EditorClipboardAdapter, format?: "jsonl" | "text") => Promise<string> | Yes | The copy member uses the (clipboard: EditorClipboardAdapter, format?: "jsonl" | "text") => Promise<string> contract. | — |
dispose | () => void | Yes | The dispose member uses the () => void contract. | — |
#notify | () => void | Yes | The #notify member uses the () => void contract. | — |
Parameters
This declaration has no public members.
Returns
This declaration does not return a value.
Throws
No thrown errors are documented for this declaration.
Related types
Source
View the secondary source reference