1// SPDX-License-Identifier: GPL-2.0-only
2// Copyright (c) 2026-present FERS Contributors (see AUTHORS.md).
4import { create } from 'zustand';
6export type FersLogLevel =
16export type FersLogEntry = {
22export type ConfigurableFersLogLevel = Exclude<FersLogLevel, 'UNKNOWN'>;
25 entries: FersLogEntry[];
28 logLevel: ConfigurableFersLogLevel;
31 appendLog: (entry: FersLogEntry) => void;
32 clearLogs: () => void;
33 setMaxLines: (maxLines: number) => void;
34 setLogLevel: (logLevel: ConfigurableFersLogLevel) => void;
35 setOpen: (isOpen: boolean) => void;
36 toggleOpen: () => void;
37 setDrawerWidth: (drawerWidth: number) => void;
40export const DEFAULT_LOG_MAX_LINES = 2000;
41export const MIN_LOG_MAX_LINES = 100;
42export const MAX_LOG_MAX_LINES = 20_000;
43export const DEFAULT_LOG_LEVEL: ConfigurableFersLogLevel = 'INFO';
44export const DEFAULT_LOG_DRAWER_WIDTH = 560;
45export const MIN_LOG_DRAWER_WIDTH = 360;
46export const MAX_LOG_DRAWER_WIDTH = 960;
47const LOG_DRAWER_STORAGE_KEY = 'fers.logViewer.v1';
48export const LOG_LEVEL_OPTIONS: ConfigurableFersLogLevel[] = [
58export const isConfigurableFersLogLevel = (
60): level is ConfigurableFersLogLevel =>
61 LOG_LEVEL_OPTIONS.some((option) => option === level);
63export const clampLogMaxLines = (maxLines: number) => {
64 if (!Number.isFinite(maxLines)) {
65 return DEFAULT_LOG_MAX_LINES;
70 Math.min(MAX_LOG_MAX_LINES, Math.floor(maxLines))
74function canUseLocalStorage(): boolean {
75 return typeof globalThis.localStorage !== 'undefined';
78export const clampLogDrawerWidth = (drawerWidth: number) => {
79 if (!Number.isFinite(drawerWidth)) {
80 return DEFAULT_LOG_DRAWER_WIDTH;
85 Math.min(MAX_LOG_DRAWER_WIDTH, Math.round(drawerWidth))
89export const getEffectiveLogDrawerWidth = (
91 maxAllowedWidth: number
93 const preferredWidth = clampLogDrawerWidth(drawerWidth);
95 if (!Number.isFinite(maxAllowedWidth)) {
96 return preferredWidth;
99 return Math.max(0, Math.min(preferredWidth, Math.floor(maxAllowedWidth)));
102export function readStoredLogDrawerWidth(): number {
103 if (!canUseLocalStorage()) {
104 return DEFAULT_LOG_DRAWER_WIDTH;
107 const raw = globalThis.localStorage.getItem(LOG_DRAWER_STORAGE_KEY);
109 return DEFAULT_LOG_DRAWER_WIDTH;
113 const parsed = JSON.parse(raw) as { drawerWidth?: unknown };
114 return clampLogDrawerWidth(Number(parsed.drawerWidth));
116 return DEFAULT_LOG_DRAWER_WIDTH;
120function writeStoredLogDrawerWidth(drawerWidth: number): void {
121 if (!canUseLocalStorage()) {
125 globalThis.localStorage.setItem(
126 LOG_DRAWER_STORAGE_KEY,
127 JSON.stringify({ drawerWidth })
132 entries: FersLogEntry[],
134): { entries: FersLogEntry[]; dropped: number } => {
135 if (entries.length <= maxLines) {
136 return { entries, dropped: 0 };
140 entries: entries.slice(entries.length - maxLines),
141 dropped: entries.length - maxLines,
145export const useFersLogStore = create<FersLogStore>()((set) => ({
148 maxLines: DEFAULT_LOG_MAX_LINES,
149 logLevel: DEFAULT_LOG_LEVEL,
151 drawerWidth: readStoredLogDrawerWidth(),
152 appendLog: (entry) =>
154 const trimmed = trimEntries(
155 [...state.entries, entry],
160 entries: trimmed.entries,
161 droppedCount: state.droppedCount + trimmed.dropped,
164 clearLogs: () => set({ entries: [], droppedCount: 0 }),
165 setMaxLines: (maxLines) =>
167 const nextMaxLines = clampLogMaxLines(maxLines);
168 const trimmed = trimEntries(state.entries, nextMaxLines);
171 maxLines: nextMaxLines,
172 entries: trimmed.entries,
173 droppedCount: state.droppedCount + trimmed.dropped,
176 setLogLevel: (logLevel) => set({ logLevel }),
177 setOpen: (isOpen) => set({ isOpen }),
178 toggleOpen: () => set((state) => ({ isOpen: !state.isOpen })),
179 setDrawerWidth: (drawerWidth) => {
180 const nextDrawerWidth = clampLogDrawerWidth(drawerWidth);
181 writeStoredLogDrawerWidth(nextDrawerWidth);
182 set({ drawerWidth: nextDrawerWidth });