From bbc5c6c1dd931d1a1f89cba16c5e35197a18b362 Mon Sep 17 00:00:00 2001 From: MrPoll0 Date: Tue, 17 Jun 2025 07:42:15 +0200 Subject: [PATCH 1/5] feat: arrays (e) --- src/components/Flow/Nodes/AssignVariable.tsx | 23 +- .../Panel/Tabs/editors/ConditionalEditor.tsx | 237 ++++- .../Panel/Tabs/editors/OutputEditor.tsx | 189 +++- .../Tabs/editors/VariableAssignmentEditor.tsx | 960 ++++++++++++------ .../editors/VariableDeclarationEditor.tsx | 181 +++- .../editors/shared/DragAndDropComponents.tsx | 97 +- src/components/ui/ArrayIndexDialog.tsx | 645 ++++++++++++ src/components/ui/ExpressionBuilder.tsx | 191 ++++ src/context/VariablesContext.tsx | 2 +- src/models/Expression.ts | 75 +- src/models/ValuedVariable.ts | 42 +- src/models/Variable.ts | 78 +- src/utils/expressionParsing.ts | 103 ++ 13 files changed, 2367 insertions(+), 456 deletions(-) create mode 100644 src/components/ui/ArrayIndexDialog.tsx create mode 100644 src/components/ui/ExpressionBuilder.tsx create mode 100644 src/utils/expressionParsing.ts diff --git a/src/components/Flow/Nodes/AssignVariable.tsx b/src/components/Flow/Nodes/AssignVariable.tsx index a292210..d59a63d 100644 --- a/src/components/Flow/Nodes/AssignVariable.tsx +++ b/src/components/Flow/Nodes/AssignVariable.tsx @@ -7,9 +7,11 @@ import { IValuedVariable, ValuedVariable } from '../../../models/ValuedVariable' import { VariableType } from '../../../models/Variable'; import { Badge } from '@/components/ui/badge'; import BreakpointIndicator from './BreakpointIndicator'; +import { ExpressionElement } from '@/models/ExpressionElement'; interface AssignVariableNode extends BaseNode { expression?: Expression; + leftSideIndex?: string; } export class AssignVariableProcessor implements NodeProcessor { @@ -63,7 +65,24 @@ export class AssignVariableProcessor implements NodeProcessor { } const AssignVariable = memo(function AssignVariableComponent({ data, id: _nodeId }: { data: AssignVariableNode; id: string }) { - const { isHovered, isSelected, isHighlighted, isCodeHighlighted, expression, width, height, visualId, isError, hasBreakpoint, isBreakpointTriggered } = data; + const { isHovered, isSelected, isHighlighted, isCodeHighlighted, expression, width, height, visualId, isError, hasBreakpoint, isBreakpointTriggered, leftSideIndex } = data as AssignVariableNode & { leftSideIndex?: string }; + + const buildDisplayString = () => { + if (!expression) return null; + const exprInstance = expression instanceof Expression ? expression : Expression.fromObject(expression); + // For assign variable expressions, leftSide is a Variable + if (exprInstance.leftSide instanceof Variable) { + const leftName = exprInstance.leftSide.name; + const leftDisplay = leftSideIndex ? `${leftName}[${leftSideIndex}]` : leftName; + const right = exprInstance.rightSide.map((e: any) => (e as ExpressionElement).toString()).join(' '); + return `${leftDisplay} = ${right}`; + } + // Fallback + return exprInstance.toString(); + }; + + const displayString = buildDisplayString(); + return (
- {expression instanceof Expression ? expression.toString() : Expression.fromObject(expression).toString()} + {displayString}
) : ( diff --git a/src/components/Panel/Tabs/editors/ConditionalEditor.tsx b/src/components/Panel/Tabs/editors/ConditionalEditor.tsx index 555fe84..91e867c 100644 --- a/src/components/Panel/Tabs/editors/ConditionalEditor.tsx +++ b/src/components/Panel/Tabs/editors/ConditionalEditor.tsx @@ -41,8 +41,10 @@ import { Label } from "@/components/ui/label"; import { DraggableExpressionElement, DraggablePaletteItem, - ExpressionDropArea + ExpressionDropArea, } from './shared/DragAndDropComponents'; +import ArrayIndexDialog from '@/components/ui/ArrayIndexDialog'; +import { parseArrayAccess, parseExpressionString } from '@/utils/expressionParsing'; // Available operators for expression building const operators = [ @@ -176,10 +178,20 @@ const ConditionalEditor = () => { const isInitialLoadRef = useRef(true); const previousNodeIdRef = useRef(null); const [expression, setExpression] = useState(null); + const [isIndexDialogOpen, setIsIndexDialogOpen] = useState(false); + const [arrayVariableForIndex, setArrayVariableForIndex] = useState(null); + const [indexSide, setIndexSide] = useState<'left' | 'right'>('left'); const [conditionalTab, setConditionalTab] = useState<'variables' | 'literals' | 'functions'>('variables'); const [_activeId, setActiveId] = useState(null); const [activeDraggableItem, setActiveDraggableItem] = useState(null); + // State for editing existing array access elements + const [editingElement, setEditingElement] = useState(null); + const [initialExpression, setInitialExpression] = useState(null); + const [initialRangeStart, setInitialRangeStart] = useState(null); + const [initialRangeEnd, setInitialRangeEnd] = useState(null); + const [initialTab, setInitialTab] = useState<'single' | 'range'>('single'); + const reactFlowInstance = useReactFlow(); const sensors = useSensors( @@ -352,6 +364,9 @@ const ConditionalEditor = () => { const variable = getAllVariables().find(v => v.id === varId); if (variable) { determinedActiveElement = new ExpressionElement(active.id, 'variable', variable.name, variable); + if (variable.type === 'array') { + determinedActiveElement.value = `${variable.name}[]`; + } } } else if (active.id.startsWith('op-')) { const op = active.id.replace('op-', ''); @@ -444,6 +459,19 @@ const ConditionalEditor = () => { return; } + // Intercept array variable drag + if (activeDraggableItem.type === 'variable' && activeDraggableItem.variable?.type === 'array') { + const overLocation = getElementLocationInfo(over.id, expression); + if (overLocation && overLocation.side && (overLocation.isMainDropArea || overLocation.isNestedDropArea || overLocation.isMainExpressionElement || overLocation.isNestedExpressionElement)) { + setArrayVariableForIndex(activeDraggableItem.variable); + setIndexSide(overLocation.side); // Set the side + setIsIndexDialogOpen(true); + setActiveId(null); + setActiveDraggableItem(null); + return; // Stop further processing + } + } + const activeLocation = getElementLocationInfo(active.id, expression); const overLocation = getElementLocationInfo(over.id, expression); @@ -530,6 +558,161 @@ const ConditionalEditor = () => { setActiveDraggableItem(null); }; + // Handle add variable including array index dialog + const handleAddVariable = (variable: Variable, side: 'left' | 'right') => { + if (variable.type === 'array') { + setArrayVariableForIndex(variable); + setIndexSide(side); + setIsIndexDialogOpen(true); + } else if (selectedNode?.type === 'Conditional' && expression && !isRunning) { + const element = new ExpressionElement( + crypto.randomUUID(), + 'variable', + variable.name, + variable + ); + setExpression(prev => { + if (!prev) return null; + const newExpr = prev.clone(); + if (indexSide === 'left' && Array.isArray(newExpr.leftSide)) { + newExpr.leftSide.push(element as any); + } else { + newExpr.rightSide.push(element); + } + return newExpr; + }); + } + }; + + // Handle editing array access elements + const handleEditArrayAccess = (element: ExpressionElement) => { + if (!element.value.includes('[') || !element.value.includes(']')) return; + + const parsed = parseArrayAccess(element.value); + if (!parsed) return; + + // Find the array variable + const arrayVar = getAllVariables().find(v => v.name === parsed.arrayName && v.type === 'array'); + if (!arrayVar) return; + + // Determine which side the element is on + let elementSide: 'left' | 'right' = 'right'; + if (expression?.leftSide && Array.isArray(expression.leftSide)) { + const foundOnLeft = expression.leftSide.find(e => e.id === element.id); + if (foundOnLeft) { + elementSide = 'left'; + } + } + + setEditingElement(element); + setArrayVariableForIndex(arrayVar); + setIndexSide(elementSide); + + if (parsed.isRange && parsed.rangeStart && parsed.rangeEnd) { + setInitialTab('range'); + setInitialRangeStart(parseExpressionString(parsed.rangeStart, getAllVariables())); + setInitialRangeEnd(parseExpressionString(parsed.rangeEnd, getAllVariables())); + setInitialExpression(null); + } else if (parsed.indexExpression) { + setInitialTab('single'); + setInitialExpression(parseExpressionString(parsed.indexExpression, getAllVariables())); + setInitialRangeStart(null); + setInitialRangeEnd(null); + } + + setIsIndexDialogOpen(true); + }; + + // Handle dialog submission for editing + const handleDialogSubmit = (_: 'single', expr: Expression) => { + if (arrayVariableForIndex && expression) { + const exprStr = expr.toString(); + const newValue = `${arrayVariableForIndex.name}[${exprStr}]`; + + if (editingElement) { + // Replace existing element + setExpression(prev => { + if (!prev) return null; + const newExpr = prev.clone(); + const elementToUpdate = newExpr.findElement(editingElement.id); + if (elementToUpdate) { + elementToUpdate.value = newValue; + } + return newExpr; + }); + } else { + // Add new element + const element = new ExpressionElement(crypto.randomUUID(), 'literal', newValue); + setExpression(prev => { + if (!prev) return null; + const newExpr = prev.clone(); + if (indexSide === 'left' && Array.isArray(newExpr.leftSide)) { + newExpr.leftSide.push(element as any); + } else { + newExpr.rightSide.push(element); + } + return newExpr; + }); + } + } + + // Reset state + setEditingElement(null); + setInitialExpression(null); + setInitialRangeStart(null); + setInitialRangeEnd(null); + setIsIndexDialogOpen(false); + }; + + // Handle dialog submission for range editing + const handleDialogSubmitRange = (_: 'range', start: Expression, end: Expression) => { + if (arrayVariableForIndex && expression) { + const newValue = `${arrayVariableForIndex.name}[${start.toString()}:${end.toString()}]`; + + if (editingElement) { + // Replace existing element + setExpression(prev => { + if (!prev) return null; + const newExpr = prev.clone(); + const elementToUpdate = newExpr.findElement(editingElement.id); + if (elementToUpdate) { + elementToUpdate.value = newValue; + } + return newExpr; + }); + } else { + // Add new element + const element = new ExpressionElement(crypto.randomUUID(), 'literal', newValue); + setExpression(prev => { + if (!prev) return null; + const newExpr = prev.clone(); + if (indexSide === 'left' && Array.isArray(newExpr.leftSide)) { + newExpr.leftSide.push(element as any); + } else { + newExpr.rightSide.push(element); + } + return newExpr; + }); + } + } + + // Reset state + setEditingElement(null); + setInitialExpression(null); + setInitialRangeStart(null); + setInitialRangeEnd(null); + setIsIndexDialogOpen(false); + }; + + // Handle dialog cancel + const handleDialogCancel = () => { + setEditingElement(null); + setInitialExpression(null); + setInitialRangeStart(null); + setInitialRangeEnd(null); + setIsIndexDialogOpen(false); + }; + // Don't render if not a Conditional node if (!selectedNode || selectedNode.type !== 'Conditional') return null; @@ -573,13 +756,14 @@ const ConditionalEditor = () => { ); } return ( - + ); }) ) : ( @@ -639,6 +823,7 @@ const ConditionalEditor = () => { index={index} removeExpressionElement={removeExpressionElement} disabled={isRunning} + onEdit={handleEditArrayAccess} /> ) )) || ( @@ -676,27 +861,11 @@ const ConditionalEditor = () => { key={`var-${variable.id}`} id={`var-${variable.id}`} type="variable" - value={variable.name} + value={variable.type === 'array' ? `${variable.name}[]` : variable.name} backgroundColor="#d1e7ff" disabled={isRunning} - onClick={() => { - const element = new ExpressionElement( - crypto.randomUUID(), - 'variable', - variable.name, - variable - ); - addElementOnLeftClick(element); - }} - onRightClick={() => { - const element = new ExpressionElement( - crypto.randomUUID(), - 'variable', - variable.name, - variable - ); - addElementOnRightClick(element); - }} + onClick={() => handleAddVariable(variable, 'left')} + onRightClick={() => handleAddVariable(variable, 'right')} /> ))} {getAllVariables().length === 0 && ( @@ -1012,12 +1181,26 @@ const ConditionalEditor = () => { px-2 py-1 m-1 rounded text-sm shadow-lg cursor-grabbing ${activeDraggableItem.type === 'variable' ? 'bg-blue-100' : activeDraggableItem.type === 'operator' ? 'bg-red-100' : + activeDraggableItem.type === 'literal' && activeDraggableItem.value.includes('[') && activeDraggableItem.value.includes(']') ? 'bg-blue-100' : activeDraggableItem.type === 'literal' ? 'bg-green-100' : 'bg-purple-100'} `}> {activeDraggableItem.value} ) : null} + + {/* Array index dialog */} + ); }; diff --git a/src/components/Panel/Tabs/editors/OutputEditor.tsx b/src/components/Panel/Tabs/editors/OutputEditor.tsx index 67a9dcd..9d09624 100644 --- a/src/components/Panel/Tabs/editors/OutputEditor.tsx +++ b/src/components/Panel/Tabs/editors/OutputEditor.tsx @@ -36,6 +36,8 @@ import { ExpressionDropArea } from './shared/DragAndDropComponents'; import { CSS } from '@dnd-kit/utilities'; +import ArrayIndexDialog from '@/components/ui/ArrayIndexDialog'; +import { parseArrayAccess, parseExpressionString } from '@/utils/expressionParsing'; // Available operators for expression building const operators = [ @@ -138,10 +140,19 @@ const OutputEditor = () => { const isInitialLoadRef = useRef(true); const previousNodeIdRef = useRef(null); const [expression, setExpression] = useState(null); + const [isIndexDialogOpen, setIsIndexDialogOpen] = useState(false); + const [arrayVariableForIndex, setArrayVariableForIndex] = useState(null); const [outputTab, setOutputTab] = useState<'variables' | 'literals' | 'functions'>('variables'); const [_activeId, setActiveId] = useState(null); const [activeDraggableItem, setActiveDraggableItem] = useState(null); + // State for editing existing array access elements + const [editingElement, setEditingElement] = useState(null); + const [initialExpression, setInitialExpression] = useState(null); + const [initialRangeStart, setInitialRangeStart] = useState(null); + const [initialRangeEnd, setInitialRangeEnd] = useState(null); + const [initialTab, setInitialTab] = useState<'single' | 'range'>('single'); + const reactFlowInstance = useReactFlow(); const sensors = useSensors( @@ -281,6 +292,9 @@ const OutputEditor = () => { const variable = getAllVariables().find(v => v.id === varId); if (variable) { determinedActiveElement = new ExpressionElement(active.id, 'variable', variable.name, variable); + if (variable.type === 'array') { + determinedActiveElement.value = `${variable.name}[]`; + } } } else if (active.id.startsWith('op-')) { const op = active.id.replace('op-', ''); @@ -384,6 +398,19 @@ const OutputEditor = () => { return; } + // Intercept array variable drag + if (activeDraggableItem.type === 'variable' && activeDraggableItem.variable?.type === 'array') { + const overLocation = getElementLocationInfo(over.id, expression); + // Check if it's a valid drop target + if (overLocation && (overLocation.isMainDropArea || overLocation.isNestedDropArea || overLocation.isMainExpressionElement || overLocation.isNestedExpressionElement)) { + setArrayVariableForIndex(activeDraggableItem.variable); + setIsIndexDialogOpen(true); + setActiveId(null); + setActiveDraggableItem(null); + return; // Stop further processing + } + } + const activeLocation = getElementLocationInfo(active.id, expression); const overLocation = getElementLocationInfo(over.id, expression); @@ -463,6 +490,138 @@ const OutputEditor = () => { setActiveDraggableItem(null); }; + // Handle add variable with array support + const handleAddVariable = (variable: Variable) => { + if (variable.type === 'array') { + setArrayVariableForIndex(variable); + setIsIndexDialogOpen(true); + } else if (selectedNode?.type === 'Output' && expression && !isRunning) { + const element = new ExpressionElement( + crypto.randomUUID(), + 'variable', + variable.name, + variable + ); + setExpression(prev => { + if (!prev) return null; + const newExpr = prev.clone(); + newExpr.addElement(element); + return newExpr; + }); + } + }; + + // Handle editing array access elements + const handleEditArrayAccess = (element: ExpressionElement) => { + if (!element.value.includes('[') || !element.value.includes(']')) return; + + const parsed = parseArrayAccess(element.value); + if (!parsed) return; + + // Find the array variable + const arrayVar = getAllVariables().find(v => v.name === parsed.arrayName && v.type === 'array'); + if (!arrayVar) return; + + setEditingElement(element); + setArrayVariableForIndex(arrayVar); + + if (parsed.isRange && parsed.rangeStart && parsed.rangeEnd) { + setInitialTab('range'); + setInitialRangeStart(parseExpressionString(parsed.rangeStart, getAllVariables())); + setInitialRangeEnd(parseExpressionString(parsed.rangeEnd, getAllVariables())); + setInitialExpression(null); + } else if (parsed.indexExpression) { + setInitialTab('single'); + setInitialExpression(parseExpressionString(parsed.indexExpression, getAllVariables())); + setInitialRangeStart(null); + setInitialRangeEnd(null); + } + + setIsIndexDialogOpen(true); + }; + + // Handle dialog submission for editing + const handleDialogSubmit = (_: 'single', expr: Expression) => { + if (arrayVariableForIndex) { + const exprStr = expr.toString(); + const newValue = `${arrayVariableForIndex.name}[${exprStr}]`; + + if (editingElement) { + // Replace existing element + setExpression(prev => { + if (!prev) return null; + const newExpr = prev.clone(); + const elementToUpdate = newExpr.findElement(editingElement.id); + if (elementToUpdate) { + elementToUpdate.value = newValue; + } + return newExpr; + }); + } else { + // Add new element + const element = new ExpressionElement(crypto.randomUUID(), 'literal', newValue); + setExpression(prev => { + if (!prev) return null; + const newExpr = prev.clone(); + newExpr.addElement(element); + return newExpr; + }); + } + } + + // Reset state + setEditingElement(null); + setInitialExpression(null); + setInitialRangeStart(null); + setInitialRangeEnd(null); + setIsIndexDialogOpen(false); + }; + + // Handle dialog submission for range editing + const handleDialogSubmitRange = (_: 'range', start: Expression, end: Expression) => { + if (arrayVariableForIndex) { + const newValue = `${arrayVariableForIndex.name}[${start.toString()}:${end.toString()}]`; + + if (editingElement) { + // Replace existing element + setExpression(prev => { + if (!prev) return null; + const newExpr = prev.clone(); + const elementToUpdate = newExpr.findElement(editingElement.id); + if (elementToUpdate) { + elementToUpdate.value = newValue; + } + return newExpr; + }); + } else { + // Add new element + const element = new ExpressionElement(crypto.randomUUID(), 'literal', newValue); + setExpression(prev => { + if (!prev) return null; + const newExpr = prev.clone(); + newExpr.addElement(element); + return newExpr; + }); + } + } + + // Reset state + setEditingElement(null); + setInitialExpression(null); + setInitialRangeStart(null); + setInitialRangeEnd(null); + setIsIndexDialogOpen(false); + }; + + // Handle dialog cancel + const handleDialogCancel = () => { + setEditingElement(null); + setInitialExpression(null); + setInitialRangeStart(null); + setInitialRangeEnd(null); + setIsIndexDialogOpen(false); + }; + // Don't render if not an Output node if (!selectedNode || selectedNode.type !== 'Output' || !expression) return null; @@ -507,6 +666,7 @@ const OutputEditor = () => { index={index} removeExpressionElement={removeExpressionElement} disabled={isRunning} + onEdit={handleEditArrayAccess} /> ); })} @@ -538,18 +698,10 @@ const OutputEditor = () => { key={`var-${variable.id}`} id={`var-${variable.id}`} type="variable" - value={variable.name} + value={variable.type === 'array' ? `${variable.name}[]` : variable.name} backgroundColor="#d1e7ff" disabled={isRunning} - onClick={() => { - const element = new ExpressionElement( - crypto.randomUUID(), - 'variable', - variable.name, - variable - ); - addExpressionElement(element); - }} + onClick={() => handleAddVariable(variable)} /> ))} {allVariables.length === 0 && ( @@ -752,12 +904,27 @@ const OutputEditor = () => {
{activeDraggableItem.value}
) : null} + + {/* Render dialog */} + ); }; diff --git a/src/components/Panel/Tabs/editors/VariableAssignmentEditor.tsx b/src/components/Panel/Tabs/editors/VariableAssignmentEditor.tsx index f824b92..82d3c7e 100644 --- a/src/components/Panel/Tabs/editors/VariableAssignmentEditor.tsx +++ b/src/components/Panel/Tabs/editors/VariableAssignmentEditor.tsx @@ -43,6 +43,8 @@ import { DraggablePaletteItem, ExpressionDropArea } from './shared/DragAndDropComponents'; +import ArrayIndexDialog from '@/components/ui/ArrayIndexDialog'; +import { parseArrayAccess, parseExpressionString } from '@/utils/expressionParsing'; // Available operators for expression building const operators = [ @@ -149,6 +151,18 @@ const VariableAssignmentEditor = () => { const [assignmentTab, setAssignmentTab] = useState<'variables' | 'literals' | 'functions'>('variables'); const [_activeId, setActiveId] = useState(null); const [activeDraggableItem, setActiveDraggableItem] = useState(null); + const [isIndexDialogOpen, setIsIndexDialogOpen] = useState(false); + const [arrayVariableForIndex, setArrayVariableForIndex] = useState(null); + // Left-side (assignment target) index state + const [leftSideIndexString, setLeftSideIndexString] = useState(null); + const [isEditingLeftSideIndex, setIsEditingLeftSideIndex] = useState(false); + + // State for editing existing array access elements + const [editingElement, setEditingElement] = useState(null); + const [initialExpression, setInitialExpression] = useState(null); + const [initialRangeStart, setInitialRangeStart] = useState(null); + const [initialRangeEnd, setInitialRangeEnd] = useState(null); + const [initialTab, setInitialTab] = useState<'single' | 'range'>('single'); const reactFlowInstance = useReactFlow(); @@ -193,6 +207,11 @@ const VariableAssignmentEditor = () => { return; } + // Reset index state if variable type changed to non-array + if (varInstance.type !== 'array' && leftSideIndexString) { + setLeftSideIndexString(null); + } + // Only update if the left side has changed or if the expression is null if (!expression || (expression.leftSide instanceof Variable && expression.leftSide.id !== varInstance.id)) { // Preserve right side if expression already exists and has one @@ -202,24 +221,19 @@ const VariableAssignmentEditor = () => { if (expression !== null) setExpression(null); } } - }, [leftSideVariable, selectedNode, getAllVariables, expression]); + }, [leftSideVariable, selectedNode, getAllVariables, expression, leftSideIndexString]); // Update the node data when expression changes useEffect(() => { if (selectedNode?.type === 'AssignVariable' && !isInitialLoadRef.current) { - let updatedData; - if (expression) { - updatedData = { - expression: expression.toObject() - }; - } else { - updatedData = { expression: null }; - } - - // Update the node data + const updatedData: any = { + expression: expression ? expression.toObject() : null, + leftSideIndex: leftSideIndexString || undefined, + }; + reactFlowInstance.updateNodeData(selectedNode.id, updatedData); } - }, [expression, reactFlowInstance, selectedNode]); + }, [expression, leftSideIndexString, reactFlowInstance, selectedNode]); // Load assignment data when the selected node changes or when its data changes (for collaboration) useEffect(() => { @@ -246,6 +260,12 @@ const VariableAssignmentEditor = () => { // Check if the variable with this ID still exists if (allVariables.some(v => v.id === varId)) { setLeftSideVariable(varId); + // Load stored leftSideIndex if present + if (selectedNode.data.leftSideIndex) { + setLeftSideIndexString(selectedNode.data.leftSideIndex as string); + } else { + setLeftSideIndexString(null); + } // Try to recreate the expression const leftVar = allVariables.find(v => v.id === varId); if (leftVar) { @@ -262,12 +282,14 @@ const VariableAssignmentEditor = () => { // Only reset form state when it's a new node (not when collaborative data is cleared) setLeftSideVariable(''); setExpression(null); + setLeftSideIndexString(null); } } } else { previousNodeIdRef.current = null; setLeftSideVariable(''); setExpression(null); + setLeftSideIndexString(null); } isInitialLoadRef.current = false; @@ -328,6 +350,9 @@ const VariableAssignmentEditor = () => { const variable = getAllVariables().find(v => v.id === varId); if (variable) { determinedActiveElement = new ExpressionElement(active.id, 'variable', variable.name, variable); + if (variable.type === 'array') { + determinedActiveElement.value = `${variable.name}[]`; + } } } else if (active.id.startsWith('op-')) { const op = active.id.replace('op-', ''); @@ -431,6 +456,19 @@ const VariableAssignmentEditor = () => { return; } + // Intercept array variable drag + if (activeDraggableItem.type === 'variable' && activeDraggableItem.variable?.type === 'array') { + const overLocation = getElementLocationInfo(over.id, expression); + // Check if it's a valid drop target + if (overLocation && (overLocation.isMainDropArea || overLocation.isNestedDropArea || overLocation.isMainExpressionElement || overLocation.isNestedExpressionElement)) { + setArrayVariableForIndex(activeDraggableItem.variable); + setIsIndexDialogOpen(true); + setActiveId(null); + setActiveDraggableItem(null); + return; // Stop further processing + } + } + const activeLocation = getElementLocationInfo(active.id, expression); const overLocation = getElementLocationInfo(over.id, expression); @@ -510,371 +548,623 @@ const VariableAssignmentEditor = () => { setActiveDraggableItem(null); }; + // Handle drop/click for array variable + const handleAddVariable = (variable: Variable) => { + if (variable.type === 'array') { + setArrayVariableForIndex(variable); + setIsIndexDialogOpen(true); + } else if (selectedNode?.type === 'AssignVariable' && expression && !isRunning) { + const element = new ExpressionElement( + crypto.randomUUID(), + 'variable', + variable.name, + variable + ); + setExpression(prev => { + if (!prev) return null; + const newExpr = prev.clone(); + newExpr.addElement(element); + return newExpr; + }); + } + }; + + // Modify variable dropdown change handler to support array index dialog + const handleLeftVariableChange = (value: string) => { + if (value === '__CLEAR__') { + setLeftSideVariable(''); + setLeftSideIndexString(null); + return; + } + + const variable = getAllVariables().find(v => v.id === value); + if (!variable) return; + + setLeftSideVariable(variable.id); + + if (variable.type === 'array') { + // Open index dialog immediately + setArrayVariableForIndex(variable); + setIsEditingLeftSideIndex(true); + // Prefill if already had an index + if (leftSideIndexString) { + const parsed = parseArrayAccess(`${variable.name}[${leftSideIndexString}]`); + if (parsed) { + if (parsed.isRange && parsed.rangeStart && parsed.rangeEnd) { + setInitialTab('range'); + setInitialRangeStart(parseExpressionString(parsed.rangeStart, getAllVariables())); + setInitialRangeEnd(parseExpressionString(parsed.rangeEnd, getAllVariables())); + setInitialExpression(null); + } else if (parsed.indexExpression) { + setInitialTab('single'); + setInitialExpression(parseExpressionString(parsed.indexExpression, getAllVariables())); + setInitialRangeStart(null); + setInitialRangeEnd(null); + } + } + } else { + setLeftSideIndexString(null); + } + setIsIndexDialogOpen(true); + } else { + setLeftSideIndexString(null); + } + }; + + // Override dialog submit handlers to manage left-side edits first + const handleDialogSubmit = (_: 'single', expr: Expression) => { + if (isEditingLeftSideIndex && arrayVariableForIndex) { + if (expr.isEmpty()) { + // Treat as cancel – clear variable selection + setLeftSideVariable(''); + setLeftSideIndexString(null); + } else { + setLeftSideIndexString(expr.toString()); + } + setIsEditingLeftSideIndex(false); + setIsIndexDialogOpen(false); + return; + } + if (arrayVariableForIndex) { + const exprStr = expr.toString(); + const newValue = `${arrayVariableForIndex.name}[${exprStr}]`; + + if (editingElement) { + // Replace existing element + setExpression(prev => { + if (!prev) return null; + const newExpr = prev.clone(); + const elementToUpdate = newExpr.findElement(editingElement.id); + if (elementToUpdate) { + elementToUpdate.value = newValue; + } + return newExpr; + }); + } else { + // Add new element + const element = new ExpressionElement(crypto.randomUUID(), 'literal', newValue); + setExpression(prev => { + if (!prev) return null; + const newExpr = prev.clone(); + newExpr.addElement(element); + return newExpr; + }); + } + } + + // Reset state + setEditingElement(null); + setInitialExpression(null); + setInitialRangeStart(null); + setInitialRangeEnd(null); + setIsIndexDialogOpen(false); + }; + + // Handle dialog submission for range editing + const handleDialogSubmitRange = (_: 'range', start: Expression, end: Expression) => { + if (isEditingLeftSideIndex && arrayVariableForIndex) { + if (start.isEmpty() || end.isEmpty()) { + setLeftSideVariable(''); + setLeftSideIndexString(null); + } else { + setLeftSideIndexString(`${start.toString()}:${end.toString()}`); + } + setIsEditingLeftSideIndex(false); + setIsIndexDialogOpen(false); + return; + } + if (arrayVariableForIndex) { + const newValue = `${arrayVariableForIndex.name}[${start.toString()}:${end.toString()}]`; + + if (editingElement) { + // Replace existing element + setExpression(prev => { + if (!prev) return null; + const newExpr = prev.clone(); + const elementToUpdate = newExpr.findElement(editingElement.id); + if (elementToUpdate) { + elementToUpdate.value = newValue; + } + return newExpr; + }); + } else { + // Add new element + const element = new ExpressionElement(crypto.randomUUID(), 'literal', newValue); + setExpression(prev => { + if (!prev) return null; + const newExpr = prev.clone(); + newExpr.addElement(element); + return newExpr; + }); + } + } + + // Reset state + setEditingElement(null); + setInitialExpression(null); + setInitialRangeStart(null); + setInitialRangeEnd(null); + setIsIndexDialogOpen(false); + }; + + // Handle dialog cancel + const handleDialogCancel = () => { + setEditingElement(null); + setInitialExpression(null); + setInitialRangeStart(null); + setInitialRangeEnd(null); + setIsIndexDialogOpen(false); + + if (isEditingLeftSideIndex && !leftSideIndexString) { + // User canceled before providing an index -> deselect variable + setLeftSideVariable(''); + setIsEditingLeftSideIndex(false); + setArrayVariableForIndex(null); + } + }; + + // Handler to edit existing array access elements on RHS (dragged literals) + const handleEditArrayAccess = (element: ExpressionElement) => { + if (!element.value.includes('[') || !element.value.includes(']')) return; + + const parsed = parseArrayAccess(element.value); + if (!parsed) return; + + const arrayVar = getAllVariables().find(v => v.name === parsed.arrayName && v.type === 'array'); + if (!arrayVar) return; + + setEditingElement(element); + setArrayVariableForIndex(arrayVar); + + if (parsed.isRange && parsed.rangeStart && parsed.rangeEnd) { + setInitialTab('range'); + setInitialRangeStart(parseExpressionString(parsed.rangeStart, getAllVariables())); + setInitialRangeEnd(parseExpressionString(parsed.rangeEnd, getAllVariables())); + setInitialExpression(null); + } else if (parsed.indexExpression) { + setInitialTab('single'); + setInitialExpression(parseExpressionString(parsed.indexExpression, getAllVariables())); + setInitialRangeStart(null); + setInitialRangeEnd(null); + } + + setIsIndexDialogOpen(true); + }; + // Don't render if not an AssignVariable node if (!selectedNode || selectedNode.type !== 'AssignVariable') return null; const allVariables = getAllVariables(); return ( - -
- {/* Assignment expression display box */} - - -
-
- -
-
- {/* Spacer for equals sign */} -
-
- -
-
-
- -
+ <> + + +
+ {/* Assignment expression display box */} + +
- {/* Variable selection */}
- +
- - {/* Equals sign */}
- = + {/* Spacer for equals sign */}
- - {/* Expression area */}
- {leftSideVariable && expression ? ( - - item.id)} - strategy={horizontalListSortingStrategy} - > - {expression.rightSide.map((element, index) => { - if (element.type === 'function') { + +
+
+
+ +
+
+ {/* Variable selection */} +
+ + {/* Index display / edit button for arrays */} + {(() => { + const selectedVar = allVariables.find(v => v.id === leftSideVariable); + if (selectedVar && selectedVar.type === 'array') { + return ( + + ); + } + return null; + })()} +
+ + {/* Equals sign */} +
+ = +
+ + {/* Expression area */} +
+ {leftSideVariable && expression ? ( + + item.id)} + strategy={horizontalListSortingStrategy} + > + {expression.rightSide.map((element, index) => { + if (element.type === 'function') { + return ( + + ); + } return ( - - ); - } - return ( - - ); - })} - - - ) : ( -
- - Select a variable to build expression - -
- )} + ); + })} + + + ) : ( +
+ + Select a variable to build expression + +
+ )} +
-
- - - - {/* Building blocks: variables, operators, literals */} - {leftSideVariable && ( - <> -
- {/* Left column: Tabbed Variables and Literals */} - - - setAssignmentTab(value as 'variables' | 'literals' | 'functions')} className="w-full"> - - Variables - Literals - Functions - - - - - setAssignmentTab(value as 'variables' | 'literals' | 'functions')}> - -
- {allVariables.map(variable => ( - { - const element = new ExpressionElement( - crypto.randomUUID(), - 'variable', - variable.name, - variable - ); - addExpressionElement(element); - }} - /> - ))} - {allVariables.length === 0 && ( -
No variables declared
- )} -
-
- - -
- {/* Boolean literals */} -
- -
- { - const element = new ExpressionElement( - crypto.randomUUID(), - 'literal', - 'true' - ); - addExpressionElement(element); - }} - /> + + + + {/* Building blocks: variables, operators, literals */} + {leftSideVariable && ( + <> +
+ {/* Left column: Tabbed Variables and Literals */} + + + setAssignmentTab(value as 'variables' | 'literals' | 'functions')} className="w-full"> + + Variables + Literals + Functions + + + + + setAssignmentTab(value as 'variables' | 'literals' | 'functions')}> + +
+ {allVariables.map(variable => ( { - const element = new ExpressionElement( - crypto.randomUUID(), - 'literal', - 'false' - ); - addExpressionElement(element); - }} + onClick={() => handleAddVariable(variable)} /> -
+ ))} + {allVariables.length === 0 && ( +
No variables declared
+ )}
- - {/* Integer literal */} -
- -
- - -
-
- - {/* String literal */} -
- -
- - + }} + /> +
+
+ + {/* Integer literal */} +
+ +
+ + +
+
+ + {/* String literal */} +
+ +
+ + +
+
+ + {/* Float literal */} +
+ +
+ + +
- - {/* Float literal */} -
- -
- + + +
+ {/* Conversion functions */} + {['integer', 'string', 'float', 'boolean'].map(func => ( + - -
+ /> + ))}
-
- - - -
- {/* Conversion functions */} - {['integer', 'string', 'float', 'boolean'].map(func => ( - { - const element = new ExpressionElement( - crypto.randomUUID(), - 'function', - func, - new Expression(undefined, []) - ); - addExpressionElement(element); - }} - /> - ))} -
-
- - - - - {/* Right column: Operators */} - - - Operators - - - {operators.map(op => ( - { - const element = new ExpressionElement( - crypto.randomUUID(), - 'operator', - op - ); - addExpressionElement(element); - }} - /> - ))} - - + + + + + + {/* Right column: Operators */} + + + Operators + + + {operators.map(op => ( + { + const element = new ExpressionElement( + crypto.randomUUID(), + 'operator', + op + ); + addExpressionElement(element); + }} + /> + ))} + + +
+ + )} +
+ + + {activeDraggableItem ? ( +
+ {activeDraggableItem.value}
- - )} -
- - - {activeDraggableItem ? ( -
- {activeDraggableItem.value} -
- ) : null} -
-
+ ) : null} + + + ); }; diff --git a/src/components/Panel/Tabs/editors/VariableDeclarationEditor.tsx b/src/components/Panel/Tabs/editors/VariableDeclarationEditor.tsx index 729f7b8..f7b57ba 100644 --- a/src/components/Panel/Tabs/editors/VariableDeclarationEditor.tsx +++ b/src/components/Panel/Tabs/editors/VariableDeclarationEditor.tsx @@ -1,7 +1,7 @@ import { useContext, useState, useEffect, useRef, useCallback } from 'react'; import { SelectedNodeContext } from '../../../../context/SelectedNodeContext'; import { useVariables } from '../../../../context/VariablesContext'; -import { Variable } from '../../../../models'; +import { Variable, arraySubtypes } from '../../../../models'; import { variableTypes } from '../../../../models'; import { useFlowExecutorState } from '../../../../context/FlowExecutorContext'; @@ -20,6 +20,8 @@ const VariableDeclarationEditor = () => { const { selectedNode } = useContext(SelectedNodeContext); const { updateNodeVariables, getAllVariables } = useVariables(); const [variables, setVariables] = useState([]); + const [arraySizeInputs, setArraySizeInputs] = useState>({}); + const [arraySizeErrors, setArraySizeErrors] = useState>({}); const isInitialLoadRef = useRef(true); const updateTimeoutRef = useRef(null); const previousNodeIdRef = useRef(null); @@ -61,7 +63,7 @@ const VariableDeclarationEditor = () => { setVariables(nodeVars); } else { // Show placeholder if no variables are declared - const newVar = new Variable(crypto.randomUUID(), 'integer', '', selectedNode.id); + const newVar = new Variable(crypto.randomUUID(), 'integer', '', selectedNode.id, undefined, undefined); setVariables([newVar]); } } else { @@ -108,17 +110,38 @@ const VariableDeclarationEditor = () => { crypto.randomUUID(), 'integer', '', - selectedNode.id + selectedNode.id, + undefined, + undefined ); setVariables(prev => [...prev, newVar]); } }; - const updateVariable = (id: string, field: 'type' | 'name', value: string) => { + const updateVariable = (id: string, field: 'type' | 'name' | 'arraySubtype' | 'arraySize', value: string | number) => { if (selectedNode && selectedNode.type === 'DeclareVariable' && !isRunning) { setVariables(prev => - prev.map(v => v.id === id ? v.update({ [field]: value }) : v) + prev.map(v => { + if (v.id === id) { + const updates: Partial = { [field]: value }; + + // When changing from array to non-array type, clear array properties + if (field === 'type' && value !== 'array') { + updates.arraySubtype = undefined; + updates.arraySize = undefined; + } + + // When changing to array type, set default array properties if not set + if (field === 'type' && value === 'array') { + updates.arraySubtype = v.arraySubtype || 'integer'; + updates.arraySize = v.arraySize !== undefined && v.arraySize > 0 ? v.arraySize : 10; + } + + return v.update(updates); + } + return v; + }) ); } }; @@ -139,40 +162,124 @@ const VariableDeclarationEditor = () => { {variables.map((variable) => ( -
- - - updateVariable(variable.id, 'name', e.target.value)} - placeholder="Variable name" - className="flex-1" - disabled={isRunning} - /> - - {variables.length > 1 && ( - + )} +
+ + {/* Array configuration - only show when type is 'array' */} + {variable.type === 'array' && ( +
+ Array of: + + + Size: + { + const inputValue = e.target.value; + // Update the local input state immediately for responsive UI + setArraySizeInputs(prev => ({ + ...prev, + [variable.id]: inputValue + })); + + const size = parseInt(inputValue); + const isValid = !isNaN(size) && size >= 1; + setArraySizeErrors(prev => ({ + ...prev, + [variable.id]: !isValid + })); + + if (isValid) { + updateVariable(variable.id, 'arraySize', size); + } + }} + onBlur={(e) => { + const inputValue = e.target.value; + const size = parseInt(inputValue); + const isValid = !isNaN(size) && size >= 1; + if (!isValid) { + // Reset to minimum value if empty or invalid + const newSize = 1; + updateVariable(variable.id, 'arraySize', newSize); + setArraySizeInputs(prev => ({ + ...prev, + [variable.id]: newSize.toString() + })); + setArraySizeErrors(prev => ({ + ...prev, + [variable.id]: false + })); + } else { + // Clear local input state if valid + setArraySizeInputs(prev => { + const newState = { ...prev }; + delete newState[variable.id]; + return newState; + }); + setArraySizeErrors(prev => ({ + ...prev, + [variable.id]: false + })); + } + }} + placeholder="Size" + className={`w-20 ${arraySizeErrors[variable.id] ? 'border-destructive focus-visible:ring-destructive' : ''}`} + disabled={isRunning} + min="1" + /> + {arraySizeErrors[variable.id] && ( + Enter > 0 + )} +
)}
))} diff --git a/src/components/Panel/Tabs/editors/shared/DragAndDropComponents.tsx b/src/components/Panel/Tabs/editors/shared/DragAndDropComponents.tsx index 1c30951..54bb96e 100644 --- a/src/components/Panel/Tabs/editors/shared/DragAndDropComponents.tsx +++ b/src/components/Panel/Tabs/editors/shared/DragAndDropComponents.tsx @@ -8,6 +8,8 @@ import { import { CSS } from '@dnd-kit/utilities'; import { ExpressionElement } from '../../../../../models'; import { Button } from "@/components/ui/button"; +import React from 'react'; +import { SortableContext, horizontalListSortingStrategy } from '@dnd-kit/sortable'; // Define props interfaces for components export interface DraggableExpressionElementProps { @@ -15,10 +17,11 @@ export interface DraggableExpressionElementProps { index: number; removeExpressionElement: (id: string) => void; disabled: boolean; + onEdit?: (element: ExpressionElement) => void; } // Draggable expression element component -export const DraggableExpressionElement = ({ element, removeExpressionElement, disabled }: DraggableExpressionElementProps) => { +export const DraggableExpressionElement = ({ element, removeExpressionElement, disabled, onEdit }: DraggableExpressionElementProps) => { const { attributes, listeners, @@ -28,9 +31,22 @@ export const DraggableExpressionElement = ({ element, removeExpressionElement, d isDragging } = useSortable({ id: element.id, disabled }); + // Determine background color - treat array access as variables (blue) const bgColor = element.type === 'variable' ? 'bg-blue-100' : - element.type === 'operator' ? 'bg-red-100' : 'bg-green-100'; + element.type === 'operator' ? 'bg-red-100' : + element.type === 'literal' && element.value.includes('[') && element.value.includes(']') ? 'bg-blue-100' : + element.type === 'literal' ? 'bg-green-100' : 'bg-purple-100'; + + // Check if this is an array access element that can be edited + const isArrayAccess = element.type === 'literal' && element.value.includes('[') && element.value.includes(']'); + + const handleClick = (e: React.MouseEvent) => { + if (isArrayAccess && onEdit && !disabled) { + e.stopPropagation(); + onEdit(element); + } + }; return (
{element.value} +
+ ); +}; +// END: FunctionExpressionElement \ No newline at end of file diff --git a/src/components/ui/ArrayIndexDialog.tsx b/src/components/ui/ArrayIndexDialog.tsx new file mode 100644 index 0000000..7afaa4a --- /dev/null +++ b/src/components/ui/ArrayIndexDialog.tsx @@ -0,0 +1,645 @@ +import React, { useState } from 'react'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'; +import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'; +import { Button } from '@/components/ui/button'; +import { Label } from '@/components/ui/label'; +import { Input } from '@/components/ui/input'; +import { DraggablePaletteItem } from '@/components/Panel/Tabs/editors/shared/DragAndDropComponents'; +import { Expression, ExpressionElement, Variable } from '../../models'; +import ExpressionBuilder from './ExpressionBuilder'; +import { + DndContext, + useSensors, + useSensor, + PointerSensor, + DragOverlay, + rectIntersection, + DragStartEvent, + DragEndEvent, +} from '@dnd-kit/core'; +import { useVariables } from '../../context/VariablesContext'; +import { operators as expressionOperators } from '../../models/Expression'; + +const uuid = () => crypto.randomUUID(); + +// Operators list for quick lookup inside dialog +const operators = [...expressionOperators]; + +// Interface to describe the location of an element for drag & drop logic +interface ElementLocation { + id: string; + isPaletteItem: boolean; + isMainDropArea: boolean; + isMainExpressionElement: boolean; + isNestedDropArea: boolean; + isNestedExpressionElement: boolean; + dropAreaId?: string; // Which expression builder (single-index-drop, range-start-drop, etc.) + funcId?: string; + funcIdPath?: string[]; + elementActualId?: string; + item?: ExpressionElement; + index?: number; +} + +interface ArrayIndexDialogProps { + open: boolean; + variableName: string; + onCancel: () => void; + onSubmit: (type: 'single', expr: Expression) => void; + onSubmitRange?: (type: 'range', start: Expression, end: Expression) => void; + initialExpression?: Expression; + initialRangeStart?: Expression; + initialRangeEnd?: Expression; + initialTab?: 'single' | 'range'; +} + +const ArrayIndexDialog: React.FC = ({ + open, + variableName, + onCancel, + onSubmit, + onSubmitRange, + initialExpression, + initialRangeStart, + initialRangeEnd, + initialTab = 'single' +}) => { + const { getAllVariables } = useVariables(); + const [tab, setTab] = useState<'single' | 'range'>(initialTab); + + const [singleExpr, setSingleExpr] = useState(initialExpression || new Expression(undefined, [])); + const [rangeStart, setRangeStart] = useState(initialRangeStart || new Expression(undefined, [])); + const [rangeEnd, setRangeEnd] = useState(initialRangeEnd || new Expression(undefined, [])); + + const [_activeId, setActiveId] = useState(null); + const [activeItem, setActiveItem] = useState(null); + const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 8 } })); + + // Reset expressions when dialog opens or when initial values change + React.useEffect(() => { + if (open) { + setTab(initialTab); + setSingleExpr(initialExpression || new Expression(undefined, [])); + setRangeStart(initialRangeStart || new Expression(undefined, [])); + setRangeEnd(initialRangeEnd || new Expression(undefined, [])); + } + }, [open, initialExpression, initialRangeStart, initialRangeEnd, initialTab]); + + // Reset expressions when dialog is cancelled or closed + const handleCancel = () => { + setSingleExpr(new Expression(undefined, [])); + setRangeStart(new Expression(undefined, [])); + setRangeEnd(new Expression(undefined, [])); + setTab('single'); + onCancel(); + }; + + // Helper function to create new expression element (same as editors) + const createNewElement = (activeItem: ExpressionElement, getAllVars: () => Variable[]): ExpressionElement | null => { + if (activeItem.type === 'variable') { + const varId = activeItem.variable?.id || (activeItem.id.startsWith('var-') ? activeItem.id.replace('var-', '') : null); + if (varId) { + const variable = getAllVars().find(v => v.id === varId); + if (variable) { + return new ExpressionElement(uuid(), 'variable', variable.name, variable); + } + } + if (activeItem.variable instanceof Variable) { + return new ExpressionElement(uuid(), 'variable', activeItem.variable.name, activeItem.variable.clone()); + } + return null; + } else if (activeItem.type === 'function') { + const nestedExpr = activeItem.nestedExpression ? activeItem.nestedExpression.clone() : new Expression(undefined, []); + return new ExpressionElement(uuid(), 'function', activeItem.value, nestedExpr); + } else { + return new ExpressionElement(uuid(), activeItem.type, activeItem.value); + } + }; + + // Recursive element location finder (same as editors) + const findElementLocationRecursive = ( + id: string, + elements: ExpressionElement[], + funcIdPath: string[] + ): ElementLocation | null => { + for (let index = 0; index < elements.length; index++) { + const item = elements[index]; + if (item.id === id) { + if (funcIdPath.length > 0) { + return { + id, isPaletteItem: false, isMainDropArea: false, isMainExpressionElement: false, + isNestedDropArea: false, isNestedExpressionElement: true, + funcId: funcIdPath[funcIdPath.length - 1], funcIdPath, elementActualId: id, item, index + }; + } else { + return { + id, isPaletteItem: false, isMainDropArea: false, isMainExpressionElement: true, + isNestedDropArea: false, isNestedExpressionElement: false, + elementActualId: id, item, index, funcIdPath + }; + } + } + if (item.isFunction() && item.nestedExpression) { + const found = findElementLocationRecursive(id, item.nestedExpression.rightSide, [...funcIdPath, item.id]); + if (found) return found; + } + } + return null; + }; + + // Element location detection (adapted from editors) + const getElementLocationInfo = (id: string): ElementLocation | null => { + if (id.startsWith('var-') || id.startsWith('op-') || id.startsWith('lit-') || id.startsWith('func-')) { + return { id, isPaletteItem: true, isMainDropArea: false, isMainExpressionElement: false, isNestedDropArea: false, isNestedExpressionElement: false }; + } + + // Main drop areas + if (id === 'single-index-drop') { + return { id, isPaletteItem: false, isMainDropArea: true, isMainExpressionElement: false, isNestedDropArea: false, isNestedExpressionElement: false, dropAreaId: 'single-index-drop' }; + } + if (id === 'range-start-drop') { + return { id, isPaletteItem: false, isMainDropArea: true, isMainExpressionElement: false, isNestedDropArea: false, isNestedExpressionElement: false, dropAreaId: 'range-start-drop' }; + } + if (id === 'range-end-drop') { + return { id, isPaletteItem: false, isMainDropArea: true, isMainExpressionElement: false, isNestedDropArea: false, isNestedExpressionElement: false, dropAreaId: 'range-end-drop' }; + } + + // Nested drop areas + if (id.startsWith('nested-')) { + const funcId = id.replace('nested-', ''); + let dropAreaId = ''; + let funcElemPath: ElementLocation | null = null; + + // Try to find in each expression + const allExpressions = [ + { expr: singleExpr, areaId: 'single-index-drop' }, + { expr: rangeStart, areaId: 'range-start-drop' }, + { expr: rangeEnd, areaId: 'range-end-drop' } + ]; + + for (const { expr, areaId } of allExpressions) { + funcElemPath = findElementLocationRecursive(funcId, expr.rightSide, []); + if (funcElemPath) { + dropAreaId = areaId; + break; + } + } + + return { + id, isPaletteItem: false, isMainDropArea: false, isMainExpressionElement: false, + isNestedDropArea: true, isNestedExpressionElement: false, + dropAreaId, funcId, funcIdPath: funcElemPath ? [...(funcElemPath.funcIdPath || []), funcId] : [funcId] + }; + } + + // Check for expression elements in each expression + const allExpressions = [ + { expr: singleExpr, areaId: 'single-index-drop' }, + { expr: rangeStart, areaId: 'range-start-drop' }, + { expr: rangeEnd, areaId: 'range-end-drop' } + ]; + + for (const { expr, areaId } of allExpressions) { + const found = findElementLocationRecursive(id, expr.rightSide, []); + if (found) { + return { ...found, dropAreaId: areaId }; + } + } + + return null; + }; + + + + const getExpressionStateUpdater = (dropAreaId: string): React.Dispatch> | null => { + if (dropAreaId === 'single-index-drop') return setSingleExpr; + if (dropAreaId === 'range-start-drop') return setRangeStart; + if (dropAreaId === 'range-end-drop') return setRangeEnd; + return null; + }; + + const handleAddExpressionElement = (dropAreaId: string, type: 'literal' | 'variable' | 'operator' | 'function', value: string) => { + const setState = getExpressionStateUpdater(dropAreaId); + if (!setState) return; + + let newElement: ExpressionElement; + if (type === 'variable') { + const variable = getAllVariables().find(v => v.id === value); + if (!variable) return; + newElement = new ExpressionElement(uuid(), 'variable', variable.name, variable); + } else if (type === 'function') { + newElement = new ExpressionElement(uuid(), 'function', value, new Expression(undefined, [])); + } else { + newElement = new ExpressionElement(uuid(), type, value); + } + + setState(prev => prev.clone().addElement(newElement)); + }; + + const handleRemoveExpressionElement = (dropAreaId: string, elementId: string) => { + const setState = getExpressionStateUpdater(dropAreaId); + if (setState) { + setState(prev => prev.clone().removeElement(elementId)); + } + }; + + const handleDragStart = (event: DragStartEvent) => { + const { active } = event; + setActiveId(active.id as string); + + const findActiveElementRecursive = (elements: ExpressionElement[], id: string): ExpressionElement | null => { + for (const element of elements) { + if (element.id === id) { + return element; + } + if (element.isFunction() && element.nestedExpression) { + const found = findActiveElementRecursive(element.nestedExpression.rightSide, id); + if (found) { + return found; + } + } + } + return null; + }; + + let determinedActiveElement: ExpressionElement | null = null; + + // Check all expressions for the active element + const allExpressions = [singleExpr, rangeStart, rangeEnd]; + for (const expr of allExpressions) { + determinedActiveElement = findActiveElementRecursive(expr.rightSide, active.id as string); + if (determinedActiveElement) break; + } + + if (!determinedActiveElement) { + // Element is from palette + if ((active.id as string).startsWith('var-')) { + const varId = (active.id as string).replace('var-', ''); + const variable = getAllVariables().find(v => v.id === varId); + if (variable) { + determinedActiveElement = new ExpressionElement(active.id as string, 'variable', variable.name, variable); + if (variable.type === 'array') { + determinedActiveElement.value = `${variable.name}[]`; + } + } + } else if ((active.id as string).startsWith('op-')) { + const op = (active.id as string).replace('op-', ''); + determinedActiveElement = new ExpressionElement(active.id as string, 'operator', op); + } else if ((active.id as string).startsWith('lit-')) { + const parts = (active.id as string).split('-'); + if (parts.length >= 3) { + const value = parts.slice(2).join('-'); + determinedActiveElement = new ExpressionElement(active.id as string, 'literal', value); + } + } else if ((active.id as string).startsWith('func-')) { + const funcName = (active.id as string).replace('func-', ''); + determinedActiveElement = new ExpressionElement(active.id as string, 'function', funcName, new Expression(undefined, [])); + } + } + + setActiveItem(determinedActiveElement); + }; + + const handleDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + setActiveId(null); + setActiveItem(null); + + if (!over || !active || !activeItem) { + return; + } + + const activeLocation = getElementLocationInfo(active.id as string); + const overLocation = getElementLocationInfo(over.id as string); + + if (!activeLocation || !overLocation) { + return; + } + + // Determine which expression to update + const targetDropAreaId = overLocation.dropAreaId || activeLocation.dropAreaId; + if (!targetDropAreaId) return; + + const setState = getExpressionStateUpdater(targetDropAreaId); + if (!setState) return; + + setState(prevExpr => { + const newExpr = prevExpr.clone(); + + const itemToMove = activeLocation.isPaletteItem + ? createNewElement(activeItem, getAllVariables) + : activeLocation.item?.clone(); + + if (!itemToMove) return newExpr; + + const getList = (path: string[] = []): ExpressionElement[] | null => { + let currentList: ExpressionElement[] = newExpr.rightSide; + let currentFunc: ExpressionElement | undefined; + for (const funcId of path) { + currentFunc = currentList.find(e => e.id === funcId); + if (!currentFunc || !currentFunc.isFunction() || !currentFunc.nestedExpression) return null; + currentList = currentFunc.nestedExpression.rightSide; + } + return currentList; + }; + + if (active.id === over.id) { + // No change + } else if ( + !activeLocation.isPaletteItem && + JSON.stringify(activeLocation.funcIdPath) === JSON.stringify(overLocation.funcIdPath) && + (overLocation.isNestedExpressionElement || overLocation.isMainExpressionElement) + ) { + // Reorder within the same list + const list = getList(activeLocation.funcIdPath); + if (list && activeLocation.index !== undefined && overLocation.index !== undefined) { + const [movedItem] = list.splice(activeLocation.index, 1); + list.splice(overLocation.index, 0, movedItem); + } + } else { + // Move between different lists + if (!activeLocation.isPaletteItem) { + const sourceList = getList(activeLocation.funcIdPath); + if (sourceList && activeLocation.index !== undefined) { + sourceList.splice(activeLocation.index, 1); + } + } + + let targetPath = overLocation.funcIdPath || []; + let targetIndex = overLocation.index; + + if (overLocation.isNestedDropArea) { + targetPath = overLocation.funcIdPath!; + targetIndex = undefined; + } else if (overLocation.isMainDropArea) { + targetPath = []; + targetIndex = undefined; + } + + const targetList = getList(targetPath); + if (targetList) { + if (targetIndex !== undefined) { + targetList.splice(targetIndex, 0, itemToMove); + } else { + targetList.push(itemToMove); + } + } + } + + return newExpr; + }); + }; + + + + // Utility function to convert an expression string back into an Expression object + const parseExpressionString = (exprStr: string): Expression => { + const elements: ExpressionElement[] = []; + + const vars = getAllVariables(); + + const splitTokens = (s: string): string[] => { + const toks: string[] = []; + let current = ''; + let depth = 0; + for (let i = 0; i < s.length; i++) { + const ch = s[i]; + if (ch === ' ' && depth === 0) { + if (current !== '') { toks.push(current); current = ''; } + continue; + } + if (ch === '(') depth++; + if (ch === ')') depth--; + current += ch; + } + if (current.trim() !== '') toks.push(current); + return toks; + }; + + const tokens = splitTokens(exprStr.trim()); + const functionRegex = /^(integer|string|float|boolean)\((.*)\)$/; + + tokens.forEach(tok => { + const funcMatch = tok.match(functionRegex); + if (funcMatch) { + const funcName = funcMatch[1]; + const inner = funcMatch[2]; + const nestedExpr = parseExpressionString(inner); + elements.push(new ExpressionElement(uuid(), 'function', funcName, nestedExpr)); + return; + } + + if (operators.includes(tok as any)) { + elements.push(new ExpressionElement(uuid(), 'operator', tok)); + return; + } + + const variable = vars.find(v => v.name === tok); + if (variable) { + elements.push(new ExpressionElement(uuid(), 'variable', variable.name, variable)); + } else { + elements.push(new ExpressionElement(uuid(), 'literal', tok)); + } + }); + + return new Expression(undefined, elements); + }; + + return ( + { if (!val) handleCancel(); }}> + + + + Index for: {variableName} + + + setTab(value as 'single' | 'range')}> + + Single Position + Range + + + + handleAddExpressionElement('single-index-drop', type, value)} + removeExpressionElement={(id) => handleRemoveExpressionElement('single-index-drop', id)} + dropAreaId="single-index-drop" + excludeVariables={(v: Variable) => v.type !== 'array'} + /> + + + +
+
+
+

Start Index

+ handleAddExpressionElement('range-start-drop', type, value)} + removeExpressionElement={(id) => handleRemoveExpressionElement('range-start-drop', id)} + dropAreaId="range-start-drop" + excludeVariables={(v: Variable) => v.type !== 'array'} + showPalette={false} + /> +
+
+

End Index

+ handleAddExpressionElement('range-end-drop', type, value)} + removeExpressionElement={(id) => handleRemoveExpressionElement('range-end-drop', id)} + dropAreaId="range-end-drop" + excludeVariables={(v: Variable) => v.type !== 'array'} + showPalette={false} + /> +
+
+ + {/* Shared palette */} +
+ + + Variables + Literals + Operators + Functions + + + {/* Variables */} + +
+ {getAllVariables().filter(v=>v.type!=='array').map(variable => ( + handleAddExpressionElement('range-start-drop','variable',variable.id)} + onRightClick={() => handleAddExpressionElement('range-end-drop','variable',variable.id)} + /> + ))} +
+
+ + {/* Literals */} + +
+ {/* Boolean literals */} +
+ +
+ {['true','false'].map(val=> ( + handleAddExpressionElement('range-start-drop','literal',val)} + onRightClick={() => handleAddExpressionElement('range-end-drop','literal',val)} + /> + ))} +
+
+ + {/* Integer literal */} +
+ +
+ + + +
+
+ + {/* String literal */} +
+ +
+ + + +
+
+
+
+ + {/* Operators */} + +
+ {operators.map(op => ( + handleAddExpressionElement('range-start-drop','operator',op)} + onRightClick={() => handleAddExpressionElement('range-end-drop','operator',op)} + /> + ))} +
+
+ + {/* Functions */} + +
+ {['integer','string','float','boolean'].map(func=> ( + handleAddExpressionElement('range-start-drop','function',func)} + onRightClick={() => handleAddExpressionElement('range-end-drop','function',func)} + /> + ))} +
+
+
+
+
+
+
+ + + + + +
+ + {activeItem ? ( +
+ {activeItem.value} +
+ ) : null} +
+
+
+ ); +}; + +export default ArrayIndexDialog; \ No newline at end of file diff --git a/src/components/ui/ExpressionBuilder.tsx b/src/components/ui/ExpressionBuilder.tsx new file mode 100644 index 0000000..3fa4453 --- /dev/null +++ b/src/components/ui/ExpressionBuilder.tsx @@ -0,0 +1,191 @@ +import React from 'react'; +import { useVariables } from '../../context/VariablesContext'; +import { Expression } from '../../models'; +import { operators as expressionOperators } from '../../models/Expression'; +import { SortableContext, horizontalListSortingStrategy } from '@dnd-kit/sortable'; +import { + DraggableExpressionElement, + DraggablePaletteItem, + ExpressionDropArea, + FunctionExpressionElement, +} from '../Panel/Tabs/editors/shared/DragAndDropComponents'; +import { Tabs, TabsList, TabsTrigger, TabsContent } from './tabs'; +import { Label } from './label'; +import { Input } from './input'; +import { Button } from './button'; +import { Variable } from '../../models/Variable'; + +interface ExpressionBuilderProps { + expression: Expression; + removeExpressionElement: (id: string) => void; + addExpressionElement: (type: 'literal' | 'variable' | 'operator' | 'function', value: string) => void; + disabled?: boolean; + dropAreaId: string; + excludeVariables?: (variable: Variable) => boolean; + showPalette?: boolean; +} + +const ExpressionBuilder: React.FC = ({ + expression, + removeExpressionElement, + addExpressionElement, + disabled = false, + dropAreaId, + excludeVariables, + showPalette = true, +}) => { + const { getAllVariables } = useVariables(); + + // Apply variable exclusion if provided + const variables = excludeVariables + ? getAllVariables().filter((variable: Variable) => excludeVariables(variable)) + : getAllVariables(); + + return ( +
+ + item.id)} strategy={horizontalListSortingStrategy}> + {expression.rightSide.map((element, index) => + element.isFunction() ? ( + + ) : ( + + ) + )} + + + + {showPalette && ( + + + Variables + Literals + Operators + Functions + + + +
+ {variables.map((variable) => ( + addExpressionElement('variable', variable.id)} + /> + ))} +
+
+ + +
+
+ +
+ addExpressionElement('literal', 'true')} + /> + addExpressionElement('literal', 'false')} + /> +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ + +
+ {expressionOperators.map((op) => ( + addExpressionElement('operator', op)} + /> + ))} +
+
+ + +
+ {['integer', 'string', 'float', 'boolean'].map((func) => ( + addExpressionElement('function', func)} + /> + ))} +
+
+
+ )} +
+ ); +}; + +export default ExpressionBuilder; \ No newline at end of file diff --git a/src/context/VariablesContext.tsx b/src/context/VariablesContext.tsx index 30609d3..4f5f730 100644 --- a/src/context/VariablesContext.tsx +++ b/src/context/VariablesContext.tsx @@ -65,7 +65,7 @@ export const VariablesProvider: React.FC = ({ children } const updateNodeVariables = useCallback((nodeId: string, nodeVariables: Variable[]): void => { setVariables(prevVariables => [ ...prevVariables.filter(v => v.nodeId !== nodeId), - ...nodeVariables.map(v => new Variable(v.id, v.type, v.name, nodeId)) + ...nodeVariables.map(v => new Variable(v.id, v.type, v.name, nodeId, v.arraySubtype, v.arraySize)) ]); }, []); diff --git a/src/models/Expression.ts b/src/models/Expression.ts index 10f9ff6..a7b4064 100644 --- a/src/models/Expression.ts +++ b/src/models/Expression.ts @@ -2,6 +2,7 @@ import { Variable, VariableType, ValueTypeMap } from './Variable'; import { ExpressionElement } from './ExpressionElement'; import { ValuedVariable } from './ValuedVariable'; import { buildAST } from './ExpressionParser'; +import { IVariable } from './Variable'; export const operators = ['+', '-', '*', '/', '!', '%', '&&', '||', '==', '!=', '>', '<', '>=', '<=', '(', ')']; export type IOperator = typeof operators[number]; @@ -363,33 +364,51 @@ export class Expression implements IExpression { } /** - * Adds an element to the expression + * Finds an element by its ID recursively in the expression */ - addElement(element: ExpressionElement): void { + findElement(id: string): ExpressionElement | undefined { + for (const element of this.rightSide) { + if (element.id === id) { + return element; + } + if (element.isFunction() && element.nestedExpression) { + const found = element.nestedExpression.findElement(id); + if (found) { + return found; + } + } + } + return undefined; + } + + /** + * Adds an element to the right side of the expression + */ + addElement(element: ExpressionElement): Expression { this.rightSide.push(element); + return this; } /** - * Removes an element from the expression by id + * Removes an element from the right side of the expression */ - removeElement(id: string): void { - const filterRecursive = (elements: ExpressionElement[]): ExpressionElement[] => { - if (!elements) return []; - return elements.filter(e => { - if (e.id === id) { - return false; - } - if (e.isFunction() && e.nestedExpression) { - e.nestedExpression.removeElement(id); - } - return true; - }); - }; + removeElement(id: string): Expression { + // Recursively remove the element if it exists either on the top level or inside nested function calls + this.rightSide = this.rightSide.flatMap(e => { + // If this element matches the id, remove it by filtering it out + if (e.id === id) { + return []; + } - if (Array.isArray(this.leftSide)) { - this.leftSide = filterRecursive(this.leftSide as ExpressionElement[]); - } - this.rightSide = filterRecursive(this.rightSide); + // If this is a function element, delegate the removal to its nested expression + if (e.isFunction() && e.nestedExpression) { + e.nestedExpression.removeElement(id); + } + + return [e]; + }); + + return this; } /** @@ -485,9 +504,19 @@ export class Expression implements IExpression { * Creates an Expression from a plain object */ static fromObject(obj: IExpression): Expression { - const leftSide = obj.equality ? (obj.leftSide as ExpressionElement[]).map(e => ExpressionElement.fromObject(e)) : obj.leftSide != undefined ? Variable.fromObject(obj.leftSide as Variable) : undefined; + let leftSide: Variable | ExpressionElement[] | undefined; + if (obj.leftSide) { + if (Array.isArray(obj.leftSide)) { + leftSide = obj.leftSide.map(e => ExpressionElement.fromObject(e)); + } else { + leftSide = Variable.fromObject(obj.leftSide as IVariable); + } + } else { + leftSide = undefined; + } + const rightSide = obj.rightSide.map(e => ExpressionElement.fromObject(e)); - const equality = obj.equality; - return new Expression(leftSide, rightSide, equality); + + return new Expression(leftSide, rightSide, obj.equality); } } \ No newline at end of file diff --git a/src/models/ValuedVariable.ts b/src/models/ValuedVariable.ts index c84b54c..eb06b7b 100644 --- a/src/models/ValuedVariable.ts +++ b/src/models/ValuedVariable.ts @@ -1,4 +1,4 @@ -import { IVariable, ValueTypeMap, Variable, VariableType } from "./Variable"; +import { IVariable, ValueTypeMap, Variable, VariableType, ArraySubtype } from "./Variable"; export interface IValuedVariable extends IVariable { value: ValueTypeMap[T]; @@ -7,8 +7,8 @@ export interface IValuedVariable extends IVariable { export class ValuedVariable extends Variable implements IValuedVariable { value: ValueTypeMap[T]; - constructor(id: string, type: T, name: string, nodeId: string, value: ValueTypeMap[T]) { - super(id, type, name, nodeId); + constructor(id: string, type: T, name: string, nodeId: string, value: ValueTypeMap[T], arraySubtype?: ArraySubtype, arraySize?: number) { + super(id, type, name, nodeId, arraySubtype, arraySize); this.value = value; } @@ -16,6 +16,9 @@ export class ValuedVariable extends Variable implements * Creates a string representation of the valued variable */ toString(): string { + if (this.type === 'array' && Array.isArray(this.value)) { + return `${this.name}: [${this.value.join(', ')}]`; + } return `${this.name}: ${this.value}`; } @@ -29,6 +32,8 @@ export class ValuedVariable extends Variable implements name: this.name, nodeId: this.nodeId, value: this.value, + arraySubtype: this.arraySubtype, + arraySize: this.arraySize, } } @@ -36,7 +41,7 @@ export class ValuedVariable extends Variable implements * Creates a ValuedVariable from an object representation */ static fromObject(obj: IValuedVariable): ValuedVariable { - return new ValuedVariable(obj.id, obj.type as T, obj.name, obj.nodeId, obj.value); + return new ValuedVariable(obj.id, obj.type as T, obj.name, obj.nodeId, obj.value, obj.arraySubtype as ArraySubtype, obj.arraySize); } /** @@ -46,7 +51,7 @@ export class ValuedVariable extends Variable implements static fromVariable(variable: Variable, value: ValueTypeMap[T] | null): ValuedVariable { // If value is provided and not null, use it if (value !== null && value !== undefined) { - return new ValuedVariable(variable.id, variable.type as T, variable.name, variable.nodeId, value); + return new ValuedVariable(variable.id, variable.type as T, variable.name, variable.nodeId, value, variable.arraySubtype, variable.arraySize); } // Initialize value with correct type (TODO: does this make sense? should it be undefined instead?) @@ -65,10 +70,35 @@ export class ValuedVariable extends Variable implements case 'boolean': initializedValue = false as ValueTypeMap[T]; break; + case 'array': + // Initialize array with default values based on subtype and size + const size = variable.arraySize || 10; + const subtype = variable.arraySubtype || 'integer'; + let defaultValue: any; + + switch (subtype) { + case 'string': + defaultValue = ''; + break; + case 'integer': + defaultValue = 0; + break; + case 'float': + defaultValue = 0.0; + break; + case 'boolean': + defaultValue = false; + break; + default: + defaultValue = 0; + } + + initializedValue = Array(size).fill(defaultValue) as ValueTypeMap[T]; + break; default: console.error(`Unsupported variable type: ${variable.type}`); initializedValue = '' as ValueTypeMap[T]; } - return new ValuedVariable(variable.id, variable.type as T, variable.name, variable.nodeId, initializedValue); + return new ValuedVariable(variable.id, variable.type as T, variable.name, variable.nodeId, initializedValue, variable.arraySubtype, variable.arraySize); } } \ No newline at end of file diff --git a/src/models/Variable.ts b/src/models/Variable.ts index 948244d..11f7130 100644 --- a/src/models/Variable.ts +++ b/src/models/Variable.ts @@ -4,17 +4,27 @@ export const variableTypes = [ 'string', 'float', 'boolean', - //'array' + 'array' ] as const; export type VariableType = typeof variableTypes[number]; +// Available array subtypes (excluding array itself to prevent nested arrays) +export const arraySubtypes = [ + 'integer', + 'string', + 'float', + 'boolean' +] as const; + +export type ArraySubtype = typeof arraySubtypes[number]; + export type ValueTypeMap = { string: string; integer: number; float: number; boolean: boolean; - //array: any[]; + array: any[]; }; // Define the variable structure @@ -23,6 +33,8 @@ export interface IVariable { type: VariableType; name: string; nodeId: string; // ID of the node that declared this variable + arraySubtype?: ArraySubtype; // Only applicable when type is 'array' + arraySize?: number; // Only applicable when type is 'array' } export class Variable implements IVariable { @@ -30,12 +42,26 @@ export class Variable implements IVariable { type: VariableType; name: string; nodeId: string; + arraySubtype?: ArraySubtype; + arraySize?: number; - constructor(id: string, type: VariableType, name: string, nodeId: string) { + constructor(id: string, type: VariableType, name: string, nodeId: string, arraySubtype?: ArraySubtype, arraySize?: number) { this.id = id; this.type = type; this.name = name; this.nodeId = nodeId; + + // Ensure array metadata is always valid when the variable is an array + if (type === 'array') { + // Fallback to integer subtype if none provided (should not normally happen) + this.arraySubtype = arraySubtype ?? 'integer'; + // Enforce a positive, non-zero size. Default to 1 if invalid. + const validSize = arraySize !== undefined && arraySize >= 1 ? arraySize : 1; + this.arraySize = validSize; + } else { + this.arraySubtype = undefined; + this.arraySize = undefined; + } } /** @@ -49,6 +75,9 @@ export class Variable implements IVariable { * Returns a string representing the variable declaration */ toDeclarationString(): string { + if (this.type === 'array' && this.arraySubtype && this.arraySize !== undefined) { + return `${this.arraySubtype}[${this.arraySize}] ${this.name}`; + } return `${this.type} ${this.name}`; } @@ -56,10 +85,28 @@ export class Variable implements IVariable { * Updates the variable with new values */ update(updates: Partial): Variable { - return new Variable(this.id, - updates.type ?? this.type, - updates.name ?? this.name, - updates.nodeId ?? this.nodeId + // If changing to array, ensure subtype/size defaults are present and valid + if (updates.type === 'array') { + if (updates.arraySubtype === undefined) { + updates.arraySubtype = this.arraySubtype ?? 'integer'; + } + if (updates.arraySize === undefined || (typeof updates.arraySize === 'number' && updates.arraySize < 1)) { + updates.arraySize = this.arraySize && this.arraySize >= 1 ? this.arraySize : 1; + } + } + // When switching away from array, strip array-specific fields + if (updates.type && updates.type !== 'array') { + updates.arraySubtype = undefined; + updates.arraySize = undefined; + } + + return new Variable( + this.id, + updates.type ?? this.type, + updates.name ?? this.name, + updates.nodeId ?? this.nodeId, + updates.hasOwnProperty('arraySubtype') ? updates.arraySubtype : this.arraySubtype, + updates.hasOwnProperty('arraySize') ? updates.arraySize : this.arraySize ); } @@ -67,7 +114,7 @@ export class Variable implements IVariable { * Clones the variable */ clone(): Variable { - return new Variable(this.id, this.type, this.name, this.nodeId); + return new Variable(this.id, this.type, this.name, this.nodeId, this.arraySubtype, this.arraySize); } /** @@ -81,13 +128,22 @@ export class Variable implements IVariable { * Creates an object representation of the variable */ toObject(): IVariable { - return {id: this.id, type: this.type, name: this.name, nodeId: this.nodeId}; + return { + id: this.id, + type: this.type, + name: this.name, + nodeId: this.nodeId, + arraySubtype: this.arraySubtype, + arraySize: this.arraySize + }; } /** * Creates a Variable from a plain object */ static fromObject(obj: IVariable): Variable { - return new Variable(obj.id, obj.type as VariableType, obj.name, obj.nodeId); + return new Variable(obj.id, obj.type as VariableType, obj.name, obj.nodeId, obj.arraySubtype, obj.arraySize); } -} \ No newline at end of file +} + +export type FixedArray = { length: N } & T[]; \ No newline at end of file diff --git a/src/utils/expressionParsing.ts b/src/utils/expressionParsing.ts new file mode 100644 index 0000000..b60de93 --- /dev/null +++ b/src/utils/expressionParsing.ts @@ -0,0 +1,103 @@ +// Utility helpers for parsing expression-related strings +// Intended to be used by the panel editors to avoid code duplication + +import { Expression, ExpressionElement, Variable } from "@/models"; +import { operators } from "@/models/Expression"; + +// Parses array access literal strings (e.g., "arr[i]" or "arr[0:5]") and returns parts +export function parseArrayAccess(value: string): { + arrayName: string; + indexExpression?: string; + rangeStart?: string; + rangeEnd?: string; + isRange: boolean; +} | null { + const match = value.match(/^(.+?)\[(.+)\]$/); + if (!match) return null; + const arrayName = match[1]; + const indexPart = match[2]; + + if (indexPart.includes(":")) { + const [start, end] = indexPart.split(":"); + if (start !== undefined && end !== undefined) { + return { + arrayName, + rangeStart: start.trim(), + rangeEnd: end.trim(), + isRange: true, + }; + } + } + return { + arrayName, + indexExpression: indexPart.trim(), + isRange: false, + }; +} + +// Converts a raw string expression back into an Expression instance +// Requires the current list of variables to resolve identifiers +export function parseExpressionString(exprStr: string, allVariables: Variable[]): Expression { + const elements: ExpressionElement[] = []; + + const splitTokens = (s: string): string[] => { + const toks: string[] = []; + let current = ""; + let depth = 0; + for (let i = 0; i < s.length; i++) { + const ch = s[i]; + if (ch === " " && depth === 0) { + if (current !== "") { + toks.push(current); + current = ""; + } + continue; + } + if (ch === "(") depth++; + if (ch === ")") depth--; + current += ch; + } + if (current.trim() !== "") toks.push(current); + return toks; + }; + + const tokens = splitTokens(exprStr.trim()); + const functionRegex = /^(integer|string|float|boolean)\((.*)\)$/; + + tokens.forEach((tok) => { + // Function call + const funcMatch = tok.match(functionRegex); + if (funcMatch) { + const funcName = funcMatch[1]; + const inner = funcMatch[2]; + const nestedExpr = parseExpressionString(inner, allVariables); // recursion + elements.push( + new ExpressionElement(crypto.randomUUID(), "function", funcName, nestedExpr) + ); + return; + } + + // Operator token + if (operators.includes(tok as any)) { + elements.push(new ExpressionElement(crypto.randomUUID(), "operator", tok)); + return; + } + + // Variable or literal + const variable = allVariables.find((v) => v.name === tok); + if (variable) { + elements.push( + new ExpressionElement( + crypto.randomUUID(), + "variable", + variable.name, + variable + ) + ); + } else { + elements.push(new ExpressionElement(crypto.randomUUID(), "literal", tok)); + } + }); + + return new Expression(undefined, elements); +} \ No newline at end of file From c3a8ab2af924a0c13958d72c4da09d282ddc7a31 Mon Sep 17 00:00:00 2001 From: MrPoll0 Date: Tue, 17 Jun 2025 08:10:30 +0200 Subject: [PATCH 2/5] feat: y-webrtc-server incorporated with monorepo + README updated --- .github/workflows/deploy.yml | 17 +- .github/workflows/test.yml | 8 + README.md | 124 ++- docs/TODOadd_doc | 0 components.json => flowming/components.json | 0 eslint.config.js => flowming/eslint.config.js | 0 index.html => flowming/index.html | 0 flowming/package.json | 66 ++ .../postcss.config.js | 0 {public => flowming/public}/logo.svg | 0 .../public}/logo_favicon-128x128.png | Bin .../public}/logo_favicon-16x16.png | Bin .../public}/logo_favicon-180x180.png | Bin .../public}/logo_favicon-32x32.png | Bin .../public}/logo_favicon-64x64.png | Bin .../public}/logo_favicon-96x96.png | Bin {src => flowming/src}/App.css | 0 {src => flowming/src}/App.tsx | 0 .../src}/components/ExecutionControl.tsx | 0 .../src}/components/FilenameEditor.tsx | 0 .../src}/components/Flow/ContextMenu.tsx | 0 .../src}/components/Flow/Edges/Flowline.tsx | 0 .../src}/components/Flow/FlowContent.tsx | 0 .../src}/components/Flow/FlowTypes.tsx | 0 .../components/Flow/Nodes/AssignVariable.tsx | 0 .../Flow/Nodes/BreakpointIndicator.tsx | 0 .../components/Flow/Nodes/Conditional.tsx | 0 .../components/Flow/Nodes/DeclareVariable.tsx | 0 .../src}/components/Flow/Nodes/End.tsx | 0 .../src}/components/Flow/Nodes/ErrorNode.tsx | 0 .../src}/components/Flow/Nodes/Input.tsx | 0 .../src}/components/Flow/Nodes/NodeTypes.ts | 0 .../src}/components/Flow/Nodes/Output.tsx | 0 .../src}/components/Flow/Nodes/Start.tsx | 0 .../components/Flow/Nodes/ValueOutputNode.tsx | 0 .../components/Flow/RemoteCursorsOverlay.tsx | 0 .../src}/components/ImportExport.tsx | 0 .../src}/components/Panel/Panel.tsx | 0 .../src}/components/Panel/PanelList.tsx | 0 .../src}/components/Panel/Tabs/CodeTab.tsx | 0 .../components/Panel/Tabs/CollaborateTab.tsx | 0 .../components/Panel/Tabs/DebuggerTab.tsx | 0 .../src}/components/Panel/Tabs/DetailsTab.tsx | 0 .../components/Panel/Tabs/ExercisesTab.tsx | 0 .../Panel/Tabs/editors/ConditionalEditor.tsx | 0 .../Panel/Tabs/editors/InputEditor.tsx | 0 .../Panel/Tabs/editors/OutputEditor.tsx | 0 .../Tabs/editors/VariableAssignmentEditor.tsx | 0 .../editors/VariableDeclarationEditor.tsx | 0 .../editors/shared/DragAndDropComponents.tsx | 0 .../src}/components/SolutionDialog.tsx | 0 .../src}/components/SystemSettings.tsx | 0 .../src}/components/Toolbar/Toolbar.tsx | 0 .../src}/components/Toolbar/ToolbarBlock.tsx | 0 .../components/Toolbar/ToolbarBlocksList.tsx | 0 .../src}/components/Toolbar/ToolbarTypes.ts | 0 .../src}/components/ui/ArrayIndexDialog.tsx | 0 .../src}/components/ui/ExpressionBuilder.tsx | 0 .../src}/components/ui/InputDialog.tsx | 0 .../src}/components/ui/accordion.tsx | 0 {src => flowming/src}/components/ui/alert.tsx | 0 {src => flowming/src}/components/ui/badge.tsx | 0 .../src}/components/ui/button.tsx | 0 {src => flowming/src}/components/ui/card.tsx | 0 .../src}/components/ui/command.tsx | 0 .../src}/components/ui/dialog.tsx | 0 {src => flowming/src}/components/ui/input.tsx | 0 {src => flowming/src}/components/ui/label.tsx | 0 .../src}/components/ui/popover.tsx | 0 .../src}/components/ui/resizable.tsx | 0 .../src}/components/ui/scroll-area.tsx | 0 .../src}/components/ui/select.tsx | 0 .../src}/components/ui/separator.tsx | 0 .../src}/components/ui/sonner.tsx | 0 {src => flowming/src}/components/ui/tabs.tsx | 0 .../src}/components/ui/textarea.tsx | 0 .../src}/components/ui/tooltip.tsx | 0 .../src}/context/CollaborationContext.tsx | 0 .../src}/context/DebuggerContext.tsx | 0 {src => flowming/src}/context/DnDContext.tsx | 0 .../src}/context/FilenameContext.tsx | 0 .../src}/context/FlowExecutorContext.tsx | 0 .../src}/context/FlowInteractionContext.tsx | 0 .../src}/context/InputDialogContext.tsx | 0 .../src}/context/SelectedNodeContext.tsx | 0 .../src}/context/SystemSettingsContext.tsx | 0 .../src}/context/VariablesContext.tsx | 0 {src => flowming/src}/data/exercises.json | 0 .../src}/data/solutions/hello_world.json | 0 .../src}/data/solutions/sum_two_numbers.json | 0 .../src}/hooks/useFlowExecutor.ts | 0 {src => flowming/src}/index.css | 0 {src => flowming/src}/lib/utils.ts | 0 {src => flowming/src}/main.tsx | 0 {src => flowming/src}/models/Expression.ts | 0 .../src}/models/ExpressionElement.ts | 0 .../src}/models/ExpressionParser.ts | 0 .../src}/models/ValuedVariable.ts | 0 {src => flowming/src}/models/Variable.ts | 0 {src => flowming/src}/models/index.ts | 0 {src => flowming/src}/models/pythonAST.ts | 0 {src => flowming/src}/tests/CodeTab.test.ts | 0 .../src}/tests/Expression.test.ts | 0 {src => flowming/src}/tests/Models.test.ts | 0 .../src}/tests/codeGeneration.test.ts | 0 {src => flowming/src}/utils/codeGeneration.ts | 0 .../src}/utils/expressionParsing.ts | 0 .../src}/utils/flowExecutorUtils.ts | 0 {src => flowming/src}/utils/flowTestRunner.ts | 0 {src => flowming/src}/utils/nodeStyles.ts | 0 {src => flowming/src}/vite-env.d.ts | 0 .../tailwind.config.js | 0 .../tsconfig.app.json | 0 tsconfig.json => flowming/tsconfig.json | 0 .../tsconfig.node.json | 0 vite.config.ts => flowming/vite.config.ts | 0 package-lock.json | 970 ++++++++++++------ package.json | 76 +- y-webrtc-server/LICENSE | 21 + y-webrtc-server/package.json | 13 + y-webrtc-server/server.js | 137 +++ 121 files changed, 1040 insertions(+), 392 deletions(-) create mode 100644 docs/TODOadd_doc rename components.json => flowming/components.json (100%) rename eslint.config.js => flowming/eslint.config.js (100%) rename index.html => flowming/index.html (100%) create mode 100644 flowming/package.json rename postcss.config.js => flowming/postcss.config.js (100%) rename {public => flowming/public}/logo.svg (100%) rename {public => flowming/public}/logo_favicon-128x128.png (100%) rename {public => flowming/public}/logo_favicon-16x16.png (100%) rename {public => flowming/public}/logo_favicon-180x180.png (100%) rename {public => flowming/public}/logo_favicon-32x32.png (100%) rename {public => flowming/public}/logo_favicon-64x64.png (100%) rename {public => flowming/public}/logo_favicon-96x96.png (100%) rename {src => flowming/src}/App.css (100%) rename {src => flowming/src}/App.tsx (100%) rename {src => flowming/src}/components/ExecutionControl.tsx (100%) rename {src => flowming/src}/components/FilenameEditor.tsx (100%) rename {src => flowming/src}/components/Flow/ContextMenu.tsx (100%) rename {src => flowming/src}/components/Flow/Edges/Flowline.tsx (100%) rename {src => flowming/src}/components/Flow/FlowContent.tsx (100%) rename {src => flowming/src}/components/Flow/FlowTypes.tsx (100%) rename {src => flowming/src}/components/Flow/Nodes/AssignVariable.tsx (100%) rename {src => flowming/src}/components/Flow/Nodes/BreakpointIndicator.tsx (100%) rename {src => flowming/src}/components/Flow/Nodes/Conditional.tsx (100%) rename {src => flowming/src}/components/Flow/Nodes/DeclareVariable.tsx (100%) rename {src => flowming/src}/components/Flow/Nodes/End.tsx (100%) rename {src => flowming/src}/components/Flow/Nodes/ErrorNode.tsx (100%) rename {src => flowming/src}/components/Flow/Nodes/Input.tsx (100%) rename {src => flowming/src}/components/Flow/Nodes/NodeTypes.ts (100%) rename {src => flowming/src}/components/Flow/Nodes/Output.tsx (100%) rename {src => flowming/src}/components/Flow/Nodes/Start.tsx (100%) rename {src => flowming/src}/components/Flow/Nodes/ValueOutputNode.tsx (100%) rename {src => flowming/src}/components/Flow/RemoteCursorsOverlay.tsx (100%) rename {src => flowming/src}/components/ImportExport.tsx (100%) rename {src => flowming/src}/components/Panel/Panel.tsx (100%) rename {src => flowming/src}/components/Panel/PanelList.tsx (100%) rename {src => flowming/src}/components/Panel/Tabs/CodeTab.tsx (100%) rename {src => flowming/src}/components/Panel/Tabs/CollaborateTab.tsx (100%) rename {src => flowming/src}/components/Panel/Tabs/DebuggerTab.tsx (100%) rename {src => flowming/src}/components/Panel/Tabs/DetailsTab.tsx (100%) rename {src => flowming/src}/components/Panel/Tabs/ExercisesTab.tsx (100%) rename {src => flowming/src}/components/Panel/Tabs/editors/ConditionalEditor.tsx (100%) rename {src => flowming/src}/components/Panel/Tabs/editors/InputEditor.tsx (100%) rename {src => flowming/src}/components/Panel/Tabs/editors/OutputEditor.tsx (100%) rename {src => flowming/src}/components/Panel/Tabs/editors/VariableAssignmentEditor.tsx (100%) rename {src => flowming/src}/components/Panel/Tabs/editors/VariableDeclarationEditor.tsx (100%) rename {src => flowming/src}/components/Panel/Tabs/editors/shared/DragAndDropComponents.tsx (100%) rename {src => flowming/src}/components/SolutionDialog.tsx (100%) rename {src => flowming/src}/components/SystemSettings.tsx (100%) rename {src => flowming/src}/components/Toolbar/Toolbar.tsx (100%) rename {src => flowming/src}/components/Toolbar/ToolbarBlock.tsx (100%) rename {src => flowming/src}/components/Toolbar/ToolbarBlocksList.tsx (100%) rename {src => flowming/src}/components/Toolbar/ToolbarTypes.ts (100%) rename {src => flowming/src}/components/ui/ArrayIndexDialog.tsx (100%) rename {src => flowming/src}/components/ui/ExpressionBuilder.tsx (100%) rename {src => flowming/src}/components/ui/InputDialog.tsx (100%) rename {src => flowming/src}/components/ui/accordion.tsx (100%) rename {src => flowming/src}/components/ui/alert.tsx (100%) rename {src => flowming/src}/components/ui/badge.tsx (100%) rename {src => flowming/src}/components/ui/button.tsx (100%) rename {src => flowming/src}/components/ui/card.tsx (100%) rename {src => flowming/src}/components/ui/command.tsx (100%) rename {src => flowming/src}/components/ui/dialog.tsx (100%) rename {src => flowming/src}/components/ui/input.tsx (100%) rename {src => flowming/src}/components/ui/label.tsx (100%) rename {src => flowming/src}/components/ui/popover.tsx (100%) rename {src => flowming/src}/components/ui/resizable.tsx (100%) rename {src => flowming/src}/components/ui/scroll-area.tsx (100%) rename {src => flowming/src}/components/ui/select.tsx (100%) rename {src => flowming/src}/components/ui/separator.tsx (100%) rename {src => flowming/src}/components/ui/sonner.tsx (100%) rename {src => flowming/src}/components/ui/tabs.tsx (100%) rename {src => flowming/src}/components/ui/textarea.tsx (100%) rename {src => flowming/src}/components/ui/tooltip.tsx (100%) rename {src => flowming/src}/context/CollaborationContext.tsx (100%) rename {src => flowming/src}/context/DebuggerContext.tsx (100%) rename {src => flowming/src}/context/DnDContext.tsx (100%) rename {src => flowming/src}/context/FilenameContext.tsx (100%) rename {src => flowming/src}/context/FlowExecutorContext.tsx (100%) rename {src => flowming/src}/context/FlowInteractionContext.tsx (100%) rename {src => flowming/src}/context/InputDialogContext.tsx (100%) rename {src => flowming/src}/context/SelectedNodeContext.tsx (100%) rename {src => flowming/src}/context/SystemSettingsContext.tsx (100%) rename {src => flowming/src}/context/VariablesContext.tsx (100%) rename {src => flowming/src}/data/exercises.json (100%) rename {src => flowming/src}/data/solutions/hello_world.json (100%) rename {src => flowming/src}/data/solutions/sum_two_numbers.json (100%) rename {src => flowming/src}/hooks/useFlowExecutor.ts (100%) rename {src => flowming/src}/index.css (100%) rename {src => flowming/src}/lib/utils.ts (100%) rename {src => flowming/src}/main.tsx (100%) rename {src => flowming/src}/models/Expression.ts (100%) rename {src => flowming/src}/models/ExpressionElement.ts (100%) rename {src => flowming/src}/models/ExpressionParser.ts (100%) rename {src => flowming/src}/models/ValuedVariable.ts (100%) rename {src => flowming/src}/models/Variable.ts (100%) rename {src => flowming/src}/models/index.ts (100%) rename {src => flowming/src}/models/pythonAST.ts (100%) rename {src => flowming/src}/tests/CodeTab.test.ts (100%) rename {src => flowming/src}/tests/Expression.test.ts (100%) rename {src => flowming/src}/tests/Models.test.ts (100%) rename {src => flowming/src}/tests/codeGeneration.test.ts (100%) rename {src => flowming/src}/utils/codeGeneration.ts (100%) rename {src => flowming/src}/utils/expressionParsing.ts (100%) rename {src => flowming/src}/utils/flowExecutorUtils.ts (100%) rename {src => flowming/src}/utils/flowTestRunner.ts (100%) rename {src => flowming/src}/utils/nodeStyles.ts (100%) rename {src => flowming/src}/vite-env.d.ts (100%) rename tailwind.config.js => flowming/tailwind.config.js (100%) rename tsconfig.app.json => flowming/tsconfig.app.json (100%) rename tsconfig.json => flowming/tsconfig.json (100%) rename tsconfig.node.json => flowming/tsconfig.node.json (100%) rename vite.config.ts => flowming/vite.config.ts (100%) create mode 100644 y-webrtc-server/LICENSE create mode 100644 y-webrtc-server/package.json create mode 100644 y-webrtc-server/server.js diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 706afef..2605913 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -3,6 +3,9 @@ name: Deploy to Server on: push: branches: [main] + paths: + - 'flowming/**' + - '.github/workflows/deploy.yml' jobs: build-and-deploy: @@ -18,18 +21,18 @@ jobs: rm -f package-lock.json - name: Install dependencies - run: npm install + run: npm install --workspace=flowming - name: Run tests - run: npm test + run: npm test --workspace=flowming - name: Build project - run: npm run build + run: npm run build --workspace=flowming - name: Deploy files run: | rm -rf /var/www/flow-diagram/* - cp -a dist/* /var/www/flow-diagram/ + cp -a flowming/dist/* /var/www/flow-diagram/ chown -R github-runner:www-data /var/www/flow-diagram - name: Restart server @@ -38,6 +41,8 @@ jobs: - name: Clean up run: | - rm -rf dist + rm -rf flowming/dist rm -rf node_modules - rm package-lock.json + rm -rf flowming/node_modules + rm -rf y-webrtc-server/node_modules + rm -f package-lock.json diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9aff0df..f8ae687 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,8 +3,16 @@ name: Test on: pull_request: branches: [main] + paths: + - 'flowming/**' + - 'y-webrtc-server/**' + - '.github/workflows/test.yml' push: branches: [main] + paths: + - 'flowming/**' + - 'y-webrtc-server/**' + - '.github/workflows/test.yml' jobs: test: diff --git a/README.md b/README.md index 54bebc7..5b96360 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,121 @@ -## Comment annotations -- TODO: {...} -- NOTE: {...} \ No newline at end of file +# Flowming + +Interactive web application for designing, executing and debugging algorithms using standard flowchart notation. + +Flowming helps beginners focus on computational thinking before wrestling with the syntax of a textual language. Students build flowcharts visually, run them step-by-step, watch variables change in real time and export the equivalent Python code – all from their browser. + +--- + +## ✨ Key features + +* Visual editor powered by React Flow: drag-and-drop blocks, connect them and rearrange freely. +* Strongly-typed execution engine with breakpoint support, variable watch panel and execution trace. +* Automatic code generation (Python 3) showing the 1-to-1 mapping from flowchart to text. +* Exercises bank & auto-evaluation. +* Real-time collaboration (peer-to-peer WebRTC): edit the same diagram together *(experimental)*. +* Modern SPA built with React 19, TypeScript, Vite and Tailwind. + +--- + +## 🚀 Getting started + +### Prerequisites + +* Node.js **>= 18** (https://nodejs.org) +* Git (to clone the repo) + *(all other dependencies are installed via **npm**)* + +### 1. Clone & install + +```bash +git clone https://github.com/MrPoll0/flowming.git +cd flowming +npm install # Installs dependencies for all packages +``` + +### 2. Run development servers + +To start the Vite dev server for the main application and the WebRTC signaling server simultaneously, run: +```bash +# From the project root +npm run dev +``` +This will start the `flowming` app (usually on `http://localhost:5173`) and the collaboration server. + +### 3. Production build + +```bash +# From the project root +npm run build # Builds the flowming app to flowming/dist/ +npm run preview # Serves the production build locally +``` + +### 4. Test + +```bash +# From the project root +npm run test # Runs Vitest unit tests for flowming +``` + +--- + +## 🗂️ Repository layout (short) + +``` +. +├── flowming/ # Main React application +│ └── src/ +│ ├── components/ # React UI & flowchart nodes +│ ├── context/ # React context providers (state) +│ ├── models/ # Core domain classes (Variable, Expression, …) +│ └── utils/ # Execution engine, code generation, helpers +├── y-webrtc-server/ # WebSocket-based WebRTC signaling server +│ └── server.js +├── README.md # This file +├── LICENSE # CC BY-NC-SA 4.0 +├── CONTRIBUTING.md +└── CLA.md +``` + +--- + +## 🤝 Contributing + +We love contributions! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for the workflow, coding style and commit conventions. +The first time you open a pull request you will be asked to sign our **Contributor License Agreement (CLA)** via CLA-Assistant. + +--- + +## 📚 Documentation & paper + +This repository includes the full bachelor thesis (Spanish) that motivated the project, detailing state-of-the-art, design decisions and user evaluation. + +*Title:* "Aplicación web para el diseño y ejecución de diagramas de flujo" +*Author:* Daniel Pérez Fernández +*Advisor*: Álvaro Montero Montes +**Universidad Carlos III de Madrid**, 2025 + +You can find the PDF and additional assets in the `docs/` release section. + +--- + +## 📜 License & trademarks + +* **Source code**: Creative Commons **Attribution-NonCommercial-ShareAlike 4.0** International (CC BY-NC-SA 4.0). + You may study, fork and improve Flowming for non-commercial purposes, but must share derivatives under the same license. +* **Documentation** (thesis, README, etc.): Creative Commons **Attribution-NonCommercial-NoDerivatives 4.0** (CC BY-NC-ND 4.0). +* The name **"Flowming"** and the logo are **not** part of the CC license and remain protected trademarks of the author. + +See [LICENSE](LICENSE) for the full text. + +--- + +## ❤️ Acknowledgements + +Built with: + +* React & TypeScript +* React Flow – amazing node-based UI toolkit +* Yjs – CRDT magic for collaboration +* shadcn/ui & Lucide icons +* Vite & Vitest \ No newline at end of file diff --git a/docs/TODOadd_doc b/docs/TODOadd_doc new file mode 100644 index 0000000..e69de29 diff --git a/components.json b/flowming/components.json similarity index 100% rename from components.json rename to flowming/components.json diff --git a/eslint.config.js b/flowming/eslint.config.js similarity index 100% rename from eslint.config.js rename to flowming/eslint.config.js diff --git a/index.html b/flowming/index.html similarity index 100% rename from index.html rename to flowming/index.html diff --git a/flowming/package.json b/flowming/package.json new file mode 100644 index 0000000..da48af4 --- /dev/null +++ b/flowming/package.json @@ -0,0 +1,66 @@ +{ + "name": "flowming", + "version": "1.0.0", + "author": "Daniel Pérez Fernández", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview", + "test": "vitest run" + }, + "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@radix-ui/react-accordion": "^1.2.11", + "@radix-ui/react-dialog": "^1.1.14", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-popover": "^1.1.14", + "@radix-ui/react-scroll-area": "^1.2.9", + "@radix-ui/react-select": "^2.2.5", + "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-tabs": "^1.1.12", + "@radix-ui/react-tooltip": "^1.2.7", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@xyflow/react": "^12.4.3", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "framer-motion": "^12.6.3", + "lucide-react": "^0.511.0", + "next-themes": "^0.4.6", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-resizable-panels": "^3.0.2", + "react-syntax-highlighter": "^15.6.1", + "sonner": "^2.0.5", + "tailwind-merge": "^3.3.0", + "tailwindcss-animate": "^1.0.7", + "y-webrtc": "^10.3.0", + "yjs": "^13.6.27" + }, + "devDependencies": { + "@eslint/js": "^9.19.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.0", + "@types/node": "^22.15.21", + "@types/react-syntax-highlighter": "^15.5.13", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.21", + "eslint": "^9.19.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.18", + "globals": "^15.14.0", + "postcss": "^8.5.3", + "tailwindcss": "^3.4.0", + "typescript": "~5.7.2", + "typescript-eslint": "^8.22.0", + "vite": "^6.1.0", + "vitest": "^3.1.1" + } +} \ No newline at end of file diff --git a/postcss.config.js b/flowming/postcss.config.js similarity index 100% rename from postcss.config.js rename to flowming/postcss.config.js diff --git a/public/logo.svg b/flowming/public/logo.svg similarity index 100% rename from public/logo.svg rename to flowming/public/logo.svg diff --git a/public/logo_favicon-128x128.png b/flowming/public/logo_favicon-128x128.png similarity index 100% rename from public/logo_favicon-128x128.png rename to flowming/public/logo_favicon-128x128.png diff --git a/public/logo_favicon-16x16.png b/flowming/public/logo_favicon-16x16.png similarity index 100% rename from public/logo_favicon-16x16.png rename to flowming/public/logo_favicon-16x16.png diff --git a/public/logo_favicon-180x180.png b/flowming/public/logo_favicon-180x180.png similarity index 100% rename from public/logo_favicon-180x180.png rename to flowming/public/logo_favicon-180x180.png diff --git a/public/logo_favicon-32x32.png b/flowming/public/logo_favicon-32x32.png similarity index 100% rename from public/logo_favicon-32x32.png rename to flowming/public/logo_favicon-32x32.png diff --git a/public/logo_favicon-64x64.png b/flowming/public/logo_favicon-64x64.png similarity index 100% rename from public/logo_favicon-64x64.png rename to flowming/public/logo_favicon-64x64.png diff --git a/public/logo_favicon-96x96.png b/flowming/public/logo_favicon-96x96.png similarity index 100% rename from public/logo_favicon-96x96.png rename to flowming/public/logo_favicon-96x96.png diff --git a/src/App.css b/flowming/src/App.css similarity index 100% rename from src/App.css rename to flowming/src/App.css diff --git a/src/App.tsx b/flowming/src/App.tsx similarity index 100% rename from src/App.tsx rename to flowming/src/App.tsx diff --git a/src/components/ExecutionControl.tsx b/flowming/src/components/ExecutionControl.tsx similarity index 100% rename from src/components/ExecutionControl.tsx rename to flowming/src/components/ExecutionControl.tsx diff --git a/src/components/FilenameEditor.tsx b/flowming/src/components/FilenameEditor.tsx similarity index 100% rename from src/components/FilenameEditor.tsx rename to flowming/src/components/FilenameEditor.tsx diff --git a/src/components/Flow/ContextMenu.tsx b/flowming/src/components/Flow/ContextMenu.tsx similarity index 100% rename from src/components/Flow/ContextMenu.tsx rename to flowming/src/components/Flow/ContextMenu.tsx diff --git a/src/components/Flow/Edges/Flowline.tsx b/flowming/src/components/Flow/Edges/Flowline.tsx similarity index 100% rename from src/components/Flow/Edges/Flowline.tsx rename to flowming/src/components/Flow/Edges/Flowline.tsx diff --git a/src/components/Flow/FlowContent.tsx b/flowming/src/components/Flow/FlowContent.tsx similarity index 100% rename from src/components/Flow/FlowContent.tsx rename to flowming/src/components/Flow/FlowContent.tsx diff --git a/src/components/Flow/FlowTypes.tsx b/flowming/src/components/Flow/FlowTypes.tsx similarity index 100% rename from src/components/Flow/FlowTypes.tsx rename to flowming/src/components/Flow/FlowTypes.tsx diff --git a/src/components/Flow/Nodes/AssignVariable.tsx b/flowming/src/components/Flow/Nodes/AssignVariable.tsx similarity index 100% rename from src/components/Flow/Nodes/AssignVariable.tsx rename to flowming/src/components/Flow/Nodes/AssignVariable.tsx diff --git a/src/components/Flow/Nodes/BreakpointIndicator.tsx b/flowming/src/components/Flow/Nodes/BreakpointIndicator.tsx similarity index 100% rename from src/components/Flow/Nodes/BreakpointIndicator.tsx rename to flowming/src/components/Flow/Nodes/BreakpointIndicator.tsx diff --git a/src/components/Flow/Nodes/Conditional.tsx b/flowming/src/components/Flow/Nodes/Conditional.tsx similarity index 100% rename from src/components/Flow/Nodes/Conditional.tsx rename to flowming/src/components/Flow/Nodes/Conditional.tsx diff --git a/src/components/Flow/Nodes/DeclareVariable.tsx b/flowming/src/components/Flow/Nodes/DeclareVariable.tsx similarity index 100% rename from src/components/Flow/Nodes/DeclareVariable.tsx rename to flowming/src/components/Flow/Nodes/DeclareVariable.tsx diff --git a/src/components/Flow/Nodes/End.tsx b/flowming/src/components/Flow/Nodes/End.tsx similarity index 100% rename from src/components/Flow/Nodes/End.tsx rename to flowming/src/components/Flow/Nodes/End.tsx diff --git a/src/components/Flow/Nodes/ErrorNode.tsx b/flowming/src/components/Flow/Nodes/ErrorNode.tsx similarity index 100% rename from src/components/Flow/Nodes/ErrorNode.tsx rename to flowming/src/components/Flow/Nodes/ErrorNode.tsx diff --git a/src/components/Flow/Nodes/Input.tsx b/flowming/src/components/Flow/Nodes/Input.tsx similarity index 100% rename from src/components/Flow/Nodes/Input.tsx rename to flowming/src/components/Flow/Nodes/Input.tsx diff --git a/src/components/Flow/Nodes/NodeTypes.ts b/flowming/src/components/Flow/Nodes/NodeTypes.ts similarity index 100% rename from src/components/Flow/Nodes/NodeTypes.ts rename to flowming/src/components/Flow/Nodes/NodeTypes.ts diff --git a/src/components/Flow/Nodes/Output.tsx b/flowming/src/components/Flow/Nodes/Output.tsx similarity index 100% rename from src/components/Flow/Nodes/Output.tsx rename to flowming/src/components/Flow/Nodes/Output.tsx diff --git a/src/components/Flow/Nodes/Start.tsx b/flowming/src/components/Flow/Nodes/Start.tsx similarity index 100% rename from src/components/Flow/Nodes/Start.tsx rename to flowming/src/components/Flow/Nodes/Start.tsx diff --git a/src/components/Flow/Nodes/ValueOutputNode.tsx b/flowming/src/components/Flow/Nodes/ValueOutputNode.tsx similarity index 100% rename from src/components/Flow/Nodes/ValueOutputNode.tsx rename to flowming/src/components/Flow/Nodes/ValueOutputNode.tsx diff --git a/src/components/Flow/RemoteCursorsOverlay.tsx b/flowming/src/components/Flow/RemoteCursorsOverlay.tsx similarity index 100% rename from src/components/Flow/RemoteCursorsOverlay.tsx rename to flowming/src/components/Flow/RemoteCursorsOverlay.tsx diff --git a/src/components/ImportExport.tsx b/flowming/src/components/ImportExport.tsx similarity index 100% rename from src/components/ImportExport.tsx rename to flowming/src/components/ImportExport.tsx diff --git a/src/components/Panel/Panel.tsx b/flowming/src/components/Panel/Panel.tsx similarity index 100% rename from src/components/Panel/Panel.tsx rename to flowming/src/components/Panel/Panel.tsx diff --git a/src/components/Panel/PanelList.tsx b/flowming/src/components/Panel/PanelList.tsx similarity index 100% rename from src/components/Panel/PanelList.tsx rename to flowming/src/components/Panel/PanelList.tsx diff --git a/src/components/Panel/Tabs/CodeTab.tsx b/flowming/src/components/Panel/Tabs/CodeTab.tsx similarity index 100% rename from src/components/Panel/Tabs/CodeTab.tsx rename to flowming/src/components/Panel/Tabs/CodeTab.tsx diff --git a/src/components/Panel/Tabs/CollaborateTab.tsx b/flowming/src/components/Panel/Tabs/CollaborateTab.tsx similarity index 100% rename from src/components/Panel/Tabs/CollaborateTab.tsx rename to flowming/src/components/Panel/Tabs/CollaborateTab.tsx diff --git a/src/components/Panel/Tabs/DebuggerTab.tsx b/flowming/src/components/Panel/Tabs/DebuggerTab.tsx similarity index 100% rename from src/components/Panel/Tabs/DebuggerTab.tsx rename to flowming/src/components/Panel/Tabs/DebuggerTab.tsx diff --git a/src/components/Panel/Tabs/DetailsTab.tsx b/flowming/src/components/Panel/Tabs/DetailsTab.tsx similarity index 100% rename from src/components/Panel/Tabs/DetailsTab.tsx rename to flowming/src/components/Panel/Tabs/DetailsTab.tsx diff --git a/src/components/Panel/Tabs/ExercisesTab.tsx b/flowming/src/components/Panel/Tabs/ExercisesTab.tsx similarity index 100% rename from src/components/Panel/Tabs/ExercisesTab.tsx rename to flowming/src/components/Panel/Tabs/ExercisesTab.tsx diff --git a/src/components/Panel/Tabs/editors/ConditionalEditor.tsx b/flowming/src/components/Panel/Tabs/editors/ConditionalEditor.tsx similarity index 100% rename from src/components/Panel/Tabs/editors/ConditionalEditor.tsx rename to flowming/src/components/Panel/Tabs/editors/ConditionalEditor.tsx diff --git a/src/components/Panel/Tabs/editors/InputEditor.tsx b/flowming/src/components/Panel/Tabs/editors/InputEditor.tsx similarity index 100% rename from src/components/Panel/Tabs/editors/InputEditor.tsx rename to flowming/src/components/Panel/Tabs/editors/InputEditor.tsx diff --git a/src/components/Panel/Tabs/editors/OutputEditor.tsx b/flowming/src/components/Panel/Tabs/editors/OutputEditor.tsx similarity index 100% rename from src/components/Panel/Tabs/editors/OutputEditor.tsx rename to flowming/src/components/Panel/Tabs/editors/OutputEditor.tsx diff --git a/src/components/Panel/Tabs/editors/VariableAssignmentEditor.tsx b/flowming/src/components/Panel/Tabs/editors/VariableAssignmentEditor.tsx similarity index 100% rename from src/components/Panel/Tabs/editors/VariableAssignmentEditor.tsx rename to flowming/src/components/Panel/Tabs/editors/VariableAssignmentEditor.tsx diff --git a/src/components/Panel/Tabs/editors/VariableDeclarationEditor.tsx b/flowming/src/components/Panel/Tabs/editors/VariableDeclarationEditor.tsx similarity index 100% rename from src/components/Panel/Tabs/editors/VariableDeclarationEditor.tsx rename to flowming/src/components/Panel/Tabs/editors/VariableDeclarationEditor.tsx diff --git a/src/components/Panel/Tabs/editors/shared/DragAndDropComponents.tsx b/flowming/src/components/Panel/Tabs/editors/shared/DragAndDropComponents.tsx similarity index 100% rename from src/components/Panel/Tabs/editors/shared/DragAndDropComponents.tsx rename to flowming/src/components/Panel/Tabs/editors/shared/DragAndDropComponents.tsx diff --git a/src/components/SolutionDialog.tsx b/flowming/src/components/SolutionDialog.tsx similarity index 100% rename from src/components/SolutionDialog.tsx rename to flowming/src/components/SolutionDialog.tsx diff --git a/src/components/SystemSettings.tsx b/flowming/src/components/SystemSettings.tsx similarity index 100% rename from src/components/SystemSettings.tsx rename to flowming/src/components/SystemSettings.tsx diff --git a/src/components/Toolbar/Toolbar.tsx b/flowming/src/components/Toolbar/Toolbar.tsx similarity index 100% rename from src/components/Toolbar/Toolbar.tsx rename to flowming/src/components/Toolbar/Toolbar.tsx diff --git a/src/components/Toolbar/ToolbarBlock.tsx b/flowming/src/components/Toolbar/ToolbarBlock.tsx similarity index 100% rename from src/components/Toolbar/ToolbarBlock.tsx rename to flowming/src/components/Toolbar/ToolbarBlock.tsx diff --git a/src/components/Toolbar/ToolbarBlocksList.tsx b/flowming/src/components/Toolbar/ToolbarBlocksList.tsx similarity index 100% rename from src/components/Toolbar/ToolbarBlocksList.tsx rename to flowming/src/components/Toolbar/ToolbarBlocksList.tsx diff --git a/src/components/Toolbar/ToolbarTypes.ts b/flowming/src/components/Toolbar/ToolbarTypes.ts similarity index 100% rename from src/components/Toolbar/ToolbarTypes.ts rename to flowming/src/components/Toolbar/ToolbarTypes.ts diff --git a/src/components/ui/ArrayIndexDialog.tsx b/flowming/src/components/ui/ArrayIndexDialog.tsx similarity index 100% rename from src/components/ui/ArrayIndexDialog.tsx rename to flowming/src/components/ui/ArrayIndexDialog.tsx diff --git a/src/components/ui/ExpressionBuilder.tsx b/flowming/src/components/ui/ExpressionBuilder.tsx similarity index 100% rename from src/components/ui/ExpressionBuilder.tsx rename to flowming/src/components/ui/ExpressionBuilder.tsx diff --git a/src/components/ui/InputDialog.tsx b/flowming/src/components/ui/InputDialog.tsx similarity index 100% rename from src/components/ui/InputDialog.tsx rename to flowming/src/components/ui/InputDialog.tsx diff --git a/src/components/ui/accordion.tsx b/flowming/src/components/ui/accordion.tsx similarity index 100% rename from src/components/ui/accordion.tsx rename to flowming/src/components/ui/accordion.tsx diff --git a/src/components/ui/alert.tsx b/flowming/src/components/ui/alert.tsx similarity index 100% rename from src/components/ui/alert.tsx rename to flowming/src/components/ui/alert.tsx diff --git a/src/components/ui/badge.tsx b/flowming/src/components/ui/badge.tsx similarity index 100% rename from src/components/ui/badge.tsx rename to flowming/src/components/ui/badge.tsx diff --git a/src/components/ui/button.tsx b/flowming/src/components/ui/button.tsx similarity index 100% rename from src/components/ui/button.tsx rename to flowming/src/components/ui/button.tsx diff --git a/src/components/ui/card.tsx b/flowming/src/components/ui/card.tsx similarity index 100% rename from src/components/ui/card.tsx rename to flowming/src/components/ui/card.tsx diff --git a/src/components/ui/command.tsx b/flowming/src/components/ui/command.tsx similarity index 100% rename from src/components/ui/command.tsx rename to flowming/src/components/ui/command.tsx diff --git a/src/components/ui/dialog.tsx b/flowming/src/components/ui/dialog.tsx similarity index 100% rename from src/components/ui/dialog.tsx rename to flowming/src/components/ui/dialog.tsx diff --git a/src/components/ui/input.tsx b/flowming/src/components/ui/input.tsx similarity index 100% rename from src/components/ui/input.tsx rename to flowming/src/components/ui/input.tsx diff --git a/src/components/ui/label.tsx b/flowming/src/components/ui/label.tsx similarity index 100% rename from src/components/ui/label.tsx rename to flowming/src/components/ui/label.tsx diff --git a/src/components/ui/popover.tsx b/flowming/src/components/ui/popover.tsx similarity index 100% rename from src/components/ui/popover.tsx rename to flowming/src/components/ui/popover.tsx diff --git a/src/components/ui/resizable.tsx b/flowming/src/components/ui/resizable.tsx similarity index 100% rename from src/components/ui/resizable.tsx rename to flowming/src/components/ui/resizable.tsx diff --git a/src/components/ui/scroll-area.tsx b/flowming/src/components/ui/scroll-area.tsx similarity index 100% rename from src/components/ui/scroll-area.tsx rename to flowming/src/components/ui/scroll-area.tsx diff --git a/src/components/ui/select.tsx b/flowming/src/components/ui/select.tsx similarity index 100% rename from src/components/ui/select.tsx rename to flowming/src/components/ui/select.tsx diff --git a/src/components/ui/separator.tsx b/flowming/src/components/ui/separator.tsx similarity index 100% rename from src/components/ui/separator.tsx rename to flowming/src/components/ui/separator.tsx diff --git a/src/components/ui/sonner.tsx b/flowming/src/components/ui/sonner.tsx similarity index 100% rename from src/components/ui/sonner.tsx rename to flowming/src/components/ui/sonner.tsx diff --git a/src/components/ui/tabs.tsx b/flowming/src/components/ui/tabs.tsx similarity index 100% rename from src/components/ui/tabs.tsx rename to flowming/src/components/ui/tabs.tsx diff --git a/src/components/ui/textarea.tsx b/flowming/src/components/ui/textarea.tsx similarity index 100% rename from src/components/ui/textarea.tsx rename to flowming/src/components/ui/textarea.tsx diff --git a/src/components/ui/tooltip.tsx b/flowming/src/components/ui/tooltip.tsx similarity index 100% rename from src/components/ui/tooltip.tsx rename to flowming/src/components/ui/tooltip.tsx diff --git a/src/context/CollaborationContext.tsx b/flowming/src/context/CollaborationContext.tsx similarity index 100% rename from src/context/CollaborationContext.tsx rename to flowming/src/context/CollaborationContext.tsx diff --git a/src/context/DebuggerContext.tsx b/flowming/src/context/DebuggerContext.tsx similarity index 100% rename from src/context/DebuggerContext.tsx rename to flowming/src/context/DebuggerContext.tsx diff --git a/src/context/DnDContext.tsx b/flowming/src/context/DnDContext.tsx similarity index 100% rename from src/context/DnDContext.tsx rename to flowming/src/context/DnDContext.tsx diff --git a/src/context/FilenameContext.tsx b/flowming/src/context/FilenameContext.tsx similarity index 100% rename from src/context/FilenameContext.tsx rename to flowming/src/context/FilenameContext.tsx diff --git a/src/context/FlowExecutorContext.tsx b/flowming/src/context/FlowExecutorContext.tsx similarity index 100% rename from src/context/FlowExecutorContext.tsx rename to flowming/src/context/FlowExecutorContext.tsx diff --git a/src/context/FlowInteractionContext.tsx b/flowming/src/context/FlowInteractionContext.tsx similarity index 100% rename from src/context/FlowInteractionContext.tsx rename to flowming/src/context/FlowInteractionContext.tsx diff --git a/src/context/InputDialogContext.tsx b/flowming/src/context/InputDialogContext.tsx similarity index 100% rename from src/context/InputDialogContext.tsx rename to flowming/src/context/InputDialogContext.tsx diff --git a/src/context/SelectedNodeContext.tsx b/flowming/src/context/SelectedNodeContext.tsx similarity index 100% rename from src/context/SelectedNodeContext.tsx rename to flowming/src/context/SelectedNodeContext.tsx diff --git a/src/context/SystemSettingsContext.tsx b/flowming/src/context/SystemSettingsContext.tsx similarity index 100% rename from src/context/SystemSettingsContext.tsx rename to flowming/src/context/SystemSettingsContext.tsx diff --git a/src/context/VariablesContext.tsx b/flowming/src/context/VariablesContext.tsx similarity index 100% rename from src/context/VariablesContext.tsx rename to flowming/src/context/VariablesContext.tsx diff --git a/src/data/exercises.json b/flowming/src/data/exercises.json similarity index 100% rename from src/data/exercises.json rename to flowming/src/data/exercises.json diff --git a/src/data/solutions/hello_world.json b/flowming/src/data/solutions/hello_world.json similarity index 100% rename from src/data/solutions/hello_world.json rename to flowming/src/data/solutions/hello_world.json diff --git a/src/data/solutions/sum_two_numbers.json b/flowming/src/data/solutions/sum_two_numbers.json similarity index 100% rename from src/data/solutions/sum_two_numbers.json rename to flowming/src/data/solutions/sum_two_numbers.json diff --git a/src/hooks/useFlowExecutor.ts b/flowming/src/hooks/useFlowExecutor.ts similarity index 100% rename from src/hooks/useFlowExecutor.ts rename to flowming/src/hooks/useFlowExecutor.ts diff --git a/src/index.css b/flowming/src/index.css similarity index 100% rename from src/index.css rename to flowming/src/index.css diff --git a/src/lib/utils.ts b/flowming/src/lib/utils.ts similarity index 100% rename from src/lib/utils.ts rename to flowming/src/lib/utils.ts diff --git a/src/main.tsx b/flowming/src/main.tsx similarity index 100% rename from src/main.tsx rename to flowming/src/main.tsx diff --git a/src/models/Expression.ts b/flowming/src/models/Expression.ts similarity index 100% rename from src/models/Expression.ts rename to flowming/src/models/Expression.ts diff --git a/src/models/ExpressionElement.ts b/flowming/src/models/ExpressionElement.ts similarity index 100% rename from src/models/ExpressionElement.ts rename to flowming/src/models/ExpressionElement.ts diff --git a/src/models/ExpressionParser.ts b/flowming/src/models/ExpressionParser.ts similarity index 100% rename from src/models/ExpressionParser.ts rename to flowming/src/models/ExpressionParser.ts diff --git a/src/models/ValuedVariable.ts b/flowming/src/models/ValuedVariable.ts similarity index 100% rename from src/models/ValuedVariable.ts rename to flowming/src/models/ValuedVariable.ts diff --git a/src/models/Variable.ts b/flowming/src/models/Variable.ts similarity index 100% rename from src/models/Variable.ts rename to flowming/src/models/Variable.ts diff --git a/src/models/index.ts b/flowming/src/models/index.ts similarity index 100% rename from src/models/index.ts rename to flowming/src/models/index.ts diff --git a/src/models/pythonAST.ts b/flowming/src/models/pythonAST.ts similarity index 100% rename from src/models/pythonAST.ts rename to flowming/src/models/pythonAST.ts diff --git a/src/tests/CodeTab.test.ts b/flowming/src/tests/CodeTab.test.ts similarity index 100% rename from src/tests/CodeTab.test.ts rename to flowming/src/tests/CodeTab.test.ts diff --git a/src/tests/Expression.test.ts b/flowming/src/tests/Expression.test.ts similarity index 100% rename from src/tests/Expression.test.ts rename to flowming/src/tests/Expression.test.ts diff --git a/src/tests/Models.test.ts b/flowming/src/tests/Models.test.ts similarity index 100% rename from src/tests/Models.test.ts rename to flowming/src/tests/Models.test.ts diff --git a/src/tests/codeGeneration.test.ts b/flowming/src/tests/codeGeneration.test.ts similarity index 100% rename from src/tests/codeGeneration.test.ts rename to flowming/src/tests/codeGeneration.test.ts diff --git a/src/utils/codeGeneration.ts b/flowming/src/utils/codeGeneration.ts similarity index 100% rename from src/utils/codeGeneration.ts rename to flowming/src/utils/codeGeneration.ts diff --git a/src/utils/expressionParsing.ts b/flowming/src/utils/expressionParsing.ts similarity index 100% rename from src/utils/expressionParsing.ts rename to flowming/src/utils/expressionParsing.ts diff --git a/src/utils/flowExecutorUtils.ts b/flowming/src/utils/flowExecutorUtils.ts similarity index 100% rename from src/utils/flowExecutorUtils.ts rename to flowming/src/utils/flowExecutorUtils.ts diff --git a/src/utils/flowTestRunner.ts b/flowming/src/utils/flowTestRunner.ts similarity index 100% rename from src/utils/flowTestRunner.ts rename to flowming/src/utils/flowTestRunner.ts diff --git a/src/utils/nodeStyles.ts b/flowming/src/utils/nodeStyles.ts similarity index 100% rename from src/utils/nodeStyles.ts rename to flowming/src/utils/nodeStyles.ts diff --git a/src/vite-env.d.ts b/flowming/src/vite-env.d.ts similarity index 100% rename from src/vite-env.d.ts rename to flowming/src/vite-env.d.ts diff --git a/tailwind.config.js b/flowming/tailwind.config.js similarity index 100% rename from tailwind.config.js rename to flowming/tailwind.config.js diff --git a/tsconfig.app.json b/flowming/tsconfig.app.json similarity index 100% rename from tsconfig.app.json rename to flowming/tsconfig.app.json diff --git a/tsconfig.json b/flowming/tsconfig.json similarity index 100% rename from tsconfig.json rename to flowming/tsconfig.json diff --git a/tsconfig.node.json b/flowming/tsconfig.node.json similarity index 100% rename from tsconfig.node.json rename to flowming/tsconfig.node.json diff --git a/vite.config.ts b/flowming/vite.config.ts similarity index 100% rename from vite.config.ts rename to flowming/vite.config.ts diff --git a/package-lock.json b/package-lock.json index 359f44f..7d52a24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,22 @@ { - "name": "flowming", - "version": "0.0.0", + "name": "flowming-monorepo", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "flowming", - "version": "0.0.0", + "name": "flowming-monorepo", + "version": "1.0.0", + "workspaces": [ + "flowming", + "y-webrtc-server" + ], + "devDependencies": { + "concurrently": "^8.2.2" + } + }, + "flowming": { + "version": "1.0.0", "dependencies": { "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^9.0.0", @@ -860,6 +870,23 @@ "node": ">=18" } }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", @@ -903,9 +930,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", + "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -918,9 +945,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", - "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", + "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==", "dev": true, "license": "Apache-2.0", "engines": { @@ -978,9 +1005,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", - "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", + "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", "dev": true, "license": "MIT", "engines": { @@ -1001,19 +1028,32 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", - "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.2.tgz", + "integrity": "sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.14.0", + "@eslint/core": "^0.15.0", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.0.tgz", + "integrity": "sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@floating-ui/core": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.1.tgz", @@ -2055,16 +2095,16 @@ "license": "MIT" }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.9.tgz", - "integrity": "sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==", + "version": "1.0.0-beta.11", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.11.tgz", + "integrity": "sha512-L/gAA/hyCSuzTF1ftlzUSI/IKr2POHsv1Dd78GfqkR83KMNuswWD61JxGV2L7nRwBBBSDr6R1gCkdTmoN7W4ag==", "dev": true, "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.42.0.tgz", - "integrity": "sha512-gldmAyS9hpj+H6LpRNlcjQWbuKUtb94lodB9uCz71Jm+7BxK1VIOo7y62tZZwxhA7j1ylv/yQz080L5WkS+LoQ==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.43.0.tgz", + "integrity": "sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==", "cpu": [ "arm" ], @@ -2076,9 +2116,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.42.0.tgz", - "integrity": "sha512-bpRipfTgmGFdCZDFLRvIkSNO1/3RGS74aWkJJTFJBH7h3MRV4UijkaEUeOMbi9wxtxYmtAbVcnMtHTPBhLEkaw==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.43.0.tgz", + "integrity": "sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==", "cpu": [ "arm64" ], @@ -2090,9 +2130,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.42.0.tgz", - "integrity": "sha512-JxHtA081izPBVCHLKnl6GEA0w3920mlJPLh89NojpU2GsBSB6ypu4erFg/Wx1qbpUbepn0jY4dVWMGZM8gplgA==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.43.0.tgz", + "integrity": "sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==", "cpu": [ "arm64" ], @@ -2104,9 +2144,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.42.0.tgz", - "integrity": "sha512-rv5UZaWVIJTDMyQ3dCEK+m0SAn6G7H3PRc2AZmExvbDvtaDc+qXkei0knQWcI3+c9tEs7iL/4I4pTQoPbNL2SA==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.43.0.tgz", + "integrity": "sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==", "cpu": [ "x64" ], @@ -2118,9 +2158,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.42.0.tgz", - "integrity": "sha512-fJcN4uSGPWdpVmvLuMtALUFwCHgb2XiQjuECkHT3lWLZhSQ3MBQ9pq+WoWeJq2PrNxr9rPM1Qx+IjyGj8/c6zQ==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.43.0.tgz", + "integrity": "sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==", "cpu": [ "arm64" ], @@ -2132,9 +2172,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.42.0.tgz", - "integrity": "sha512-CziHfyzpp8hJpCVE/ZdTizw58gr+m7Y2Xq5VOuCSrZR++th2xWAz4Nqk52MoIIrV3JHtVBhbBsJcAxs6NammOQ==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.43.0.tgz", + "integrity": "sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==", "cpu": [ "x64" ], @@ -2146,9 +2186,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.42.0.tgz", - "integrity": "sha512-UsQD5fyLWm2Fe5CDM7VPYAo+UC7+2Px4Y+N3AcPh/LdZu23YcuGPegQly++XEVaC8XUTFVPscl5y5Cl1twEI4A==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.43.0.tgz", + "integrity": "sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==", "cpu": [ "arm" ], @@ -2160,9 +2200,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.42.0.tgz", - "integrity": "sha512-/i8NIrlgc/+4n1lnoWl1zgH7Uo0XK5xK3EDqVTf38KvyYgCU/Rm04+o1VvvzJZnVS5/cWSd07owkzcVasgfIkQ==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.43.0.tgz", + "integrity": "sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==", "cpu": [ "arm" ], @@ -2174,9 +2214,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.42.0.tgz", - "integrity": "sha512-eoujJFOvoIBjZEi9hJnXAbWg+Vo1Ov8n/0IKZZcPZ7JhBzxh2A+2NFyeMZIRkY9iwBvSjloKgcvnjTbGKHE44Q==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.43.0.tgz", + "integrity": "sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==", "cpu": [ "arm64" ], @@ -2188,9 +2228,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.42.0.tgz", - "integrity": "sha512-/3NrcOWFSR7RQUQIuZQChLND36aTU9IYE4j+TB40VU78S+RA0IiqHR30oSh6P1S9f9/wVOenHQnacs/Byb824g==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.43.0.tgz", + "integrity": "sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==", "cpu": [ "arm64" ], @@ -2202,9 +2242,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.42.0.tgz", - "integrity": "sha512-O8AplvIeavK5ABmZlKBq9/STdZlnQo7Sle0LLhVA7QT+CiGpNVe197/t8Aph9bhJqbDVGCHpY2i7QyfEDDStDg==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.43.0.tgz", + "integrity": "sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==", "cpu": [ "loong64" ], @@ -2216,9 +2256,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.42.0.tgz", - "integrity": "sha512-6Qb66tbKVN7VyQrekhEzbHRxXXFFD8QKiFAwX5v9Xt6FiJ3BnCVBuyBxa2fkFGqxOCSGGYNejxd8ht+q5SnmtA==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.43.0.tgz", + "integrity": "sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==", "cpu": [ "ppc64" ], @@ -2230,9 +2270,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.42.0.tgz", - "integrity": "sha512-KQETDSEBamQFvg/d8jajtRwLNBlGc3aKpaGiP/LvEbnmVUKlFta1vqJqTrvPtsYsfbE/DLg5CC9zyXRX3fnBiA==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.43.0.tgz", + "integrity": "sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==", "cpu": [ "riscv64" ], @@ -2244,9 +2284,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.42.0.tgz", - "integrity": "sha512-qMvnyjcU37sCo/tuC+JqeDKSuukGAd+pVlRl/oyDbkvPJ3awk6G6ua7tyum02O3lI+fio+eM5wsVd66X0jQtxw==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.43.0.tgz", + "integrity": "sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==", "cpu": [ "riscv64" ], @@ -2258,9 +2298,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.42.0.tgz", - "integrity": "sha512-I2Y1ZUgTgU2RLddUHXTIgyrdOwljjkmcZ/VilvaEumtS3Fkuhbw4p4hgHc39Ypwvo2o7sBFNl2MquNvGCa55Iw==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.43.0.tgz", + "integrity": "sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==", "cpu": [ "s390x" ], @@ -2272,9 +2312,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.42.0.tgz", - "integrity": "sha512-Gfm6cV6mj3hCUY8TqWa63DB8Mx3NADoFwiJrMpoZ1uESbK8FQV3LXkhfry+8bOniq9pqY1OdsjFWNsSbfjPugw==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.43.0.tgz", + "integrity": "sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==", "cpu": [ "x64" ], @@ -2286,9 +2326,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.42.0.tgz", - "integrity": "sha512-g86PF8YZ9GRqkdi0VoGlcDUb4rYtQKyTD1IVtxxN4Hpe7YqLBShA7oHMKU6oKTCi3uxwW4VkIGnOaH/El8de3w==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.43.0.tgz", + "integrity": "sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==", "cpu": [ "x64" ], @@ -2300,9 +2340,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.42.0.tgz", - "integrity": "sha512-+axkdyDGSp6hjyzQ5m1pgcvQScfHnMCcsXkx8pTgy/6qBmWVhtRVlgxjWwDp67wEXXUr0x+vD6tp5W4x6V7u1A==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.43.0.tgz", + "integrity": "sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==", "cpu": [ "arm64" ], @@ -2314,9 +2354,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.42.0.tgz", - "integrity": "sha512-F+5J9pelstXKwRSDq92J0TEBXn2nfUrQGg+HK1+Tk7VOL09e0gBqUHugZv7SW4MGrYj41oNCUe3IKCDGVlis2g==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.43.0.tgz", + "integrity": "sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==", "cpu": [ "ia32" ], @@ -2327,6 +2367,20 @@ "win32" ] }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.43.0.tgz", + "integrity": "sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@testing-library/dom": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", @@ -2561,9 +2615,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.15.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz", - "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==", + "version": "22.15.32", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.32.tgz", + "integrity": "sha512-3jigKqgSjsH6gYZv2nEsqdXfZqIFGAV36XYYjf9KGZ3PSG+IhLecqPnI310RvjutyMwifE2hhhNEklOUrvx/wA==", "dev": true, "license": "MIT", "dependencies": { @@ -2571,9 +2625,9 @@ } }, "node_modules/@types/react": { - "version": "19.1.6", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz", - "integrity": "sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q==", + "version": "19.1.8", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", + "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -2605,17 +2659,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.1.tgz", - "integrity": "sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A==", + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz", + "integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/type-utils": "8.33.1", - "@typescript-eslint/utils": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/scope-manager": "8.34.1", + "@typescript-eslint/type-utils": "8.34.1", + "@typescript-eslint/utils": "8.34.1", + "@typescript-eslint/visitor-keys": "8.34.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -2629,7 +2683,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.33.1", + "@typescript-eslint/parser": "^8.34.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } @@ -2645,16 +2699,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.1.tgz", - "integrity": "sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA==", + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.1.tgz", + "integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/typescript-estree": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/scope-manager": "8.34.1", + "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/typescript-estree": "8.34.1", + "@typescript-eslint/visitor-keys": "8.34.1", "debug": "^4.3.4" }, "engines": { @@ -2670,14 +2724,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.1.tgz", - "integrity": "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==", + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz", + "integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.33.1", - "@typescript-eslint/types": "^8.33.1", + "@typescript-eslint/tsconfig-utils": "^8.34.1", + "@typescript-eslint/types": "^8.34.1", "debug": "^4.3.4" }, "engines": { @@ -2692,14 +2746,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz", - "integrity": "sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==", + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz", + "integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1" + "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/visitor-keys": "8.34.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2710,9 +2764,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.1.tgz", - "integrity": "sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==", + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz", + "integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==", "dev": true, "license": "MIT", "engines": { @@ -2727,14 +2781,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.1.tgz", - "integrity": "sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww==", + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz", + "integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.33.1", - "@typescript-eslint/utils": "8.33.1", + "@typescript-eslint/typescript-estree": "8.34.1", + "@typescript-eslint/utils": "8.34.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -2751,9 +2805,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.1.tgz", - "integrity": "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==", + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz", + "integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==", "dev": true, "license": "MIT", "engines": { @@ -2765,16 +2819,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.1.tgz", - "integrity": "sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==", + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz", + "integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.33.1", - "@typescript-eslint/tsconfig-utils": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/project-service": "8.34.1", + "@typescript-eslint/tsconfig-utils": "8.34.1", + "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/visitor-keys": "8.34.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2794,9 +2848,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/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==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2833,16 +2887,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.1.tgz", - "integrity": "sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==", + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz", + "integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/typescript-estree": "8.33.1" + "@typescript-eslint/scope-manager": "8.34.1", + "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/typescript-estree": "8.34.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2857,14 +2911,14 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.1.tgz", - "integrity": "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==", + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz", + "integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.33.1", - "eslint-visitor-keys": "^4.2.0" + "@typescript-eslint/types": "8.34.1", + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2875,16 +2929,16 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.1.tgz", - "integrity": "sha512-uPZBqSI0YD4lpkIru6M35sIfylLGTyhGHvDZbNLuMA73lMlwJKz5xweH7FajfcCAc2HnINciejA9qTz0dr0M7A==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.2.tgz", + "integrity": "sha512-QNVT3/Lxx99nMQWJWF7K4N6apUEuT0KlZA3mx/mVaoGj3smm/8rc8ezz15J1pcbcjDK0V15rpHetVfya08r76Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.26.10", - "@babel/plugin-transform-react-jsx-self": "^7.25.9", - "@babel/plugin-transform-react-jsx-source": "^7.25.9", - "@rolldown/pluginutils": "1.0.0-beta.9", + "@babel/core": "^7.27.4", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.11", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, @@ -2892,19 +2946,19 @@ "node": "^14.18.0 || >=16.0.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" } }, "node_modules/@vitest/expect": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.2.tgz", - "integrity": "sha512-ipHw0z669vEMjzz3xQE8nJX1s0rQIb7oEl4jjl35qWTwm/KIHERIg/p/zORrjAaZKXfsv7IybcNGHwhOOAPMwQ==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.3.tgz", + "integrity": "sha512-W2RH2TPWVHA1o7UmaFKISPvdicFJH+mjykctJFoAkUw+SPTJTGjUNdKscFBrqM7IPnCVu6zihtKYa7TkZS1dkQ==", "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.2", - "@vitest/utils": "3.2.2", + "@vitest/spy": "3.2.3", + "@vitest/utils": "3.2.3", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -2913,13 +2967,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.2.tgz", - "integrity": "sha512-jKojcaRyIYpDEf+s7/dD3LJt53c0dPfp5zCPXz9H/kcGrSlovU/t1yEaNzM9oFME3dcd4ULwRI/x0Po1Zf+LTw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.3.tgz", + "integrity": "sha512-cP6fIun+Zx8he4rbWvi+Oya6goKQDZK+Yq4hhlggwQBbrlOQ4qtZ+G4nxB6ZnzI9lyIb+JnvyiJnPC2AGbKSPA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.2.2", + "@vitest/spy": "3.2.3", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -2940,9 +2994,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.2.tgz", - "integrity": "sha512-FY4o4U1UDhO9KMd2Wee5vumwcaHw7Vg4V7yR4Oq6uK34nhEJOmdRYrk3ClburPRUA09lXD/oXWZ8y/Sdma0aUQ==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.3.tgz", + "integrity": "sha512-yFglXGkr9hW/yEXngO+IKMhP0jxyFw2/qys/CK4fFUZnSltD+MU7dVYGrH8rvPcK/O6feXQA+EU33gjaBBbAng==", "dev": true, "license": "MIT", "dependencies": { @@ -2953,27 +3007,28 @@ } }, "node_modules/@vitest/runner": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.2.tgz", - "integrity": "sha512-GYcHcaS3ejGRZYed2GAkvsjBeXIEerDKdX3orQrBJqLRiea4NSS9qvn9Nxmuy1IwIB+EjFOaxXnX79l8HFaBwg==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.3.tgz", + "integrity": "sha512-83HWYisT3IpMaU9LN+VN+/nLHVBCSIUKJzGxC5RWUOsK1h3USg7ojL+UXQR3b4o4UBIWCYdD2fxuzM7PQQ1u8w==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.2.2", - "pathe": "^2.0.3" + "@vitest/utils": "3.2.3", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.2.tgz", - "integrity": "sha512-aMEI2XFlR1aNECbBs5C5IZopfi5Lb8QJZGGpzS8ZUHML5La5wCbrbhLOVSME68qwpT05ROEEOAZPRXFpxZV2wA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.3.tgz", + "integrity": "sha512-9gIVWx2+tysDqUmmM1L0hwadyumqssOL1r8KJipwLx5JVYyxvVRfxvMq7DaWbZZsCqZnu/dZedaZQh4iYTtneA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.2", + "@vitest/pretty-format": "3.2.3", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -2982,9 +3037,9 @@ } }, "node_modules/@vitest/spy": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.2.tgz", - "integrity": "sha512-6Utxlx3o7pcTxvp0u8kUiXtRFScMrUg28KjB3R2hon7w4YqOFAEA9QwzPVVS1QNL3smo4xRNOpNZClRVfpMcYg==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.3.tgz", + "integrity": "sha512-JHu9Wl+7bf6FEejTCREy+DmgWe+rQKbK+y32C/k5f4TBIAlijhJbRBIRIOCEpVevgRsCQR2iHRUH2/qKVM/plw==", "dev": true, "license": "MIT", "dependencies": { @@ -2995,13 +3050,13 @@ } }, "node_modules/@vitest/utils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.2.tgz", - "integrity": "sha512-qJYMllrWpF/OYfWHP32T31QCaLa3BAzT/n/8mNGhPdVcjY+JYazQFO1nsJvXU12Kp1xMpNY4AGuljPTNjQve6A==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.3.tgz", + "integrity": "sha512-4zFBCU5Pf+4Z6v+rwnZ1HU1yzOKKvDkMXZrymE2PBlbjKJRlrOxbvpfPSvJTGRIwGoahaOGvp+kbCoxifhzJ1Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.2", + "@vitest/pretty-format": "3.2.3", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" }, @@ -3010,12 +3065,12 @@ } }, "node_modules/@xyflow/react": { - "version": "12.6.4", - "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.6.4.tgz", - "integrity": "sha512-/dOQ43Nu217cwHzy7f8kNUrFMeJJENzftVgT2VdFFHi6fHlG83pF+gLmvkRW9Be7alCsR6G+LFxxCdsQQbazHg==", + "version": "12.7.0", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.7.0.tgz", + "integrity": "sha512-U6VMEbYjiCg1byHrR7S+b5ZdHTjgCFX4KpBc634G/WtEBUvBLoMQdlCD6uJHqodnOAxpt3+G2wiDeTmXAFJzgQ==", "license": "MIT", "dependencies": { - "@xyflow/system": "0.0.61", + "@xyflow/system": "0.0.62", "classcat": "^5.0.3", "zustand": "^4.4.0" }, @@ -3025,16 +3080,18 @@ } }, "node_modules/@xyflow/system": { - "version": "0.0.61", - "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.61.tgz", - "integrity": "sha512-TsZG/Ez8dzxX6/Ol44LvFqVZsYvyz6dpDlAQZZk6hTL7JLGO5vN3dboRJqMwU8/Qtr5IEv5YBzojjAwIqW1HCA==", + "version": "0.0.62", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.62.tgz", + "integrity": "sha512-Z2ufbnvuYxIOCGyzE/8eX8TAEM8Lpzc/JafjD1Tzy6ZJs/E7KGVU17Q1F5WDHVW+dbztJAdyXMG0ejR9bwSUAA==", "license": "MIT", "dependencies": { "@types/d3-drag": "^3.0.7", + "@types/d3-interpolate": "^3.0.4", "@types/d3-selection": "^3.0.10", "@types/d3-transition": "^3.0.8", "@types/d3-zoom": "^3.0.8", "d3-drag": "^3.0.0", + "d3-interpolate": "^3.0.1", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0" } @@ -3244,9 +3301,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -3353,9 +3410,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001721", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz", - "integrity": "sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==", + "version": "1.0.30001723", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz", + "integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==", "dev": true, "funding": [ { @@ -3501,6 +3558,74 @@ "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", "license": "MIT" }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/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/cliui/node_modules/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/cliui/node_modules/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/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -3570,6 +3695,50 @@ "dev": true, "license": "MIT" }, + "node_modules/concurrently": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", + "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "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": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "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/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -3721,6 +3890,23 @@ "node": ">=12" } }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -3798,9 +3984,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.165", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz", - "integrity": "sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw==", + "version": "1.5.168", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.168.tgz", + "integrity": "sha512-RUNQmFLNIWVW6+z32EJQ5+qx8ci6RGvdtDC0Ls+F89wz6I2AthpXF0w0DIrn2jpLX0/PU9ZCo+Qp7bg/EckJmA==", "dev": true, "license": "ISC" }, @@ -3864,23 +4050,6 @@ "@esbuild/win32-x64": "0.25.5" } }, - "node_modules/esbuild/node_modules/@esbuild/win32-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", - "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -3905,19 +4074,19 @@ } }, "node_modules/eslint": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", - "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", + "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", + "@eslint/config-array": "^0.20.1", "@eslint/config-helpers": "^0.2.1", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.28.0", + "@eslint/js": "9.29.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -3929,9 +4098,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -3989,9 +4158,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -4006,9 +4175,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4019,15 +4188,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4236,6 +4405,10 @@ "dev": true, "license": "ISC" }, + "node_modules/flowming": { + "resolved": "flowming", + "link": true + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -4275,13 +4448,13 @@ } }, "node_modules/framer-motion": { - "version": "12.16.0", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.16.0.tgz", - "integrity": "sha512-xryrmD4jSBQrS2IkMdcTmiS4aSKckbS7kLDCuhUn9110SQKG1w3zlq1RTqCblewg+ZYe+m3sdtzQA6cRwo5g8Q==", + "version": "12.18.1", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.18.1.tgz", + "integrity": "sha512-6o4EDuRPLk4LSZ1kRnnEOurbQ86MklVk+Y1rFBUKiF+d2pCdvMjWVu0ZkyMVCTwl5UyTH2n/zJEJx+jvTYuxow==", "license": "MIT", "dependencies": { - "motion-dom": "^12.16.0", - "motion-utils": "^12.12.1", + "motion-dom": "^12.18.1", + "motion-utils": "^12.18.1", "tslib": "^2.4.0" }, "peerDependencies": { @@ -4340,6 +4513,16 @@ "integrity": "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==", "license": "MIT" }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-nonce": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", @@ -4382,9 +4565,9 @@ } }, "node_modules/glob/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==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -4873,9 +5056,9 @@ "license": "MIT" }, "node_modules/loupe": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", - "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", + "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==", "dev": true, "license": "MIT" }, @@ -4988,18 +5171,18 @@ } }, "node_modules/motion-dom": { - "version": "12.16.0", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.16.0.tgz", - "integrity": "sha512-Z2nGwWrrdH4egLEtgYMCEN4V2qQt1qxlKy/uV7w691ztyA41Q5Rbn0KNGbsNVDZr9E8PD2IOQ3hSccRnB6xWzw==", + "version": "12.18.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.18.1.tgz", + "integrity": "sha512-dR/4EYT23Snd+eUSLrde63Ws3oXQtJNw/krgautvTfwrN/2cHfCZMdu6CeTxVfRRWREW3Fy1f5vobRDiBb/q+w==", "license": "MIT", "dependencies": { - "motion-utils": "^12.12.1" + "motion-utils": "^12.18.1" } }, "node_modules/motion-utils": { - "version": "12.12.1", - "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.12.1.tgz", - "integrity": "sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w==", + "version": "12.18.1", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.18.1.tgz", + "integrity": "sha512-az26YDU4WoDP0ueAkUtABLk2BIxe28d8NH1qWT8jPGhPyf44XTdDUh8pDk9OPphaSrR9McgpcJlgwSOIw/sfkA==", "license": "MIT" }, "node_modules/ms": { @@ -5286,9 +5469,9 @@ } }, "node_modules/postcss": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", - "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "funding": [ { "type": "opencollective", @@ -5616,9 +5799,9 @@ } }, "node_modules/react-resizable-panels": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-3.0.2.tgz", - "integrity": "sha512-j4RNII75fnHkLnbsTb5G5YsDvJsSEZrJK2XSF2z0Tc2jIonYlIVir/Yh/5LvcUFCfs1HqrMAoiBFmIrRjC4XnA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-3.0.3.tgz", + "integrity": "sha512-7HA8THVBHTzhDK4ON0tvlGXyMAJN1zBeRpuyyremSikgYh2ku6ltD7tsGQOcXx4NKPrZtYCm/5CBr+dkruTGQw==", "license": "MIT", "peerDependencies": { "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", @@ -5737,6 +5920,16 @@ "node": ">=6" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -5778,9 +5971,9 @@ } }, "node_modules/rollup": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.42.0.tgz", - "integrity": "sha512-LW+Vse3BJPyGJGAJt1j8pWDKPd73QM8cRXYK1IxOBgL2AGLu7Xd2YOW0M2sLUBCkF5MshXXtMApyEAEzMVMsnw==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.43.0.tgz", + "integrity": "sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==", "dev": true, "license": "MIT", "dependencies": { @@ -5794,43 +5987,29 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.42.0", - "@rollup/rollup-android-arm64": "4.42.0", - "@rollup/rollup-darwin-arm64": "4.42.0", - "@rollup/rollup-darwin-x64": "4.42.0", - "@rollup/rollup-freebsd-arm64": "4.42.0", - "@rollup/rollup-freebsd-x64": "4.42.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.42.0", - "@rollup/rollup-linux-arm-musleabihf": "4.42.0", - "@rollup/rollup-linux-arm64-gnu": "4.42.0", - "@rollup/rollup-linux-arm64-musl": "4.42.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.42.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.42.0", - "@rollup/rollup-linux-riscv64-gnu": "4.42.0", - "@rollup/rollup-linux-riscv64-musl": "4.42.0", - "@rollup/rollup-linux-s390x-gnu": "4.42.0", - "@rollup/rollup-linux-x64-gnu": "4.42.0", - "@rollup/rollup-linux-x64-musl": "4.42.0", - "@rollup/rollup-win32-arm64-msvc": "4.42.0", - "@rollup/rollup-win32-ia32-msvc": "4.42.0", - "@rollup/rollup-win32-x64-msvc": "4.42.0", + "@rollup/rollup-android-arm-eabi": "4.43.0", + "@rollup/rollup-android-arm64": "4.43.0", + "@rollup/rollup-darwin-arm64": "4.43.0", + "@rollup/rollup-darwin-x64": "4.43.0", + "@rollup/rollup-freebsd-arm64": "4.43.0", + "@rollup/rollup-freebsd-x64": "4.43.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.43.0", + "@rollup/rollup-linux-arm-musleabihf": "4.43.0", + "@rollup/rollup-linux-arm64-gnu": "4.43.0", + "@rollup/rollup-linux-arm64-musl": "4.43.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.43.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.43.0", + "@rollup/rollup-linux-riscv64-gnu": "4.43.0", + "@rollup/rollup-linux-riscv64-musl": "4.43.0", + "@rollup/rollup-linux-s390x-gnu": "4.43.0", + "@rollup/rollup-linux-x64-gnu": "4.43.0", + "@rollup/rollup-linux-x64-musl": "4.43.0", + "@rollup/rollup-win32-arm64-msvc": "4.43.0", + "@rollup/rollup-win32-ia32-msvc": "4.43.0", + "@rollup/rollup-win32-x64-msvc": "4.43.0", "fsevents": "~2.3.2" } }, - "node_modules/rollup/node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.42.0.tgz", - "integrity": "sha512-LpHiJRwkaVz/LqjHjK8LCi8osq7elmpwujwbXKNW88bM8eeGxavJIKKjkjpMHAh/2xfnrt1ZSnhTv41WYUHYmA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/rollup/node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", @@ -5861,6 +6040,16 @@ "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.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -5918,6 +6107,19 @@ "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -5995,6 +6197,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", + "dev": true + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -6134,6 +6342,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-literal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -6182,9 +6410,9 @@ } }, "node_modules/tailwind-merge": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.0.tgz", - "integrity": "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", + "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==", "license": "MIT", "funding": { "type": "github", @@ -6290,9 +6518,9 @@ } }, "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", - "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "dev": true, "license": "MIT", "peerDependencies": { @@ -6318,9 +6546,9 @@ } }, "node_modules/tinypool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.0.tgz", - "integrity": "sha512-7CotroY9a8DKsKprEy/a14aCCm8jYVmR7aFy4fpkZM8sdpNJbKkixuNjgM50yCmip2ezc8z4N7k3oe2+rfRJCQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", "dev": true, "license": "MIT", "engines": { @@ -6359,6 +6587,16 @@ "node": ">=8.0" } }, + "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/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -6412,15 +6650,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.33.1.tgz", - "integrity": "sha512-AgRnV4sKkWOiZ0Kjbnf5ytTJXMUZQ0qhSVdQtDNYLPLnjsATEYhaO94GlRQwi4t4gO8FfjM6NnikHeKjUm8D7A==", + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.1.tgz", + "integrity": "sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.33.1", - "@typescript-eslint/parser": "8.33.1", - "@typescript-eslint/utils": "8.33.1" + "@typescript-eslint/eslint-plugin": "8.34.1", + "@typescript-eslint/parser": "8.34.1", + "@typescript-eslint/utils": "8.34.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6616,9 +6854,9 @@ } }, "node_modules/vite-node": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.2.tgz", - "integrity": "sha512-Xj/jovjZvDXOq2FgLXu8NsY4uHUMWtzVmMC2LkCu9HWdr9Qu1Is5sanX3Z4jOFKdohfaWDnEJWp9pRP0vVpAcA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.3.tgz", + "integrity": "sha512-gc8aAifGuDIpZHrPjuHyP4dpQmYXqWw7D1GmDnWeNWP654UEXzVfQ5IHPSK5HaHkwB/+p1atpYpSdw/2kOv8iQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6639,9 +6877,9 @@ } }, "node_modules/vite/node_modules/fdir": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", - "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "dev": true, "license": "MIT", "peerDependencies": { @@ -6667,20 +6905,20 @@ } }, "node_modules/vitest": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.2.tgz", - "integrity": "sha512-fyNn/Rp016Bt5qvY0OQvIUCwW2vnaEBLxP42PmKbNIoasSYjML+8xyeADOPvBe+Xfl/ubIw4og7Lt9jflRsCNw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.3.tgz", + "integrity": "sha512-E6U2ZFXe3N/t4f5BwUaVCKRLHqUpk1CBWeMh78UT4VaTPH/2dyvH6ALl29JTovEPu9dVKr/K/J4PkXgrMbw4Ww==", "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.2", - "@vitest/mocker": "3.2.2", - "@vitest/pretty-format": "^3.2.2", - "@vitest/runner": "3.2.2", - "@vitest/snapshot": "3.2.2", - "@vitest/spy": "3.2.2", - "@vitest/utils": "3.2.2", + "@vitest/expect": "3.2.3", + "@vitest/mocker": "3.2.3", + "@vitest/pretty-format": "^3.2.3", + "@vitest/runner": "3.2.3", + "@vitest/snapshot": "3.2.3", + "@vitest/spy": "3.2.3", + "@vitest/utils": "3.2.3", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", @@ -6694,7 +6932,7 @@ "tinypool": "^1.1.0", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", - "vite-node": "3.2.2", + "vite-node": "3.2.3", "why-is-node-running": "^2.3.0" }, "bin": { @@ -6710,8 +6948,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.2", - "@vitest/ui": "3.2.2", + "@vitest/browser": "3.2.3", + "@vitest/ui": "3.2.3", "happy-dom": "*", "jsdom": "*" }, @@ -6878,7 +7116,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", "license": "MIT", - "optional": true, "engines": { "node": ">=10.0.0" }, @@ -6951,6 +7188,20 @@ "yjs": "^13.6.8" } }, + "node_modules/y-webrtc-server": { + "resolved": "y-webrtc-server", + "link": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -6970,6 +7221,70 @@ "node": ">= 14.6" } }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/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/yargs/node_modules/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/yjs": { "version": "13.6.27", "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.27.tgz", @@ -7027,6 +7342,13 @@ "optional": true } } + }, + "y-webrtc-server": { + "version": "1.0.0", + "dependencies": { + "lib0": "^0.2.62", + "ws": "^8.18.0" + } } } } diff --git a/package.json b/package.json index edba92e..694debe 100644 --- a/package.json +++ b/package.json @@ -1,66 +1,24 @@ { - "name": "flowming", + "name": "flowming-monorepo", + "author": "Daniel Pérez Fernández", "private": true, - "version": "0.0.0", + "version": "1.0.0", "type": "module", + "workspaces": [ + "flowming", + "y-webrtc-server" + ], "scripts": { - "dev": "vite", - "build": "tsc -b && vite build", - "lint": "eslint .", - "preview": "vite preview", - "test": "vitest run" - }, - "dependencies": { - "@dnd-kit/core": "^6.3.1", - "@dnd-kit/modifiers": "^9.0.0", - "@dnd-kit/sortable": "^10.0.0", - "@dnd-kit/utilities": "^3.2.2", - "@radix-ui/react-accordion": "^1.2.11", - "@radix-ui/react-dialog": "^1.1.14", - "@radix-ui/react-label": "^2.1.7", - "@radix-ui/react-popover": "^1.1.14", - "@radix-ui/react-scroll-area": "^1.2.9", - "@radix-ui/react-select": "^2.2.5", - "@radix-ui/react-separator": "^1.1.7", - "@radix-ui/react-slot": "^1.2.3", - "@radix-ui/react-tabs": "^1.1.12", - "@radix-ui/react-tooltip": "^1.2.7", - "@types/react": "^19.0.10", - "@types/react-dom": "^19.0.4", - "@xyflow/react": "^12.4.3", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "cmdk": "^1.1.1", - "framer-motion": "^12.6.3", - "lucide-react": "^0.511.0", - "next-themes": "^0.4.6", - "react": "^19.0.0", - "react-dom": "^19.0.0", - "react-resizable-panels": "^3.0.2", - "react-syntax-highlighter": "^15.6.1", - "sonner": "^2.0.5", - "tailwind-merge": "^3.3.0", - "tailwindcss-animate": "^1.0.7", - "y-webrtc": "^10.3.0", - "yjs": "^13.6.27" + "dev": "concurrently \"npm:dev-client\" \"npm:dev-server\"", + "dev-client": "npm run dev --workspace=flowming", + "dev-server": "npm run start --workspace=y-webrtc-server", + "build": "npm run build --workspace=flowming", + "lint": "npm run lint --workspace=flowming", + "preview": "npm run preview --workspace=flowming", + "test": "npm run test --workspace=flowming", + "install-all": "npm install && npm install --workspace=flowming && npm install --workspace=y-webrtc-server" }, "devDependencies": { - "@eslint/js": "^9.19.0", - "@testing-library/jest-dom": "^6.6.3", - "@testing-library/react": "^16.3.0", - "@types/node": "^22.15.21", - "@types/react-syntax-highlighter": "^15.5.13", - "@vitejs/plugin-react": "^4.3.4", - "autoprefixer": "^10.4.21", - "eslint": "^9.19.0", - "eslint-plugin-react-hooks": "^5.0.0", - "eslint-plugin-react-refresh": "^0.4.18", - "globals": "^15.14.0", - "postcss": "^8.5.3", - "tailwindcss": "^3.4.0", - "typescript": "~5.7.2", - "typescript-eslint": "^8.22.0", - "vite": "^6.1.0", - "vitest": "^3.1.1" + "concurrently": "^8.2.2" } -} +} \ No newline at end of file diff --git a/y-webrtc-server/LICENSE b/y-webrtc-server/LICENSE new file mode 100644 index 0000000..cf37410 --- /dev/null +++ b/y-webrtc-server/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Kevin Jahns . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/y-webrtc-server/package.json b/y-webrtc-server/package.json new file mode 100644 index 0000000..3e5e21f --- /dev/null +++ b/y-webrtc-server/package.json @@ -0,0 +1,13 @@ +{ + "name": "y-webrtc-server", + "version": "1.0.0", + "main": "server.js", + "type": "module", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "lib0": "^0.2.62", + "ws": "^8.18.0" + } +} diff --git a/y-webrtc-server/server.js b/y-webrtc-server/server.js new file mode 100644 index 0000000..1d5ae6a --- /dev/null +++ b/y-webrtc-server/server.js @@ -0,0 +1,137 @@ +import { WebSocketServer } from 'ws' +import http from 'http' +import * as map from 'lib0/map' + +const wsReadyStateConnecting = 0 +const wsReadyStateOpen = 1 +const wsReadyStateClosing = 2 // eslint-disable-line +const wsReadyStateClosed = 3 // eslint-disable-line + +const pingTimeout = 30000 + +const port = process.env.PORT || 4444 +const wss = new WebSocketServer({ noServer: true }) + +const server = http.createServer((request, response) => { + response.writeHead(200, { 'Content-Type': 'text/plain' }) + response.end('okay') +}) + +/** + * Map froms topic-name to set of subscribed clients. + * @type {Map>} + */ +const topics = new Map() + +/** + * @param {any} conn + * @param {object} message + */ +const send = (conn, message) => { + if (conn.readyState !== wsReadyStateConnecting && conn.readyState !== wsReadyStateOpen) { + conn.close() + } + try { + conn.send(JSON.stringify(message)) + } catch (e) { + conn.close() + } +} + +/** + * Setup a new client + * @param {any} conn + */ +const onconnection = conn => { + /** + * @type {Set} + */ + const subscribedTopics = new Set() + let closed = false + // Check if connection is still alive + let pongReceived = true + const pingInterval = setInterval(() => { + if (!pongReceived) { + conn.close() + clearInterval(pingInterval) + } else { + pongReceived = false + try { + conn.ping() + } catch (e) { + conn.close() + } + } + }, pingTimeout) + conn.on('pong', () => { + pongReceived = true + }) + conn.on('close', () => { + subscribedTopics.forEach(topicName => { + const subs = topics.get(topicName) || new Set() + subs.delete(conn) + if (subs.size === 0) { + topics.delete(topicName) + } + }) + subscribedTopics.clear() + closed = true + }) + conn.on('message', /** @param {object} message */ message => { + if (typeof message === 'string' || message instanceof Buffer) { + message = JSON.parse(message) + } + if (message && message.type && !closed) { + switch (message.type) { + case 'subscribe': + /** @type {Array} */ (message.topics || []).forEach(topicName => { + if (typeof topicName === 'string') { + // add conn to topic + const topic = map.setIfUndefined(topics, topicName, () => new Set()) + topic.add(conn) + // add topic to conn + subscribedTopics.add(topicName) + } + }) + break + case 'unsubscribe': + /** @type {Array} */ (message.topics || []).forEach(topicName => { + const subs = topics.get(topicName) + if (subs) { + subs.delete(conn) + } + }) + break + case 'publish': + if (message.topic) { + const receivers = topics.get(message.topic) + if (receivers) { + message.clients = receivers.size + receivers.forEach(receiver => + send(receiver, message) + ) + } + } + break + case 'ping': + send(conn, { type: 'pong' }) + } + } + }) +} +wss.on('connection', onconnection) + +server.on('upgrade', (request, socket, head) => { + // You may check auth of request here.. + /** + * @param {any} ws + */ + const handleAuth = ws => { + wss.emit('connection', ws, request) + } + wss.handleUpgrade(request, socket, head, handleAuth) +}) + +server.listen(port) + +console.log('Signaling server running on localhost:', port) From 14f8b10106f5e08a35a734cd0795d6d7a84ca8e2 Mon Sep 17 00:00:00 2001 From: MrPoll0 Date: Tue, 17 Jun 2025 08:10:43 +0200 Subject: [PATCH 3/5] doc: small README change --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 694debe..f429696 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,6 @@ { "name": "flowming-monorepo", "author": "Daniel Pérez Fernández", - "private": true, "version": "1.0.0", "type": "module", "workspaces": [ From bd1041b4414ccb028c4c4178e5e9802884e68a7e Mon Sep 17 00:00:00 2001 From: MrPoll0 Date: Tue, 17 Jun 2025 08:14:55 +0200 Subject: [PATCH 4/5] docs: logo, status, license in README title --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5b96360..aba5f5e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,13 @@ -# Flowming +

+ + Flowming Logo + +

+ +

+ Build Status + License +

Interactive web application for designing, executing and debugging algorithms using standard flowchart notation. From e7c831406025dbe2673a42d0e30b14f953204f94 Mon Sep 17 00:00:00 2001 From: MrPoll0 Date: Tue, 17 Jun 2025 08:18:44 +0200 Subject: [PATCH 5/5] docs: minor README change --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aba5f5e..f164fd5 100644 --- a/README.md +++ b/README.md @@ -95,9 +95,9 @@ The first time you open a pull request you will be asked to sign our **Contribut --- -## 📚 Documentation & paper +## 📚 Project Thesis -This repository includes the full bachelor thesis (Spanish) that motivated the project, detailing state-of-the-art, design decisions and user evaluation. +This repository includes the full bachelor's thesis (Spanish) that motivated the project, detailing state-of-the-art, design decisions and user evaluation. *Title:* "Aplicación web para el diseño y ejecución de diagramas de flujo" *Author:* Daniel Pérez Fernández