import { createGlobalState } from '@vueuse/core' import { type Ref, shallowRef } from 'vue' /** * Maximum number of console output lines to store * @type {number} */ const maxLines = 10000 const batchTimeout = 300 // ms const initialBatchSize = 256 /** * Provides a global console output state management system * Allows adding, storing, and clearing console output with a maximum line limit * * @returns {Object} Console state management methods and reactive state * @property {Ref} consoleOutput - Reactive array of console output lines * @property {function(string): void} addConsoleOutput - Method to add a new console output line * @property {function(): void} clear - Method to clear all console output */ export const useModrinthServersConsole = createGlobalState(() => { /** * Reactive array storing console output lines * @type {Ref} */ const output: Ref = shallowRef([]) const searchQuery: Ref = shallowRef('') const filteredOutput: Ref = shallowRef([]) let searchRegex: RegExp | null = null let lineBuffer: string[] = [] let batchTimer: NodeJS.Timeout | null = null let isProcessingInitialBatch = false let refilterTimer: NodeJS.Timeout | null = null const refilterTimeout = 100 // ms const updateFilter = () => { if (!searchQuery.value) { filteredOutput.value = [] return } if (!searchRegex) { searchRegex = new RegExp(searchQuery.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i') } filteredOutput.value = output.value.filter((line) => searchRegex?.test(line) ?? false) } const scheduleRefilter = () => { if (refilterTimer) clearTimeout(refilterTimer) refilterTimer = setTimeout(updateFilter, refilterTimeout) } const flushBuffer = () => { if (lineBuffer.length === 0) return const processedLines = lineBuffer.flatMap((line) => line.split('\n').filter(Boolean)) if (isProcessingInitialBatch && processedLines.length >= initialBatchSize) { isProcessingInitialBatch = false output.value = processedLines.slice(-maxLines) } else { const newOutput = [...output.value, ...processedLines] output.value = newOutput.slice(-maxLines) } lineBuffer = [] batchTimer = null if (searchQuery.value) { scheduleRefilter() } } /** * Adds a new output line to the console output * Automatically removes the oldest line if max output is exceeded * * @param {string} line - The console output line to add */ const addLine = (line: string): void => { lineBuffer.push(line) if (!batchTimer) { batchTimer = setTimeout(flushBuffer, batchTimeout) } } /** * Adds multiple output lines to the console output * Automatically removes the oldest lines if max output is exceeded * * @param {string[]} lines - The console output lines to add * @returns {void} */ const addLines = (lines: string[]): void => { if (output.value.length === 0 && lines.length >= initialBatchSize) { isProcessingInitialBatch = true lineBuffer = lines flushBuffer() return } lineBuffer.push(...lines) if (!batchTimer) { batchTimer = setTimeout(flushBuffer, batchTimeout) } } /** * Sets the search query and filters the output based on the query * * @param {string} query - The search query */ const setSearchQuery = (query: string): void => { searchQuery.value = query searchRegex = null updateFilter() } /** * Clears all console output lines */ const clear = (): void => { output.value = [] filteredOutput.value = [] searchQuery.value = '' lineBuffer = [] isProcessingInitialBatch = false if (batchTimer) { clearTimeout(batchTimer) batchTimer = null } if (refilterTimer) { clearTimeout(refilterTimer) refilterTimer = null } searchRegex = null } /** * Finds the index of a line in the main output * * @param {string} line - The line to find * @returns {number} The index of the line, or -1 if not found */ const findLineIndex = (line: string): number => { return output.value.findIndex((l) => l === line) } return { output, searchQuery, filteredOutput, addLine, addLines, setSearchQuery, clear, findLineIndex, } })