From f9858fbd4bcbe839aa257ba9232be7f0e1b02cce Mon Sep 17 00:00:00 2001 From: chamiakJ Date: Sun, 18 May 2025 20:58:20 +0530 Subject: [PATCH 1/5] refactor(task-list): enhance performance with useMemo and useCallback - Introduced useMemo to optimize loading state and empty state calculations. - Added useMemo for socket event handler functions to prevent unnecessary re-renders. - Refactored data fetching logic to improve initial data load handling. - Improved drag-and-drop functionality with memoized handlers for better performance. --- .../taskList/project-view-task-list.tsx | 84 ++-- .../task-group-wrapper/task-group-wrapper.tsx | 395 +++++++++--------- 2 files changed, 250 insertions(+), 229 deletions(-) diff --git a/worklenz-frontend/src/pages/projects/projectView/taskList/project-view-task-list.tsx b/worklenz-frontend/src/pages/projects/projectView/taskList/project-view-task-list.tsx index fcd4931a..410644fb 100644 --- a/worklenz-frontend/src/pages/projects/projectView/taskList/project-view-task-list.tsx +++ b/worklenz-frontend/src/pages/projects/projectView/taskList/project-view-task-list.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useMemo } from 'react'; import Flex from 'antd/es/flex'; import Skeleton from 'antd/es/skeleton'; import { useSearchParams } from 'react-router-dom'; @@ -17,8 +17,8 @@ const ProjectViewTaskList = () => { const dispatch = useAppDispatch(); const { projectView } = useTabSearchParam(); const [searchParams, setSearchParams] = useSearchParams(); - // Add local loading state to immediately show skeleton const [isLoading, setIsLoading] = useState(true); + const [initialLoadComplete, setInitialLoadComplete] = useState(false); const { projectId } = useAppSelector(state => state.projectReducer); const { taskGroups, loadingGroups, groupBy, archived, fields, search } = useAppSelector( @@ -30,47 +30,73 @@ const ProjectViewTaskList = () => { const { loadingPhases } = useAppSelector(state => state.phaseReducer); const { loadingColumns } = useAppSelector(state => state.taskReducer); + // Memoize the loading state calculation - ignoring task list filter loading + const isLoadingState = useMemo(() => + loadingGroups || loadingPhases || loadingStatusCategories, + [loadingGroups, loadingPhases, loadingStatusCategories] + ); + + // Memoize the empty state check + const isEmptyState = useMemo(() => + taskGroups && taskGroups.length === 0 && !isLoadingState, + [taskGroups, isLoadingState] + ); + + // Handle view type changes useEffect(() => { - // Set default view to list if projectView is not list or board if (projectView !== 'list' && projectView !== 'board') { - searchParams.set('tab', 'tasks-list'); - searchParams.set('pinned_tab', 'tasks-list'); - setSearchParams(searchParams); + const newParams = new URLSearchParams(searchParams); + newParams.set('tab', 'tasks-list'); + newParams.set('pinned_tab', 'tasks-list'); + setSearchParams(newParams); } - }, [projectView, searchParams, setSearchParams]); + }, [projectView, setSearchParams]); + // Update loading state useEffect(() => { - // Set loading state based on all loading conditions - setIsLoading(loadingGroups || loadingColumns || loadingPhases || loadingStatusCategories); - }, [loadingGroups, loadingColumns, loadingPhases, loadingStatusCategories]); + setIsLoading(isLoadingState); + }, [isLoadingState]); + // Fetch initial data only once useEffect(() => { - const loadData = async () => { - if (projectId && groupBy) { - const promises = []; - - if (!loadingColumns) promises.push(dispatch(fetchTaskListColumns(projectId))); - if (!loadingPhases) promises.push(dispatch(fetchPhasesByProjectId(projectId))); - if (!loadingGroups && projectView === 'list') { - promises.push(dispatch(fetchTaskGroups(projectId))); - } - if (!statusCategories.length) { - promises.push(dispatch(fetchStatusesCategories())); - } - - // Wait for all data to load - await Promise.all(promises); + const fetchInitialData = async () => { + if (!projectId || !groupBy || initialLoadComplete) return; + + try { + await Promise.all([ + dispatch(fetchTaskListColumns(projectId)), + dispatch(fetchPhasesByProjectId(projectId)), + dispatch(fetchStatusesCategories()) + ]); + setInitialLoadComplete(true); + } catch (error) { + console.error('Error fetching initial data:', error); } }; - - loadData(); - }, [dispatch, projectId, groupBy, fields, search, archived]); + + fetchInitialData(); + }, [projectId, groupBy, dispatch, initialLoadComplete]); + + // Fetch task groups + useEffect(() => { + const fetchTasks = async () => { + if (!projectId || !groupBy || projectView !== 'list' || !initialLoadComplete) return; + + try { + await dispatch(fetchTaskGroups(projectId)); + } catch (error) { + console.error('Error fetching task groups:', error); + } + }; + + fetchTasks(); + }, [projectId, groupBy, projectView, dispatch, fields, search, archived, initialLoadComplete]); return ( - {(taskGroups && taskGroups.length === 0 && !isLoading) ? ( + {isEmptyState ? ( ) : ( diff --git a/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-group-wrapper/task-group-wrapper.tsx b/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-group-wrapper/task-group-wrapper.tsx index f619f20a..6a0e9374 100644 --- a/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-group-wrapper/task-group-wrapper.tsx +++ b/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-group-wrapper/task-group-wrapper.tsx @@ -2,7 +2,7 @@ import { useAppSelector } from '@/hooks/useAppSelector'; import { useAppDispatch } from '@/hooks/useAppDispatch'; import { useSocket } from '@/socket/socketContext'; import { useAuthService } from '@/hooks/useAuth'; -import { useState, useEffect, useCallback } from 'react'; +import { useState, useEffect, useCallback, useMemo } from 'react'; import { createPortal } from 'react-dom'; import Flex from 'antd/es/flex'; import useIsomorphicLayoutEffect from '@/hooks/useIsomorphicLayoutEffect'; @@ -87,16 +87,22 @@ const TaskGroupWrapper = ({ taskGroups, groupBy }: TaskGroupWrapperProps) => { const loadingAssignees = useAppSelector(state => state.taskReducer.loadingAssignees); const { projectId } = useAppSelector(state => state.projectReducer); - const sensors = useSensors( - useSensor(PointerSensor, { + // Move useSensors to top level and memoize its configuration + const sensorConfig = useMemo( + () => ({ activationConstraint: { distance: 8 }, - }) + }), + [] ); + const pointerSensor = useSensor(PointerSensor, sensorConfig); + const sensors = useSensors(pointerSensor); + useEffect(() => { setGroups(taskGroups); }, [taskGroups]); + // Memoize resetTaskRowStyles to prevent unnecessary re-renders const resetTaskRowStyles = useCallback(() => { document.querySelectorAll('.task-row').forEach(row => { row.style.transition = 'transform 0.2s ease, opacity 0.2s ease'; @@ -106,21 +112,18 @@ const TaskGroupWrapper = ({ taskGroups, groupBy }: TaskGroupWrapperProps) => { }); }, []); - // Socket handler for assignee updates - useEffect(() => { - if (!socket) return; - - const handleAssigneesUpdate = (data: ITaskAssigneesUpdateResponse) => { + // Memoize socket event handlers + const handleAssigneesUpdate = useCallback( + (data: ITaskAssigneesUpdateResponse) => { if (!data) return; - const updatedAssignees = data.assignees.map(assignee => ({ + const updatedAssignees = data.assignees?.map(assignee => ({ ...assignee, selected: true, - })); + })) || []; - // Find the group that contains the task or its subtasks - const groupId = groups.find(group => - group.tasks.some( + const groupId = groups?.find(group => + group.tasks?.some( task => task.id === data.id || (task.sub_tasks && task.sub_tasks.some(subtask => subtask.id === data.id)) @@ -136,47 +139,41 @@ const TaskGroupWrapper = ({ taskGroups, groupBy }: TaskGroupWrapperProps) => { }) ); - dispatch(setTaskAssignee(data)); + dispatch( + setTaskAssignee({ + ...data, + manual_progress: false, + } as IProjectTask) + ); if (currentSession?.team_id && !loadingAssignees) { dispatch(fetchTaskAssignees(currentSession.team_id)); } } - }; + }, + [groups, dispatch, currentSession?.team_id, loadingAssignees] + ); - socket.on(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), handleAssigneesUpdate); - return () => { - socket.off(SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), handleAssigneesUpdate); - }; - }, [socket, currentSession?.team_id, loadingAssignees, groups, dispatch]); - - // Socket handler for label updates - useEffect(() => { - if (!socket) return; - - const handleLabelsChange = async (labels: ILabelsChangeResponse) => { + // Memoize socket event handlers + const handleLabelsChange = useCallback( + async (labels: ILabelsChangeResponse) => { + if (!labels) return; + await Promise.all([ dispatch(updateTaskLabel(labels)), dispatch(setTaskLabels(labels)), dispatch(fetchLabels()), projectId && dispatch(fetchLabelsByProject(projectId)), ]); - }; + }, + [dispatch, projectId] + ); - socket.on(SocketEvents.TASK_LABELS_CHANGE.toString(), handleLabelsChange); - socket.on(SocketEvents.CREATE_LABEL.toString(), handleLabelsChange); + // Memoize socket event handlers + const handleTaskStatusChange = useCallback( + (response: ITaskListStatusChangeResponse) => { + if (!response) return; - return () => { - socket.off(SocketEvents.TASK_LABELS_CHANGE.toString(), handleLabelsChange); - socket.off(SocketEvents.CREATE_LABEL.toString(), handleLabelsChange); - }; - }, [socket, dispatch, projectId]); - - // Socket handler for status updates - useEffect(() => { - if (!socket) return; - - const handleTaskStatusChange = (response: ITaskListStatusChangeResponse) => { if (response.completed_deps === false) { alertService.error( 'Task is not completed', @@ -186,11 +183,14 @@ const TaskGroupWrapper = ({ taskGroups, groupBy }: TaskGroupWrapperProps) => { } dispatch(updateTaskStatus(response)); - // dispatch(setTaskStatus(response)); dispatch(deselectAll()); - }; + }, + [dispatch] + ); - const handleTaskProgress = (data: { + // Memoize socket event handlers + const handleTaskProgress = useCallback( + (data: { id: string; status: string; complete_ratio: number; @@ -198,6 +198,8 @@ const TaskGroupWrapper = ({ taskGroups, groupBy }: TaskGroupWrapperProps) => { total_tasks_count: number; parent_task: string; }) => { + if (!data) return; + dispatch( updateTaskProgress({ taskId: data.parent_task || data.id, @@ -206,190 +208,150 @@ const TaskGroupWrapper = ({ taskGroups, groupBy }: TaskGroupWrapperProps) => { completedCount: data.completed_count, }) ); - }; + }, + [dispatch] + ); - socket.on(SocketEvents.TASK_STATUS_CHANGE.toString(), handleTaskStatusChange); - socket.on(SocketEvents.GET_TASK_PROGRESS.toString(), handleTaskProgress); + // Memoize socket event handlers + const handlePriorityChange = useCallback( + (response: ITaskListPriorityChangeResponse) => { + if (!response) return; - return () => { - socket.off(SocketEvents.TASK_STATUS_CHANGE.toString(), handleTaskStatusChange); - socket.off(SocketEvents.GET_TASK_PROGRESS.toString(), handleTaskProgress); - }; - }, [socket, dispatch]); - - // Socket handler for priority updates - useEffect(() => { - if (!socket) return; - - const handlePriorityChange = (response: ITaskListPriorityChangeResponse) => { dispatch(updateTaskPriority(response)); dispatch(setTaskPriority(response)); dispatch(deselectAll()); - }; + }, + [dispatch] + ); - socket.on(SocketEvents.TASK_PRIORITY_CHANGE.toString(), handlePriorityChange); - - return () => { - socket.off(SocketEvents.TASK_PRIORITY_CHANGE.toString(), handlePriorityChange); - }; - }, [socket, dispatch]); - - // Socket handler for due date updates - useEffect(() => { - if (!socket) return; - - const handleEndDateChange = (task: { + // Memoize socket event handlers + const handleEndDateChange = useCallback( + (task: { id: string; parent_task: string | null; end_date: string; }) => { - dispatch(updateTaskEndDate({ task })); - dispatch(setTaskEndDate(task)); - }; + if (!task) return; - socket.on(SocketEvents.TASK_END_DATE_CHANGE.toString(), handleEndDateChange); + const taskWithProgress = { + ...task, + manual_progress: false, + } as IProjectTask; - return () => { - socket.off(SocketEvents.TASK_END_DATE_CHANGE.toString(), handleEndDateChange); - }; - }, [socket, dispatch]); + dispatch(updateTaskEndDate({ task: taskWithProgress })); + dispatch(setTaskEndDate(taskWithProgress)); + }, + [dispatch] + ); - // Socket handler for task name updates - useEffect(() => { - if (!socket) return; + // Memoize socket event handlers + const handleTaskNameChange = useCallback( + (data: { id: string; parent_task: string; name: string }) => { + if (!data) return; - const handleTaskNameChange = (data: { id: string; parent_task: string; name: string }) => { dispatch(updateTaskName(data)); - }; + }, + [dispatch] + ); - socket.on(SocketEvents.TASK_NAME_CHANGE.toString(), handleTaskNameChange); + // Memoize socket event handlers + const handlePhaseChange = useCallback( + (data: ITaskPhaseChangeResponse) => { + if (!data) return; - return () => { - socket.off(SocketEvents.TASK_NAME_CHANGE.toString(), handleTaskNameChange); - }; - }, [socket, dispatch]); - - // Socket handler for phase updates - useEffect(() => { - if (!socket) return; - - const handlePhaseChange = (data: ITaskPhaseChangeResponse) => { dispatch(updateTaskPhase(data)); dispatch(deselectAll()); - }; + }, + [dispatch] + ); - socket.on(SocketEvents.TASK_PHASE_CHANGE.toString(), handlePhaseChange); - - return () => { - socket.off(SocketEvents.TASK_PHASE_CHANGE.toString(), handlePhaseChange); - }; - }, [socket, dispatch]); - - // Socket handler for start date updates - useEffect(() => { - if (!socket) return; - - const handleStartDateChange = (task: { + // Memoize socket event handlers + const handleStartDateChange = useCallback( + (task: { id: string; parent_task: string | null; start_date: string; }) => { - dispatch(updateTaskStartDate({ task })); - dispatch(setStartDate(task)); - }; + if (!task) return; - socket.on(SocketEvents.TASK_START_DATE_CHANGE.toString(), handleStartDateChange); + const taskWithProgress = { + ...task, + manual_progress: false, + } as IProjectTask; - return () => { - socket.off(SocketEvents.TASK_START_DATE_CHANGE.toString(), handleStartDateChange); - }; - }, [socket, dispatch]); + dispatch(updateTaskStartDate({ task: taskWithProgress })); + dispatch(setStartDate(taskWithProgress)); + }, + [dispatch] + ); - // Socket handler for task subscribers updates - useEffect(() => { - if (!socket) return; + // Memoize socket event handlers + const handleTaskSubscribersChange = useCallback( + (data: InlineMember[]) => { + if (!data) return; - const handleTaskSubscribersChange = (data: InlineMember[]) => { dispatch(setTaskSubscribers(data)); - }; + }, + [dispatch] + ); - socket.on(SocketEvents.TASK_SUBSCRIBERS_CHANGE.toString(), handleTaskSubscribersChange); - - return () => { - socket.off(SocketEvents.TASK_SUBSCRIBERS_CHANGE.toString(), handleTaskSubscribersChange); - }; - }, [socket, dispatch]); - - // Socket handler for task estimation updates - useEffect(() => { - if (!socket) return; - - const handleEstimationChange = (task: { + // Memoize socket event handlers + const handleEstimationChange = useCallback( + (task: { id: string; parent_task: string | null; estimation: number; }) => { - dispatch(updateTaskEstimation({ task })); - }; + if (!task) return; - socket.on(SocketEvents.TASK_TIME_ESTIMATION_CHANGE.toString(), handleEstimationChange); + const taskWithProgress = { + ...task, + manual_progress: false, + } as IProjectTask; - return () => { - socket.off(SocketEvents.TASK_TIME_ESTIMATION_CHANGE.toString(), handleEstimationChange); - }; - }, [socket, dispatch]); + dispatch(updateTaskEstimation({ task: taskWithProgress })); + }, + [dispatch] + ); - // Socket handler for task description updates - useEffect(() => { - if (!socket) return; - - const handleTaskDescriptionChange = (data: { + // Memoize socket event handlers + const handleTaskDescriptionChange = useCallback( + (data: { id: string; parent_task: string; description: string; }) => { + if (!data) return; + dispatch(updateTaskDescription(data)); - }; + }, + [dispatch] + ); - socket.on(SocketEvents.TASK_DESCRIPTION_CHANGE.toString(), handleTaskDescriptionChange); - - return () => { - socket.off(SocketEvents.TASK_DESCRIPTION_CHANGE.toString(), handleTaskDescriptionChange); - }; - }, [socket, dispatch]); - - // Socket handler for new task creation - useEffect(() => { - if (!socket) return; - - const handleNewTaskReceived = (data: IProjectTask) => { + // Memoize socket event handlers + const handleNewTaskReceived = useCallback( + (data: IProjectTask) => { if (!data) return; if (data.parent_task_id) { dispatch(updateSubTasks(data)); } - }; + }, + [dispatch] + ); - socket.on(SocketEvents.QUICK_TASK.toString(), handleNewTaskReceived); - - return () => { - socket.off(SocketEvents.QUICK_TASK.toString(), handleNewTaskReceived); - }; - }, [socket, dispatch]); - - // Socket handler for task progress updates - useEffect(() => { - if (!socket) return; - - const handleTaskProgressUpdated = (data: { + // Memoize socket event handlers + const handleTaskProgressUpdated = useCallback( + (data: { task_id: string; progress_value?: number; weight?: number; }) => { + if (!data || !taskGroups) return; + if (data.progress_value !== undefined) { - // Find the task in the task groups and update its progress for (const group of taskGroups) { - const task = group.tasks.find(task => task.id === data.task_id); + const task = group.tasks?.find(task => task.id === data.task_id); if (task) { dispatch( updateTaskProgress({ @@ -403,25 +365,76 @@ const TaskGroupWrapper = ({ taskGroups, groupBy }: TaskGroupWrapperProps) => { } } } + }, + [dispatch, taskGroups] + ); + + // Set up socket event listeners + useEffect(() => { + if (!socket) return; + + const eventHandlers = { + [SocketEvents.QUICK_ASSIGNEES_UPDATE.toString()]: handleAssigneesUpdate, + [SocketEvents.TASK_LABELS_CHANGE.toString()]: handleLabelsChange, + [SocketEvents.CREATE_LABEL.toString()]: handleLabelsChange, + [SocketEvents.TASK_STATUS_CHANGE.toString()]: handleTaskStatusChange, + [SocketEvents.GET_TASK_PROGRESS.toString()]: handleTaskProgress, + [SocketEvents.TASK_PRIORITY_CHANGE.toString()]: handlePriorityChange, + [SocketEvents.TASK_END_DATE_CHANGE.toString()]: handleEndDateChange, + [SocketEvents.TASK_NAME_CHANGE.toString()]: handleTaskNameChange, + [SocketEvents.TASK_PHASE_CHANGE.toString()]: handlePhaseChange, + [SocketEvents.TASK_START_DATE_CHANGE.toString()]: handleStartDateChange, + [SocketEvents.TASK_SUBSCRIBERS_CHANGE.toString()]: handleTaskSubscribersChange, + [SocketEvents.TASK_TIME_ESTIMATION_CHANGE.toString()]: handleEstimationChange, + [SocketEvents.TASK_DESCRIPTION_CHANGE.toString()]: handleTaskDescriptionChange, + [SocketEvents.QUICK_TASK.toString()]: handleNewTaskReceived, + [SocketEvents.TASK_PROGRESS_UPDATED.toString()]: handleTaskProgressUpdated, }; - socket.on(SocketEvents.TASK_PROGRESS_UPDATED.toString(), handleTaskProgressUpdated); + // Register all event handlers + Object.entries(eventHandlers).forEach(([event, handler]) => { + if (handler) { + socket.on(event, handler); + } + }); + // Cleanup function return () => { - socket.off(SocketEvents.TASK_PROGRESS_UPDATED.toString(), handleTaskProgressUpdated); + Object.entries(eventHandlers).forEach(([event, handler]) => { + if (handler) { + socket.off(event, handler); + } + }); }; - }, [socket, dispatch, taskGroups]); + }, [ + socket, + handleAssigneesUpdate, + handleLabelsChange, + handleTaskStatusChange, + handleTaskProgress, + handlePriorityChange, + handleEndDateChange, + handleTaskNameChange, + handlePhaseChange, + handleStartDateChange, + handleTaskSubscribersChange, + handleEstimationChange, + handleTaskDescriptionChange, + handleNewTaskReceived, + handleTaskProgressUpdated, + ]); + // Memoize drag handlers const handleDragStart = useCallback(({ active }: DragStartEvent) => { setActiveId(active.id as string); - // Add smooth transition to the dragged item const draggedElement = document.querySelector(`[data-id="${active.id}"]`); if (draggedElement) { (draggedElement as HTMLElement).style.transition = 'transform 0.2s ease'; } }, []); + // Memoize drag handlers const handleDragEnd = useCallback( async ({ active, over }: DragEndEvent) => { setActiveId(null); @@ -440,10 +453,8 @@ const TaskGroupWrapper = ({ taskGroups, groupBy }: TaskGroupWrapperProps) => { const fromIndex = sourceGroup.tasks.findIndex(t => t.id === activeTaskId); if (fromIndex === -1) return; - // Create a deep clone of the task to avoid reference issues const task = JSON.parse(JSON.stringify(sourceGroup.tasks[fromIndex])); - // Check if task dependencies allow the move if (activeGroupId !== overGroupId) { const canContinue = await checkTaskDependencyStatus(task.id, overGroupId); if (!canContinue) { @@ -455,7 +466,6 @@ const TaskGroupWrapper = ({ taskGroups, groupBy }: TaskGroupWrapperProps) => { return; } - // Update task properties based on target group switch (groupBy) { case IGroupBy.STATUS: task.status = overGroupId; @@ -468,35 +478,29 @@ const TaskGroupWrapper = ({ taskGroups, groupBy }: TaskGroupWrapperProps) => { task.priority_color_dark = targetGroup.color_code_dark; break; case IGroupBy.PHASE: - // Check if ALPHA_CHANNEL is already added const baseColor = targetGroup.color_code.endsWith(ALPHA_CHANNEL) - ? targetGroup.color_code.slice(0, -ALPHA_CHANNEL.length) // Remove ALPHA_CHANNEL - : targetGroup.color_code; // Use as is if not present + ? targetGroup.color_code.slice(0, -ALPHA_CHANNEL.length) + : targetGroup.color_code; task.phase_id = overGroupId; - task.phase_color = baseColor; // Set the cleaned color + task.phase_color = baseColor; break; } } const isTargetGroupEmpty = targetGroup.tasks.length === 0; - - // Calculate toIndex - for empty groups, always add at index 0 const toIndex = isTargetGroupEmpty ? 0 : overTaskId ? targetGroup.tasks.findIndex(t => t.id === overTaskId) : targetGroup.tasks.length; - // Calculate toPos similar to Angular implementation const toPos = isTargetGroupEmpty ? -1 : targetGroup.tasks[toIndex]?.sort_order || targetGroup.tasks[targetGroup.tasks.length - 1]?.sort_order || -1; - // Update Redux state if (activeGroupId === overGroupId) { - // Same group - move within array const updatedTasks = [...sourceGroup.tasks]; updatedTasks.splice(fromIndex, 1); updatedTasks.splice(toIndex, 0, task); @@ -514,7 +518,6 @@ const TaskGroupWrapper = ({ taskGroups, groupBy }: TaskGroupWrapperProps) => { }, }); } else { - // Different groups - transfer between arrays const updatedSourceTasks = sourceGroup.tasks.filter((_, i) => i !== fromIndex); const updatedTargetTasks = [...targetGroup.tasks]; @@ -540,7 +543,6 @@ const TaskGroupWrapper = ({ taskGroups, groupBy }: TaskGroupWrapperProps) => { }); } - // Emit socket event socket?.emit(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), { project_id: projectId, from_index: sourceGroup.tasks[fromIndex].sort_order, @@ -549,13 +551,11 @@ const TaskGroupWrapper = ({ taskGroups, groupBy }: TaskGroupWrapperProps) => { from_group: sourceGroup.id, to_group: targetGroup.id, group_by: groupBy, - task: sourceGroup.tasks[fromIndex], // Send original task to maintain references + task: sourceGroup.tasks[fromIndex], team_id: currentSession?.team_id, }); - // Reset styles setTimeout(resetTaskRowStyles, 0); - trackMixpanelEvent(evt_project_task_list_drag_and_move); }, [ @@ -570,6 +570,7 @@ const TaskGroupWrapper = ({ taskGroups, groupBy }: TaskGroupWrapperProps) => { ] ); + // Memoize drag handlers const handleDragOver = useCallback( ({ active, over }: DragEndEvent) => { if (!over) return; @@ -589,12 +590,9 @@ const TaskGroupWrapper = ({ taskGroups, groupBy }: TaskGroupWrapperProps) => { if (fromIndex === -1 || toIndex === -1) return; - // Create a deep clone of the task to avoid reference issues const task = JSON.parse(JSON.stringify(sourceGroup.tasks[fromIndex])); - // Update Redux state if (activeGroupId === overGroupId) { - // Same group - move within array const updatedTasks = [...sourceGroup.tasks]; updatedTasks.splice(fromIndex, 1); updatedTasks.splice(toIndex, 0, task); @@ -612,10 +610,8 @@ const TaskGroupWrapper = ({ taskGroups, groupBy }: TaskGroupWrapperProps) => { }, }); } else { - // Different groups - transfer between arrays const updatedSourceTasks = sourceGroup.tasks.filter((_, i) => i !== fromIndex); const updatedTargetTasks = [...targetGroup.tasks]; - updatedTargetTasks.splice(toIndex, 0, task); dispatch({ @@ -663,7 +659,6 @@ const TaskGroupWrapper = ({ taskGroups, groupBy }: TaskGroupWrapperProps) => { // Handle animation cleanup after drag ends useIsomorphicLayoutEffect(() => { if (activeId === null) { - // Final cleanup after React updates DOM const timeoutId = setTimeout(resetTaskRowStyles, 50); return () => clearTimeout(timeoutId); } From 312c6b5be82dcca07eb0bbe33aa0373a7a57ddcf Mon Sep 17 00:00:00 2001 From: chamiakJ Date: Tue, 27 May 2025 17:13:04 +0530 Subject: [PATCH 2/5] feat(settings): add project templates settings to the configuration - Restored the project templates settings in the settings constants file, making it accessible for admin users. --- .../src/lib/settings/settings-constants.ts | 16 ++++++++-------- .../project-templates-settings.tsx | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/worklenz-frontend/src/lib/settings/settings-constants.ts b/worklenz-frontend/src/lib/settings/settings-constants.ts index 9855b008..7f9beb2a 100644 --- a/worklenz-frontend/src/lib/settings/settings-constants.ts +++ b/worklenz-frontend/src/lib/settings/settings-constants.ts @@ -108,14 +108,14 @@ export const settingsItems: SettingMenuItems[] = [ element: React.createElement(CategoriesSettings), adminOnly: true, }, - // { - // key: 'project-templates', - // name: 'project-templates', - // endpoint: 'project-templates', - // icon: React.createElement(FileZipOutlined), - // element: React.createElement(ProjectTemplatesSettings), - // adminOnly: true, - // }, + { + key: 'project-templates', + name: 'project-templates', + endpoint: 'project-templates', + icon: React.createElement(FileZipOutlined), + element: React.createElement(ProjectTemplatesSettings), + adminOnly: true, + }, { key: 'task-templates', name: 'task-templates', diff --git a/worklenz-frontend/src/pages/settings/project-templates/project-templates-settings.tsx b/worklenz-frontend/src/pages/settings/project-templates/project-templates-settings.tsx index 714947fc..7da55d59 100644 --- a/worklenz-frontend/src/pages/settings/project-templates/project-templates-settings.tsx +++ b/worklenz-frontend/src/pages/settings/project-templates/project-templates-settings.tsx @@ -51,7 +51,7 @@ const ProjectTemplatesSettings = () => { style={{ display: 'flex', gap: '10px', justifyContent: 'right' }} className="button-visibilty" > - + {/* - + */} Date: Fri, 30 May 2025 10:56:19 +0530 Subject: [PATCH 3/5] refactor(build): remove Gruntfile and transition to npm scripts for build process - Deleted Gruntfile.js to streamline the build process. - Updated package.json to include new npm scripts for build, clean, and watch tasks. - Added dependencies for concurrent execution and CSRF token management. - Integrated csrf-sync for improved CSRF protection in the application. - Refactored app and API client to utilize the new CSRF token management approach. --- worklenz-backend/Gruntfile.js | 131 - worklenz-backend/package-lock.json | 2443 ++++++----------- worklenz-backend/package.json | 43 +- worklenz-backend/scripts/compress.js | 53 + worklenz-backend/src/app.ts | 53 +- worklenz-frontend/src/App.tsx | 8 + worklenz-frontend/src/api/api-client.ts | 45 +- .../api/home-page/home-page.api.service.ts | 15 +- .../api/projects/projects.v1.api.service.ts | 15 +- 9 files changed, 982 insertions(+), 1824 deletions(-) delete mode 100644 worklenz-backend/Gruntfile.js create mode 100644 worklenz-backend/scripts/compress.js diff --git a/worklenz-backend/Gruntfile.js b/worklenz-backend/Gruntfile.js deleted file mode 100644 index b621cbc0..00000000 --- a/worklenz-backend/Gruntfile.js +++ /dev/null @@ -1,131 +0,0 @@ -module.exports = function (grunt) { - - // Project configuration. - grunt.initConfig({ - pkg: grunt.file.readJSON("package.json"), - clean: { - dist: "build" - }, - compress: require("./grunt/grunt-compress"), - copy: { - main: { - files: [ - {expand: true, cwd: "src", src: ["public/**"], dest: "build"}, - {expand: true, cwd: "src", src: ["views/**"], dest: "build"}, - {expand: true, cwd: "landing-page-assets", src: ["**"], dest: "build/public/assets"}, - {expand: true, cwd: "src", src: ["shared/sample-data.json"], dest: "build", filter: "isFile"}, - {expand: true, cwd: "src", src: ["shared/templates/**"], dest: "build", filter: "isFile"}, - {expand: true, cwd: "src", src: ["shared/postgresql-error-codes.json"], dest: "build", filter: "isFile"}, - ] - }, - packages: { - files: [ - {expand: true, cwd: "", src: [".env"], dest: "build", filter: "isFile"}, - {expand: true, cwd: "", src: [".gitignore"], dest: "build", filter: "isFile"}, - {expand: true, cwd: "", src: ["release"], dest: "build", filter: "isFile"}, - {expand: true, cwd: "", src: ["jest.config.js"], dest: "build", filter: "isFile"}, - {expand: true, cwd: "", src: ["package.json"], dest: "build", filter: "isFile"}, - {expand: true, cwd: "", src: ["package-lock.json"], dest: "build", filter: "isFile"}, - {expand: true, cwd: "", src: ["common_modules/**"], dest: "build"} - ] - } - }, - sync: { - main: { - files: [ - {cwd: "src", src: ["views/**", "public/**"], dest: "build/"}, // makes all src relative to cwd - ], - verbose: true, - failOnError: true, - compareUsing: "md5" - } - }, - uglify: { - all: { - files: [{ - expand: true, - cwd: "build", - src: "**/*.js", - dest: "build" - }] - }, - controllers: { - files: [{ - expand: true, - cwd: "build", - src: "controllers/*.js", - dest: "build" - }] - }, - routes: { - files: [{ - expand: true, - cwd: "build", - src: "routes/**/*.js", - dest: "build" - }] - }, - assets: { - files: [{ - expand: true, - cwd: "build", - src: "public/assets/**/*.js", - dest: "build" - }] - } - }, - shell: { - tsc: { - command: "tsc --build tsconfig.prod.json" - }, - esbuild: { - // command: "esbuild `find src -type f -name '*.ts'` --platform=node --minify=false --target=esnext --format=cjs --tsconfig=tsconfig.prod.json --outdir=build" - command: "node esbuild && node cli/esbuild-patch" - }, - tsc_dev: { - command: "tsc --build tsconfig.json" - }, - swagger: { - command: "node ./cli/swagger" - }, - inline_queries: { - command: "node ./cli/inline-queries" - } - }, - watch: { - scripts: { - files: ["src/**/*.ts"], - tasks: ["shell:tsc_dev"], - options: { - debounceDelay: 250, - spawn: false, - } - }, - other: { - files: ["src/**/*.pug", "landing-page-assets/**"], - tasks: ["sync"] - } - } - }); - - grunt.registerTask("clean", ["clean"]); - grunt.registerTask("copy", ["copy:main"]); - grunt.registerTask("swagger", ["shell:swagger"]); - grunt.registerTask("build:tsc", ["shell:tsc"]); - grunt.registerTask("build", ["clean", "shell:tsc", "copy:main", "compress"]); - grunt.registerTask("build:es", ["clean", "shell:esbuild", "copy:main", "uglify:assets", "compress"]); - grunt.registerTask("build:strict", ["clean", "shell:tsc", "copy:packages", "uglify:all", "copy:main", "compress"]); - grunt.registerTask("dev", ["clean", "copy:main", "shell:tsc_dev", "shell:inline_queries", "watch"]); - - // Load the plugin that provides the "uglify" task. - grunt.loadNpmTasks("grunt-contrib-watch"); - grunt.loadNpmTasks("grunt-contrib-clean"); - grunt.loadNpmTasks("grunt-contrib-copy"); - grunt.loadNpmTasks("grunt-contrib-uglify"); - grunt.loadNpmTasks("grunt-contrib-compress"); - grunt.loadNpmTasks("grunt-shell"); - grunt.loadNpmTasks("grunt-sync"); - - // Default task(s). - grunt.registerTask("default", []); -}; diff --git a/worklenz-backend/package-lock.json b/worklenz-backend/package-lock.json index 2953defa..138d01ff 100644 --- a/worklenz-backend/package-lock.json +++ b/worklenz-backend/package-lock.json @@ -24,6 +24,7 @@ "cors": "^2.8.5", "cron": "^2.4.0", "crypto-js": "^4.1.1", + "csrf-sync": "^4.2.1", "csurf": "^1.11.0", "debug": "^4.3.4", "dotenv": "^16.3.1", @@ -99,26 +100,22 @@ "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", "chokidar": "^3.5.3", + "concurrently": "^9.1.2", + "cpx2": "^8.0.0", "esbuild": "^0.17.19", "esbuild-envfile-plugin": "^1.0.5", "esbuild-node-externals": "^1.8.0", "eslint": "^8.45.0", "eslint-plugin-security": "^1.7.1", "fs-extra": "^10.1.0", - "grunt": "^1.6.1", - "grunt-contrib-clean": "^2.0.1", - "grunt-contrib-compress": "^2.0.0", - "grunt-contrib-copy": "^1.0.0", - "grunt-contrib-uglify": "^5.2.2", - "grunt-contrib-watch": "^1.1.0", - "grunt-shell": "^4.0.0", - "grunt-sync": "^0.8.2", "highcharts": "^11.1.0", "jest": "^28.1.3", "jest-sonar-reporter": "^2.0.0", "ncp": "^2.0.0", "nodeman": "^1.1.2", + "rimraf": "^6.0.1", "swagger-jsdoc": "^6.2.8", + "terser": "^5.40.0", "ts-jest": "^28.0.8", "ts-node": "^10.9.1", "tslint": "^6.1.3", @@ -3527,6 +3524,109 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -3834,6 +3934,23 @@ "node": ">=8" } }, + "node_modules/@jest/core/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@jest/core/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -4271,14 +4388,15 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -4294,14 +4412,26 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", @@ -4309,21 +4439,16 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", @@ -4360,6 +4485,22 @@ "node": ">=10" } }, + "node_modules/@mapbox/node-pre-gyp/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -5940,10 +6081,11 @@ } }, "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -5969,15 +6111,6 @@ "node": ">=0.4.0" } }, - "node_modules/adm-zip": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", - "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", - "dev": true, - "engines": { - "node": ">=6.0" - } - }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -6141,29 +6274,11 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "node_modules/array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, - "node_modules/array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -6539,18 +6654,6 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, - "node_modules/body": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", - "integrity": "sha512-chUsBxGRtuElD6fmw1gHLpvnKdVLK302peeFa9ZqAEk8TyzZ3fygLyUEDDPTJvL9+Bor0dIwn6ePOsRM2y0zQQ==", - "dev": true, - "dependencies": { - "continuable-cache": "^0.3.1", - "error": "^7.0.0", - "raw-body": "~1.1.0", - "safe-json-parse": "~1.0.1" - } - }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -6599,31 +6702,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/body/node_modules/bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", - "integrity": "sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ==", - "dev": true - }, - "node_modules/body/node_modules/raw-body": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", - "integrity": "sha512-WmJJU2e9Y6M5UzTOkHaM7xJGAPQD8PNzx3bAd2+uhZAim6wDk6dAZxPVYLF67XhbR4hmKGh33Lpmh4XWrCH5Mg==", - "dev": true, - "dependencies": { - "bytes": "1", - "string_decoder": "0.10" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/body/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", - "dev": true - }, "node_modules/bowser": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", @@ -7174,6 +7252,124 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/concurrently": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.1.2.tgz", + "integrity": "sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/concurrently/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/concurrently/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concurrently/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/connect-flash": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz", @@ -7247,12 +7443,6 @@ "node": ">= 0.6" } }, - "node_modules/continuable-cache": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", - "integrity": "sha512-TF30kpKhTH8AGCG3dut0rdd/19B7Z+qCnrMoBLpyQu/2drZdNrrpcjPEoJeSVsQM+8KmWG5O56oPDjSSUsuTyA==", - "dev": true - }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -7316,6 +7506,141 @@ "node": ">= 0.10" } }, + "node_modules/cpx2": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/cpx2/-/cpx2-8.0.0.tgz", + "integrity": "sha512-RxD9jrSVNSOmfcbiPlr3XnKbUKH9K1w2HCv0skczUKhsZTueiDBecxuaSAKQkYSLQaGVA4ZQJZlTj5hVNNEvKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debounce": "^2.0.0", + "debug": "^4.1.1", + "duplexer": "^0.1.1", + "fs-extra": "^11.1.0", + "glob": "^11.0.0", + "glob2base": "0.0.12", + "ignore": "^6.0.2", + "minimatch": "^10.0.1", + "p-map": "^7.0.0", + "resolve": "^1.12.0", + "safe-buffer": "^5.2.0", + "shell-quote": "^1.8.0", + "subarg": "^1.0.0" + }, + "bin": { + "cpx": "bin/index.js" + }, + "engines": { + "node": "^20.0.0 || >=22.0.0", + "npm": ">=10" + } + }, + "node_modules/cpx2/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cpx2/node_modules/fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/cpx2/node_modules/glob": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.2.tgz", + "integrity": "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cpx2/node_modules/ignore": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", + "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/cpx2/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cpx2/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/cpx2/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -7387,6 +7712,15 @@ "node": ">= 0.8" } }, + "node_modules/csrf-sync": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/csrf-sync/-/csrf-sync-4.2.1.tgz", + "integrity": "sha512-+q9tlUSCi/kbwr1NYwn5+MeuNhwxz3wSv1yl42BgIWfIuErZ3HajRwzvZTkfiyIqt1PZT8lQSlffhSYjCneN7g==", + "license": "ISC", + "dependencies": { + "http-errors": "^2.0.0" + } + }, "node_modules/csurf": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/csurf/-/csurf-1.11.0.tgz", @@ -7454,20 +7788,24 @@ "node": ">=0.6" } }, - "node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/dayjs": { "version": "1.11.9", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" }, + "node_modules/debounce": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-2.2.0.tgz", + "integrity": "sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -7557,15 +7895,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/detect-libc": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", @@ -7743,6 +8072,13 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -7842,15 +8178,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/error": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", - "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", - "dev": true, - "dependencies": { - "string-template": "~0.2.1" - } - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -8318,12 +8645,6 @@ "node": ">= 0.6" } }, - "node_modules/eventemitter2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", - "integrity": "sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==", - "dev": true - }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -8404,18 +8725,6 @@ "node": ">=6" } }, - "node_modules/expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/expect": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", @@ -8599,12 +8908,6 @@ } ] }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, "node_modules/fast-csv": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz", @@ -8686,18 +8989,6 @@ "reusify": "^1.0.4" } }, - "node_modules/faye-websocket": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", - "integrity": "sha512-Xhj93RXbMSq8urNCUq4p9l0P6hnySJ/7YNRhYNug0bLOuii7pKO7xQFb5mx9xZXWCar88pLPb805PvUkwrLZpQ==", - "dev": true, - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -8712,21 +9003,6 @@ "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -8739,12 +9015,6 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/file-sync-cmp": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz", - "integrity": "sha512-0k45oWBokCqh2MOexeYKpyqmGKG+8mQ2Wd8iawx+uWd/weWJQAZ6SoPybagdCI4xFisag8iAR77WPm4h3pTfxA==", - "dev": true - }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -8796,6 +9066,13 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/find-index": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz", + "integrity": "sha512-uJ5vWrfBKMcE6y2Z8834dwEZj9mNGxYa3t3I53OwFeuZ8D9oc2E5zcsrkuhX6h4iYrjhiv0T3szQmxlAV9uxDg==", + "dev": true, + "license": "MIT" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -8812,46 +9089,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/findup-sync": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", - "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==", - "dev": true, - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.3", - "micromatch": "^4.0.4", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/fined": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", - "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", - "dev": true, - "dependencies": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/flagged-respawn": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -8865,6 +9102,23 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/flat-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/flatted": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", @@ -8896,25 +9150,34 @@ } } }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, + "license": "ISC", "dependencies": { - "for-in": "^1.0.1" + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/form-data": { @@ -9076,18 +9339,6 @@ "node": ">=10" } }, - "node_modules/gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", - "dev": true, - "dependencies": { - "globule": "^1.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/generic-pool": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", @@ -9159,15 +9410,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/getobject": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/getobject/-/getobject-1.0.2.tgz", - "integrity": "sha512-2zblDBaFcb3rB4rF77XVnuINOE2h2k/OnqXAiy0IrTxUfV1iFp3la33oAQVY9pCpWU268WFYVt2t71hlMuLsOg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -9204,46 +9446,16 @@ "node": ">= 6" } }, - "node_modules/global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "node_modules/glob2base": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz", + "integrity": "sha512-ZyqlgowMbfj2NPjxaZZ/EtsXlOch28FRXgMd64vqZWk1bT9+wvSRLYD1om9M7QfQru51zJPAT17qXm4/zd+9QA==", "dev": true, "dependencies": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" + "find-index": "^0.1.1" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", - "dev": true, - "dependencies": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/global-prefix/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" + "node": ">= 0.10" } }, "node_modules/globals": { @@ -9275,52 +9487,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globule": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", - "integrity": "sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==", - "dev": true, - "dependencies": { - "glob": "~7.1.1", - "lodash": "^4.17.21", - "minimatch": "~3.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/globule/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globule/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -9344,709 +9510,6 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, - "node_modules/grunt": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.6.1.tgz", - "integrity": "sha512-/ABUy3gYWu5iBmrUSRBP97JLpQUm0GgVveDCp6t3yRNIoltIYw7rEj3g5y1o2PGPR2vfTRGa7WC/LZHLTXnEzA==", - "dev": true, - "dependencies": { - "dateformat": "~4.6.2", - "eventemitter2": "~0.4.13", - "exit": "~0.1.2", - "findup-sync": "~5.0.0", - "glob": "~7.1.6", - "grunt-cli": "~1.4.3", - "grunt-known-options": "~2.0.0", - "grunt-legacy-log": "~3.0.0", - "grunt-legacy-util": "~2.0.1", - "iconv-lite": "~0.6.3", - "js-yaml": "~3.14.0", - "minimatch": "~3.0.4", - "nopt": "~3.0.6" - }, - "bin": { - "grunt": "bin/grunt" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/grunt-cli": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.4.3.tgz", - "integrity": "sha512-9Dtx/AhVeB4LYzsViCjUQkd0Kw0McN2gYpdmGYKtE2a5Yt7v1Q+HYZVWhqXc/kGnxlMtqKDxSwotiGeFmkrCoQ==", - "dev": true, - "dependencies": { - "grunt-known-options": "~2.0.0", - "interpret": "~1.1.0", - "liftup": "~3.0.1", - "nopt": "~4.0.1", - "v8flags": "~3.2.0" - }, - "bin": { - "grunt": "bin/grunt" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/grunt-cli/node_modules/nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", - "dev": true, - "dependencies": { - "abbrev": "1", - "osenv": "^0.1.4" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/grunt-contrib-clean": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-2.0.1.tgz", - "integrity": "sha512-uRvnXfhiZt8akb/ZRDHJpQQtkkVkqc/opWO4Po/9ehC2hPxgptB9S6JHDC/Nxswo4CJSM0iFPT/Iym3cEMWzKA==", - "dev": true, - "dependencies": { - "async": "^3.2.3", - "rimraf": "^2.6.2" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "grunt": ">=0.4.5" - } - }, - "node_modules/grunt-contrib-clean/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/grunt-contrib-compress": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-compress/-/grunt-contrib-compress-2.0.0.tgz", - "integrity": "sha512-r/dAGx4qG+rmBFF4lb/hTktW2huGMGxkSLf9msh3PPtq0+cdQRQerZJ30UKevX3BLQsohwLzO0p1z/LrH6aKXQ==", - "dev": true, - "dependencies": { - "adm-zip": "^0.5.1", - "archiver": "^5.1.0", - "chalk": "^4.1.0", - "lodash": "^4.17.20", - "pretty-bytes": "^5.4.1", - "stream-buffers": "^3.0.2" - }, - "engines": { - "node": ">=10.16" - } - }, - "node_modules/grunt-contrib-compress/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/grunt-contrib-compress/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/grunt-contrib-compress/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/grunt-contrib-compress/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/grunt-contrib-compress/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt-contrib-compress/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt-contrib-copy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz", - "integrity": "sha512-gFRFUB0ZbLcjKb67Magz1yOHGBkyU6uL29hiEW1tdQ9gQt72NuMKIy/kS6dsCbV0cZ0maNCb0s6y+uT1FKU7jA==", - "dev": true, - "dependencies": { - "chalk": "^1.1.1", - "file-sync-cmp": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-copy/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-copy/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-copy/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-copy/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-copy/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/grunt-contrib-uglify": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/grunt-contrib-uglify/-/grunt-contrib-uglify-5.2.2.tgz", - "integrity": "sha512-ITxiWxrjjP+RZu/aJ5GLvdele+sxlznh+6fK9Qckio5ma8f7Iv8woZjRkGfafvpuygxNefOJNc+hfjjBayRn2Q==", - "dev": true, - "dependencies": { - "chalk": "^4.1.2", - "maxmin": "^3.0.0", - "uglify-js": "^3.16.1", - "uri-path": "^1.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/grunt-contrib-uglify/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/grunt-contrib-uglify/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/grunt-contrib-uglify/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/grunt-contrib-uglify/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/grunt-contrib-uglify/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt-contrib-uglify/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt-contrib-watch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz", - "integrity": "sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg==", - "dev": true, - "dependencies": { - "async": "^2.6.0", - "gaze": "^1.1.0", - "lodash": "^4.17.10", - "tiny-lr": "^1.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-watch/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dev": true, - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/grunt-known-options": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-2.0.0.tgz", - "integrity": "sha512-GD7cTz0I4SAede1/+pAbmJRG44zFLPipVtdL9o3vqx9IEyb7b4/Y3s7r6ofI3CchR5GvYJ+8buCSioDv5dQLiA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-legacy-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz", - "integrity": "sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA==", - "dev": true, - "dependencies": { - "colors": "~1.1.2", - "grunt-legacy-log-utils": "~2.1.0", - "hooker": "~0.2.3", - "lodash": "~4.17.19" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/grunt-legacy-log-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz", - "integrity": "sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw==", - "dev": true, - "dependencies": { - "chalk": "~4.1.0", - "lodash": "~4.17.19" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/grunt-legacy-log-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/grunt-legacy-log-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/grunt-legacy-log-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/grunt-legacy-log-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/grunt-legacy-log-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt-legacy-log-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt-legacy-util": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-2.0.1.tgz", - "integrity": "sha512-2bQiD4fzXqX8rhNdXkAywCadeqiPiay0oQny77wA2F3WF4grPJXCvAcyoWUJV+po/b15glGkxuSiQCK299UC2w==", - "dev": true, - "dependencies": { - "async": "~3.2.0", - "exit": "~0.1.2", - "getobject": "~1.0.0", - "hooker": "~0.2.3", - "lodash": "~4.17.21", - "underscore.string": "~3.3.5", - "which": "~2.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/grunt-shell": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/grunt-shell/-/grunt-shell-4.0.0.tgz", - "integrity": "sha512-dHFy8VZDfWGYLTeNvIHze4PKXGvIlDWuN0UE7hUZstTQeiEyv1VmW1MaDYQ3X5tE3bCi3bEia1gGKH8z/f1czQ==", - "dev": true, - "dependencies": { - "chalk": "^3.0.0", - "npm-run-path": "^2.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - }, - "peerDependencies": { - "grunt": ">=1" - } - }, - "node_modules/grunt-shell/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/grunt-shell/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt-shell/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/grunt-shell/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/grunt-shell/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt-shell/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt-sync": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/grunt-sync/-/grunt-sync-0.8.2.tgz", - "integrity": "sha512-PB+xKI9YPyZn3NZQXpKHfZVlxHdf1L8GMl+Wi0mLhYypWuOdZPW2EzTmSuhhFbXjkb0aIOxvII1zdZZEl9zqGg==", - "dev": true, - "dependencies": { - "fs-extra": "^6.0.1", - "glob": "^7.0.5", - "md5-file": "^2.0.3" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/grunt-sync/node_modules/fs-extra": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", - "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "node_modules/grunt-sync/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/grunt-sync/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/grunt/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/grunt/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/grunt/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/grunt/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/grunt/node_modules/nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", - "dev": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/grunt/node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/gzip-size": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", - "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", - "dev": true, - "dependencies": { - "duplexer": "^0.1.1", - "pify": "^4.0.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -10058,27 +9521,6 @@ "node": ">= 0.4.0" } }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -10146,27 +9588,6 @@ "dev": true, "license": "https://www.highcharts.com/license" }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "dependencies": { - "parse-passwd": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hooker": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", - "integrity": "sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/hpp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/hpp/-/hpp-0.2.3.tgz", @@ -10218,12 +9639,6 @@ "node": ">= 0.8" } }, - "node_modules/http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", - "dev": true - }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -10375,12 +9790,6 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, - "node_modules/interpret": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", - "integrity": "sha512-CLM8SNMDu7C5psFCn6Wg/tgpj/bKAg7hc2gWqcuR9OD5Ft9PhBpIu8PLicPeis+xDd6YX2ncI8MCA64I9tftIA==", - "dev": true - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -10389,19 +9798,6 @@ "node": ">= 0.10" } }, - "node_modules/is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "dev": true, - "dependencies": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -10508,18 +9904,6 @@ "node": ">=8" } }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-promise": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", @@ -10540,18 +9924,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "dev": true, - "dependencies": { - "is-unc-path": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -10563,27 +9935,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "dev": true, - "dependencies": { - "unc-path-regex": "^0.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -10595,15 +9946,6 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", @@ -10739,6 +10081,22 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/javascript-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz", @@ -12525,15 +11883,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -12622,40 +11971,6 @@ "immediate": "~3.0.5" } }, - "node_modules/liftup": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/liftup/-/liftup-3.0.1.tgz", - "integrity": "sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw==", - "dev": true, - "dependencies": { - "extend": "^3.0.2", - "findup-sync": "^4.0.0", - "fined": "^1.2.0", - "flagged-respawn": "^1.0.1", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.1", - "rechoir": "^0.7.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/liftup/node_modules/findup-sync": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz", - "integrity": "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==", - "dev": true, - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^4.0.2", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -12667,12 +11982,6 @@ "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==" }, - "node_modules/livereload-js": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz", - "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==", - "dev": true - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -12838,18 +12147,6 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, - "node_modules/make-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -12859,15 +12156,6 @@ "tmpl": "1.0.5" } }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -12877,100 +12165,6 @@ "node": ">= 0.4" } }, - "node_modules/maxmin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/maxmin/-/maxmin-3.0.0.tgz", - "integrity": "sha512-wcahMInmGtg/7c6a75fr21Ch/Ks1Tb+Jtoan5Ft4bAI0ZvJqyOw8kkM7e7p8hDSzY805vmxwHT50KcjGwKyJ0g==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "figures": "^3.2.0", - "gzip-size": "^5.1.1", - "pretty-bytes": "^5.3.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/maxmin/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/maxmin/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/maxmin/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/maxmin/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/maxmin/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/maxmin/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/md5-file": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-2.0.7.tgz", - "integrity": "sha512-kWAICpJv8fIY0Ka/90iOX9nCJ407Fgj82ceWwcxi2HvVkKGHRMS/Y4caqBaju5skNYXiQohGUjwGZ7rVgzUhRw==", - "dev": true - }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -13383,27 +12577,6 @@ "node": ">=0.10.0" } }, - "node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "dev": true, - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/npmlog": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", @@ -13440,46 +12613,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.defaults": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", - "dev": true, - "dependencies": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", - "integrity": "sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w==", - "dev": true, - "dependencies": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -13560,34 +12693,6 @@ "node": ">= 0.8.0" } }, - "node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "dev": true, - "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -13618,6 +12723,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-map": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -13627,6 +12745,13 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -13644,20 +12769,6 @@ "node": ">=6" } }, - "node_modules/parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", - "dev": true, - "dependencies": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -13676,15 +12787,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/parse-srcset": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", @@ -13813,25 +12915,41 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, - "node_modules/path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "path-root-regex": "^0.1.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=0.10.0" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", "dev": true, + "license": "ISC", "engines": { - "node": ">=0.10.0" + "node": "20 || >=22" + } + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" } }, "node_modules/path-to-regexp": { @@ -14081,15 +13199,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", @@ -14264,18 +13373,6 @@ "node": ">= 0.8.0" } }, - "node_modules/pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/pretty-format": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", @@ -14650,18 +13747,6 @@ "node": ">=8.10.0" } }, - "node_modules/rechoir": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", - "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", - "dev": true, - "dependencies": { - "resolve": "^1.9.0" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/redis": { "version": "4.6.7", "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.7.tgz", @@ -14801,19 +13886,6 @@ "node": ">=8" } }, - "node_modules/resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", - "dev": true, - "dependencies": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -14843,19 +13915,85 @@ } }, "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "license": "ISC", "dependencies": { - "glob": "^7.1.3" + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" }, "bin": { - "rimraf": "bin.js" + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.2.tgz", + "integrity": "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/rndm": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz", @@ -14884,17 +14022,21 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, - "node_modules/safe-json-parse": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", - "integrity": "sha512-o0JmTu17WGUaUOHa1l0FPGXKBfijbxK6qoHzlkihsDXxzBHvJcA7zgviKR92Xs841rX9pK16unfphLq0/KqX7A==", - "dev": true - }, "node_modules/safe-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", @@ -15161,6 +14303,19 @@ "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -15392,12 +14547,6 @@ "node": ">= 10.x" } }, - "node_modules/sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", - "dev": true - }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -15435,15 +14584,6 @@ "node": ">= 0.8" } }, - "node_modules/stream-buffers": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz", - "integrity": "sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==", - "dev": true, - "engines": { - "node": ">= 0.10.0" - } - }, "node_modules/streamx": { "version": "2.15.5", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.5.tgz", @@ -15493,12 +14633,6 @@ "node": ">=10" } }, - "node_modules/string-template": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", - "integrity": "sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==", - "dev": true - }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -15512,6 +14646,22 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -15523,6 +14673,20 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -15558,6 +14722,16 @@ "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" }, + "node_modules/subarg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "integrity": "sha512-RIrIdRY0X1xojthNcVtgT9sjpOGagEUKpZdgBUi054OEPFo282yg+zE+t1Rj3+RqKq2xStL7uUHhY+AjbC4BXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.1.0" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -15736,6 +14910,43 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/terser": { + "version": "5.40.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.40.0.tgz", + "integrity": "sha512-cfeKl/jjwSR5ar7d0FGmave9hFGJT8obyo0z+CrQOylLDbk7X81nPU6vq9VORa5jU30SkDnT2FXjLbR8HLP+xA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.14.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -15761,29 +14972,6 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "node_modules/tiny-lr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz", - "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==", - "dev": true, - "dependencies": { - "body": "^5.1.0", - "debug": "^3.1.0", - "faye-websocket": "~0.10.0", - "livereload-js": "^2.3.0", - "object-assign": "^4.1.0", - "qs": "^6.4.0" - } - }, - "node_modules/tiny-lr/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, "node_modules/tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -15795,6 +14983,22 @@ "node": ">=8.17.0" } }, + "node_modules/tmp/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -15848,6 +15052,16 @@ "node": "*" } }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", @@ -16210,28 +15424,6 @@ "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==" }, - "node_modules/unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/underscore.string": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.6.tgz", - "integrity": "sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==", - "dev": true, - "dependencies": { - "sprintf-js": "^1.1.1", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": "*" - } - }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -16373,15 +15565,6 @@ "punycode": "^2.1.0" } }, - "node_modules/uri-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/uri-path/-/uri-path-1.0.0.tgz", - "integrity": "sha512-8pMuAn4KacYdGMkFaoQARicp4HSw24/DHOVKWqVRJ8LhhAwPPFpdGvdL9184JVmUwe7vz7Z9n6IqI6t5n2ELdg==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/util": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", @@ -16436,18 +15619,6 @@ "node": ">=10.12.0" } }, - "node_modules/v8flags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", - "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/validator": { "version": "13.9.0", "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz", @@ -16487,29 +15658,6 @@ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -16608,6 +15756,61 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", diff --git a/worklenz-backend/package.json b/worklenz-backend/package.json index f3faaaec..cffa800b 100644 --- a/worklenz-backend/package.json +++ b/worklenz-backend/package.json @@ -11,16 +11,30 @@ "repository": "GITHUB_REPO_HERE", "author": "worklenz.com", "scripts": { - "start": "node ./build/bin/www", - "tcs": "grunt build:tsc", - "build": "grunt build", - "watch": "grunt watch", - "dev": "grunt dev", - "es": "esbuild `find src -type f -name '*.ts'` --platform=node --minify=true --watch=true --target=esnext --format=cjs --tsconfig=tsconfig.prod.json --outdir=dist", - "copy": "grunt copy", + "test": "jest", + "start": "node build/bin/www.js", + "dev": "npm run build:dev && npm run watch", + "build": "npm run clean && npm run compile && npm run copy && npm run compress", + "build:dev": "npm run clean && npm run compile:dev && npm run copy", + "build:prod": "npm run clean && npm run compile:prod && npm run copy && npm run minify && npm run compress", + "clean": "rimraf build", + "compile": "tsc --build tsconfig.prod.json", + "compile:dev": "tsc --build tsconfig.json", + "compile:prod": "tsc --build tsconfig.prod.json", + "copy": "npm run copy:assets && npm run copy:views && npm run copy:config && npm run copy:shared", + "copy:assets": "npx cpx2 \"src/public/**\" build/public", + "copy:views": "npx cpx2 \"src/views/**\" build/views", + "copy:config": "npx cpx2 \".env\" build && npx cpx2 \"package.json\" build", + "copy:shared": "npx cpx2 \"src/shared/postgresql-error-codes.json\" build/shared && npx cpx2 \"src/shared/sample-data.json\" build/shared && npx cpx2 \"src/shared/templates/**\" build/shared/templates", + "watch": "concurrently \"npm run watch:ts\" \"npm run watch:assets\"", + "watch:ts": "tsc --build tsconfig.json --watch", + "watch:assets": "npx cpx2 \"src/{public,views}/**\" build --watch", + "minify": "terser build/**/*.js --compress --mangle --output-dir build", + "compress": "node scripts/compress.js", + "swagger": "node ./cli/swagger", + "inline-queries": "node ./cli/inline-queries", "sonar": "sonar-scanner -Dproject.settings=sonar-project-dev.properties", "tsc": "tsc", - "test": "jest --setupFiles dotenv/config", "test:watch": "jest --watch --setupFiles dotenv/config" }, "jestSonar": { @@ -45,6 +59,7 @@ "cors": "^2.8.5", "cron": "^2.4.0", "crypto-js": "^4.1.1", + "csrf-sync": "^4.2.1", "csurf": "^1.11.0", "debug": "^4.3.4", "dotenv": "^16.3.1", @@ -120,26 +135,22 @@ "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", "chokidar": "^3.5.3", + "concurrently": "^9.1.2", + "cpx2": "^8.0.0", "esbuild": "^0.17.19", "esbuild-envfile-plugin": "^1.0.5", "esbuild-node-externals": "^1.8.0", "eslint": "^8.45.0", "eslint-plugin-security": "^1.7.1", "fs-extra": "^10.1.0", - "grunt": "^1.6.1", - "grunt-contrib-clean": "^2.0.1", - "grunt-contrib-compress": "^2.0.0", - "grunt-contrib-copy": "^1.0.0", - "grunt-contrib-uglify": "^5.2.2", - "grunt-contrib-watch": "^1.1.0", - "grunt-shell": "^4.0.0", - "grunt-sync": "^0.8.2", "highcharts": "^11.1.0", "jest": "^28.1.3", "jest-sonar-reporter": "^2.0.0", "ncp": "^2.0.0", "nodeman": "^1.1.2", + "rimraf": "^6.0.1", "swagger-jsdoc": "^6.2.8", + "terser": "^5.40.0", "ts-jest": "^28.0.8", "ts-node": "^10.9.1", "tslint": "^6.1.3", diff --git a/worklenz-backend/scripts/compress.js b/worklenz-backend/scripts/compress.js new file mode 100644 index 00000000..6a946163 --- /dev/null +++ b/worklenz-backend/scripts/compress.js @@ -0,0 +1,53 @@ +const fs = require('fs'); +const path = require('path'); +const { createGzip } = require('zlib'); +const { pipeline } = require('stream'); + +async function compressFile(inputPath, outputPath) { + return new Promise((resolve, reject) => { + const gzip = createGzip(); + const source = fs.createReadStream(inputPath); + const destination = fs.createWriteStream(outputPath); + + pipeline(source, gzip, destination, (err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); +} + +async function compressDirectory(dir) { + const files = fs.readdirSync(dir, { withFileTypes: true }); + + for (const file of files) { + const fullPath = path.join(dir, file.name); + + if (file.isDirectory()) { + await compressDirectory(fullPath); + } else if (file.name.endsWith('.js') || file.name.endsWith('.css')) { + const gzPath = fullPath + '.gz'; + await compressFile(fullPath, gzPath); + console.log(`Compressed: ${fullPath} -> ${gzPath}`); + } + } +} + +async function main() { + try { + const buildDir = path.join(__dirname, '../build'); + if (fs.existsSync(buildDir)) { + await compressDirectory(buildDir); + console.log('Compression complete!'); + } else { + console.log('Build directory not found. Run build first.'); + } + } catch (error) { + console.error('Compression failed:', error); + process.exit(1); + } +} + +main(); \ No newline at end of file diff --git a/worklenz-backend/src/app.ts b/worklenz-backend/src/app.ts index 4da00a0e..68f18af3 100644 --- a/worklenz-backend/src/app.ts +++ b/worklenz-backend/src/app.ts @@ -6,7 +6,7 @@ import logger from "morgan"; import helmet from "helmet"; import compression from "compression"; import passport from "passport"; -import csurf from "csurf"; +import { csrfSync } from "csrf-sync"; import rateLimit from "express-rate-limit"; import cors from "cors"; import flash from "connect-flash"; @@ -112,17 +112,13 @@ function isLoggedIn(req: Request, _res: Response, next: NextFunction) { return req.user ? next() : next(createError(401)); } -// CSRF configuration -const csrfProtection = csurf({ - cookie: { - key: "XSRF-TOKEN", - path: "/", - httpOnly: false, - secure: isProduction(), // Only secure in production - sameSite: isProduction() ? "none" : "lax", // Different settings for dev vs prod - domain: isProduction() ? ".worklenz.com" : undefined // Only set domain in production - }, - ignoreMethods: ["HEAD", "OPTIONS"] +// CSRF configuration using csrf-sync for session-based authentication +const { + invalidCsrfTokenError, + generateToken, + csrfSynchronisedProtection, +} = csrfSync({ + getTokenFromRequest: (req: Request) => req.headers["x-csrf-token"] as string || (req.body && req.body["_csrf"]) }); // Apply CSRF selectively (exclude webhooks and public routes) @@ -135,38 +131,25 @@ app.use((req, res, next) => { ) { next(); } else { - csrfProtection(req, res, next); + csrfSynchronisedProtection(req, res, next); } }); -// Set CSRF token cookie +// Set CSRF token method on request object for compatibility app.use((req: Request, res: Response, next: NextFunction) => { - if (req.csrfToken) { - const token = req.csrfToken(); - res.cookie("XSRF-TOKEN", token, { - httpOnly: false, - secure: isProduction(), - sameSite: isProduction() ? "none" : "lax", - domain: isProduction() ? ".worklenz.com" : undefined, - path: "/" - }); + // Add csrfToken method to request object for compatibility + if (!req.csrfToken && generateToken) { + req.csrfToken = (overwrite?: boolean) => generateToken(req, overwrite); } next(); }); // CSRF token refresh endpoint app.get("/csrf-token", (req: Request, res: Response) => { - if (req.csrfToken) { - const token = req.csrfToken(); - res.cookie("XSRF-TOKEN", token, { - httpOnly: false, - secure: isProduction(), - sameSite: isProduction() ? "none" : "lax", - domain: isProduction() ? ".worklenz.com" : undefined, - path: "/" - }); - res.status(200).json({ done: true, message: "CSRF token refreshed" }); - } else { + try { + const token = generateToken(req); + res.status(200).json({ done: true, message: "CSRF token refreshed", token }); + } catch (error) { res.status(500).json({ done: false, message: "Failed to generate CSRF token" }); } }); @@ -219,7 +202,7 @@ if (isInternalServer()) { // CSRF error handler app.use((err: any, req: Request, res: Response, next: NextFunction) => { - if (err.code === "EBADCSRFTOKEN") { + if (err === invalidCsrfTokenError) { return res.status(403).json({ done: false, message: "Invalid CSRF token", diff --git a/worklenz-frontend/src/App.tsx b/worklenz-frontend/src/App.tsx index 8b313508..3181a25e 100644 --- a/worklenz-frontend/src/App.tsx +++ b/worklenz-frontend/src/App.tsx @@ -13,6 +13,7 @@ import router from './app/routes'; // Hooks & Utils import { useAppSelector } from './hooks/useAppSelector'; import { initMixpanel } from './utils/mixpanelInit'; +import { initializeCsrfToken } from './api/api-client'; // Types & Constants import { Language } from './features/i18n/localesSlice'; @@ -35,6 +36,13 @@ const App: React.FC<{ children: React.ReactNode }> = ({ children }) => { }); }, [language]); + // Initialize CSRF token on app startup + useEffect(() => { + initializeCsrfToken().catch(error => { + logger.error('Failed to initialize CSRF token:', error); + }); + }, []); + return ( }> diff --git a/worklenz-frontend/src/api/api-client.ts b/worklenz-frontend/src/api/api-client.ts index ec43f7a5..721a5274 100644 --- a/worklenz-frontend/src/api/api-client.ts +++ b/worklenz-frontend/src/api/api-client.ts @@ -4,27 +4,36 @@ import alertService from '@/services/alerts/alertService'; import logger from '@/utils/errorLogger'; import config from '@/config/env'; -export const getCsrfToken = (): string | null => { - const match = document.cookie.split('; ').find(cookie => cookie.startsWith('XSRF-TOKEN=')); +// Store CSRF token in memory (since csrf-sync uses session-based tokens) +let csrfToken: string | null = null; - if (!match) { - return null; - } - return decodeURIComponent(match.split('=')[1]); +export const getCsrfToken = (): string | null => { + return csrfToken; }; -// Function to refresh CSRF token if needed +// Function to refresh CSRF token from server export const refreshCsrfToken = async (): Promise => { try { // Make a GET request to the server to get a fresh CSRF token - await axios.get(`${config.apiUrl}/csrf-token`, { withCredentials: true }); - return getCsrfToken(); + const response = await axios.get(`${config.apiUrl}/csrf-token`, { withCredentials: true }); + if (response.data && response.data.token) { + csrfToken = response.data.token; + return csrfToken; + } + return null; } catch (error) { console.error('Failed to refresh CSRF token:', error); return null; } }; +// Initialize CSRF token on app load +export const initializeCsrfToken = async (): Promise => { + if (!csrfToken) { + await refreshCsrfToken(); + } +}; + const apiClient = axios.create({ baseURL: config.apiUrl, withCredentials: true, @@ -36,12 +45,16 @@ const apiClient = axios.create({ // Request interceptor apiClient.interceptors.request.use( - config => { - const token = getCsrfToken(); - if (token) { - config.headers['X-CSRF-Token'] = token; + async config => { + // Ensure we have a CSRF token before making requests + if (!csrfToken) { + await refreshCsrfToken(); + } + + if (csrfToken) { + config.headers['X-CSRF-Token'] = csrfToken; } else { - console.warn('No CSRF token found'); + console.warn('No CSRF token available'); } return config; }, @@ -84,7 +97,7 @@ apiClient.interceptors.response.use( (typeof errorResponse.data === 'object' && errorResponse.data !== null && 'message' in errorResponse.data && - errorResponse.data.message === 'Invalid CSRF token' || + (errorResponse.data.message === 'invalid csrf token' || errorResponse.data.message === 'Invalid CSRF token') || (error as any).code === 'EBADCSRFTOKEN')) { alertService.error('Security Error', 'Invalid security token. Refreshing your session...'); @@ -94,7 +107,7 @@ apiClient.interceptors.response.use( // Update the token in the failed request error.config.headers['X-CSRF-Token'] = newToken; // Retry the original request with the new token - return axios(error.config); + return apiClient(error.config); } else { // If token refresh failed, redirect to login window.location.href = '/auth/login'; diff --git a/worklenz-frontend/src/api/home-page/home-page.api.service.ts b/worklenz-frontend/src/api/home-page/home-page.api.service.ts index 74f5615a..b71e03a3 100644 --- a/worklenz-frontend/src/api/home-page/home-page.api.service.ts +++ b/worklenz-frontend/src/api/home-page/home-page.api.service.ts @@ -5,7 +5,7 @@ import { toQueryString } from '@/utils/toQueryString'; import { IHomeTasksModel, IHomeTasksConfig } from '@/types/home/home-page.types'; import { IMyTask } from '@/types/home/my-tasks.types'; import { IProject } from '@/types/project/project.types'; -import { getCsrfToken } from '../api-client'; +import { getCsrfToken, refreshCsrfToken } from '../api-client'; import config from '@/config/env'; const rootUrl = '/home'; @@ -14,9 +14,18 @@ const api = createApi({ reducerPath: 'homePageApi', baseQuery: fetchBaseQuery({ baseUrl: `${config.apiUrl}${API_BASE_URL}`, - prepareHeaders: headers => { - headers.set('X-CSRF-Token', getCsrfToken() || ''); + prepareHeaders: async headers => { + // Get CSRF token, refresh if needed + let token = getCsrfToken(); + if (!token) { + token = await refreshCsrfToken(); + } + + if (token) { + headers.set('X-CSRF-Token', token); + } headers.set('Content-Type', 'application/json'); + return headers; }, credentials: 'include', }), diff --git a/worklenz-frontend/src/api/projects/projects.v1.api.service.ts b/worklenz-frontend/src/api/projects/projects.v1.api.service.ts index 1fe279d5..1ad45b8b 100644 --- a/worklenz-frontend/src/api/projects/projects.v1.api.service.ts +++ b/worklenz-frontend/src/api/projects/projects.v1.api.service.ts @@ -5,7 +5,7 @@ import { IProjectCategory } from '@/types/project/projectCategory.types'; import { IProjectsViewModel } from '@/types/project/projectsViewModel.types'; import { IServerResponse } from '@/types/common.types'; import { IProjectMembersViewModel } from '@/types/projectMember.types'; -import { getCsrfToken } from '../api-client'; +import { getCsrfToken, refreshCsrfToken } from '../api-client'; import config from '@/config/env'; const rootUrl = '/projects'; @@ -14,9 +14,18 @@ export const projectsApi = createApi({ reducerPath: 'projectsApi', baseQuery: fetchBaseQuery({ baseUrl: `${config.apiUrl}${API_BASE_URL}`, - prepareHeaders: headers => { - headers.set('X-CSRF-Token', getCsrfToken() || ''); + prepareHeaders: async headers => { + // Get CSRF token, refresh if needed + let token = getCsrfToken(); + if (!token) { + token = await refreshCsrfToken(); + } + + if (token) { + headers.set('X-CSRF-Token', token); + } headers.set('Content-Type', 'application/json'); + return headers; }, credentials: 'include', }), From 6ffdbc64d07cd0ca56662f89542a64986a6351ff Mon Sep 17 00:00:00 2001 From: chamikaJ Date: Fri, 30 May 2025 11:10:11 +0530 Subject: [PATCH 4/5] refactor(navbar): comment out license expiry alert for future implementation - Commented out the conditional rendering of the license expiry alert in the Navbar component for future adjustments. --- worklenz-frontend/src/features/navbar/navbar.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/worklenz-frontend/src/features/navbar/navbar.tsx b/worklenz-frontend/src/features/navbar/navbar.tsx index 295a8a17..41d7c0e7 100644 --- a/worklenz-frontend/src/features/navbar/navbar.tsx +++ b/worklenz-frontend/src/features/navbar/navbar.tsx @@ -101,14 +101,6 @@ const Navbar = () => { justifyContent: 'space-between', }} > - {daysUntilExpiry !== null && ((daysUntilExpiry <= 3 && daysUntilExpiry > 0) || (daysUntilExpiry >= -7 && daysUntilExpiry < 0)) && ( - 0 ? `Your license will expire in ${daysUntilExpiry} days` : `Your license has expired ${Math.abs(daysUntilExpiry)} days ago`} - type="warning" - showIcon - style={{ width: '100%', marginTop: 12 }} - /> - )} Date: Fri, 30 May 2025 11:40:27 +0530 Subject: [PATCH 5/5] feat(task-list): implement optimized task group handling and filter data loading - Introduced `useFilterDataLoader` hook to manage asynchronous loading of filter data without blocking the main UI. - Created `TaskGroupWrapperOptimized` for improved rendering of task groups with drag-and-drop functionality. - Refactored `ProjectViewTaskList` to utilize the new optimized components and enhance loading state management. - Added `TaskGroup` component for better organization and interaction with task groups. - Updated `TaskListFilters` to leverage the new filter data loading mechanism, ensuring a smoother user experience. --- .../src/hooks/useFilterDataLoader.ts | 69 ++++ .../src/hooks/useTaskDragAndDrop.ts | 146 ++++++++ .../src/hooks/useTaskSocketHandlers.ts | 343 ++++++++++++++++++ .../components/task-group/task-group.tsx | 241 ++++++++++++ .../taskList/project-view-task-list.tsx | 82 +++-- .../taskList/task-group-wrapper-optimized.tsx | 112 ++++++ .../task-list-filters/task-list-filters.tsx | 43 ++- 7 files changed, 1000 insertions(+), 36 deletions(-) create mode 100644 worklenz-frontend/src/hooks/useFilterDataLoader.ts create mode 100644 worklenz-frontend/src/hooks/useTaskDragAndDrop.ts create mode 100644 worklenz-frontend/src/hooks/useTaskSocketHandlers.ts create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/components/task-group/task-group.tsx create mode 100644 worklenz-frontend/src/pages/projects/projectView/taskList/task-group-wrapper-optimized.tsx diff --git a/worklenz-frontend/src/hooks/useFilterDataLoader.ts b/worklenz-frontend/src/hooks/useFilterDataLoader.ts new file mode 100644 index 00000000..e3aa4f41 --- /dev/null +++ b/worklenz-frontend/src/hooks/useFilterDataLoader.ts @@ -0,0 +1,69 @@ +import { useEffect, useCallback } from 'react'; +import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { useAppSelector } from '@/hooks/useAppSelector'; +import { fetchPriorities } from '@/features/taskAttributes/taskPrioritySlice'; +import { + fetchLabelsByProject, + fetchTaskAssignees, +} from '@/features/tasks/tasks.slice'; +import { getTeamMembers } from '@/features/team-members/team-members.slice'; + +/** + * Hook to manage filter data loading independently of main task list loading + * This ensures filter data loading doesn't block the main UI skeleton + */ +export const useFilterDataLoader = () => { + const dispatch = useAppDispatch(); + + const { priorities } = useAppSelector(state => ({ + priorities: state.priorityReducer.priorities, + })); + + const { projectId } = useAppSelector(state => ({ + projectId: state.projectReducer.projectId, + })); + + // Load filter data asynchronously + const loadFilterData = useCallback(async () => { + try { + // Load priorities if not already loaded (usually fast/cached) + if (!priorities.length) { + dispatch(fetchPriorities()); + } + + // Load project-specific data in parallel without blocking + if (projectId) { + // These dispatch calls are fire-and-forget + // They will update the UI when ready, but won't block initial render + dispatch(fetchLabelsByProject(projectId)); + dispatch(fetchTaskAssignees(projectId)); + } + + // Load team members for member filters + dispatch(getTeamMembers({ + index: 0, + size: 100, + field: null, + order: null, + search: null, + all: true + })); + } catch (error) { + console.error('Error loading filter data:', error); + // Don't throw - filter loading errors shouldn't break the main UI + } + }, [dispatch, priorities.length, projectId]); + + // Load filter data on mount and when dependencies change + useEffect(() => { + // Use setTimeout to ensure this runs after the main component render + // This prevents filter loading from blocking the initial render + const timeoutId = setTimeout(loadFilterData, 0); + + return () => clearTimeout(timeoutId); + }, [loadFilterData]); + + return { + loadFilterData, + }; +}; \ No newline at end of file diff --git a/worklenz-frontend/src/hooks/useTaskDragAndDrop.ts b/worklenz-frontend/src/hooks/useTaskDragAndDrop.ts new file mode 100644 index 00000000..cab0a361 --- /dev/null +++ b/worklenz-frontend/src/hooks/useTaskDragAndDrop.ts @@ -0,0 +1,146 @@ +import { useMemo, useCallback } from 'react'; +import { + DndContext, + DragEndEvent, + DragOverEvent, + DragStartEvent, + PointerSensor, + useSensor, + useSensors, + KeyboardSensor, + TouchSensor, +} from '@dnd-kit/core'; +import { sortableKeyboardCoordinates } from '@dnd-kit/sortable'; +import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { useAppSelector } from '@/hooks/useAppSelector'; +import { updateTaskStatus } from '@/features/tasks/tasks.slice'; +import { ITaskListGroup } from '@/types/tasks/taskList.types'; +import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; + +export const useTaskDragAndDrop = () => { + const dispatch = useAppDispatch(); + const { taskGroups, groupBy } = useAppSelector(state => ({ + taskGroups: state.taskReducer.taskGroups, + groupBy: state.taskReducer.groupBy, + })); + + // Memoize sensors configuration for better performance + const sensors = useSensors( + useSensor(PointerSensor, { + activationConstraint: { + distance: 8, + }, + }), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }), + useSensor(TouchSensor, { + activationConstraint: { + delay: 250, + tolerance: 5, + }, + }) + ); + + const handleDragStart = useCallback((event: DragStartEvent) => { + // Add visual feedback for drag start + const { active } = event; + if (active) { + document.body.style.cursor = 'grabbing'; + } + }, []); + + const handleDragOver = useCallback((event: DragOverEvent) => { + // Handle drag over logic if needed + // This can be used for visual feedback during drag + }, []); + + const handleDragEnd = useCallback( + (event: DragEndEvent) => { + // Reset cursor + document.body.style.cursor = ''; + + const { active, over } = event; + + if (!active || !over || !taskGroups) { + return; + } + + try { + const activeId = active.id as string; + const overId = over.id as string; + + // Find the task being dragged + let draggedTask: IProjectTask | null = null; + let sourceGroupId: string | null = null; + + for (const group of taskGroups) { + const task = group.tasks?.find((t: IProjectTask) => t.id === activeId); + if (task) { + draggedTask = task; + sourceGroupId = group.id; + break; + } + } + + if (!draggedTask || !sourceGroupId) { + console.warn('Could not find dragged task'); + return; + } + + // Determine target group + let targetGroupId: string | null = null; + + // Check if dropped on a group container + const targetGroup = taskGroups.find((group: ITaskListGroup) => group.id === overId); + if (targetGroup) { + targetGroupId = targetGroup.id; + } else { + // Check if dropped on another task + for (const group of taskGroups) { + const targetTask = group.tasks?.find((t: IProjectTask) => t.id === overId); + if (targetTask) { + targetGroupId = group.id; + break; + } + } + } + + if (!targetGroupId || targetGroupId === sourceGroupId) { + return; // No change needed + } + + // Update task status based on group change + const targetGroupData = taskGroups.find((group: ITaskListGroup) => group.id === targetGroupId); + if (targetGroupData && groupBy === 'status') { + const updatePayload: any = { + task_id: draggedTask.id, + status_id: targetGroupData.id, + }; + + if (draggedTask.parent_task_id) { + updatePayload.parent_task = draggedTask.parent_task_id; + } + + dispatch(updateTaskStatus(updatePayload)); + } + } catch (error) { + console.error('Error handling drag end:', error); + } + }, + [taskGroups, groupBy, dispatch] + ); + + // Memoize the drag and drop configuration + const dragAndDropConfig = useMemo( + () => ({ + sensors, + onDragStart: handleDragStart, + onDragOver: handleDragOver, + onDragEnd: handleDragEnd, + }), + [sensors, handleDragStart, handleDragOver, handleDragEnd] + ); + + return dragAndDropConfig; +}; \ No newline at end of file diff --git a/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts b/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts new file mode 100644 index 00000000..7c85ead6 --- /dev/null +++ b/worklenz-frontend/src/hooks/useTaskSocketHandlers.ts @@ -0,0 +1,343 @@ +import { useCallback, useEffect } from 'react'; +import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { useAppSelector } from '@/hooks/useAppSelector'; +import { useSocket } from '@/socket/socketContext'; +import { useAuthService } from '@/hooks/useAuth'; +import { SocketEvents } from '@/shared/socket-events'; +import logger from '@/utils/errorLogger'; +import alertService from '@/services/alerts/alertService'; + +import { ITaskAssigneesUpdateResponse } from '@/types/tasks/task-assignee-update-response'; +import { ILabelsChangeResponse } from '@/types/tasks/taskList.types'; +import { ITaskListStatusChangeResponse } from '@/types/tasks/task-list-status.types'; +import { ITaskListPriorityChangeResponse } from '@/types/tasks/task-list-priority.types'; +import { ITaskPhaseChangeResponse } from '@/types/tasks/task-phase-change-response'; +import { InlineMember } from '@/types/teamMembers/inlineMember.types'; +import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; +import { ITaskListGroup } from '@/types/tasks/taskList.types'; + +import { + fetchTaskAssignees, + updateTaskAssignees, + fetchLabelsByProject, + updateTaskLabel, + updateTaskStatus, + updateTaskPriority, + updateTaskEndDate, + updateTaskEstimation, + updateTaskName, + updateTaskPhase, + updateTaskStartDate, + updateTaskDescription, + updateSubTasks, + updateTaskProgress, +} from '@/features/tasks/tasks.slice'; +import { fetchLabels } from '@/features/taskAttributes/taskLabelSlice'; +import { + setStartDate, + setTaskAssignee, + setTaskEndDate, + setTaskLabels, + setTaskPriority, + setTaskStatus, + setTaskSubscribers, +} from '@/features/task-drawer/task-drawer.slice'; +import { deselectAll } from '@/features/projects/bulkActions/bulkActionSlice'; + +export const useTaskSocketHandlers = () => { + const dispatch = useAppDispatch(); + const { socket } = useSocket(); + const currentSession = useAuthService().getCurrentSession(); + + const { loadingAssignees, taskGroups } = useAppSelector((state: any) => state.taskReducer); + const { projectId } = useAppSelector((state: any) => state.projectReducer); + + // Memoize socket event handlers + const handleAssigneesUpdate = useCallback( + (data: ITaskAssigneesUpdateResponse) => { + if (!data) return; + + const updatedAssignees = data.assignees?.map(assignee => ({ + ...assignee, + selected: true, + })) || []; + + const groupId = taskGroups?.find((group: ITaskListGroup) => + group.tasks?.some( + (task: IProjectTask) => + task.id === data.id || + (task.sub_tasks && task.sub_tasks.some((subtask: IProjectTask) => subtask.id === data.id)) + ) + )?.id; + + if (groupId) { + dispatch( + updateTaskAssignees({ + groupId, + taskId: data.id, + assignees: updatedAssignees, + }) + ); + + dispatch( + setTaskAssignee({ + ...data, + manual_progress: false, + } as IProjectTask) + ); + + if (currentSession?.team_id && !loadingAssignees) { + dispatch(fetchTaskAssignees(currentSession.team_id)); + } + } + }, + [taskGroups, dispatch, currentSession?.team_id, loadingAssignees] + ); + + const handleLabelsChange = useCallback( + async (labels: ILabelsChangeResponse) => { + if (!labels) return; + + await Promise.all([ + dispatch(updateTaskLabel(labels)), + dispatch(setTaskLabels(labels)), + dispatch(fetchLabels()), + projectId && dispatch(fetchLabelsByProject(projectId)), + ]); + }, + [dispatch, projectId] + ); + + const handleTaskStatusChange = useCallback( + (response: ITaskListStatusChangeResponse) => { + if (!response) return; + + if (response.completed_deps === false) { + alertService.error( + 'Task is not completed', + 'Please complete the task dependencies before proceeding' + ); + return; + } + + dispatch(updateTaskStatus(response)); + dispatch(deselectAll()); + }, + [dispatch] + ); + + const handleTaskProgress = useCallback( + (data: { + id: string; + status: string; + complete_ratio: number; + completed_count: number; + total_tasks_count: number; + parent_task: string; + }) => { + if (!data) return; + + dispatch( + updateTaskProgress({ + taskId: data.parent_task || data.id, + progress: data.complete_ratio, + totalTasksCount: data.total_tasks_count, + completedCount: data.completed_count, + }) + ); + }, + [dispatch] + ); + + const handlePriorityChange = useCallback( + (response: ITaskListPriorityChangeResponse) => { + if (!response) return; + + dispatch(updateTaskPriority(response)); + dispatch(setTaskPriority(response)); + dispatch(deselectAll()); + }, + [dispatch] + ); + + const handleEndDateChange = useCallback( + (task: { + id: string; + parent_task: string | null; + end_date: string; + }) => { + if (!task) return; + + const taskWithProgress = { + ...task, + manual_progress: false, + } as IProjectTask; + + dispatch(updateTaskEndDate({ task: taskWithProgress })); + dispatch(setTaskEndDate(taskWithProgress)); + }, + [dispatch] + ); + + const handleTaskNameChange = useCallback( + (data: { id: string; parent_task: string; name: string }) => { + if (!data) return; + dispatch(updateTaskName(data)); + }, + [dispatch] + ); + + const handlePhaseChange = useCallback( + (data: ITaskPhaseChangeResponse) => { + if (!data) return; + dispatch(updateTaskPhase(data)); + dispatch(deselectAll()); + }, + [dispatch] + ); + + const handleStartDateChange = useCallback( + (task: { + id: string; + parent_task: string | null; + start_date: string; + }) => { + if (!task) return; + + const taskWithProgress = { + ...task, + manual_progress: false, + } as IProjectTask; + + dispatch(updateTaskStartDate({ task: taskWithProgress })); + dispatch(setStartDate(taskWithProgress)); + }, + [dispatch] + ); + + const handleTaskSubscribersChange = useCallback( + (data: InlineMember[]) => { + if (!data) return; + dispatch(setTaskSubscribers(data)); + }, + [dispatch] + ); + + const handleEstimationChange = useCallback( + (task: { + id: string; + parent_task: string | null; + estimation: number; + }) => { + if (!task) return; + + const taskWithProgress = { + ...task, + manual_progress: false, + } as IProjectTask; + + dispatch(updateTaskEstimation({ task: taskWithProgress })); + }, + [dispatch] + ); + + const handleTaskDescriptionChange = useCallback( + (data: { + id: string; + parent_task: string; + description: string; + }) => { + if (!data) return; + dispatch(updateTaskDescription(data)); + }, + [dispatch] + ); + + const handleNewTaskReceived = useCallback( + (data: IProjectTask) => { + if (!data) return; + if (data.parent_task_id) { + dispatch(updateSubTasks(data)); + } + }, + [dispatch] + ); + + const handleTaskProgressUpdated = useCallback( + (data: { + task_id: string; + progress_value?: number; + weight?: number; + }) => { + if (!data || !taskGroups) return; + + if (data.progress_value !== undefined) { + for (const group of taskGroups) { + const task = group.tasks?.find((task: IProjectTask) => task.id === data.task_id); + if (task) { + dispatch( + updateTaskProgress({ + taskId: data.task_id, + progress: data.progress_value, + totalTasksCount: task.total_tasks_count || 0, + completedCount: task.completed_count || 0, + }) + ); + break; + } + } + } + }, + [dispatch, taskGroups] + ); + + // Register socket event listeners + useEffect(() => { + if (!socket) return; + + const eventHandlers = [ + { event: SocketEvents.QUICK_ASSIGNEES_UPDATE.toString(), handler: handleAssigneesUpdate }, + { event: SocketEvents.TASK_LABELS_CHANGE.toString(), handler: handleLabelsChange }, + { event: SocketEvents.TASK_STATUS_CHANGE.toString(), handler: handleTaskStatusChange }, + { event: SocketEvents.TASK_PROGRESS_UPDATED.toString(), handler: handleTaskProgress }, + { event: SocketEvents.TASK_PRIORITY_CHANGE.toString(), handler: handlePriorityChange }, + { event: SocketEvents.TASK_END_DATE_CHANGE.toString(), handler: handleEndDateChange }, + { event: SocketEvents.TASK_NAME_CHANGE.toString(), handler: handleTaskNameChange }, + { event: SocketEvents.TASK_PHASE_CHANGE.toString(), handler: handlePhaseChange }, + { event: SocketEvents.TASK_START_DATE_CHANGE.toString(), handler: handleStartDateChange }, + { event: SocketEvents.TASK_SUBSCRIBERS_CHANGE.toString(), handler: handleTaskSubscribersChange }, + { event: SocketEvents.TASK_TIME_ESTIMATION_CHANGE.toString(), handler: handleEstimationChange }, + { event: SocketEvents.TASK_DESCRIPTION_CHANGE.toString(), handler: handleTaskDescriptionChange }, + { event: SocketEvents.QUICK_TASK.toString(), handler: handleNewTaskReceived }, + { event: SocketEvents.TASK_PROGRESS_UPDATED.toString(), handler: handleTaskProgressUpdated }, + ]; + + // Register all event listeners + eventHandlers.forEach(({ event, handler }) => { + socket.on(event, handler); + }); + + // Cleanup function + return () => { + eventHandlers.forEach(({ event, handler }) => { + socket.off(event, handler); + }); + }; + }, [ + socket, + handleAssigneesUpdate, + handleLabelsChange, + handleTaskStatusChange, + handleTaskProgress, + handlePriorityChange, + handleEndDateChange, + handleTaskNameChange, + handlePhaseChange, + handleStartDateChange, + handleTaskSubscribersChange, + handleEstimationChange, + handleTaskDescriptionChange, + handleNewTaskReceived, + handleTaskProgressUpdated, + ]); +}; \ No newline at end of file diff --git a/worklenz-frontend/src/pages/projects/projectView/taskList/components/task-group/task-group.tsx b/worklenz-frontend/src/pages/projects/projectView/taskList/components/task-group/task-group.tsx new file mode 100644 index 00000000..e5800fe4 --- /dev/null +++ b/worklenz-frontend/src/pages/projects/projectView/taskList/components/task-group/task-group.tsx @@ -0,0 +1,241 @@ +import React, { useState, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useDroppable } from '@dnd-kit/core'; +import Flex from 'antd/es/flex'; +import Badge from 'antd/es/badge'; +import Button from 'antd/es/button'; +import Dropdown from 'antd/es/dropdown'; +import Input from 'antd/es/input'; +import Typography from 'antd/es/typography'; +import { MenuProps } from 'antd/es/menu'; +import { EditOutlined, EllipsisOutlined, RetweetOutlined, RightOutlined } from '@ant-design/icons'; + +import { colors } from '@/styles/colors'; +import { useAppSelector } from '@/hooks/useAppSelector'; +import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { IProjectTask } from '@/types/project/projectTasksViewModel.types'; +import { ITaskListGroup } from '@/types/tasks/taskList.types'; +import Collapsible from '@/components/collapsible/collapsible'; +import TaskListTable from '../../task-list-table/task-list-table'; +import { IGroupBy, updateTaskGroupColor } from '@/features/tasks/tasks.slice'; +import { useAuthService } from '@/hooks/useAuth'; +import { statusApiService } from '@/api/taskAttributes/status/status.api.service'; +import { phasesApiService } from '@/api/taskAttributes/phases/phases.api.service'; +import { ITaskPhase } from '@/types/tasks/taskPhase.types'; +import { fetchPhasesByProjectId } from '@/features/projects/singleProject/phase/phases.slice'; +import { fetchStatuses } from '@/features/taskAttributes/taskStatusSlice'; +import { useMixpanelTracking } from '@/hooks/useMixpanelTracking'; +import { evt_project_board_column_setting_click } from '@/shared/worklenz-analytics-events'; +import { ALPHA_CHANNEL } from '@/shared/constants'; +import useIsProjectManager from '@/hooks/useIsProjectManager'; +import logger from '@/utils/errorLogger'; + +interface TaskGroupProps { + taskGroup: ITaskListGroup; + groupBy: string; + color: string; + activeId?: string | null; +} + +const TaskGroup: React.FC = ({ + taskGroup, + groupBy, + color, + activeId +}) => { + const { t } = useTranslation('task-list-table'); + const dispatch = useAppDispatch(); + const { trackMixpanelEvent } = useMixpanelTracking(); + const isProjectManager = useIsProjectManager(); + const currentSession = useAuthService().getCurrentSession(); + + const [isExpanded, setIsExpanded] = useState(true); + const [isRenaming, setIsRenaming] = useState(false); + const [groupName, setGroupName] = useState(taskGroup.name || ''); + + const { projectId } = useAppSelector((state: any) => state.projectReducer); + const themeMode = useAppSelector((state: any) => state.themeReducer.mode); + + // Memoize droppable configuration + const { setNodeRef } = useDroppable({ + id: taskGroup.id, + data: { + type: 'group', + groupId: taskGroup.id, + }, + }); + + // Memoize task count + const taskCount = useMemo(() => taskGroup.tasks?.length || 0, [taskGroup.tasks]); + + // Memoize dropdown items + const dropdownItems: MenuProps['items'] = useMemo(() => { + if (groupBy !== IGroupBy.STATUS || !isProjectManager) return []; + + return [ + { + key: 'rename', + label: t('renameText'), + icon: , + onClick: () => setIsRenaming(true), + }, + { + key: 'change-category', + label: t('changeCategoryText'), + icon: , + children: [ + { + key: 'todo', + label: t('todoText'), + onClick: () => handleStatusCategoryChange('0'), + }, + { + key: 'doing', + label: t('doingText'), + onClick: () => handleStatusCategoryChange('1'), + }, + { + key: 'done', + label: t('doneText'), + onClick: () => handleStatusCategoryChange('2'), + }, + ], + }, + ]; + }, [groupBy, isProjectManager, t]); + + const handleStatusCategoryChange = async (category: string) => { + if (!projectId || !taskGroup.id) return; + + try { + await statusApiService.updateStatus({ + id: taskGroup.id, + category_id: category, + project_id: projectId, + }); + + dispatch(fetchStatuses()); + trackMixpanelEvent(evt_project_board_column_setting_click, { + column_id: taskGroup.id, + action: 'change_category', + category, + }); + } catch (error) { + logger.error('Error updating status category:', error); + } + }; + + const handleRename = async () => { + if (!projectId || !taskGroup.id || !groupName.trim()) return; + + try { + if (groupBy === IGroupBy.STATUS) { + await statusApiService.updateStatus({ + id: taskGroup.id, + name: groupName.trim(), + project_id: projectId, + }); + dispatch(fetchStatuses()); + } else if (groupBy === IGroupBy.PHASE) { + const phaseData: ITaskPhase = { + id: taskGroup.id, + name: groupName.trim(), + project_id: projectId, + color_code: taskGroup.color_code, + }; + await phasesApiService.updatePhase(phaseData); + dispatch(fetchPhasesByProjectId(projectId)); + } + + setIsRenaming(false); + } catch (error) { + logger.error('Error renaming group:', error); + } + }; + + const handleColorChange = async (newColor: string) => { + if (!projectId || !taskGroup.id) return; + + try { + const baseColor = newColor.endsWith(ALPHA_CHANNEL) + ? newColor.slice(0, -ALPHA_CHANNEL.length) + : newColor; + + if (groupBy === IGroupBy.PHASE) { + const phaseData: ITaskPhase = { + id: taskGroup.id, + name: taskGroup.name || '', + project_id: projectId, + color_code: baseColor, + }; + await phasesApiService.updatePhase(phaseData); + dispatch(fetchPhasesByProjectId(projectId)); + } + + dispatch(updateTaskGroupColor({ + groupId: taskGroup.id, + color: baseColor, + })); + } catch (error) { + logger.error('Error updating group color:', error); + } + }; + + return ( +
+ + {/* Group Header */} + + + + {dropdownItems.length > 0 && !isRenaming && ( + +
+ ); +}; + +export default React.memo(TaskGroup); \ No newline at end of file diff --git a/worklenz-frontend/src/pages/projects/projectView/taskList/project-view-task-list.tsx b/worklenz-frontend/src/pages/projects/projectView/taskList/project-view-task-list.tsx index 410644fb..29914771 100644 --- a/worklenz-frontend/src/pages/projects/projectView/taskList/project-view-task-list.tsx +++ b/worklenz-frontend/src/pages/projects/projectView/taskList/project-view-task-list.tsx @@ -4,7 +4,7 @@ import Skeleton from 'antd/es/skeleton'; import { useSearchParams } from 'react-router-dom'; import TaskListFilters from './task-list-filters/task-list-filters'; -import TaskGroupWrapper from './task-list-table/task-group-wrapper/task-group-wrapper'; +import TaskGroupWrapperOptimized from './task-group-wrapper-optimized'; import { useAppSelector } from '@/hooks/useAppSelector'; import { useAppDispatch } from '@/hooks/useAppDispatch'; import { fetchTaskGroups, fetchTaskListColumns } from '@/features/tasks/tasks.slice'; @@ -17,29 +17,50 @@ const ProjectViewTaskList = () => { const dispatch = useAppDispatch(); const { projectView } = useTabSearchParam(); const [searchParams, setSearchParams] = useSearchParams(); - const [isLoading, setIsLoading] = useState(true); const [initialLoadComplete, setInitialLoadComplete] = useState(false); - const { projectId } = useAppSelector(state => state.projectReducer); - const { taskGroups, loadingGroups, groupBy, archived, fields, search } = useAppSelector( - state => state.taskReducer - ); - const { statusCategories, loading: loadingStatusCategories } = useAppSelector( - state => state.taskStatusReducer - ); - const { loadingPhases } = useAppSelector(state => state.phaseReducer); - const { loadingColumns } = useAppSelector(state => state.taskReducer); + // Combine related selectors to reduce subscriptions + const { + projectId, + taskGroups, + loadingGroups, + groupBy, + archived, + fields, + search, + } = useAppSelector(state => ({ + projectId: state.projectReducer.projectId, + taskGroups: state.taskReducer.taskGroups, + loadingGroups: state.taskReducer.loadingGroups, + groupBy: state.taskReducer.groupBy, + archived: state.taskReducer.archived, + fields: state.taskReducer.fields, + search: state.taskReducer.search, + })); - // Memoize the loading state calculation - ignoring task list filter loading - const isLoadingState = useMemo(() => - loadingGroups || loadingPhases || loadingStatusCategories, - [loadingGroups, loadingPhases, loadingStatusCategories] + const { + statusCategories, + loading: loadingStatusCategories, + } = useAppSelector(state => ({ + statusCategories: state.taskStatusReducer.statusCategories, + loading: state.taskStatusReducer.loading, + })); + + const { loadingPhases } = useAppSelector(state => ({ + loadingPhases: state.phaseReducer.loadingPhases, + })); + + // Single source of truth for loading state - EXCLUDE labels loading from skeleton + // Labels loading should not block the main task list display + const isLoading = useMemo(() => + loadingGroups || loadingPhases || loadingStatusCategories || !initialLoadComplete, + [loadingGroups, loadingPhases, loadingStatusCategories, initialLoadComplete] ); // Memoize the empty state check const isEmptyState = useMemo(() => - taskGroups && taskGroups.length === 0 && !isLoadingState, - [taskGroups, isLoadingState] + taskGroups && taskGroups.length === 0 && !isLoading, + [taskGroups, isLoading] ); // Handle view type changes @@ -50,34 +71,32 @@ const ProjectViewTaskList = () => { newParams.set('pinned_tab', 'tasks-list'); setSearchParams(newParams); } - }, [projectView, setSearchParams]); + }, [projectView, setSearchParams, searchParams]); - // Update loading state - useEffect(() => { - setIsLoading(isLoadingState); - }, [isLoadingState]); - - // Fetch initial data only once + // Batch initial data fetching - core data only useEffect(() => { const fetchInitialData = async () => { if (!projectId || !groupBy || initialLoadComplete) return; try { - await Promise.all([ + // Batch only essential API calls for initial load + // Filter data (labels, assignees, etc.) will load separately and not block the UI + await Promise.allSettled([ dispatch(fetchTaskListColumns(projectId)), dispatch(fetchPhasesByProjectId(projectId)), - dispatch(fetchStatusesCategories()) + dispatch(fetchStatusesCategories()), ]); setInitialLoadComplete(true); } catch (error) { console.error('Error fetching initial data:', error); + setInitialLoadComplete(true); // Still mark as complete to prevent infinite loading } }; fetchInitialData(); }, [projectId, groupBy, dispatch, initialLoadComplete]); - // Fetch task groups + // Fetch task groups with dependency on initial load completion useEffect(() => { const fetchTasks = async () => { if (!projectId || !groupBy || projectView !== 'list' || !initialLoadComplete) return; @@ -92,15 +111,22 @@ const ProjectViewTaskList = () => { fetchTasks(); }, [projectId, groupBy, projectView, dispatch, fields, search, archived, initialLoadComplete]); + // Memoize the task groups to prevent unnecessary re-renders + const memoizedTaskGroups = useMemo(() => taskGroups || [], [taskGroups]); + return ( + {/* Filters load independently and don't block the main content */} {isEmptyState ? ( ) : ( - + )} diff --git a/worklenz-frontend/src/pages/projects/projectView/taskList/task-group-wrapper-optimized.tsx b/worklenz-frontend/src/pages/projects/projectView/taskList/task-group-wrapper-optimized.tsx new file mode 100644 index 00000000..71257305 --- /dev/null +++ b/worklenz-frontend/src/pages/projects/projectView/taskList/task-group-wrapper-optimized.tsx @@ -0,0 +1,112 @@ +import React, { useEffect, useMemo } from 'react'; +import { createPortal } from 'react-dom'; +import Flex from 'antd/es/flex'; +import useIsomorphicLayoutEffect from '@/hooks/useIsomorphicLayoutEffect'; + +import { + DndContext, + pointerWithin, +} from '@dnd-kit/core'; + +import { ITaskListGroup } from '@/types/tasks/taskList.types'; +import { useAppSelector } from '@/hooks/useAppSelector'; + +import TaskListTableWrapper from './task-list-table/task-list-table-wrapper/task-list-table-wrapper'; +import TaskListBulkActionsBar from '@/components/taskListCommon/task-list-bulk-actions-bar/task-list-bulk-actions-bar'; +import TaskTemplateDrawer from '@/components/task-templates/task-template-drawer'; + +import { useTaskSocketHandlers } from '@/hooks/useTaskSocketHandlers'; +import { useTaskDragAndDrop } from '@/hooks/useTaskDragAndDrop'; + +interface TaskGroupWrapperOptimizedProps { + taskGroups: ITaskListGroup[]; + groupBy: string; +} + +const TaskGroupWrapperOptimized = ({ taskGroups, groupBy }: TaskGroupWrapperOptimizedProps) => { + const themeMode = useAppSelector((state: any) => state.themeReducer.mode); + + // Use extracted hooks + useTaskSocketHandlers(); + const { + activeId, + sensors, + handleDragStart, + handleDragEnd, + handleDragOver, + resetTaskRowStyles, + } = useTaskDragAndDrop({ taskGroups, groupBy }); + + // Memoize task groups with colors + const taskGroupsWithColors = useMemo(() => + taskGroups?.map(taskGroup => ({ + ...taskGroup, + displayColor: themeMode === 'dark' ? taskGroup.color_code_dark : taskGroup.color_code, + })) || [], + [taskGroups, themeMode] + ); + + // Add drag styles + useEffect(() => { + const style = document.createElement('style'); + style.textContent = ` + .task-row[data-is-dragging="true"] { + opacity: 0.5 !important; + transform: rotate(5deg) !important; + z-index: 1000 !important; + position: relative !important; + } + .task-row { + transition: transform 0.2s ease, opacity 0.2s ease; + } + `; + document.head.appendChild(style); + + return () => { + document.head.removeChild(style); + }; + }, []); + + // Handle animation cleanup after drag ends + useIsomorphicLayoutEffect(() => { + if (activeId === null) { + const timeoutId = setTimeout(resetTaskRowStyles, 50); + return () => clearTimeout(timeoutId); + } + }, [activeId, resetTaskRowStyles]); + + return ( + + + {taskGroupsWithColors.map(taskGroup => ( + + ))} + + {createPortal(, document.body, 'bulk-action-container')} + + {createPortal( + {}} />, + document.body, + 'task-template-drawer' + )} + + + ); +}; + +export default React.memo(TaskGroupWrapperOptimized); \ No newline at end of file diff --git a/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-filters/task-list-filters.tsx b/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-filters/task-list-filters.tsx index c32153b8..fcf866f1 100644 --- a/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-filters/task-list-filters.tsx +++ b/worklenz-frontend/src/pages/projects/projectView/taskList/task-list-filters/task-list-filters.tsx @@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next'; import { fetchPriorities } from '@/features/taskAttributes/taskPrioritySlice'; import { useAppSelector } from '@/hooks/useAppSelector'; import { useAppDispatch } from '@/hooks/useAppDispatch'; +import { useFilterDataLoader } from '@/hooks/useFilterDataLoader'; import { fetchLabelsByProject, fetchTaskAssignees, @@ -33,23 +34,49 @@ const TaskListFilters: React.FC = ({ position }) => { const { projectView } = useTabSearchParam(); const priorities = useAppSelector(state => state.priorityReducer.priorities); - const projectId = useAppSelector(state => state.projectReducer.projectId); const archived = useAppSelector(state => state.taskReducer.archived); const handleShowArchivedChange = () => dispatch(toggleArchived()); + // Load filter data asynchronously and non-blocking + // This runs independently of the main task list loading useEffect(() => { - const fetchInitialData = async () => { - if (!priorities.length) await dispatch(fetchPriorities()); - if (projectId) { - await dispatch(fetchLabelsByProject(projectId)); - await dispatch(fetchTaskAssignees(projectId)); + const loadFilterData = async () => { + try { + // Load priorities first (usually cached/fast) + if (!priorities.length) { + dispatch(fetchPriorities()); + } + + // Load project-specific filter data in parallel, but don't await + // This allows the main task list to load while filters are still loading + if (projectId) { + // Fire and forget - these will update the UI when ready + dispatch(fetchLabelsByProject(projectId)); + dispatch(fetchTaskAssignees(projectId)); + } + + // Load team members (usually needed for member filters) + dispatch(getTeamMembers({ + index: 0, + size: 100, + field: null, + order: null, + search: null, + all: true + })); + } catch (error) { + console.error('Error loading filter data:', error); + // Don't throw - filter loading errors shouldn't break the main UI } - dispatch(getTeamMembers({ index: 0, size: 100, field: null, order: null, search: null, all: true })); }; - fetchInitialData(); + // Use setTimeout to ensure this runs after the main component render + // This prevents filter loading from blocking the initial render + const timeoutId = setTimeout(loadFilterData, 0); + + return () => clearTimeout(timeoutId); }, [dispatch, priorities.length, projectId]); return (