diff --git a/src/features/editor/components/output/log-item.tsx b/src/features/editor/components/output/log-item.tsx index 32cff2f..b99147c 100644 --- a/src/features/editor/components/output/log-item.tsx +++ b/src/features/editor/components/output/log-item.tsx @@ -11,11 +11,7 @@ import React from 'react'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { CONSOLE_AVAILABLE_HANDLERS } from '@/features/editor/config/constants'; -import type { - consoleOutput, - Loggable, - SystemError, -} from '@/features/editor/types'; +import type { Log, Loggable, SystemError } from '@/features/editor/types'; import { cn } from '@/lib/utils'; import { FormatOutput } from './format-output'; @@ -56,7 +52,10 @@ const Container: React.FC = ({ ...props }) => { return ( -
+
{children}
); @@ -115,7 +114,7 @@ const Value: React.FC = ({ variant, value, repeats, duration }) => { }; interface Props { - type: consoleOutput['type']; + type: Log['type']; value: Loggable | SystemError; repeats: number; duration: number; @@ -131,14 +130,12 @@ export const LogItem: React.FC = ({ }) => { const isDetailsString = typeof details === 'string'; - const consoleAvailableHandlersAsString: readonly consoleOutput['type'][] = [ - ...CONSOLE_AVAILABLE_HANDLERS, - 'systemError', - ]; + const consoleAvailableHandlersAsString: readonly Log['type'][] = + CONSOLE_AVAILABLE_HANDLERS; const isVariantTypeSupported = !!consoleAvailableHandlersAsString.includes(type); - const variantType: consoleOutput['type'] | 'default' = isVariantTypeSupported + const variantType: Log['type'] | 'default' = isVariantTypeSupported ? type : 'default'; diff --git a/src/features/editor/components/output/log-renderer.tsx b/src/features/editor/components/output/log-renderer.tsx deleted file mode 100644 index c26011c..0000000 --- a/src/features/editor/components/output/log-renderer.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React from 'react'; - -import type { baseErrorObj, consoleOutput, log } from '@/features/editor/types'; - -import { LogItem } from './log-item'; - -function isError(obj: unknown) { - if (!(typeof obj === 'object' && !Array.isArray(obj) && obj !== null)) { - return false; - } - - if (!('message' in obj) || !('name' in obj)) { - return false; - } - - const tiposErrorValido = [ - 'EvalError', - 'RangeError', - 'ReferenceError', - 'SyntaxError', - 'InternalError', - 'TypeError', - ]; - if (!tiposErrorValido.includes(obj.name as string)) { - return false; - } - - return true; -} - -interface CustomLog extends Omit { - type: consoleOutput['type']; -} - -interface Props { - log: CustomLog; -} - -export const LogRenderer: React.FC = ({ log }) => { - const { value, repeats, type, duration } = log; - - if ( - type === 'systemError' && - value && - Array.isArray(value) && - value[0] && - isError(value[0]) - ) { - const error = value[0] as baseErrorObj; - return ( -
- -
- ); - } - - if (type === 'systemError' && value && isError(value)) { - const error = value as baseErrorObj; - return ( -
- -
- ); - } - - return ( -
- -
- ); -}; diff --git a/src/features/editor/components/output/logs-list.tsx b/src/features/editor/components/output/logs-list.tsx index dcd6511..20f4618 100644 --- a/src/features/editor/components/output/logs-list.tsx +++ b/src/features/editor/components/output/logs-list.tsx @@ -7,7 +7,7 @@ import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils'; import { useLogsStore } from '../../stores/editor'; -import { LogRenderer } from './log-renderer'; +import { LogItem } from './log-item'; const EmptyListView = () => (
@@ -23,6 +23,7 @@ const EmptyListFirstTimeView = () => (
); +// eslint-disable-next-line max-lines-per-function export const LogsList = () => { const virtuosoRef = useRef(null); const { logs, isFirstTime } = useLogsStore(); @@ -68,7 +69,16 @@ export const LogsList = () => { data={logs} atBottomStateChange={setAtBottom} itemContent={(index, log) => { - return ; + return ( + + ); }} followOutput={'auto'} /> diff --git a/src/features/editor/lib/linked-log.ts b/src/features/editor/lib/linked-log.ts index 7f70e22..7e897ea 100644 --- a/src/features/editor/lib/linked-log.ts +++ b/src/features/editor/lib/linked-log.ts @@ -1,27 +1,29 @@ import DeepEqual from 'deep-equal'; -import type { log } from '../types'; +import type { Log } from '../types'; +import { addLogDecorators } from '../utils/log-decorator'; class LogNode { public next: LogNode | null = null; - public value: log; + public value: Log; - constructor(data: log) { + constructor(data: Log) { this.value = data; } } interface ILinkedLogs { - append(data: log): void; + append(data: Log): void; clearFirst(): void; - getAllLogsInArray(): log[]; + getAllLogsInArray(): Log[]; } class LinkedLogs implements ILinkedLogs { private head: LogNode | null = null; - append(data: log) { - const node = new LogNode(data); + append(data: Log) { + const decoratedData = addLogDecorators(data); + const node = new LogNode(decoratedData); if (!this.head) { this.head = node; @@ -33,11 +35,11 @@ class LinkedLogs implements ILinkedLogs { const lastNode = getLast(this.head); if ( - lastNode.value.type === data.type && - DeepEqual(lastNode.value.value, data.value) + lastNode.value.type === decoratedData.type && + DeepEqual(lastNode.value.value, decoratedData.value) ) { lastNode.value.repeats += 1; - lastNode.value.duration = data.duration; + lastNode.value.duration = decoratedData.duration; } else { lastNode.next = node; } @@ -48,8 +50,8 @@ class LinkedLogs implements ILinkedLogs { this.head = null; } - getAllLogsInArray(): log[] { - const array: log[] = []; + getAllLogsInArray(): Log[] { + const array: Log[] = []; let currentNode: LogNode | null = this.head; while (currentNode) { diff --git a/src/features/editor/stores/editor/logs-store.ts b/src/features/editor/stores/editor/logs-store.ts index 7dcbb75..0a28d97 100644 --- a/src/features/editor/stores/editor/logs-store.ts +++ b/src/features/editor/stores/editor/logs-store.ts @@ -1,6 +1,6 @@ import { create } from 'zustand'; -import type { log } from '@/features/editor/types'; +import type { Log } from '@/features/editor/types'; import { createSelectors } from '@/lib/utils'; import LinkedLogs from '../../lib/linked-log'; @@ -8,10 +8,10 @@ import LinkedLogs from '../../lib/linked-log'; const logsList = new LinkedLogs(); interface LogsState { - logs: log[]; + logs: Log[]; isFirstTime: boolean; alert: boolean; - appendLogs: (logs: log) => void; + appendLogs: (logs: Log) => void; clearLogs: () => void; clearAlert: () => void; } @@ -20,7 +20,7 @@ const _useLogsStore = create((set) => ({ logs: [], isFirstTime: true, alert: false, - appendLogs: (log: log) => { + appendLogs: (log: Log) => { logsList.append(log); set({ logs: logsList.getAllLogsInArray(), @@ -39,5 +39,5 @@ const _useLogsStore = create((set) => ({ export const useLogsStore = createSelectors(_useLogsStore); -export const appendLogs = (log: log) => +export const appendLogs = (log: Log) => _useLogsStore.getState().appendLogs(log); diff --git a/src/features/editor/types/index.ts b/src/features/editor/types/index.ts index 0a7c97f..33aebad 100644 --- a/src/features/editor/types/index.ts +++ b/src/features/editor/types/index.ts @@ -14,7 +14,8 @@ export type Loggable = | { error: string; stack: string } | (Record | undefined)[] | Loggable[] - | Error; + | Error + | baseErrorObj; export type baseErrorObj = { message: string; @@ -22,40 +23,21 @@ export type baseErrorObj = { stack?: string; lineNumber?: string; }; +export type SystemError = string | SyntaxError | Error | baseErrorObj; -export type SystemError = - | string - | SyntaxError - | Error - | { - message: string; - name: string; - stack: string; - }; - -type baseLog = { +export type Log = { + internalError?: boolean; + type: consoleAvailableHandlers; duration: number; repeats: number; -}; - -type baseConsoleOutput = { - type: consoleAvailableHandlers; value: Loggable; + detail?: string; }; -export type consoleOutput = baseConsoleOutput | consoleOutputSystemError; -type consoleOutputSystemError = { - type: 'systemError'; - value: SystemError; -}; - -export type log = consoleOutput & baseLog; -export type logWithoutSystemError = baseConsoleOutput & baseLog; - export type remoteControlerOutsideWorker = | { command: 'log'; - data: Pick; + data: Pick; } | { command: 'error'; diff --git a/src/features/editor/utils/engine/controller.ts b/src/features/editor/utils/engine/controller.ts index e0d497f..de4df16 100644 --- a/src/features/editor/utils/engine/controller.ts +++ b/src/features/editor/utils/engine/controller.ts @@ -1,5 +1,5 @@ import type { - logWithoutSystemError, + Log, remoteControlerInsideWorker, remoteControlerOutsideWorker, SystemError, @@ -41,6 +41,7 @@ export function runJs(code: string) { DEBUG = debugMode ? DebugLog : DebugLogVoid; + // eslint-disable-next-line max-lines-per-function return new Promise((resolve, reject) => { const worker = new Worker( new URL('./execution-manager.ts', import.meta.url), @@ -52,17 +53,17 @@ export function runJs(code: string) { const logError = (message: SystemError, duration: number = 0) => { appendLogs({ - type: 'systemError', + internalError: true, + type: 'error', value: message, duration: duration, repeats: 1, }); }; - const logger = ( - log: Pick, - ) => { + const logger = (log: Pick) => { appendLogs({ + internalError: false, type: log.type, value: log.value, duration: log.duration, @@ -72,7 +73,10 @@ export function runJs(code: string) { timeoutIdRef = setTimeout(() => { stopJs(); - logError('Process terminated to avoid infinite loop'); + logError({ + name: 'InternalError', + message: 'timeout', + }); const executionTime = Date.now() - startTime; reject(new Error(`Execution timed out after ${executionTime}ms`)); diff --git a/src/features/editor/utils/log-decorator.ts b/src/features/editor/utils/log-decorator.ts new file mode 100644 index 0000000..7eca2a7 --- /dev/null +++ b/src/features/editor/utils/log-decorator.ts @@ -0,0 +1,47 @@ +import type { baseErrorObj, Log, Loggable } from '../types'; + +const getLogErrorValue = ( + internalError: Log['internalError'], + value: Log['value'], +): Loggable => { + if (internalError) { + const error = value as baseErrorObj; + + return `${error?.name}: ${error?.message}`; + } + + return value; +}; + +const getLogErrorDetail = ( + internalError: Log['internalError'], + value: Log['value'], +): string | undefined => { + if (internalError) { + const error = value as baseErrorObj; + + if (error.name === 'InternalError') { + if (error.message === 'interrupted') { + return 'This error throw on intense usage of loops. If this is intended, you can workaround by increasing the loop threshold value in settings.'; + } + if (error.message === 'timeout') { + return 'Process terminated by timeout. If a longer execution is expected, you can workaround by increasing the loop timeout value in settings.'; + } + } + } + + return; +}; + +export const addLogDecorators = (log: Log): Log => { + if (log.internalError) { + const Log = structuredClone(log); + + Log.value = getLogErrorValue(log.internalError, log.value); + Log.detail = getLogErrorDetail(log.internalError, log.value); + + return Log; + } + + return log; +};