From 1774e7b40e4b3fc5068eff192e8464f25173d7d1 Mon Sep 17 00:00:00 2001 From: Dibyendu Sahoo Date: Mon, 8 Dec 2025 12:10:55 +0000 Subject: [PATCH] linting errors since past how many commits not sure --- .../ColorSelector/CustomColorInput.tsx | 2 +- .../__tests__/ColorSelector.test.tsx | 158 +++-- .../__tests__/CustomColorInput.test.tsx | 71 ++- .../ConnectorEmptySpaceTooltip.tsx | 41 +- .../ConnectorHintTooltip.tsx | 33 +- .../ConnectorRerouteTooltip.tsx | 21 +- .../ConnectorSettings/ConnectorSettings.tsx | 22 +- .../components/ContextMenu/ContextMenu.tsx | 6 +- .../ContextMenu/ContextMenuManager.tsx | 13 +- .../ExportImageDialog/ExportImageDialog.tsx | 282 ++++++--- .../src/components/HelpDialog/HelpDialog.tsx | 26 +- .../HotkeySettings/HotkeySettings.tsx | 63 +- .../IconPackSettings/IconPackSettings.tsx | 121 ++-- .../ImportHintTooltip/ImportHintTooltip.tsx | 15 +- .../IconSelectionControls/IconGrid.tsx | 13 +- .../IconSelectionControls.tsx | 297 +++++---- .../NodeSettings/NodeSettings.tsx | 56 +- .../NodeControls/QuickIconSelector.tsx | 117 ++-- .../RectangleControls/RectangleControls.tsx | 12 +- .../src/components/Label/ExpandableLabel.tsx | 55 +- .../LabelSettings/LabelSettings.tsx | 20 +- .../LassoHintTooltip/LassoHintTooltip.tsx | 13 +- .../LazyLoadingWelcomeNotification.tsx | 20 +- .../src/components/MainMenu/MainMenu.tsx | 7 +- .../components/PanSettings/PanSettings.tsx | 52 +- .../src/components/Renderer/Renderer.tsx | 6 +- .../RichTextEditorErrorBoundary.tsx | 67 +- .../src/components/SceneLayer/SceneLayer.tsx | 87 ++- .../ConnectorLabels/ConnectorLabel.tsx | 1 - .../SceneLayers/Connectors/Connector.tsx | 594 +++++++++--------- .../SceneLayers/Rectangles/Rectangle.tsx | 46 +- .../SettingsDialog/SettingsDialog.tsx | 27 +- .../src/components/UiOverlay/UiOverlay.tsx | 12 +- .../components/ZoomSettings/ZoomSettings.tsx | 12 +- packages/fossflow-lib/src/config/hotkeys.ts | 2 +- .../fossflow-lib/src/config/panSettings.ts | 10 +- .../src/hooks/__tests__/useHistory.test.tsx | 224 ++++--- .../__tests__/useInitialDataManager.test.tsx | 132 ++-- .../src/hooks/useInitialDataManager.ts | 30 +- packages/fossflow-lib/src/hooks/useScene.ts | 11 +- packages/fossflow-lib/src/i18n/bn-BD.ts | 316 +++++----- packages/fossflow-lib/src/i18n/en-US.ts | 315 +++++----- packages/fossflow-lib/src/i18n/es-ES.ts | 322 +++++----- packages/fossflow-lib/src/i18n/fr-FR.ts | 304 ++++----- packages/fossflow-lib/src/i18n/hi-IN.ts | 317 +++++----- packages/fossflow-lib/src/i18n/index.ts | 18 +- packages/fossflow-lib/src/i18n/it-IT.ts | 306 ++++----- packages/fossflow-lib/src/i18n/pl-PL.ts | 323 +++++----- packages/fossflow-lib/src/i18n/pt-BR.ts | 317 +++++----- packages/fossflow-lib/src/i18n/ru-RU.ts | 318 +++++----- packages/fossflow-lib/src/i18n/zh-CN.ts | 309 ++++----- .../src/interaction/modes/Connector.ts | 39 +- .../src/interaction/modes/DragItems.ts | 24 +- .../src/interaction/modes/FreehandLasso.ts | 15 +- .../src/interaction/modes/Lasso.ts | 6 +- .../src/interaction/modes/PlaceIcon.ts | 6 +- .../src/interaction/useInteractionManager.ts | 52 +- .../src/interaction/usePanHandlers.ts | 160 ++--- .../fossflow-lib/src/schemas/connector.ts | 6 +- .../fossflow-lib/src/stores/localeStore.tsx | 17 +- .../reducers/__tests__/connector.test.ts | 365 ++++++----- .../reducers/__tests__/rectangle.test.ts | 262 ++++---- .../stores/reducers/__tests__/textBox.test.ts | 247 ++++---- .../reducers/__tests__/viewItem.test.ts | 213 ++++--- .../src/stores/reducers/connector.ts | 18 +- .../src/stores/reducers/viewItem.ts | 18 +- packages/fossflow-lib/src/types/common.ts | 11 +- .../src/types/dom-to-image-more.d.ts | 7 +- packages/fossflow-lib/src/types/ui.ts | 5 +- .../fossflow-lib/src/utils/exportOptions.ts | 81 ++- .../src/utils/findNearestUnoccupiedTile.ts | 22 +- .../fossflow-lib/src/utils/pointInPolygon.ts | 4 +- packages/fossflow-lib/src/utils/renderer.ts | 29 +- 73 files changed, 4212 insertions(+), 3357 deletions(-) diff --git a/packages/fossflow-lib/src/components/ColorSelector/CustomColorInput.tsx b/packages/fossflow-lib/src/components/ColorSelector/CustomColorInput.tsx index 529e9f1a..bfdd4766 100644 --- a/packages/fossflow-lib/src/components/ColorSelector/CustomColorInput.tsx +++ b/packages/fossflow-lib/src/components/ColorSelector/CustomColorInput.tsx @@ -67,7 +67,7 @@ export const CustomColorInput = ({ value, onChange }: Props) => { size="small" InputProps={{ disableUnderline: true, - sx: { + sx: { fontSize: '0.875rem', color: 'text.secondary', width: '80px' diff --git a/packages/fossflow-lib/src/components/ColorSelector/__tests__/ColorSelector.test.tsx b/packages/fossflow-lib/src/components/ColorSelector/__tests__/ColorSelector.test.tsx index ff116b10..b3a6e40b 100644 --- a/packages/fossflow-lib/src/components/ColorSelector/__tests__/ColorSelector.test.tsx +++ b/packages/fossflow-lib/src/components/ColorSelector/__tests__/ColorSelector.test.tsx @@ -14,11 +14,15 @@ const mockColors = [ { id: 'color5', value: '#FF00FF', name: 'Magenta' } ]; -jest.mock('../../../hooks/useScene', () => ({ - useScene: jest.fn(() => ({ - colors: mockColors - })) -})); +jest.mock('../../../hooks/useScene', () => { + return { + useScene: jest.fn(() => { + return { + colors: mockColors + }; + }) + }; +}); describe('ColorSelector', () => { const defaultProps = { @@ -41,7 +45,7 @@ describe('ColorSelector', () => { describe('rendering', () => { it('should render all colors from the scene', () => { renderComponent(); - + // Should render a button for each color const buttons = screen.getAllByRole('button'); expect(buttons).toHaveLength(mockColors.length); @@ -49,11 +53,11 @@ describe('ColorSelector', () => { it('should render all color swatches', () => { renderComponent(); - + // Should render a button for each color const buttons = screen.getAllByRole('button'); expect(buttons).toHaveLength(mockColors.length); - + // Each button should be clickable buttons.forEach((button) => { expect(button).toBeEnabled(); @@ -62,16 +66,20 @@ describe('ColorSelector', () => { it('should render empty state when no colors available', () => { const useScene = require('../../../hooks/useScene').useScene; - useScene.mockImplementation(() => ({ colors: [] })); - + useScene.mockImplementation(() => { + return { colors: [] }; + }); + const { container } = renderComponent(); - + // Should render container but no buttons expect(container.firstChild).toBeInTheDocument(); expect(screen.queryAllByRole('button')).toHaveLength(0); - + // Restore mock - useScene.mockImplementation(() => ({ colors: mockColors })); + useScene.mockImplementation(() => { + return { colors: mockColors }; + }); }); }); @@ -79,32 +87,32 @@ describe('ColorSelector', () => { it('should call onChange with correct color ID when clicked', () => { const onChange = jest.fn(); renderComponent({ onChange }); - + const buttons = screen.getAllByRole('button'); - + // Click the first color fireEvent.click(buttons[0]); expect(onChange).toHaveBeenCalledWith('color1'); - + // Click the third color fireEvent.click(buttons[2]); expect(onChange).toHaveBeenCalledWith('color3'); - + expect(onChange).toHaveBeenCalledTimes(2); }); it('should handle multiple rapid clicks', () => { const onChange = jest.fn(); renderComponent({ onChange }); - + const buttons = screen.getAllByRole('button'); - + // Rapidly click different colors fireEvent.click(buttons[0]); fireEvent.click(buttons[1]); fireEvent.click(buttons[2]); fireEvent.click(buttons[1]); - + expect(onChange).toHaveBeenCalledTimes(4); expect(onChange).toHaveBeenNthCalledWith(1, 'color1'); expect(onChange).toHaveBeenNthCalledWith(2, 'color2'); @@ -115,18 +123,18 @@ describe('ColorSelector', () => { it('should be keyboard accessible', () => { const onChange = jest.fn(); renderComponent({ onChange }); - + const buttons = screen.getAllByRole('button'); - + // All buttons should be focusable (have tabIndex) buttons.forEach((button) => { expect(button).toHaveAttribute('tabindex', '0'); }); - + // Buttons should respond to clicks (keyboard Enter/Space triggers click) fireEvent.click(buttons[0]); expect(onChange).toHaveBeenCalledWith('color1'); - + fireEvent.click(buttons[1]); expect(onChange).toHaveBeenCalledWith('color2'); }); @@ -135,15 +143,15 @@ describe('ColorSelector', () => { describe('active color indication', () => { it('should indicate the active color with scaled transform', () => { const { container } = renderComponent({ activeColor: 'color2' }); - + // Find all buttons (color swatches are inside buttons) const buttons = screen.getAllByRole('button'); expect(buttons).toHaveLength(mockColors.length); - + // The second button should contain the active color // We can check if the active prop was passed correctly const activeButton = buttons[1]; // color2 is at index 1 - + // Check that ColorSwatch received isActive=true for color2 // Since we can't easily check transform in JSDOM, we'll verify the component renders expect(activeButton).toBeInTheDocument(); @@ -151,17 +159,17 @@ describe('ColorSelector', () => { it('should update active indication when activeColor prop changes', () => { const { rerender } = renderComponent({ activeColor: 'color1' }); - + let buttons = screen.getAllByRole('button'); expect(buttons).toHaveLength(mockColors.length); - + // Change active color rerender( ); - + buttons = screen.getAllByRole('button'); expect(buttons).toHaveLength(mockColors.length); // Verify buttons still render after prop change @@ -170,7 +178,7 @@ describe('ColorSelector', () => { it('should handle no active color', () => { renderComponent({ activeColor: undefined }); - + // All buttons should still render const buttons = screen.getAllByRole('button'); expect(buttons).toHaveLength(mockColors.length); @@ -181,7 +189,7 @@ describe('ColorSelector', () => { it('should handle invalid active color ID gracefully', () => { renderComponent({ activeColor: 'invalid-color-id' }); - + // All buttons should still render even with invalid active color const buttons = screen.getAllByRole('button'); expect(buttons).toHaveLength(mockColors.length); @@ -194,50 +202,60 @@ describe('ColorSelector', () => { describe('edge cases', () => { it('should handle color with special characters in hex value', () => { const useScene = require('../../../hooks/useScene').useScene; - useScene.mockImplementation(() => ({ - colors: [ - { id: 'special', value: '#C0FFEE', name: 'Coffee' } - ] - })); - + useScene.mockImplementation(() => { + return { + colors: [{ id: 'special', value: '#C0FFEE', name: 'Coffee' }] + }; + }); + const onChange = jest.fn(); renderComponent({ onChange }); - + const button = screen.getByRole('button'); fireEvent.click(button); - + expect(onChange).toHaveBeenCalledWith('special'); - + // Restore mock - useScene.mockImplementation(() => ({ colors: mockColors })); + useScene.mockImplementation(() => { + return { colors: mockColors }; + }); }); it('should handle very long color lists efficiently', () => { const useScene = require('../../../hooks/useScene').useScene; - const manyColors = Array.from({ length: 100 }, (_, i) => ({ - id: `color${i}`, - value: `#${Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0')}`, - name: `Color ${i}` - })); - - useScene.mockImplementation(() => ({ colors: manyColors })); - + const manyColors = Array.from({ length: 100 }, (_, i) => { + return { + id: `color${i}`, + value: `#${Math.floor(Math.random() * 16777215) + .toString(16) + .padStart(6, '0')}`, + name: `Color ${i}` + }; + }); + + useScene.mockImplementation(() => { + return { colors: manyColors }; + }); + const { container } = renderComponent(); - + const buttons = screen.getAllByRole('button'); expect(buttons).toHaveLength(100); - + // Restore mock - useScene.mockImplementation(() => ({ colors: mockColors })); + useScene.mockImplementation(() => { + return { colors: mockColors }; + }); }); it('should handle onChange being required properly', () => { // onChange is a required prop, so we test with a valid function const onChange = jest.fn(); renderComponent({ onChange }); - + const buttons = screen.getAllByRole('button'); - + // Should work normally with onChange provided fireEvent.click(buttons[0]); expect(onChange).toHaveBeenCalledWith('color1'); @@ -246,32 +264,36 @@ describe('ColorSelector', () => { it('should handle colors being updated dynamically', () => { const useScene = require('../../../hooks/useScene').useScene; const onChange = jest.fn(); - + // Start with 3 colors - useScene.mockImplementation(() => ({ - colors: mockColors.slice(0, 3) - })); - + useScene.mockImplementation(() => { + return { + colors: mockColors.slice(0, 3) + }; + }); + const { rerender } = renderComponent({ onChange }); expect(screen.getAllByRole('button')).toHaveLength(3); - + // Update to 5 colors - useScene.mockImplementation(() => ({ - colors: mockColors - })); - + useScene.mockImplementation(() => { + return { + colors: mockColors + }; + }); + rerender( ); - + expect(screen.getAllByRole('button')).toHaveLength(5); - + // Click the newly added color const buttons = screen.getAllByRole('button'); fireEvent.click(buttons[4]); expect(onChange).toHaveBeenCalledWith('color5'); }); }); -}); \ No newline at end of file +}); diff --git a/packages/fossflow-lib/src/components/ColorSelector/__tests__/CustomColorInput.test.tsx b/packages/fossflow-lib/src/components/ColorSelector/__tests__/CustomColorInput.test.tsx index 86652719..14339d04 100644 --- a/packages/fossflow-lib/src/components/ColorSelector/__tests__/CustomColorInput.test.tsx +++ b/packages/fossflow-lib/src/components/ColorSelector/__tests__/CustomColorInput.test.tsx @@ -6,13 +6,28 @@ import { ThemeProvider } from '@mui/material/styles'; import { theme } from 'src/styles/theme'; // Mock ColorPicker since we don't need to test external library behavior -jest.mock('../ColorPicker', () => ({ - ColorPicker: ({ value, onChange }: { value: string; onChange: (color: string) => void }) => ( -
onChange('#FFFFFF')}> - {value} -
- ) -})); +jest.mock('../ColorPicker', () => { + return { + ColorPicker: ({ + value, + onChange + }: { + value: string; + onChange: (color: string) => void; + }) => { + return ( +
{ + return onChange('#FFFFFF'); + }} + > + {value} +
+ ); + } + }; +}); describe('CustomColorInput', () => { const defaultProps = { @@ -42,7 +57,7 @@ describe('CustomColorInput', () => { it('updates input value on change', () => { renderComponent(); const input = screen.getByRole('textbox') as HTMLInputElement; - + fireEvent.change(input, { target: { value: '#00FF00' } }); expect(input.value).toBe('#00FF00'); }); @@ -51,7 +66,7 @@ describe('CustomColorInput', () => { const onChange = jest.fn(); renderComponent({ onChange }); const input = screen.getByRole('textbox'); - + fireEvent.change(input, { target: { value: '#00FF00' } }); expect(onChange).toHaveBeenCalledWith('#00FF00'); }); @@ -60,7 +75,7 @@ describe('CustomColorInput', () => { const onChange = jest.fn(); renderComponent({ onChange }); const input = screen.getByRole('textbox'); - + fireEvent.change(input, { target: { value: 'invalid' } }); expect(onChange).not.toHaveBeenCalled(); }); @@ -68,10 +83,10 @@ describe('CustomColorInput', () => { it('reverts to prop value on blur if input is invalid', () => { renderComponent({ value: '#FF0000' }); const input = screen.getByRole('textbox') as HTMLInputElement; - + fireEvent.change(input, { target: { value: 'invalid' } }); fireEvent.blur(input); - + expect(input.value).toBe('#FF0000'); }); @@ -79,10 +94,10 @@ describe('CustomColorInput', () => { const onChange = jest.fn(); renderComponent({ value: '#FF0000', onChange }); const input = screen.getByRole('textbox') as HTMLInputElement; - + fireEvent.change(input, { target: { value: '#00FF00' } }); fireEvent.blur(input); - + expect(input.value).toBe('#00FF00'); expect(onChange).toHaveBeenCalledWith('#00FF00'); }); @@ -105,9 +120,11 @@ describe('CustomColorInput', () => { // Mock EyeDropper API Object.defineProperty(window, 'EyeDropper', { writable: true, - value: jest.fn().mockImplementation(() => ({ - open: jest.fn().mockResolvedValue({ sRGBHex: '#123456' }) - })) + value: jest.fn().mockImplementation(() => { + return { + open: jest.fn().mockResolvedValue({ sRGBHex: '#123456' }) + }; + }) }); }); @@ -118,13 +135,15 @@ describe('CustomColorInput', () => { it('renders eyedropper button when API is supported', () => { renderComponent(); - expect(screen.getByRole('button', { name: /pick color/i })).toBeInTheDocument(); + expect( + screen.getByRole('button', { name: /pick color/i }) + ).toBeInTheDocument(); }); it('calls onChange with picked color', async () => { const onChange = jest.fn(); renderComponent({ onChange }); - + const button = screen.getByRole('button', { name: /pick color/i }); await act(async () => { fireEvent.click(button); @@ -136,12 +155,14 @@ describe('CustomColorInput', () => { it('handles EyeDropper cancellation gracefully', async () => { const onChange = jest.fn(); // Mock rejection (user cancelled) - (window.EyeDropper as any).mockImplementation(() => ({ - open: jest.fn().mockRejectedValue(new Error('Canceled')) - })); + (window.EyeDropper as any).mockImplementation(() => { + return { + open: jest.fn().mockRejectedValue(new Error('Canceled')) + }; + }); renderComponent({ onChange }); - + const button = screen.getByRole('button', { name: /pick color/i }); await act(async () => { fireEvent.click(button); @@ -159,7 +180,9 @@ describe('CustomColorInput', () => { it('does not render eyedropper button when API is not supported', () => { renderComponent(); - expect(screen.queryByRole('button', { name: /pick color/i })).not.toBeInTheDocument(); + expect( + screen.queryByRole('button', { name: /pick color/i }) + ).not.toBeInTheDocument(); }); }); }); diff --git a/packages/fossflow-lib/src/components/ConnectorEmptySpaceTooltip/ConnectorEmptySpaceTooltip.tsx b/packages/fossflow-lib/src/components/ConnectorEmptySpaceTooltip/ConnectorEmptySpaceTooltip.tsx index 55f0a44e..9e621010 100644 --- a/packages/fossflow-lib/src/components/ConnectorEmptySpaceTooltip/ConnectorEmptySpaceTooltip.tsx +++ b/packages/fossflow-lib/src/components/ConnectorEmptySpaceTooltip/ConnectorEmptySpaceTooltip.tsx @@ -6,15 +6,19 @@ import { useScene } from 'src/hooks/useScene'; export const ConnectorEmptySpaceTooltip = () => { const [showTooltip, setShowTooltip] = useState(false); const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 }); - const mode = useUiStateStore((state) => state.mode); - const mouse = useUiStateStore((state) => state.mouse); + const mode = useUiStateStore((state) => { + return state.mode; + }); + const mouse = useUiStateStore((state) => { + return state.mouse; + }); const { connectors } = useScene(); const previousModeRef = useRef(mode); const shownForConnectorRef = useRef(null); useEffect(() => { const previousMode = previousModeRef.current; - + // Detect when we transition from isConnecting to not isConnecting (connection completed) if ( mode.type === 'CONNECTOR' && @@ -25,13 +29,18 @@ export const ConnectorEmptySpaceTooltip = () => { ) { // Find the most recently created connector const latestConnector = connectors[connectors.length - 1]; - - if (latestConnector && latestConnector.id !== shownForConnectorRef.current) { + + if ( + latestConnector && + latestConnector.id !== shownForConnectorRef.current + ) { // Check if either end is connected to empty space (tile reference) const hasEmptySpaceConnection = latestConnector.anchors.some( - anchor => anchor.ref.tile && !anchor.ref.item + (anchor) => { + return anchor.ref.tile && !anchor.ref.item; + } ); - + if (hasEmptySpaceConnection) { // Show tooltip near the mouse position setTooltipPosition({ @@ -40,22 +49,24 @@ export const ConnectorEmptySpaceTooltip = () => { }); setShowTooltip(true); shownForConnectorRef.current = latestConnector.id; - + // Auto-hide after 12 seconds const timer = setTimeout(() => { setShowTooltip(false); }, 12000); - - return () => clearTimeout(timer); + + return () => { + return clearTimeout(timer); + }; } } } - + // Hide tooltip when switching away from connector mode if (mode.type !== 'CONNECTOR') { setShowTooltip(false); } - + previousModeRef.current = mode; }, [mode, connectors, mouse.position.screen]); @@ -88,10 +99,12 @@ export const ConnectorEmptySpaceTooltip = () => { }} > - To connect this connector to a node, left-click on the end of the connector and drag it to the desired node. + To connect this connector to a node,{' '} + left-click on the end of the connector and drag it + to the desired node. ); -}; \ No newline at end of file +}; diff --git a/packages/fossflow-lib/src/components/ConnectorHintTooltip/ConnectorHintTooltip.tsx b/packages/fossflow-lib/src/components/ConnectorHintTooltip/ConnectorHintTooltip.tsx index 51be4206..3f587061 100644 --- a/packages/fossflow-lib/src/components/ConnectorHintTooltip/ConnectorHintTooltip.tsx +++ b/packages/fossflow-lib/src/components/ConnectorHintTooltip/ConnectorHintTooltip.tsx @@ -13,8 +13,12 @@ interface Props { export const ConnectorHintTooltip = ({ toolMenuRef }: Props) => { const { t } = useTranslation('connectorHintTooltip'); const theme = useTheme(); - const connectorInteractionMode = useUiStateStore((state) => state.connectorInteractionMode); - const mode = useUiStateStore((state) => state.mode); + const connectorInteractionMode = useUiStateStore((state) => { + return state.connectorInteractionMode; + }); + const mode = useUiStateStore((state) => { + return state.mode; + }); const [isDismissed, setIsDismissed] = useState(true); const [position, setPosition] = useState({ top: 16, right: 16 }); @@ -85,17 +89,25 @@ export const ConnectorHintTooltip = ({ toolMenuRef }: Props) => { > - + - {connectorInteractionMode === 'click' ? t('tipCreatingConnectors') : t('tipConnectorTools')} + {connectorInteractionMode === 'click' + ? t('tipCreatingConnectors') + : t('tipConnectorTools')} - + {connectorInteractionMode === 'click' ? ( <> - {t('clickInstructionStart')} {t('clickInstructionMiddle')} {t('clickInstructionStart')} {t('clickInstructionEnd')} + {t('clickInstructionStart')}{' '} + {t('clickInstructionMiddle')}{' '} + {t('clickInstructionStart')}{' '} + {t('clickInstructionEnd')} {mode.type === 'CONNECTOR' && mode.isConnecting && ( - + {t('nowClickTarget')} )} @@ -106,11 +118,12 @@ export const ConnectorHintTooltip = ({ toolMenuRef }: Props) => { )} - + - {t('rerouteStart')} {t('rerouteMiddle')} {t('rerouteEnd')} + {t('rerouteStart')} {t('rerouteMiddle')}{' '} + {t('rerouteEnd')} ); -}; \ No newline at end of file +}; diff --git a/packages/fossflow-lib/src/components/ConnectorRerouteTooltip/ConnectorRerouteTooltip.tsx b/packages/fossflow-lib/src/components/ConnectorRerouteTooltip/ConnectorRerouteTooltip.tsx index bda5fe81..25df1a51 100644 --- a/packages/fossflow-lib/src/components/ConnectorRerouteTooltip/ConnectorRerouteTooltip.tsx +++ b/packages/fossflow-lib/src/components/ConnectorRerouteTooltip/ConnectorRerouteTooltip.tsx @@ -11,8 +11,12 @@ export const ConnectorRerouteTooltip = () => { const { t } = useTranslation('connectorRerouteTooltip'); const [showTooltip, setShowTooltip] = useState(false); const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 }); - const mode = useUiStateStore((state) => state.mode); - const mouse = useUiStateStore((state) => state.mouse); + const mode = useUiStateStore((state) => { + return state.mode; + }); + const mouse = useUiStateStore((state) => { + return state.mouse; + }); const { connectors } = useScene(); const previousModeRef = useRef(mode); const shownForConnectorRef = useRef(null); @@ -44,7 +48,10 @@ export const ConnectorRerouteTooltip = () => { // Find the most recently created connector const latestConnector = connectors[connectors.length - 1]; - if (latestConnector && latestConnector.id !== shownForConnectorRef.current) { + if ( + latestConnector && + latestConnector.id !== shownForConnectorRef.current + ) { // Show tooltip near the mouse position setTooltipPosition({ x: mouse.position.screen.x, @@ -58,7 +65,9 @@ export const ConnectorRerouteTooltip = () => { setShowTooltip(false); }, 15000); - return () => clearTimeout(timer); + return () => { + return clearTimeout(timer); + }; } } @@ -122,7 +131,9 @@ export const ConnectorRerouteTooltip = () => { - {t('instructionSelect')} {t('instructionMiddle')} {t('instructionClick')} {t('instructionAnd')} {t('instructionDrag')} {t('instructionEnd')} + {t('instructionSelect')} {t('instructionMiddle')}{' '} + {t('instructionClick')} {t('instructionAnd')}{' '} + {t('instructionDrag')} {t('instructionEnd')} diff --git a/packages/fossflow-lib/src/components/ConnectorSettings/ConnectorSettings.tsx b/packages/fossflow-lib/src/components/ConnectorSettings/ConnectorSettings.tsx index 29c0d646..63538003 100644 --- a/packages/fossflow-lib/src/components/ConnectorSettings/ConnectorSettings.tsx +++ b/packages/fossflow-lib/src/components/ConnectorSettings/ConnectorSettings.tsx @@ -13,8 +13,12 @@ import { useUiStateStore } from 'src/stores/uiStateStore'; import { useTranslation } from 'src/stores/localeStore'; export const ConnectorSettings = () => { - const connectorInteractionMode = useUiStateStore((state) => state.connectorInteractionMode); - const setConnectorInteractionMode = useUiStateStore((state) => state.actions.setConnectorInteractionMode); + const connectorInteractionMode = useUiStateStore((state) => { + return state.connectorInteractionMode; + }); + const setConnectorInteractionMode = useUiStateStore((state) => { + return state.actions.setConnectorInteractionMode; + }); const { t } = useTranslation(); const handleChange = (event: React.ChangeEvent) => { @@ -29,7 +33,9 @@ export const ConnectorSettings = () => { - {t('settings.connector.connectionMode')} + + {t('settings.connector.connectionMode')} + { control={} label={ - {t('settings.connector.clickMode')} + + {t('settings.connector.clickMode')} + {t('settings.connector.clickModeDesc')} @@ -52,7 +60,9 @@ export const ConnectorSettings = () => { control={} label={ - {t('settings.connector.dragMode')} + + {t('settings.connector.dragMode')} + {t('settings.connector.dragModeDesc')} @@ -69,4 +79,4 @@ export const ConnectorSettings = () => { ); -}; \ No newline at end of file +}; diff --git a/packages/fossflow-lib/src/components/ContextMenu/ContextMenu.tsx b/packages/fossflow-lib/src/components/ContextMenu/ContextMenu.tsx index 5f3c1e13..dffbd3a3 100644 --- a/packages/fossflow-lib/src/components/ContextMenu/ContextMenu.tsx +++ b/packages/fossflow-lib/src/components/ContextMenu/ContextMenu.tsx @@ -31,7 +31,11 @@ export const ContextMenu = ({ onClose={onClose} > {menuItems.map((item, index) => { - return {item.label}; + return ( + + {item.label} + + ); })} ); diff --git a/packages/fossflow-lib/src/components/ContextMenu/ContextMenuManager.tsx b/packages/fossflow-lib/src/components/ContextMenu/ContextMenuManager.tsx index f7526b50..da8e6c8e 100644 --- a/packages/fossflow-lib/src/components/ContextMenu/ContextMenuManager.tsx +++ b/packages/fossflow-lib/src/components/ContextMenu/ContextMenuManager.tsx @@ -1,6 +1,11 @@ import React, { useCallback } from 'react'; import { useUiStateStore } from 'src/stores/uiStateStore'; -import { getTilePosition, CoordsUtils, generateId, findNearestUnoccupiedTile } from 'src/utils'; +import { + getTilePosition, + CoordsUtils, + generateId, + findNearestUnoccupiedTile +} from 'src/utils'; import { useScene } from 'src/hooks/useScene'; import { useModelStore } from 'src/stores/modelStore'; import { VIEW_ITEM_DEFAULTS } from 'src/config'; @@ -50,9 +55,11 @@ export const ContextMenuManager = ({ anchorEl }: Props) => { if (model.icons.length > 0) { const modelItemId = generateId(); const firstIcon = model.icons[0]; - + // Find nearest unoccupied tile (should return the same tile since context menu is for empty tiles) - const targetTile = findNearestUnoccupiedTile(contextMenu.tile, scene) || contextMenu.tile; + const targetTile = + findNearestUnoccupiedTile(contextMenu.tile, scene) || + contextMenu.tile; scene.placeIcon({ modelItem: { diff --git a/packages/fossflow-lib/src/components/ExportImageDialog/ExportImageDialog.tsx b/packages/fossflow-lib/src/components/ExportImageDialog/ExportImageDialog.tsx index 4329caca..0460b8ae 100644 --- a/packages/fossflow-lib/src/components/ExportImageDialog/ExportImageDialog.tsx +++ b/packages/fossflow-lib/src/components/ExportImageDialog/ExportImageDialog.tsx @@ -55,12 +55,16 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => { const isExporting = useRef(false); const [isDragging, setIsDragging] = useState(false); const [dragStart, setDragStart] = useState(null); - const currentView = useUiStateStore((state) => state.view); + const currentView = useUiStateStore((state) => { + return state.view; + }); const [imageData, setImageData] = React.useState(); const [croppedImageData, setCroppedImageData] = useState(); const [exportError, setExportError] = useState(false); const { getUnprojectedBounds } = useDiagramUtils(); - const uiStateActions = useUiStateStore((state) => state.actions); + const uiStateActions = useUiStateStore((state) => { + return state.actions; + }); const model = useModelStore((state): Omit => { return modelFromModelStore(state); }); @@ -113,7 +117,12 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => { height: bounds.height }; - exportAsImage(containerRef.current as HTMLDivElement, containerSize, exportScale, transparentBackground ? 'transparent' : backgroundColor) + exportAsImage( + containerRef.current as HTMLDivElement, + containerSize, + exportScale, + transparentBackground ? 'transparent' : backgroundColor + ) .then((data) => { setImageData(data); isExporting.current = false; @@ -142,7 +151,7 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => { const scaleX = img.width / displayCanvas.width; const scaleY = img.height / displayCanvas.height; - + // Calculate the actual crop area in the source image coordinates const actualCropArea = { x: cropArea.x * scaleX, @@ -159,17 +168,25 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => { // Draw the cropped portion from the source image ctx.drawImage( img, - actualCropArea.x, actualCropArea.y, actualCropArea.width, actualCropArea.height, - 0, 0, actualCropArea.width, actualCropArea.height + actualCropArea.x, + actualCropArea.y, + actualCropArea.width, + actualCropArea.height, + 0, + 0, + actualCropArea.width, + actualCropArea.height ); - + resolve(canvas.toDataURL('image/png')); } else { reject(new Error('Could not get canvas context')); } }; - img.onerror = () => reject(new Error('Failed to load image')); + img.onerror = () => { + return reject(new Error('Failed to load image')); + }; img.src = sourceImage; }); }, []); @@ -186,50 +203,59 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => { }, [cropArea, imageData, cropToContent, cropImage, isInCropMode]); // Mouse handlers for crop selection - const handleMouseDown = useCallback((e: React.MouseEvent) => { - if (!isInCropMode) return; - - e.preventDefault(); - const canvas = cropCanvasRef.current; - if (!canvas) return; - - const rect = canvas.getBoundingClientRect(); - const x = e.clientX - rect.left; - const y = e.clientY - rect.top; - - setDragStart({ x, y }); - setIsDragging(true); - setCropArea(null); - }, [isInCropMode]); + const handleMouseDown = useCallback( + (e: React.MouseEvent) => { + if (!isInCropMode) return; - const handleMouseMove = useCallback((e: React.MouseEvent) => { - if (!isDragging || !dragStart || !isInCropMode) return; - - e.preventDefault(); - const canvas = cropCanvasRef.current; - if (!canvas) return; + e.preventDefault(); + const canvas = cropCanvasRef.current; + if (!canvas) return; - const rect = canvas.getBoundingClientRect(); - const x = e.clientX - rect.left; - const y = e.clientY - rect.top; + const rect = canvas.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; - const newCropArea: CropArea = { - x: Math.min(dragStart.x, x), - y: Math.min(dragStart.y, y), - width: Math.abs(x - dragStart.x), - height: Math.abs(y - dragStart.y) - }; + setDragStart({ x, y }); + setIsDragging(true); + setCropArea(null); + }, + [isInCropMode] + ); - setCropArea(newCropArea); - }, [isDragging, dragStart, isInCropMode]); + const handleMouseMove = useCallback( + (e: React.MouseEvent) => { + if (!isDragging || !dragStart || !isInCropMode) return; - const handleMouseUp = useCallback((e: React.MouseEvent) => { - if (!isDragging) return; - - e.preventDefault(); - setIsDragging(false); - setDragStart(null); - }, [isDragging]); + e.preventDefault(); + const canvas = cropCanvasRef.current; + if (!canvas) return; + + const rect = canvas.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + const newCropArea: CropArea = { + x: Math.min(dragStart.x, x), + y: Math.min(dragStart.y, y), + width: Math.abs(x - dragStart.x), + height: Math.abs(y - dragStart.y) + }; + + setCropArea(newCropArea); + }, + [isDragging, dragStart, isInCropMode] + ); + + const handleMouseUp = useCallback( + (e: React.MouseEvent) => { + if (!isDragging) return; + + e.preventDefault(); + setIsDragging(false); + setDragStart(null); + }, + [isDragging] + ); // Add mouse leave handler to stop dragging when leaving canvas const handleMouseLeave = useCallback(() => { @@ -250,50 +276,63 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => { // Calculate scaling factors between canvas and actual image const scaleX = img.width / canvas.width; const scaleY = img.height / canvas.height; - + // Clear canvas ctx.clearRect(0, 0, canvas.width, canvas.height); - + // Draw checkerboard if transparent background if (transparentBackground) { const squareSize = 10; for (let y = 0; y < canvas.height; y += squareSize) { for (let x = 0; x < canvas.width; x += squareSize) { - ctx.fillStyle = (x / squareSize + y / squareSize) % 2 === 0 ? '#f0f0f0' : 'transparent'; + ctx.fillStyle = + (x / squareSize + y / squareSize) % 2 === 0 + ? '#f0f0f0' + : 'transparent'; ctx.fillRect(x, y, squareSize, squareSize); } } } - + // Draw the image scaled to fit canvas ctx.drawImage(img, 0, 0, canvas.width, canvas.height); - + // Draw crop overlay if in crop mode if (isInCropMode) { // Semi-transparent overlay ctx.fillStyle = 'rgba(0, 0, 0, 0.4)'; ctx.fillRect(0, 0, canvas.width, canvas.height); - + // Clear crop area and draw border only if there's a valid selection if (cropArea && cropArea.width > 5 && cropArea.height > 5) { // Clear the selected area (remove overlay) - ctx.clearRect(cropArea.x, cropArea.y, cropArea.width, cropArea.height); - + ctx.clearRect( + cropArea.x, + cropArea.y, + cropArea.width, + cropArea.height + ); + // Redraw the original image in the selected area ctx.drawImage(img, 0, 0, canvas.width, canvas.height); - + // Redraw the overlay everywhere except the selected area ctx.save(); ctx.globalCompositeOperation = 'source-over'; ctx.fillStyle = 'rgba(0, 0, 0, 0.4)'; - + // Top area if (cropArea.y > 0) { ctx.fillRect(0, 0, canvas.width, cropArea.y); } // Bottom area if (cropArea.y + cropArea.height < canvas.height) { - ctx.fillRect(0, cropArea.y + cropArea.height, canvas.width, canvas.height - (cropArea.y + cropArea.height)); + ctx.fillRect( + 0, + cropArea.y + cropArea.height, + canvas.width, + canvas.height - (cropArea.y + cropArea.height) + ); } // Left area if (cropArea.x > 0) { @@ -301,17 +340,27 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => { } // Right area if (cropArea.x + cropArea.width < canvas.width) { - ctx.fillRect(cropArea.x + cropArea.width, cropArea.y, canvas.width - (cropArea.x + cropArea.width), cropArea.height); + ctx.fillRect( + cropArea.x + cropArea.width, + cropArea.y, + canvas.width - (cropArea.x + cropArea.width), + cropArea.height + ); } - + ctx.restore(); - + // Draw crop border ctx.strokeStyle = '#2196f3'; ctx.lineWidth = 2; - ctx.strokeRect(cropArea.x, cropArea.y, cropArea.width, cropArea.height); + ctx.strokeRect( + cropArea.x, + cropArea.y, + cropArea.width, + cropArea.height + ); } - + // Add instruction text only when no selection or dragging if (!cropArea || cropArea.width <= 5 || cropArea.height <= 5) { ctx.fillStyle = 'white'; @@ -321,7 +370,7 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => { } } }; - + img.src = imageData; }, [imageData, isInCropMode, cropArea, transparentBackground]); @@ -386,16 +435,28 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => { const timer = setTimeout(() => { exportImage(); }, 200); - return () => clearTimeout(timer); + return () => { + return clearTimeout(timer); + }; } - }, [showGrid, backgroundColor, expandLabels, exportImage, cropToContent, exportScale, transparentBackground]); + }, [ + showGrid, + backgroundColor, + expandLabels, + exportImage, + cropToContent, + exportScale, + transparentBackground + ]); useEffect(() => { if (!imageData) { const timer = setTimeout(() => { exportImage(); }, 200); - return () => clearTimeout(timer); + return () => { + return clearTimeout(timer); + }; } }, [exportImage, imageData]); @@ -419,11 +480,9 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => { - - Browser Compatibility Notice - + Browser Compatibility Notice
- For best results, please use Chrome or Edge. Firefox currently has + For best results, please use Chrome or Edge. Firefox currently has compatibility issues with the export feature.
@@ -490,7 +549,11 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => { style={{ maxWidth: '100%', maxHeight: '300px', - cursor: isInCropMode ? (isDragging ? 'grabbing' : 'crosshair') : 'default', + cursor: isInCropMode + ? isDragging + ? 'grabbing' + : 'crosshair' + : 'default', border: isInCropMode ? '2px solid #2196f3' : 'none', userSelect: 'none' }} @@ -498,7 +561,9 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => { onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} onMouseLeave={handleMouseLeave} - onContextMenu={(e) => e.preventDefault()} + onContextMenu={(e) => { + return e.preventDefault(); + }} /> {isInCropMode && ( @@ -515,9 +580,15 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => { maxWidth: '100%', maxHeight: '300px', objectFit: 'contain', - backgroundImage: transparentBackground ? 'linear-gradient(45deg, #f0f0f0 25%, transparent 25%), linear-gradient(-45deg, #f0f0f0 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #f0f0f0 75%), linear-gradient(-45deg, transparent 75%, #f0f0f0 75%)' : undefined, - backgroundSize: transparentBackground ? '20px 20px' : undefined, - backgroundPosition: transparentBackground ? '0 0, 0 10px, 10px -10px, -10px 0px' : undefined + backgroundImage: transparentBackground + ? 'linear-gradient(45deg, #f0f0f0 25%, transparent 25%), linear-gradient(-45deg, #f0f0f0 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #f0f0f0 75%), linear-gradient(-45deg, transparent 75%, #f0f0f0 75%)' + : undefined, + backgroundSize: transparentBackground + ? '20px 20px' + : undefined, + backgroundPosition: transparentBackground + ? '0 0, 0 10px, 10px -10px, -10px 0px' + : undefined }} src={displayImage} alt="preview" @@ -609,11 +680,13 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => { } }} > - {dpiPresets.map((preset) => ( - - {preset.label} - - ))} + {dpiPresets.map((preset) => { + return ( + + {preset.label} + + ); + })} Custom
@@ -621,11 +694,14 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => { {scaleMode === 'custom' && ( - Scale: {exportScale.toFixed(1)}x ({(exportScale * 72).toFixed(0)} DPI) + Scale: {exportScale.toFixed(1)}x ( + {(exportScale * 72).toFixed(0)} DPI) setExportScale(value as number)} + onChange={(_, value) => { + return setExportScale(value as number); + }} min={1} max={5} step={0.1} @@ -637,7 +713,9 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => { { value: 5, label: '5x' } ]} valueLabelDisplay="auto" - valueLabelFormat={(value) => `${value.toFixed(1)}x`} + valueLabelFormat={(value) => { + return `${value.toFixed(1)}x`; + }} /> )} @@ -649,25 +727,43 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => { {croppedImageData ? ( - - + Crop applied successfully ) : cropArea ? ( - - ) : isInCropMode ? ( - Select an area to crop, or uncheck "Crop to content" to use full image + Select an area to crop, or uncheck "Crop to content" to + use full image ) : null} @@ -680,9 +776,11 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => { - @@ -698,4 +796,4 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => { ); -}; \ No newline at end of file +}; diff --git a/packages/fossflow-lib/src/components/HelpDialog/HelpDialog.tsx b/packages/fossflow-lib/src/components/HelpDialog/HelpDialog.tsx index 18e9dd33..9f4c1176 100644 --- a/packages/fossflow-lib/src/components/HelpDialog/HelpDialog.tsx +++ b/packages/fossflow-lib/src/components/HelpDialog/HelpDialog.tsx @@ -29,7 +29,7 @@ interface ShortcutItem { export const HelpDialog = () => { const { t } = useTranslation('helpDialog'); - + const dialog = useUiStateStore((state) => { return state.dialog; }); @@ -162,9 +162,15 @@ export const HelpDialog = () => { - {t('action')} - {t('shortcut')} - {t('description')} + + {t('action')} + + + {t('shortcut')} + + + {t('description')} + @@ -203,9 +209,15 @@ export const HelpDialog = () => {
- {t('action')} - {t('method')} - {t('description')} + + {t('action')} + + + {t('method')} + + + {t('description')} + diff --git a/packages/fossflow-lib/src/components/HotkeySettings/HotkeySettings.tsx b/packages/fossflow-lib/src/components/HotkeySettings/HotkeySettings.tsx index 7e23bd19..fbb93439 100644 --- a/packages/fossflow-lib/src/components/HotkeySettings/HotkeySettings.tsx +++ b/packages/fossflow-lib/src/components/HotkeySettings/HotkeySettings.tsx @@ -19,8 +19,12 @@ import { HOTKEY_PROFILES, HotkeyProfile } from 'src/config/hotkeys'; import { useTranslation } from 'src/stores/localeStore'; export const HotkeySettings = () => { - const hotkeyProfile = useUiStateStore((state) => state.hotkeyProfile); - const setHotkeyProfile = useUiStateStore((state) => state.actions.setHotkeyProfile); + const hotkeyProfile = useUiStateStore((state) => { + return state.hotkeyProfile; + }); + const setHotkeyProfile = useUiStateStore((state) => { + return state.actions.setHotkeyProfile; + }); const { t } = useTranslation(); const currentMapping = HOTKEY_PROFILES[hotkeyProfile]; @@ -29,8 +33,14 @@ export const HotkeySettings = () => { { name: t('settings.hotkeys.toolSelect'), key: currentMapping.select }, { name: t('settings.hotkeys.toolPan'), key: currentMapping.pan }, { name: t('settings.hotkeys.toolAddItem'), key: currentMapping.addItem }, - { name: t('settings.hotkeys.toolRectangle'), key: currentMapping.rectangle }, - { name: t('settings.hotkeys.toolConnector'), key: currentMapping.connector }, + { + name: t('settings.hotkeys.toolRectangle'), + key: currentMapping.rectangle + }, + { + name: t('settings.hotkeys.toolConnector'), + key: currentMapping.connector + }, { name: t('settings.hotkeys.toolText'), key: currentMapping.text } ]; @@ -45,10 +55,16 @@ export const HotkeySettings = () => { @@ -63,24 +79,33 @@ export const HotkeySettings = () => { - {tools.map((tool) => ( - - {tool.name} - - - {tool.key ? tool.key.toUpperCase() : '-'} - - - - ))} + {tools.map((tool) => { + return ( + + {tool.name} + + + {tool.key ? tool.key.toUpperCase() : '-'} + + + + ); + })}
)} - + {t('settings.hotkeys.note')} ); -}; \ No newline at end of file +}; diff --git a/packages/fossflow-lib/src/components/IconPackSettings/IconPackSettings.tsx b/packages/fossflow-lib/src/components/IconPackSettings/IconPackSettings.tsx index 07eb4a1f..37280feb 100644 --- a/packages/fossflow-lib/src/components/IconPackSettings/IconPackSettings.tsx +++ b/packages/fossflow-lib/src/components/IconPackSettings/IconPackSettings.tsx @@ -38,12 +38,16 @@ export const IconPackSettings: React.FC = ({ }) => { const { t } = useTranslation(); - const handleLazyLoadingChange = (event: React.ChangeEvent) => { + const handleLazyLoadingChange = ( + event: React.ChangeEvent + ) => { onToggleLazyLoading(event.target.checked); }; - const handlePackToggle = (packName: string) => (event: React.ChangeEvent) => { - onTogglePack(packName, event.target.checked); + const handlePackToggle = (packName: string) => { + return (event: React.ChangeEvent) => { + onTogglePack(packName, event.target.checked); + }; }; return ( @@ -55,7 +59,13 @@ export const IconPackSettings: React.FC = ({ {/* Lazy Loading Toggle */} - + {t('settings.iconPacks.lazyLoading')} @@ -75,7 +85,13 @@ export const IconPackSettings: React.FC = ({ {/* Core Isoflow (Always Loaded) */} - + {t('settings.iconPacks.coreIsoflow')} @@ -101,47 +117,68 @@ export const IconPackSettings: React.FC = ({ )} - {packInfo.map((pack) => ( - - - - - {pack.displayName} - - - {pack.loading && ( - <> - + {packInfo.map((pack) => { + return ( + + + + + {pack.displayName} + + + {pack.loading && ( + <> + + + {t('settings.iconPacks.loading')} + + + )} + {pack.loaded && !pack.loading && ( + + {t('settings.iconPacks.loaded')} •{' '} + {t('settings.iconPacks.iconCount').replace( + '{count}', + String(pack.iconCount) + )} + + )} + {pack.error && ( + + {pack.error} + + )} + {!pack.loaded && !pack.loading && !pack.error && ( - {t('settings.iconPacks.loading')} + {t('settings.iconPacks.notLoaded')} - - )} - {pack.loaded && !pack.loading && ( - - {t('settings.iconPacks.loaded')} • {t('settings.iconPacks.iconCount').replace('{count}', String(pack.iconCount))} - - )} - {pack.error && ( - - {pack.error} - - )} - {!pack.loaded && !pack.loading && !pack.error && ( - - {t('settings.iconPacks.notLoaded')} - - )} + )} + + - - - - ))} + + ); + })} diff --git a/packages/fossflow-lib/src/components/ImportHintTooltip/ImportHintTooltip.tsx b/packages/fossflow-lib/src/components/ImportHintTooltip/ImportHintTooltip.tsx index 97aeadd6..2e94d727 100644 --- a/packages/fossflow-lib/src/components/ImportHintTooltip/ImportHintTooltip.tsx +++ b/packages/fossflow-lib/src/components/ImportHintTooltip/ImportHintTooltip.tsx @@ -1,6 +1,9 @@ import React, { useState, useEffect } from 'react'; import { Box, IconButton, Paper, Typography } from '@mui/material'; -import { Close as CloseIcon, FolderOpen as FolderOpenIcon } from '@mui/icons-material'; +import { + Close as CloseIcon, + FolderOpen as FolderOpenIcon +} from '@mui/icons-material'; import { useTranslation } from 'src/stores/localeStore'; const STORAGE_KEY = 'fossflow_import_hint_dismissed'; @@ -57,18 +60,20 @@ export const ImportHintTooltip = () => { > - + {t('title')} - + - {t('instructionStart')} {t('menuButton')} {t('instructionMiddle')} {t('openButton')} {t('instructionEnd')} + {t('instructionStart')} {t('menuButton')}{' '} + {t('instructionMiddle')} {t('openButton')}{' '} + {t('instructionEnd')} ); -}; \ No newline at end of file +}; diff --git a/packages/fossflow-lib/src/components/ItemControls/IconSelectionControls/IconGrid.tsx b/packages/fossflow-lib/src/components/ItemControls/IconSelectionControls/IconGrid.tsx index 8eac0af6..b4b6c62d 100644 --- a/packages/fossflow-lib/src/components/ItemControls/IconSelectionControls/IconGrid.tsx +++ b/packages/fossflow-lib/src/components/ItemControls/IconSelectionControls/IconGrid.tsx @@ -12,7 +12,14 @@ interface Props { onHover?: (index: number) => void; } -export const IconGrid = ({ icons, onMouseDown, onClick, onDoubleClick, hoveredIndex, onHover }: Props) => { +export const IconGrid = ({ + icons, + onMouseDown, + onClick, + onDoubleClick, + hoveredIndex, + onHover +}: Props) => { return ( {icons.map((icon, index) => { @@ -25,7 +32,9 @@ export const IconGrid = ({ icons, onMouseDown, onClick, onDoubleClick, hoveredIn borderRadius: 1, transition: 'background-color 0.2s' }} - onMouseEnter={() => onHover?.(index)} + onMouseEnter={() => { + return onHover?.(index); + }} > { const mode = useUiStateStore((state) => { return state.mode; }); - const iconCategoriesState = useUiStateStore((state) => state.iconCategoriesState); - const modelActions = useModelStore((state) => state.actions); - const currentIcons = useModelStore((state) => state.icons); + const iconCategoriesState = useUiStateStore((state) => { + return state.iconCategoriesState; + }); + const modelActions = useModelStore((state) => { + return state.actions; + }); + const currentIcons = useModelStore((state) => { + return state.icons; + }); const { setFilter, filteredIcons, filter } = useIconFiltering(); const { iconCategories } = useIconCategories(); const fileInputRef = useRef(null); @@ -33,7 +52,6 @@ export const IconSelectionControls = () => { return localStorage.getItem('fossflow-show-drag-hint') !== 'false'; }); - const onMouseDown = useCallback( (icon: Icon) => { if (mode.type !== 'PLACE_ICON') return; @@ -56,123 +74,144 @@ export const IconSelectionControls = () => { localStorage.setItem('fossflow-show-drag-hint', 'false'); }, []); - const handleFileSelect = useCallback(async (event: React.ChangeEvent) => { - const files = event.target.files; - if (!files || files.length === 0) return; + const handleFileSelect = useCallback( + async (event: React.ChangeEvent) => { + const files = event.target.files; + if (!files || files.length === 0) return; - const newIcons: Icon[] = []; - const existingNames = new Set(currentIcons.map(icon => icon.name.toLowerCase())); + const newIcons: Icon[] = []; + const existingNames = new Set( + currentIcons.map((icon) => { + return icon.name.toLowerCase(); + }) + ); - for (let i = 0; i < files.length; i++) { - const file = files[i]; - - // Check if file is an image - if (!file.type.startsWith('image/')) { - console.warn(`Skipping non-image file: ${file.name}`); - continue; - } + for (let i = 0; i < files.length; i++) { + const file = files[i]; - // Generate unique name - let baseName = file.name.replace(/\.[^/.]+$/, ''); // Remove extension - let finalName = baseName; - let counter = 1; - - while (existingNames.has(finalName.toLowerCase())) { - finalName = `${baseName}_${counter}`; - counter++; - } - - existingNames.add(finalName.toLowerCase()); + // Check if file is an image + if (!file.type.startsWith('image/')) { + console.warn(`Skipping non-image file: ${file.name}`); + continue; + } + + // Generate unique name + const baseName = file.name.replace(/\.[^/.]+$/, ''); // Remove extension + let finalName = baseName; + let counter = 1; + + while (existingNames.has(finalName.toLowerCase())) { + finalName = `${baseName}_${counter}`; + counter++; + } - // Load and scale the image - const dataUrl = await new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = async (e) => { - const originalDataUrl = e.target?.result as string; - - // For SVG files, use as-is since they scale naturally - if (file.type === 'image/svg+xml') { - resolve(originalDataUrl); - return; - } - - // For raster images, scale them to fit in a square bounding box - const img = new Image(); - img.onload = () => { - // Create canvas for scaling - const canvas = document.createElement('canvas'); - const ctx = canvas.getContext('2d'); - if (!ctx) { - resolve(originalDataUrl); // Fallback to original + existingNames.add(finalName.toLowerCase()); + + // Load and scale the image + const dataUrl = await new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = async (e) => { + const originalDataUrl = e.target?.result as string; + + // For SVG files, use as-is since they scale naturally + if (file.type === 'image/svg+xml') { + resolve(originalDataUrl); return; } - - // Use a square target size for consistent display - // This ensures all icons have the same bounding box - const TARGET_SIZE = 128; // Square size for consistency - - // Calculate scaling to fit within square while maintaining aspect ratio - const basScale = Math.min(TARGET_SIZE / img.width, TARGET_SIZE / img.height); - // Apply user's custom scaling - const finalScale = basScale * (iconScale / 100); - const scaledWidth = img.width * finalScale; - const scaledHeight = img.height * finalScale; - - // Set canvas to square size - canvas.width = TARGET_SIZE; - canvas.height = TARGET_SIZE; - - // Clear canvas with transparent background - ctx.clearRect(0, 0, TARGET_SIZE, TARGET_SIZE); - - // Calculate position to center the image in the square - const x = (TARGET_SIZE - scaledWidth) / 2; - const y = (TARGET_SIZE - scaledHeight) / 2; - - // Enable image smoothing for better quality - ctx.imageSmoothingEnabled = true; - ctx.imageSmoothingQuality = 'high'; - - // Draw scaled and centered image - ctx.drawImage(img, x, y, scaledWidth, scaledHeight); - - // Convert to data URL (using PNG for transparency) - resolve(canvas.toDataURL('image/png')); + + // For raster images, scale them to fit in a square bounding box + const img = new Image(); + img.onload = () => { + // Create canvas for scaling + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + if (!ctx) { + resolve(originalDataUrl); // Fallback to original + return; + } + + // Use a square target size for consistent display + // This ensures all icons have the same bounding box + const TARGET_SIZE = 128; // Square size for consistency + + // Calculate scaling to fit within square while maintaining aspect ratio + const basScale = Math.min( + TARGET_SIZE / img.width, + TARGET_SIZE / img.height + ); + // Apply user's custom scaling + const finalScale = basScale * (iconScale / 100); + const scaledWidth = img.width * finalScale; + const scaledHeight = img.height * finalScale; + + // Set canvas to square size + canvas.width = TARGET_SIZE; + canvas.height = TARGET_SIZE; + + // Clear canvas with transparent background + ctx.clearRect(0, 0, TARGET_SIZE, TARGET_SIZE); + + // Calculate position to center the image in the square + const x = (TARGET_SIZE - scaledWidth) / 2; + const y = (TARGET_SIZE - scaledHeight) / 2; + + // Enable image smoothing for better quality + ctx.imageSmoothingEnabled = true; + ctx.imageSmoothingQuality = 'high'; + + // Draw scaled and centered image + ctx.drawImage(img, x, y, scaledWidth, scaledHeight); + + // Convert to data URL (using PNG for transparency) + resolve(canvas.toDataURL('image/png')); + }; + img.onerror = () => { + return reject(new Error('Failed to load image')); + }; + img.src = originalDataUrl; }; - img.onerror = () => reject(new Error('Failed to load image')); - img.src = originalDataUrl; - }; - reader.onerror = reject; - reader.readAsDataURL(file); - }); + reader.onerror = reject; + reader.readAsDataURL(file); + }); - newIcons.push({ - id: generateId(), - name: finalName, - url: dataUrl, - collection: 'imported', - isIsometric: treatAsIsometric // Use user's preference - }); - } + newIcons.push({ + id: generateId(), + name: finalName, + url: dataUrl, + collection: 'imported', + isIsometric: treatAsIsometric // Use user's preference + }); + } - if (newIcons.length > 0) { - // Add new icons to the model - const updatedIcons = [...currentIcons, ...newIcons]; - modelActions.set({ icons: updatedIcons }); - - // Update icon categories to include imported collection - const hasImported = iconCategoriesState.some(cat => cat.id === 'imported'); - if (!hasImported) { - uiStateActions.setIconCategoriesState([ - ...iconCategoriesState, - { id: 'imported', isExpanded: true } - ]); + if (newIcons.length > 0) { + // Add new icons to the model + const updatedIcons = [...currentIcons, ...newIcons]; + modelActions.set({ icons: updatedIcons }); + + // Update icon categories to include imported collection + const hasImported = iconCategoriesState.some((cat) => { + return cat.id === 'imported'; + }); + if (!hasImported) { + uiStateActions.setIconCategoriesState([ + ...iconCategoriesState, + { id: 'imported', isExpanded: true } + ]); + } } - } - // Reset input - event.target.value = ''; - }, [currentIcons, modelActions, iconCategoriesState, uiStateActions, treatAsIsometric, iconScale]); + // Reset input + event.target.value = ''; + }, + [ + currentIcons, + modelActions, + iconCategoriesState, + uiStateActions, + treatAsIsometric, + iconScale + ] + ); return ( { {!filteredIcons && ( )} - +
- +
) : (
- No icons found matching "{searchTerm}" + + No icons found matching "{searchTerm}" +
)} @@ -223,12 +265,11 @@ export const QuickIconSelector = ({ onIconSelected, onClose, currentIconId }: Pr {/* Help Text */}
- {searchTerm + {searchTerm ? 'Use arrow keys to navigate • Enter to select • Double-click to select and close' - : 'Type to search • Click category to expand • Double-click to select and close' - } + : 'Type to search • Click category to expand • Double-click to select and close'}
); -}; \ No newline at end of file +}; diff --git a/packages/fossflow-lib/src/components/ItemControls/RectangleControls/RectangleControls.tsx b/packages/fossflow-lib/src/components/ItemControls/RectangleControls/RectangleControls.tsx index fd84b0d1..06137f46 100644 --- a/packages/fossflow-lib/src/components/ItemControls/RectangleControls/RectangleControls.tsx +++ b/packages/fossflow-lib/src/components/ItemControls/RectangleControls/RectangleControls.tsx @@ -1,5 +1,11 @@ import React, { useState } from 'react'; -import { Box, IconButton as MUIIconButton, FormControlLabel, Switch, Typography } from '@mui/material'; +import { + Box, + IconButton as MUIIconButton, + FormControlLabel, + Switch, + Typography +} from '@mui/material'; import { useRectangle } from 'src/hooks/useRectangle'; import { ColorSelector } from 'src/components/ColorSelector/ColorSelector'; import { ColorPicker } from 'src/components/ColorSelector/ColorPicker'; @@ -21,7 +27,9 @@ export const RectangleControls = ({ id }: Props) => { }); const rectangle = useRectangle(id); const { updateRectangle, deleteRectangle } = useScene(); - const [useCustomColor, setUseCustomColor] = useState(!!rectangle?.customColor); + const [useCustomColor, setUseCustomColor] = useState( + !!rectangle?.customColor + ); // If rectangle doesn't exist, return null if (!rectangle) { diff --git a/packages/fossflow-lib/src/components/Label/ExpandableLabel.tsx b/packages/fossflow-lib/src/components/Label/ExpandableLabel.tsx index ebeb9e38..21d46312 100644 --- a/packages/fossflow-lib/src/components/Label/ExpandableLabel.tsx +++ b/packages/fossflow-lib/src/components/Label/ExpandableLabel.tsx @@ -17,9 +17,15 @@ export const ExpandableLabel = ({ onToggleExpand, ...rest }: Props) => { - const forceExpandLabels = useUiStateStore((state) => state.expandLabels); - const editorMode = useUiStateStore((state) => state.editorMode); - const labelSettings = useUiStateStore((state) => state.labelSettings); + const forceExpandLabels = useUiStateStore((state) => { + return state.expandLabels; + }); + const editorMode = useUiStateStore((state) => { + return state.editorMode; + }); + const labelSettings = useUiStateStore((state) => { + return state.labelSettings; + }); const [isExpanded, setIsExpanded] = useState(false); const contentRef = useRef(); const { observe, size: contentSize } = useResizeObserver(); @@ -32,7 +38,8 @@ export const ExpandableLabel = ({ const effectiveExpanded = useMemo(() => { // Only force expand in NON_INTERACTIVE mode (export preview) - const shouldForceExpand = forceExpandLabels && editorMode === 'NON_INTERACTIVE'; + const shouldForceExpand = + forceExpandLabels && editorMode === 'NON_INTERACTIVE'; return shouldForceExpand || isExpanded; }, [forceExpandLabels, isExpanded, editorMode]); @@ -41,7 +48,9 @@ export const ExpandableLabel = ({ }, [effectiveExpanded]); const isContentTruncated = useMemo(() => { - return !effectiveExpanded && contentSize.height >= STANDARD_LABEL_HEIGHT - 10; + return ( + !effectiveExpanded && contentSize.height >= STANDARD_LABEL_HEIGHT - 10 + ); }, [effectiveExpanded, contentSize.height]); // Determine overflow behavior based on mode @@ -70,7 +79,10 @@ export const ExpandableLabel = ({ '&::-webkit-scrollbar': { display: 'none' }, - pb: isContentTruncated || isExpanded ? labelSettings.expandButtonPadding : 0 // Add bottom padding when expand button is visible + pb: + isContentTruncated || isExpanded + ? labelSettings.expandButtonPadding + : 0 // Add bottom padding when expand button is visible }} style={{ overflowY: overflowBehavior, @@ -92,21 +104,22 @@ export const ExpandableLabel = ({ )} - {editorMode !== 'NON_INTERACTIVE' && ((!isExpanded && isContentTruncated) || isExpanded) && ( - { - setIsExpanded(!isExpanded); - onToggleExpand?.(!isExpanded); - }} - /> - )} + {editorMode !== 'NON_INTERACTIVE' && + ((!isExpanded && isContentTruncated) || isExpanded) && ( + { + setIsExpanded(!isExpanded); + onToggleExpand?.(!isExpanded); + }} + /> + )} ); }; diff --git a/packages/fossflow-lib/src/components/LabelSettings/LabelSettings.tsx b/packages/fossflow-lib/src/components/LabelSettings/LabelSettings.tsx index 8884b661..849993f4 100644 --- a/packages/fossflow-lib/src/components/LabelSettings/LabelSettings.tsx +++ b/packages/fossflow-lib/src/components/LabelSettings/LabelSettings.tsx @@ -1,14 +1,14 @@ import React from 'react'; -import { - Box, - Typography, - Slider -} from '@mui/material'; +import { Box, Typography, Slider } from '@mui/material'; import { useUiStateStore } from 'src/stores/uiStateStore'; export const LabelSettings = () => { - const labelSettings = useUiStateStore((state) => state.labelSettings); - const setLabelSettings = useUiStateStore((state) => state.actions.setLabelSettings); + const labelSettings = useUiStateStore((state) => { + return state.labelSettings; + }); + const setLabelSettings = useUiStateStore((state) => { + return state.actions.setLabelSettings; + }); const handlePaddingChange = (_event: Event, value: number | number[]) => { setLabelSettings({ @@ -27,7 +27,11 @@ export const LabelSettings = () => { Expand Button Padding - + Bottom padding when expand button is visible (prevents text overlap) { const { t } = useTranslation('lassoHintTooltip'); const theme = useTheme(); - const mode = useUiStateStore((state) => state.mode); + const mode = useUiStateStore((state) => { + return state.mode; + }); const [isDismissed, setIsDismissed] = useState(true); const [position, setPosition] = useState({ top: 16, right: 16 }); @@ -50,7 +52,10 @@ export const LassoHintTooltip = ({ toolMenuRef }: Props) => { }; // Only show when in LASSO or FREEHAND_LASSO mode - if (isDismissed || (mode.type !== 'LASSO' && mode.type !== 'FREEHAND_LASSO')) { + if ( + isDismissed || + (mode.type !== 'LASSO' && mode.type !== 'FREEHAND_LASSO') + ) { return null; } @@ -95,7 +100,9 @@ export const LassoHintTooltip = ({ toolMenuRef }: Props) => { {isFreehandMode ? ( <> - {t('freehandDragStart')} {t('freehandDragMiddle')} {t('freehandDragEnd')} {t('freehandComplete')} + {t('freehandDragStart')}{' '} + {t('freehandDragMiddle')} {t('freehandDragEnd')}{' '} + {t('freehandComplete')} ) : ( <> diff --git a/packages/fossflow-lib/src/components/LazyLoadingWelcomeNotification/LazyLoadingWelcomeNotification.tsx b/packages/fossflow-lib/src/components/LazyLoadingWelcomeNotification/LazyLoadingWelcomeNotification.tsx index 5f000afc..c7ed92cc 100644 --- a/packages/fossflow-lib/src/components/LazyLoadingWelcomeNotification/LazyLoadingWelcomeNotification.tsx +++ b/packages/fossflow-lib/src/components/LazyLoadingWelcomeNotification/LazyLoadingWelcomeNotification.tsx @@ -70,15 +70,17 @@ export const LazyLoadingWelcomeNotification = () => { {t('message')} - + {t('configPath')} {t('configPath2')} diff --git a/packages/fossflow-lib/src/components/MainMenu/MainMenu.tsx b/packages/fossflow-lib/src/components/MainMenu/MainMenu.tsx index dab16bb1..514ce355 100644 --- a/packages/fossflow-lib/src/components/MainMenu/MainMenu.tsx +++ b/packages/fossflow-lib/src/components/MainMenu/MainMenu.tsx @@ -9,8 +9,7 @@ import { DeleteOutline as DeleteOutlineIcon, Undo as UndoIcon, Redo as RedoIcon, - Settings as SettingsIcon, - + Settings as SettingsIcon } from '@mui/icons-material'; import { UiElement } from 'src/components/UiElement/UiElement'; import { IconButton } from 'src/components/IconButton/IconButton'; @@ -134,9 +133,6 @@ export const MainMenu = () => { uiStateActions.setDialog(DialogTypeEnum.SETTINGS); }, [uiStateActions]); - - - const sectionVisibility = useMemo(() => { return { actions: Boolean( @@ -201,7 +197,6 @@ export const MainMenu = () => { {t('redo')} - {(canUndo || canRedo) && sectionVisibility.actions && } {/* File Actions */} diff --git a/packages/fossflow-lib/src/components/PanSettings/PanSettings.tsx b/packages/fossflow-lib/src/components/PanSettings/PanSettings.tsx index 64a3204f..b824408e 100644 --- a/packages/fossflow-lib/src/components/PanSettings/PanSettings.tsx +++ b/packages/fossflow-lib/src/components/PanSettings/PanSettings.tsx @@ -12,8 +12,12 @@ import { useUiStateStore } from 'src/stores/uiStateStore'; import { useTranslation } from 'src/stores/localeStore'; export const PanSettings = () => { - const panSettings = useUiStateStore((state) => state.panSettings); - const setPanSettings = useUiStateStore((state) => state.actions.setPanSettings); + const panSettings = useUiStateStore((state) => { + return state.panSettings; + }); + const setPanSettings = useUiStateStore((state) => { + return state.actions.setPanSettings; + }); const { t } = useTranslation(); const handleToggle = (setting: keyof typeof panSettings) => { @@ -47,7 +51,9 @@ export const PanSettings = () => { control={ handleToggle('emptyAreaClickPan')} + onChange={() => { + return handleToggle('emptyAreaClickPan'); + }} /> } label={t('settings.pan.emptyAreaClickPan')} @@ -57,7 +63,9 @@ export const PanSettings = () => { control={ handleToggle('middleClickPan')} + onChange={() => { + return handleToggle('middleClickPan'); + }} /> } label={t('settings.pan.middleClickPan')} @@ -67,7 +75,9 @@ export const PanSettings = () => { control={ handleToggle('rightClickPan')} + onChange={() => { + return handleToggle('rightClickPan'); + }} /> } label={t('settings.pan.rightClickPan')} @@ -77,7 +87,9 @@ export const PanSettings = () => { control={ handleToggle('ctrlClickPan')} + onChange={() => { + return handleToggle('ctrlClickPan'); + }} /> } label={t('settings.pan.ctrlClickPan')} @@ -87,7 +99,9 @@ export const PanSettings = () => { control={ handleToggle('altClickPan')} + onChange={() => { + return handleToggle('altClickPan'); + }} /> } label={t('settings.pan.altClickPan')} @@ -103,7 +117,9 @@ export const PanSettings = () => { control={ handleToggle('arrowKeysPan')} + onChange={() => { + return handleToggle('arrowKeysPan'); + }} /> } label={t('settings.pan.arrowKeys')} @@ -113,7 +129,9 @@ export const PanSettings = () => { control={ handleToggle('wasdPan')} + onChange={() => { + return handleToggle('wasdPan'); + }} /> } label={t('settings.pan.wasdKeys')} @@ -123,7 +141,9 @@ export const PanSettings = () => { control={ handleToggle('ijklPan')} + onChange={() => { + return handleToggle('ijklPan'); + }} /> } label={t('settings.pan.ijklKeys')} @@ -138,7 +158,9 @@ export const PanSettings = () => { handleSpeedChange(value as number)} + onChange={(_, value) => { + return handleSpeedChange(value as number); + }} min={5} max={50} step={5} @@ -148,9 +170,13 @@ export const PanSettings = () => {
- + {t('settings.pan.note')} ); -}; \ No newline at end of file +}; diff --git a/packages/fossflow-lib/src/components/Renderer/Renderer.tsx b/packages/fossflow-lib/src/components/Renderer/Renderer.tsx index 42ce70ac..47c87aad 100644 --- a/packages/fossflow-lib/src/components/Renderer/Renderer.tsx +++ b/packages/fossflow-lib/src/components/Renderer/Renderer.tsx @@ -53,7 +53,11 @@ export const Renderer = ({ showGrid, backgroundColor }: RendererProps) => { width: '100%', height: '100%', zIndex: 0, - bgcolor: (theme) => backgroundColor === 'transparent' ? 'transparent' : (backgroundColor ?? theme.customVars.customPalette.diagramBg) + bgcolor: (theme) => { + return backgroundColor === 'transparent' + ? 'transparent' + : (backgroundColor ?? theme.customVars.customPalette.diagramBg); + } }} > diff --git a/packages/fossflow-lib/src/components/RichTextEditor/RichTextEditorErrorBoundary.tsx b/packages/fossflow-lib/src/components/RichTextEditor/RichTextEditorErrorBoundary.tsx index 9fc3a320..71104e52 100644 --- a/packages/fossflow-lib/src/components/RichTextEditor/RichTextEditorErrorBoundary.tsx +++ b/packages/fossflow-lib/src/components/RichTextEditor/RichTextEditorErrorBoundary.tsx @@ -10,7 +10,10 @@ interface ErrorBoundaryState { errorCount: number; } -class RichTextEditorErrorBoundary extends Component { +class RichTextEditorErrorBoundary extends Component< + ErrorBoundaryProps, + ErrorBoundaryState +> { constructor(props: ErrorBoundaryProps) { super(props); this.state = { @@ -19,7 +22,9 @@ class RichTextEditorErrorBoundary extends Component | null { + static getDerivedStateFromError( + error: Error + ): Partial | null { // Check if this is the specific DOM manipulation error we're trying to handle if ( error.message.includes('removeChild') || @@ -38,18 +43,25 @@ class RichTextEditorErrorBoundary extends Component ({ - errorCount: prevState.errorCount + 1 - })); + this.setState((prevState) => { + return { + errorCount: prevState.errorCount + 1 + }; + }); // If we get too many errors in a row, show fallback if (this.state.errorCount > 3) { @@ -66,7 +78,10 @@ class RichTextEditorErrorBoundary extends Component 3) { // If too many errors, show fallback or placeholder - return this.props.fallback || ( -
- Rich text editor temporarily unavailable -
+ return ( + this.props.fallback || ( +
+ Rich text editor temporarily unavailable +
+ ) ); } @@ -94,4 +113,4 @@ class RichTextEditorErrorBoundary extends Component { - const [isFirstRender, setIsFirstRender] = useState(true); - const elementRef = useRef(null); +export const SceneLayer = memo( + ({ children, order = 0, sx, disableAnimation }: Props) => { + const [isFirstRender, setIsFirstRender] = useState(true); + const elementRef = useRef(null); - const scroll = useUiStateStore((state) => { - return state.scroll; - }); - const zoom = useUiStateStore((state) => { - return state.zoom; - }); + const scroll = useUiStateStore((state) => { + return state.scroll; + }); + const zoom = useUiStateStore((state) => { + return state.zoom; + }); - useEffect(() => { - if (!elementRef.current) return; + useEffect(() => { + if (!elementRef.current) return; - gsap.to(elementRef.current, { - duration: disableAnimation || isFirstRender ? 0 : 0.016, // ~1 frame at 60fps for smooth motion - ease: 'none', // Linear easing for immediate response - translateX: scroll.position.x, - translateY: scroll.position.y, - scale: zoom - }); + gsap.to(elementRef.current, { + duration: disableAnimation || isFirstRender ? 0 : 0.016, // ~1 frame at 60fps for smooth motion + ease: 'none', // Linear easing for immediate response + translateX: scroll.position.x, + translateY: scroll.position.y, + scale: zoom + }); - if (isFirstRender) { - setIsFirstRender(false); - } - }, [zoom, scroll, disableAnimation, isFirstRender]); + if (isFirstRender) { + setIsFirstRender(false); + } + }, [zoom, scroll, disableAnimation, isFirstRender]); - return ( - - {children} - - ); -}); + return ( + + {children} + + ); + } +); diff --git a/packages/fossflow-lib/src/components/SceneLayers/ConnectorLabels/ConnectorLabel.tsx b/packages/fossflow-lib/src/components/SceneLayers/ConnectorLabels/ConnectorLabel.tsx index 8554c225..8afe35aa 100644 --- a/packages/fossflow-lib/src/components/SceneLayers/ConnectorLabels/ConnectorLabel.tsx +++ b/packages/fossflow-lib/src/components/SceneLayers/ConnectorLabels/ConnectorLabel.tsx @@ -28,7 +28,6 @@ export const ConnectorLabel = memo(({ connector: sceneConnector }: Props) => { const labelPositions = useMemo(() => { if (!connector) return []; - return labels .map((label) => { const tileIndex = getLabelTileIndex( diff --git a/packages/fossflow-lib/src/components/SceneLayers/Connectors/Connector.tsx b/packages/fossflow-lib/src/components/SceneLayers/Connectors/Connector.tsx index dff93949..7facd023 100644 --- a/packages/fossflow-lib/src/components/SceneLayers/Connectors/Connector.tsx +++ b/packages/fossflow-lib/src/components/SceneLayers/Connectors/Connector.tsx @@ -18,312 +18,322 @@ interface Props { isSelected?: boolean; } -export const Connector = memo(({ connector: _connector, isSelected }: Props) => { - const theme = useTheme(); - const predefinedColor = useColor(_connector.color); - const { currentView } = useScene(); - const connector = useConnector(_connector.id); - - if (!connector) { - return null; - } +export const Connector = memo( + ({ connector: _connector, isSelected }: Props) => { + const theme = useTheme(); + const predefinedColor = useColor(_connector.color); + const { currentView } = useScene(); + const connector = useConnector(_connector.id); - // Use custom color if provided, otherwise use predefined color - const color = connector.customColor - ? { value: connector.customColor } - : predefinedColor; - - if (!color) { - return null; - } + if (!connector) { + return null; + } - const { css, pxSize } = useIsoProjection({ - ...connector.path.rectangle - }); - - const drawOffset = useMemo(() => { - return { - x: UNPROJECTED_TILE_SIZE / 2, - y: UNPROJECTED_TILE_SIZE / 2 - }; - }, []); - - const connectorWidthPx = useMemo(() => { - return (UNPROJECTED_TILE_SIZE / 100) * connector.width; - }, [connector.width]); - - const pathString = useMemo(() => { - return connector.path.tiles.reduce((acc, tile) => { - return `${acc} ${tile.x * UNPROJECTED_TILE_SIZE + drawOffset.x},${ - tile.y * UNPROJECTED_TILE_SIZE + drawOffset.y - }`; - }, ''); - }, [connector.path.tiles, drawOffset]); - - // Create offset paths for double lines - const offsetPaths = useMemo(() => { - if (!connector.lineType || connector.lineType === 'SINGLE') return null; - - const tiles = connector.path.tiles; - if (tiles.length < 2) return null; - - const offset = connectorWidthPx * 3; // Larger spacing between double lines for visibility - const path1Points: string[] = []; - const path2Points: string[] = []; - - for (let i = 0; i < tiles.length; i++) { - const curr = tiles[i]; - let dx = 0, dy = 0; - - // Calculate perpendicular offset based on line direction - if (i > 0 && i < tiles.length - 1) { - const prev = tiles[i - 1]; - const next = tiles[i + 1]; - const dx1 = curr.x - prev.x; - const dy1 = curr.y - prev.y; - const dx2 = next.x - curr.x; - const dy2 = next.y - curr.y; - - // Average direction for smooth corners - const avgDx = (dx1 + dx2) / 2; - const avgDy = (dy1 + dy2) / 2; - const len = Math.sqrt(avgDx * avgDx + avgDy * avgDy) || 1; - - // Perpendicular vector - dx = -avgDy / len; - dy = avgDx / len; - } else if (i === 0 && tiles.length > 1) { - // Start point - const next = tiles[1]; - const dirX = next.x - curr.x; - const dirY = next.y - curr.y; - const len = Math.sqrt(dirX * dirX + dirY * dirY) || 1; - dx = -dirY / len; - dy = dirX / len; - } else if (i === tiles.length - 1 && tiles.length > 1) { - // End point - const prev = tiles[i - 1]; - const dirX = curr.x - prev.x; - const dirY = curr.y - prev.y; - const len = Math.sqrt(dirX * dirX + dirY * dirY) || 1; - dx = -dirY / len; - dy = dirX / len; - } - - const x = curr.x * UNPROJECTED_TILE_SIZE + drawOffset.x; - const y = curr.y * UNPROJECTED_TILE_SIZE + drawOffset.y; - - path1Points.push(`${x + dx * offset},${y + dy * offset}`); - path2Points.push(`${x - dx * offset},${y - dy * offset}`); + // Use custom color if provided, otherwise use predefined color + const color = connector.customColor + ? { value: connector.customColor } + : predefinedColor; + + if (!color) { + return null; } - - return { - path1: path1Points.join(' '), - path2: path2Points.join(' ') - }; - }, [connector.path.tiles, connector.lineType, connectorWidthPx, drawOffset]); - const anchorPositions = useMemo(() => { - if (!isSelected) return []; + const { css, pxSize } = useIsoProjection({ + ...connector.path.rectangle + }); + + const drawOffset = useMemo(() => { + return { + x: UNPROJECTED_TILE_SIZE / 2, + y: UNPROJECTED_TILE_SIZE / 2 + }; + }, []); + + const connectorWidthPx = useMemo(() => { + return (UNPROJECTED_TILE_SIZE / 100) * connector.width; + }, [connector.width]); + + const pathString = useMemo(() => { + return connector.path.tiles.reduce((acc, tile) => { + return `${acc} ${tile.x * UNPROJECTED_TILE_SIZE + drawOffset.x},${ + tile.y * UNPROJECTED_TILE_SIZE + drawOffset.y + }`; + }, ''); + }, [connector.path.tiles, drawOffset]); + + // Create offset paths for double lines + const offsetPaths = useMemo(() => { + if (!connector.lineType || connector.lineType === 'SINGLE') return null; + + const tiles = connector.path.tiles; + if (tiles.length < 2) return null; - return connector.anchors.map((anchor) => { - const position = getAnchorTile(anchor, currentView); + const offset = connectorWidthPx * 3; // Larger spacing between double lines for visibility + const path1Points: string[] = []; + const path2Points: string[] = []; + + for (let i = 0; i < tiles.length; i++) { + const curr = tiles[i]; + let dx = 0, + dy = 0; + + // Calculate perpendicular offset based on line direction + if (i > 0 && i < tiles.length - 1) { + const prev = tiles[i - 1]; + const next = tiles[i + 1]; + const dx1 = curr.x - prev.x; + const dy1 = curr.y - prev.y; + const dx2 = next.x - curr.x; + const dy2 = next.y - curr.y; + + // Average direction for smooth corners + const avgDx = (dx1 + dx2) / 2; + const avgDy = (dy1 + dy2) / 2; + const len = Math.sqrt(avgDx * avgDx + avgDy * avgDy) || 1; + + // Perpendicular vector + dx = -avgDy / len; + dy = avgDx / len; + } else if (i === 0 && tiles.length > 1) { + // Start point + const next = tiles[1]; + const dirX = next.x - curr.x; + const dirY = next.y - curr.y; + const len = Math.sqrt(dirX * dirX + dirY * dirY) || 1; + dx = -dirY / len; + dy = dirX / len; + } else if (i === tiles.length - 1 && tiles.length > 1) { + // End point + const prev = tiles[i - 1]; + const dirX = curr.x - prev.x; + const dirY = curr.y - prev.y; + const len = Math.sqrt(dirX * dirX + dirY * dirY) || 1; + dx = -dirY / len; + dy = dirX / len; + } + + const x = curr.x * UNPROJECTED_TILE_SIZE + drawOffset.x; + const y = curr.y * UNPROJECTED_TILE_SIZE + drawOffset.y; + + path1Points.push(`${x + dx * offset},${y + dy * offset}`); + path2Points.push(`${x - dx * offset},${y - dy * offset}`); + } return { - id: anchor.id, - x: - (connector.path.rectangle.from.x - position.x) * - UNPROJECTED_TILE_SIZE + - drawOffset.x, - y: - (connector.path.rectangle.from.y - position.y) * - UNPROJECTED_TILE_SIZE + - drawOffset.y + path1: path1Points.join(' '), + path2: path2Points.join(' ') }; - }); - }, [ - currentView, - connector.path.rectangle, - connector.anchors, - drawOffset, - isSelected - ]); - - const directionIcon = useMemo(() => { - return getConnectorDirectionIcon(connector.path.tiles); - }, [connector.path.tiles]); - - const strokeDashArray = useMemo(() => { - switch (connector.style) { - case 'DASHED': - return `${connectorWidthPx * 2}, ${connectorWidthPx * 2}`; - case 'DOTTED': - return `0, ${connectorWidthPx * 1.8}`; - case 'SOLID': - default: - return 'none'; - } - }, [connector.style, connectorWidthPx]); - - const lineType = connector.lineType || 'SINGLE'; - - return ( - - - {lineType === 'SINGLE' ? ( - <> - - - - ) : offsetPaths ? ( - <> - {/* First line of double */} - - - {/* Second line of double */} - - - - ) : null} - - {/* Circle for port-channel representation */} - {lineType === 'DOUBLE_WITH_CIRCLE' && connector.path.tiles.length >= 2 && (() => { - const midIndex = Math.floor(connector.path.tiles.length / 2); - const midTile = connector.path.tiles[midIndex]; - const x = midTile.x * UNPROJECTED_TILE_SIZE + drawOffset.x; - const y = midTile.y * UNPROJECTED_TILE_SIZE + drawOffset.y; - - // Calculate rotation based on line direction at middle point - let rotation = 0; - if (midIndex > 0 && midIndex < connector.path.tiles.length - 1) { - const prevTile = connector.path.tiles[midIndex - 1]; - const nextTile = connector.path.tiles[midIndex + 1]; - const dx = nextTile.x - prevTile.x; - const dy = nextTile.y - prevTile.y; - rotation = Math.atan2(dy, dx) * (180 / Math.PI); - } - - // Increased size to encompass both lines with the spacing - const circleRadiusX = connectorWidthPx * 5; // Wider to cover both lines - const circleRadiusY = connectorWidthPx * 4; // Height to encompass both lines - - return ( - - { + if (!isSelected) return []; + + return connector.anchors.map((anchor) => { + const position = getAnchorTile(anchor, currentView); + + return { + id: anchor.id, + x: + (connector.path.rectangle.from.x - position.x) * + UNPROJECTED_TILE_SIZE + + drawOffset.x, + y: + (connector.path.rectangle.from.y - position.y) * + UNPROJECTED_TILE_SIZE + + drawOffset.y + }; + }); + }, [ + currentView, + connector.path.rectangle, + connector.anchors, + drawOffset, + isSelected + ]); + + const directionIcon = useMemo(() => { + return getConnectorDirectionIcon(connector.path.tiles); + }, [connector.path.tiles]); + + const strokeDashArray = useMemo(() => { + switch (connector.style) { + case 'DASHED': + return `${connectorWidthPx * 2}, ${connectorWidthPx * 2}`; + case 'DOTTED': + return `0, ${connectorWidthPx * 1.8}`; + case 'SOLID': + default: + return 'none'; + } + }, [connector.style, connectorWidthPx]); + + const lineType = connector.lineType || 'SINGLE'; + + return ( + + + {lineType === 'SINGLE' ? ( + <> + - - - ); - })()} - - {anchorPositions.map((anchor) => { - return ( - - + ) : offsetPaths ? ( + <> + {/* First line of double */} + - - - ); - })} - - {directionIcon && connector.showArrow !== false && ( - - - + + + ) : null} + + {/* Circle for port-channel representation */} + {lineType === 'DOUBLE_WITH_CIRCLE' && + connector.path.tiles.length >= 2 && + (() => { + const midIndex = Math.floor(connector.path.tiles.length / 2); + const midTile = connector.path.tiles[midIndex]; + const x = midTile.x * UNPROJECTED_TILE_SIZE + drawOffset.x; + const y = midTile.y * UNPROJECTED_TILE_SIZE + drawOffset.y; + + // Calculate rotation based on line direction at middle point + let rotation = 0; + if (midIndex > 0 && midIndex < connector.path.tiles.length - 1) { + const prevTile = connector.path.tiles[midIndex - 1]; + const nextTile = connector.path.tiles[midIndex + 1]; + const dx = nextTile.x - prevTile.x; + const dy = nextTile.y - prevTile.y; + rotation = Math.atan2(dy, dx) * (180 / Math.PI); + } + + // Increased size to encompass both lines with the spacing + const circleRadiusX = connectorWidthPx * 5; // Wider to cover both lines + const circleRadiusY = connectorWidthPx * 4; // Height to encompass both lines + + return ( + + + + + ); + })()} + + {anchorPositions.map((anchor) => { + return ( + + + + + ); + })} + + {directionIcon && connector.showArrow !== false && ( + + + + - - )} - - - ); -}); + )} + + + ); + } +); diff --git a/packages/fossflow-lib/src/components/SceneLayers/Rectangles/Rectangle.tsx b/packages/fossflow-lib/src/components/SceneLayers/Rectangles/Rectangle.tsx index 5cf187bd..7adf2c09 100644 --- a/packages/fossflow-lib/src/components/SceneLayers/Rectangles/Rectangle.tsx +++ b/packages/fossflow-lib/src/components/SceneLayers/Rectangles/Rectangle.tsx @@ -6,28 +6,28 @@ import { useColor } from 'src/hooks/useColor'; type Props = ReturnType['rectangles'][0]; -export const Rectangle = memo(({ from, to, color: colorId, customColor }: Props) => { - const predefinedColor = useColor(colorId); - - // Use custom color if provided, otherwise use predefined color - const color = customColor - ? { value: customColor } - : predefinedColor; +export const Rectangle = memo( + ({ from, to, color: colorId, customColor }: Props) => { + const predefinedColor = useColor(colorId); - if (!color) { - return null; - } + // Use custom color if provided, otherwise use predefined color + const color = customColor ? { value: customColor } : predefinedColor; + + if (!color) { + return null; + } - return ( - - ); -}); + return ( + + ); + } +); diff --git a/packages/fossflow-lib/src/components/SettingsDialog/SettingsDialog.tsx b/packages/fossflow-lib/src/components/SettingsDialog/SettingsDialog.tsx index 567510af..1f25287b 100644 --- a/packages/fossflow-lib/src/components/SettingsDialog/SettingsDialog.tsx +++ b/packages/fossflow-lib/src/components/SettingsDialog/SettingsDialog.tsx @@ -38,8 +38,12 @@ export interface SettingsDialogProps { } export const SettingsDialog = ({ iconPackManager }: SettingsDialogProps) => { - const dialog = useUiStateStore((state) => state.dialog); - const setDialog = useUiStateStore((state) => state.actions.setDialog); + const dialog = useUiStateStore((state) => { + return state.dialog; + }); + const setDialog = useUiStateStore((state) => { + return state.actions.setDialog; + }); const [tabValue, setTabValue] = useState(0); const { t } = useTranslation(); @@ -54,12 +58,7 @@ export const SettingsDialog = ({ iconPackManager }: SettingsDialogProps) => { }; return ( - + Settings { position: 'absolute', right: 8, top: 8, - color: (theme) => theme.palette.grey[500], + color: (theme) => { + return theme.palette.grey[500]; + } }} > - + @@ -107,4 +112,4 @@ export const SettingsDialog = ({ iconPackManager }: SettingsDialogProps) => { ); -}; \ No newline at end of file +}; diff --git a/packages/fossflow-lib/src/components/UiOverlay/UiOverlay.tsx b/packages/fossflow-lib/src/components/UiOverlay/UiOverlay.tsx index af599a42..f1b95abd 100644 --- a/packages/fossflow-lib/src/components/UiOverlay/UiOverlay.tsx +++ b/packages/fossflow-lib/src/components/UiOverlay/UiOverlay.tsx @@ -249,14 +249,20 @@ export const UiOverlay = () => { {dialog === DialogTypeEnum.HELP && } - {dialog === DialogTypeEnum.SETTINGS && } + {dialog === DialogTypeEnum.SETTINGS && ( + + )} {/* Show hint tooltips only in editable mode */} - {editorMode === EditorModeEnum.EDITABLE && } + {editorMode === EditorModeEnum.EDITABLE && ( + + )} {editorMode === EditorModeEnum.EDITABLE && } {editorMode === EditorModeEnum.EDITABLE && } {editorMode === EditorModeEnum.EDITABLE && } - {editorMode === EditorModeEnum.EDITABLE && } + {editorMode === EditorModeEnum.EDITABLE && ( + + )} {/* Show lazy loading welcome notification if icon pack manager is provided */} {iconPackManager && } diff --git a/packages/fossflow-lib/src/components/ZoomSettings/ZoomSettings.tsx b/packages/fossflow-lib/src/components/ZoomSettings/ZoomSettings.tsx index 87506b14..ea694e36 100644 --- a/packages/fossflow-lib/src/components/ZoomSettings/ZoomSettings.tsx +++ b/packages/fossflow-lib/src/components/ZoomSettings/ZoomSettings.tsx @@ -11,8 +11,12 @@ import { useUiStateStore } from 'src/stores/uiStateStore'; import { useLocale } from 'src/stores/localeStore'; export const ZoomSettings = () => { - const zoomSettings = useUiStateStore((state) => state.zoomSettings); - const setZoomSettings = useUiStateStore((state) => state.actions.setZoomSettings); + const zoomSettings = useUiStateStore((state) => { + return state.zoomSettings; + }); + const setZoomSettings = useUiStateStore((state) => { + return state.actions.setZoomSettings; + }); const locale = useLocale(); const handleToggle = (setting: keyof typeof zoomSettings) => { @@ -34,7 +38,9 @@ export const ZoomSettings = () => { control={ handleToggle('zoomToCursor')} + onChange={() => { + return handleToggle('zoomToCursor'); + }} /> } label={ diff --git a/packages/fossflow-lib/src/config/hotkeys.ts b/packages/fossflow-lib/src/config/hotkeys.ts index 53e7557a..62f1f56a 100644 --- a/packages/fossflow-lib/src/config/hotkeys.ts +++ b/packages/fossflow-lib/src/config/hotkeys.ts @@ -44,4 +44,4 @@ export const HOTKEY_PROFILES: Record = { } }; -export const DEFAULT_HOTKEY_PROFILE: HotkeyProfile = 'smnrct'; \ No newline at end of file +export const DEFAULT_HOTKEY_PROFILE: HotkeyProfile = 'smnrct'; diff --git a/packages/fossflow-lib/src/config/panSettings.ts b/packages/fossflow-lib/src/config/panSettings.ts index 7a16913a..8516d417 100644 --- a/packages/fossflow-lib/src/config/panSettings.ts +++ b/packages/fossflow-lib/src/config/panSettings.ts @@ -5,12 +5,12 @@ export interface PanSettings { ctrlClickPan: boolean; altClickPan: boolean; emptyAreaClickPan: boolean; - + // Keyboard pan options arrowKeysPan: boolean; wasdPan: boolean; ijklPan: boolean; - + // Pan speed keyboardPanSpeed: number; } @@ -22,12 +22,12 @@ export const DEFAULT_PAN_SETTINGS: PanSettings = { ctrlClickPan: false, altClickPan: false, emptyAreaClickPan: true, - + // Keyboard options arrowKeysPan: true, wasdPan: false, ijklPan: false, - + // Pan speed (pixels per key press) keyboardPanSpeed: 20 -}; \ No newline at end of file +}; diff --git a/packages/fossflow-lib/src/hooks/__tests__/useHistory.test.tsx b/packages/fossflow-lib/src/hooks/__tests__/useHistory.test.tsx index 24e8374a..8cd30b95 100644 --- a/packages/fossflow-lib/src/hooks/__tests__/useHistory.test.tsx +++ b/packages/fossflow-lib/src/hooks/__tests__/useHistory.test.tsx @@ -21,34 +21,38 @@ const mockSceneStore = { }; // Mock the store hooks -jest.mock('../../stores/modelStore', () => ({ - useModelStore: jest.fn((selector) => { - const state = { - actions: mockModelStore - }; - return selector ? selector(state) : state; - }) -})); - -jest.mock('../../stores/sceneStore', () => ({ - useSceneStore: jest.fn((selector) => { - const state = { - actions: mockSceneStore - }; - return selector ? selector(state) : state; - }) -})); +jest.mock('../../stores/modelStore', () => { + return { + useModelStore: jest.fn((selector) => { + const state = { + actions: mockModelStore + }; + return selector ? selector(state) : state; + }) + }; +}); + +jest.mock('../../stores/sceneStore', () => { + return { + useSceneStore: jest.fn((selector) => { + const state = { + actions: mockSceneStore + }; + return selector ? selector(state) : state; + }) + }; +}); describe('useHistory', () => { beforeEach(() => { jest.clearAllMocks(); - + // Reset mock implementations mockModelStore.canUndo.mockReturnValue(false); mockModelStore.canRedo.mockReturnValue(false); mockModelStore.undo.mockReturnValue(true); mockModelStore.redo.mockReturnValue(true); - + mockSceneStore.canUndo.mockReturnValue(false); mockSceneStore.canRedo.mockReturnValue(false); mockSceneStore.undo.mockReturnValue(true); @@ -57,71 +61,83 @@ describe('useHistory', () => { describe('undo/redo basic functionality', () => { it('should initialize with no undo/redo capability', () => { - const { result } = renderHook(() => useHistory()); - + const { result } = renderHook(() => { + return useHistory(); + }); + expect(result.current.canUndo).toBe(false); expect(result.current.canRedo).toBe(false); }); it('should call saveToHistory on both stores', () => { - const { result } = renderHook(() => useHistory()); - + const { result } = renderHook(() => { + return useHistory(); + }); + act(() => { result.current.saveToHistory(); }); - + expect(mockModelStore.saveToHistory).toHaveBeenCalled(); expect(mockSceneStore.saveToHistory).toHaveBeenCalled(); }); it('should perform undo when model store has history', () => { mockModelStore.canUndo.mockReturnValue(true); - const { result } = renderHook(() => useHistory()); - + const { result } = renderHook(() => { + return useHistory(); + }); + expect(result.current.canUndo).toBe(true); - + act(() => { const success = result.current.undo(); expect(success).toBe(true); }); - + expect(mockModelStore.undo).toHaveBeenCalled(); }); it('should perform undo when scene store has history', () => { mockSceneStore.canUndo.mockReturnValue(true); - const { result } = renderHook(() => useHistory()); - + const { result } = renderHook(() => { + return useHistory(); + }); + expect(result.current.canUndo).toBe(true); - + act(() => { const success = result.current.undo(); expect(success).toBe(true); }); - + expect(mockSceneStore.undo).toHaveBeenCalled(); }); it('should perform redo when model store has future', () => { mockModelStore.canRedo.mockReturnValue(true); - const { result } = renderHook(() => useHistory()); - + const { result } = renderHook(() => { + return useHistory(); + }); + expect(result.current.canRedo).toBe(true); - + act(() => { const success = result.current.redo(); expect(success).toBe(true); }); - + expect(mockModelStore.redo).toHaveBeenCalled(); }); it('should return false when undo is called with no history', () => { mockModelStore.undo.mockReturnValue(false); mockSceneStore.undo.mockReturnValue(false); - - const { result } = renderHook(() => useHistory()); - + + const { result } = renderHook(() => { + return useHistory(); + }); + act(() => { const success = result.current.undo(); expect(success).toBe(false); @@ -131,9 +147,11 @@ describe('useHistory', () => { it('should return false when redo is called with no future', () => { mockModelStore.redo.mockReturnValue(false); mockSceneStore.redo.mockReturnValue(false); - - const { result } = renderHook(() => useHistory()); - + + const { result } = renderHook(() => { + return useHistory(); + }); + act(() => { const success = result.current.redo(); expect(success).toBe(false); @@ -143,48 +161,54 @@ describe('useHistory', () => { describe('transaction functionality', () => { it('should save history before transaction and not during', () => { - const { result } = renderHook(() => useHistory()); - + const { result } = renderHook(() => { + return useHistory(); + }); + act(() => { result.current.transaction(() => { // This should not trigger saveToHistory due to transaction result.current.saveToHistory(); }); }); - + // Should save once before transaction starts expect(mockModelStore.saveToHistory).toHaveBeenCalledTimes(1); expect(mockSceneStore.saveToHistory).toHaveBeenCalledTimes(1); }); it('should track transaction state correctly', () => { - const { result } = renderHook(() => useHistory()); - + const { result } = renderHook(() => { + return useHistory(); + }); + expect(result.current.isInTransaction()).toBe(false); - + act(() => { result.current.transaction(() => { expect(result.current.isInTransaction()).toBe(true); }); }); - + expect(result.current.isInTransaction()).toBe(false); }); it('should prevent nested transactions', () => { - const { result } = renderHook(() => useHistory()); - + const { result } = renderHook(() => { + return useHistory(); + }); + act(() => { result.current.transaction(() => { // First transaction saves history expect(mockModelStore.saveToHistory).toHaveBeenCalledTimes(1); - + // Nested transaction should not save again result.current.transaction(() => { // Still in transaction expect(result.current.isInTransaction()).toBe(true); }); - + // Should still be 1 save expect(mockModelStore.saveToHistory).toHaveBeenCalledTimes(1); }); @@ -192,8 +216,10 @@ describe('useHistory', () => { }); it('should handle transaction errors gracefully', () => { - const { result } = renderHook(() => useHistory()); - + const { result } = renderHook(() => { + return useHistory(); + }); + expect(() => { act(() => { result.current.transaction(() => { @@ -201,7 +227,7 @@ describe('useHistory', () => { }); }); }).toThrow('Test error'); - + // Transaction should be cleaned up expect(result.current.isInTransaction()).toBe(false); }); @@ -209,12 +235,14 @@ describe('useHistory', () => { describe('history management', () => { it('should clear all history', () => { - const { result } = renderHook(() => useHistory()); - + const { result } = renderHook(() => { + return useHistory(); + }); + act(() => { result.current.clearHistory(); }); - + expect(mockModelStore.clearHistory).toHaveBeenCalled(); expect(mockSceneStore.clearHistory).toHaveBeenCalled(); }); @@ -223,29 +251,37 @@ describe('useHistory', () => { // Only model has undo mockModelStore.canUndo.mockReturnValue(true); mockSceneStore.canUndo.mockReturnValue(false); - - const { result: result1 } = renderHook(() => useHistory()); + + const { result: result1 } = renderHook(() => { + return useHistory(); + }); expect(result1.current.canUndo).toBe(true); - + // Only scene has undo mockModelStore.canUndo.mockReturnValue(false); mockSceneStore.canUndo.mockReturnValue(true); - - const { result: result2 } = renderHook(() => useHistory()); + + const { result: result2 } = renderHook(() => { + return useHistory(); + }); expect(result2.current.canUndo).toBe(true); - + // Both have undo mockModelStore.canUndo.mockReturnValue(true); mockSceneStore.canUndo.mockReturnValue(true); - - const { result: result3 } = renderHook(() => useHistory()); + + const { result: result3 } = renderHook(() => { + return useHistory(); + }); expect(result3.current.canUndo).toBe(true); - + // Neither has undo mockModelStore.canUndo.mockReturnValue(false); mockSceneStore.canUndo.mockReturnValue(false); - - const { result: result4 } = renderHook(() => useHistory()); + + const { result: result4 } = renderHook(() => { + return useHistory(); + }); expect(result4.current.canUndo).toBe(false); }); @@ -253,15 +289,19 @@ describe('useHistory', () => { // Only model has redo mockModelStore.canRedo.mockReturnValue(true); mockSceneStore.canRedo.mockReturnValue(false); - - const { result: result1 } = renderHook(() => useHistory()); + + const { result: result1 } = renderHook(() => { + return useHistory(); + }); expect(result1.current.canRedo).toBe(true); - + // Only scene has redo mockModelStore.canRedo.mockReturnValue(false); mockSceneStore.canRedo.mockReturnValue(true); - - const { result: result2 } = renderHook(() => useHistory()); + + const { result: result2 } = renderHook(() => { + return useHistory(); + }); expect(result2.current.canRedo).toBe(true); }); }); @@ -271,22 +311,24 @@ describe('useHistory', () => { // Mock stores returning undefined actions const useModelStore = require('../../stores/modelStore').useModelStore; const useSceneStore = require('../../stores/sceneStore').useSceneStore; - - useModelStore.mockImplementation((selector) => { + + useModelStore.mockImplementation((selector: any) => { const state = { actions: undefined }; return selector ? selector(state) : state; }); - useSceneStore.mockImplementation((selector) => { + useSceneStore.mockImplementation((selector: any) => { const state = { actions: undefined }; return selector ? selector(state) : state; }); - - const { result } = renderHook(() => useHistory()); - + + const { result } = renderHook(() => { + return useHistory(); + }); + // Should not throw and return safe defaults expect(result.current.canUndo).toBe(false); expect(result.current.canRedo).toBe(false); - + act(() => { expect(result.current.undo()).toBe(false); expect(result.current.redo()).toBe(false); @@ -295,30 +337,32 @@ describe('useHistory', () => { result.current.clearHistory(); result.current.transaction(() => {}); }); - + // Restore mocks for other tests - useModelStore.mockImplementation((selector) => { + useModelStore.mockImplementation((selector: any) => { const state = { actions: mockModelStore }; return selector ? selector(state) : state; }); - useSceneStore.mockImplementation((selector) => { + useSceneStore.mockImplementation((selector: any) => { const state = { actions: mockSceneStore }; return selector ? selector(state) : state; }); }); it('should not save history during active transaction', () => { - const { result } = renderHook(() => useHistory()); - + const { result } = renderHook(() => { + return useHistory(); + }); + act(() => { result.current.transaction(() => { // Clear previous calls from transaction setup mockModelStore.saveToHistory.mockClear(); mockSceneStore.saveToHistory.mockClear(); - + // Try to save during transaction result.current.saveToHistory(); - + // Should not have saved expect(mockModelStore.saveToHistory).not.toHaveBeenCalled(); expect(mockSceneStore.saveToHistory).not.toHaveBeenCalled(); @@ -326,4 +370,4 @@ describe('useHistory', () => { }); }); }); -}); \ No newline at end of file +}); diff --git a/packages/fossflow-lib/src/hooks/__tests__/useInitialDataManager.test.tsx b/packages/fossflow-lib/src/hooks/__tests__/useInitialDataManager.test.tsx index 3f15e99e..e8553a9b 100644 --- a/packages/fossflow-lib/src/hooks/__tests__/useInitialDataManager.test.tsx +++ b/packages/fossflow-lib/src/hooks/__tests__/useInitialDataManager.test.tsx @@ -26,11 +26,13 @@ afterAll(() => { jest.mock('src/stores/modelStore'); jest.mock('src/stores/uiStateStore'); jest.mock('src/hooks/useView'); -jest.mock('src/schemas/model', () => ({ - modelSchema: { - safeParse: jest.fn() - } -})); +jest.mock('src/schemas/model', () => { + return { + modelSchema: { + safeParse: jest.fn() + } + }; +}); describe('useInitialDataManager - Orphaned Connector Handling', () => { let mockModelStore: any; @@ -49,12 +51,14 @@ describe('useInitialDataManager - Orphaned Connector Handling', () => { icons: [], colors: [] }; - (modelStoreModule.useModelStore as jest.Mock).mockImplementation((selector) => { - if (typeof selector === 'function') { - return selector(mockModelStore); + (modelStoreModule.useModelStore as jest.Mock).mockImplementation( + (selector) => { + if (typeof selector === 'function') { + return selector(mockModelStore); + } + return mockModelStore; } - return mockModelStore; - }); + ); // Setup mock UI state store mockUiStateStore = { @@ -67,12 +71,14 @@ describe('useInitialDataManager - Orphaned Connector Handling', () => { rendererEl: null, editorMode: 'INTERACTIVE' }; - (uiStateStoreModule.useUiStateStore as jest.Mock).mockImplementation((selector) => { - if (typeof selector === 'function') { - return selector(mockUiStateStore); + (uiStateStoreModule.useUiStateStore as jest.Mock).mockImplementation( + (selector) => { + if (typeof selector === 'function') { + return selector(mockUiStateStore); + } + return mockUiStateStore; } - return mockUiStateStore; - }); + ); // Setup mock changeView mockChangeView = jest.fn(); @@ -86,7 +92,9 @@ describe('useInitialDataManager - Orphaned Connector Handling', () => { }); it('should filter out connectors with invalid item references during load', () => { - const { result } = renderHook(() => useInitialDataManager()); + const { result } = renderHook(() => { + return useInitialDataManager(); + }); const initialData: InitialData = { version: '1.0', @@ -107,22 +115,22 @@ describe('useInitialDataManager - Orphaned Connector Handling', () => { { id: 'connector1', anchors: [ - { id: 'anchor1', ref: { item: 'item1' }, face: 'right' }, - { id: 'anchor2', ref: { item: 'item2' }, face: 'left' } + { id: 'anchor1', ref: { item: 'item1' } }, + { id: 'anchor2', ref: { item: 'item2' } } ] }, { id: 'connector2', anchors: [ - { id: 'anchor3', ref: { item: 'item1' }, face: 'top' }, - { id: 'anchor4', ref: { item: 'nonexistent' }, face: 'bottom' } // Invalid reference + { id: 'anchor3', ref: { item: 'item1' } }, + { id: 'anchor4', ref: { item: 'nonexistent' } } // Invalid reference ] }, { id: 'connector3', anchors: [ - { id: 'anchor5', ref: { item: 'nonexistent1' }, face: 'right' }, // Invalid reference - { id: 'anchor6', ref: { item: 'nonexistent2' }, face: 'left' } // Invalid reference + { id: 'anchor5', ref: { item: 'nonexistent1' } }, // Invalid reference + { id: 'anchor6', ref: { item: 'nonexistent2' } } // Invalid reference ] } ], @@ -142,12 +150,18 @@ describe('useInitialDataManager - Orphaned Connector Handling', () => { expect(setCall.views[0].connectors[0].id).toBe('connector1'); // Check that warnings were logged for removed connectors - expect(console.warn).toHaveBeenCalledWith('Removing connector connector2 due to invalid item references'); - expect(console.warn).toHaveBeenCalledWith('Removing connector connector3 due to invalid item references'); + expect(console.warn).toHaveBeenCalledWith( + 'Removing connector connector2 due to invalid item references' + ); + expect(console.warn).toHaveBeenCalledWith( + 'Removing connector connector3 due to invalid item references' + ); }); it('should allow connectors that reference other anchors', () => { - const { result } = renderHook(() => useInitialDataManager()); + const { result } = renderHook(() => { + return useInitialDataManager(); + }); const initialData: InitialData = { version: '1.0', @@ -160,15 +174,13 @@ describe('useInitialDataManager - Orphaned Connector Handling', () => { { id: 'view1', name: 'Test View', - items: [ - { id: 'item1', tile: { x: 0, y: 0 } } - ], + items: [{ id: 'item1', tile: { x: 0, y: 0 } }], connectors: [ { id: 'connector1', anchors: [ - { id: 'anchor1', ref: { item: 'item1' }, face: 'right' }, - { id: 'anchor2', ref: { anchor: 'anchor3' }, face: 'left' } // References another anchor + { id: 'anchor1', ref: { item: 'item1' } }, + { id: 'anchor2', ref: { anchor: 'anchor3' } } // References another anchor ] } ], @@ -189,7 +201,9 @@ describe('useInitialDataManager - Orphaned Connector Handling', () => { }); it('should handle views with no connectors', () => { - const { result } = renderHook(() => useInitialDataManager()); + const { result } = renderHook(() => { + return useInitialDataManager(); + }); const initialData: InitialData = { version: '1.0', @@ -219,7 +233,9 @@ describe('useInitialDataManager - Orphaned Connector Handling', () => { }); it('should handle all connectors being invalid', () => { - const { result } = renderHook(() => useInitialDataManager()); + const { result } = renderHook(() => { + return useInitialDataManager(); + }); const initialData: InitialData = { version: '1.0', @@ -237,15 +253,15 @@ describe('useInitialDataManager - Orphaned Connector Handling', () => { { id: 'connector1', anchors: [ - { id: 'anchor1', ref: { item: 'nonexistent1' }, face: 'right' }, - { id: 'anchor2', ref: { item: 'nonexistent2' }, face: 'left' } + { id: 'anchor1', ref: { item: 'nonexistent1' } }, + { id: 'anchor2', ref: { item: 'nonexistent2' } } ] }, { id: 'connector2', anchors: [ - { id: 'anchor3', ref: { item: 'deleted1' }, face: 'top' }, - { id: 'anchor4', ref: { item: 'deleted2' }, face: 'bottom' } + { id: 'anchor3', ref: { item: 'deleted1' } }, + { id: 'anchor4', ref: { item: 'deleted2' } } ] } ], @@ -266,7 +282,9 @@ describe('useInitialDataManager - Orphaned Connector Handling', () => { }); it('should handle mixed valid and invalid anchor references', () => { - const { result } = renderHook(() => useInitialDataManager()); + const { result } = renderHook(() => { + return useInitialDataManager(); + }); const initialData: InitialData = { version: '1.0', @@ -287,9 +305,9 @@ describe('useInitialDataManager - Orphaned Connector Handling', () => { { id: 'connector1', anchors: [ - { id: 'anchor1', ref: { item: 'item1' }, face: 'right' }, // Valid - { id: 'anchor2', ref: { item: 'item2' }, face: 'left' }, // Valid - { id: 'anchor3', ref: { item: 'nonexistent' }, face: 'top' } // Invalid + { id: 'anchor1', ref: { item: 'item1' } }, // Valid + { id: 'anchor2', ref: { item: 'item2' } }, // Valid + { id: 'anchor3', ref: { item: 'nonexistent' } } // Invalid ] } ], @@ -306,11 +324,15 @@ describe('useInitialDataManager - Orphaned Connector Handling', () => { // Connector with any invalid anchor should be removed const setCall = mockModelStore.actions.set.mock.calls[0][0]; expect(setCall.views[0].connectors).toHaveLength(0); - expect(console.warn).toHaveBeenCalledWith('Removing connector connector1 due to invalid item references'); + expect(console.warn).toHaveBeenCalledWith( + 'Removing connector connector1 due to invalid item references' + ); }); it('should not modify original initialData object', () => { - const { result } = renderHook(() => useInitialDataManager()); + const { result } = renderHook(() => { + return useInitialDataManager(); + }); const initialData: InitialData = { version: '1.0', @@ -328,8 +350,8 @@ describe('useInitialDataManager - Orphaned Connector Handling', () => { { id: 'connector1', anchors: [ - { id: 'anchor1', ref: { item: 'nonexistent' }, face: 'right' }, - { id: 'anchor2', ref: { item: 'item1' }, face: 'left' } + { id: 'anchor1', ref: { item: 'nonexistent' } }, + { id: 'anchor2', ref: { item: 'item1' } } ] } ], @@ -350,7 +372,9 @@ describe('useInitialDataManager - Orphaned Connector Handling', () => { }); it('should handle validation errors gracefully', () => { - const { result } = renderHook(() => useInitialDataManager()); + const { result } = renderHook(() => { + return useInitialDataManager(); + }); mockModelSchema.safeParse.mockReturnValueOnce({ success: false, @@ -373,13 +397,17 @@ describe('useInitialDataManager - Orphaned Connector Handling', () => { result.current.load(initialData); }); - expect(window.alert).toHaveBeenCalledWith('There is an error in your model.'); + expect(window.alert).toHaveBeenCalledWith( + 'There is an error in your model.' + ); expect(mockModelStore.actions.set).not.toHaveBeenCalled(); expect(result.current.isReady).toBe(false); }); it('should preserve connectors with all valid references', () => { - const { result } = renderHook(() => useInitialDataManager()); + const { result } = renderHook(() => { + return useInitialDataManager(); + }); const initialData: InitialData = { version: '1.0', @@ -401,15 +429,15 @@ describe('useInitialDataManager - Orphaned Connector Handling', () => { { id: 'connector1', anchors: [ - { id: 'anchor1', ref: { item: 'item1' }, face: 'right' }, - { id: 'anchor2', ref: { item: 'item2' }, face: 'left' } + { id: 'anchor1', ref: { item: 'item1' } }, + { id: 'anchor2', ref: { item: 'item2' } } ] }, { id: 'connector2', anchors: [ - { id: 'anchor3', ref: { item: 'item2' }, face: 'right' }, - { id: 'anchor4', ref: { item: 'item3' }, face: 'left' } + { id: 'anchor3', ref: { item: 'item2' } }, + { id: 'anchor4', ref: { item: 'item3' } } ] } ], @@ -430,4 +458,4 @@ describe('useInitialDataManager - Orphaned Connector Handling', () => { expect(setCall.views[0].connectors[1].id).toBe('connector2'); expect(console.warn).not.toHaveBeenCalled(); }); -}); \ No newline at end of file +}); diff --git a/packages/fossflow-lib/src/hooks/useInitialDataManager.ts b/packages/fossflow-lib/src/hooks/useInitialDataManager.ts index c6373dd4..29190a17 100644 --- a/packages/fossflow-lib/src/hooks/useInitialDataManager.ts +++ b/packages/fossflow-lib/src/hooks/useInitialDataManager.ts @@ -38,8 +38,12 @@ export const useInitialDataManager = () => { // Deep comparison to prevent unnecessary reloads when data hasn't actually changed // Skip this check for NON_INTERACTIVE mode (used by export) to ensure proper initialization if (prevInitialData.current && editorMode !== 'NON_INTERACTIVE') { - const prevConnectors = JSON.stringify(prevInitialData.current.views?.[0]?.connectors || []); - const newConnectors = JSON.stringify(_initialData.views?.[0]?.connectors || []); + const prevConnectors = JSON.stringify( + prevInitialData.current.views?.[0]?.connectors || [] + ); + const newConnectors = JSON.stringify( + _initialData.views?.[0]?.connectors || [] + ); const prevItems = JSON.stringify(prevInitialData.current.items || []); const newItems = JSON.stringify(_initialData.items || []); const prevIcons = JSON.stringify(prevInitialData.current.icons || []); @@ -47,8 +51,12 @@ export const useInitialDataManager = () => { const prevColors = JSON.stringify(prevInitialData.current.colors || []); const newColors = JSON.stringify(_initialData.colors || []); - if (prevConnectors === newConnectors && prevItems === newItems && - prevIcons === newIcons && prevColors === newColors) { + if ( + prevConnectors === newConnectors && + prevItems === newItems && + prevIcons === newIcons && + prevColors === newColors + ) { // Data hasn't actually changed, skip reload return; } @@ -68,21 +76,25 @@ export const useInitialDataManager = () => { // Clean up invalid connector references before loading const initialData = { ..._initialData }; - initialData.views = initialData.views.map(view => { + initialData.views = initialData.views.map((view) => { if (!view.connectors) return view; - const validConnectors = view.connectors.filter(connector => { + const validConnectors = view.connectors.filter((connector) => { // Check if all anchors reference existing items - const hasValidAnchors = connector.anchors.every(anchor => { + const hasValidAnchors = connector.anchors.every((anchor) => { if (anchor.ref.item) { // Check if the referenced item exists in the view - return view.items.some(item => item.id === anchor.ref.item); + return view.items.some((item) => { + return item.id === anchor.ref.item; + }); } return true; // Allow anchors that reference other anchors }); if (!hasValidAnchors) { - console.warn(`Removing connector ${connector.id} due to invalid item references`); + console.warn( + `Removing connector ${connector.id} due to invalid item references` + ); } return hasValidAnchors; diff --git a/packages/fossflow-lib/src/hooks/useScene.ts b/packages/fossflow-lib/src/hooks/useScene.ts index 99d0b038..61e7e5a5 100644 --- a/packages/fossflow-lib/src/hooks/useScene.ts +++ b/packages/fossflow-lib/src/hooks/useScene.ts @@ -1,11 +1,5 @@ import { useCallback, useMemo, useRef } from 'react'; -import { - ModelItem, - ViewItem, - Connector, - TextBox, - Rectangle -} from 'src/types'; +import { ModelItem, ViewItem, Connector, TextBox, Rectangle } from 'src/types'; import { useUiStateStore } from 'src/stores/uiStateStore'; import { useModelStore } from 'src/stores/modelStore'; import { useSceneStore } from 'src/stores/sceneStore'; @@ -226,7 +220,8 @@ export const useScene = () => { const updateViewItem = useCallback( (id: string, updates: Partial, currentState?: State) => { - if (!model?.actions || !scene?.actions || !currentViewId) return getState(); + if (!model?.actions || !scene?.actions || !currentViewId) + return getState(); if (!transactionInProgress.current) { saveToHistoryBeforeChange(); diff --git a/packages/fossflow-lib/src/i18n/bn-BD.ts b/packages/fossflow-lib/src/i18n/bn-BD.ts index 9e1c4a2a..0fc4e4ab 100644 --- a/packages/fossflow-lib/src/i18n/bn-BD.ts +++ b/packages/fossflow-lib/src/i18n/bn-BD.ts @@ -2,187 +2,197 @@ import { LocaleProps } from '../types/isoflowProps'; const locale: LocaleProps = { common: { - exampleText: "এটি একটি উদাহরণ পাঠ্য" + exampleText: 'এটি একটি উদাহরণ পাঠ্য' }, mainMenu: { - undo: "পূর্বাবস্থায় ফেরান", - redo: "পুনরায় করুন", - open: "খুলুন", - exportJson: "JSON হিসাবে রপ্তানি করুন", - exportCompactJson: "কমপ্যাক্ট JSON হিসাবে রপ্তানি করুন", - exportImage: "ছবি হিসাবে রপ্তানি করুন", - clearCanvas: "ক্যানভাস পরিষ্কার করুন", - settings: "সেটিংস", - gitHub: "GitHub" + undo: 'পূর্বাবস্থায় ফেরান', + redo: 'পুনরায় করুন', + open: 'খুলুন', + exportJson: 'JSON হিসাবে রপ্তানি করুন', + exportCompactJson: 'কমপ্যাক্ট JSON হিসাবে রপ্তানি করুন', + exportImage: 'ছবি হিসাবে রপ্তানি করুন', + clearCanvas: 'ক্যানভাস পরিষ্কার করুন', + settings: 'সেটিংস', + gitHub: 'GitHub' }, helpDialog: { - title: "কীবোর্ড শর্টকাট এবং সহায়তা", - close: "বন্ধ করুন", - keyboardShortcuts: "কীবোর্ড শর্টকাট", - mouseInteractions: "মাউস ইন্টারঅ্যাকশন", - action: "ক্রিয়া", - shortcut: "শর্টকাট", - method: "পদ্ধতি", - description: "বিবরণ", - note: "নোট:", - noteContent: "দ্বন্দ্ব এড়াতে ইনপুট ফিল্ড, টেক্সট এরিয়া বা সম্পাদনাযোগ্য উপাদানে টাইপ করার সময় কীবোর্ড শর্টকাট নিষ্ক্রিয় থাকে।", + title: 'কীবোর্ড শর্টকাট এবং সহায়তা', + close: 'বন্ধ করুন', + keyboardShortcuts: 'কীবোর্ড শর্টকাট', + mouseInteractions: 'মাউস ইন্টারঅ্যাকশন', + action: 'ক্রিয়া', + shortcut: 'শর্টকাট', + method: 'পদ্ধতি', + description: 'বিবরণ', + note: 'নোট:', + noteContent: + 'দ্বন্দ্ব এড়াতে ইনপুট ফিল্ড, টেক্সট এরিয়া বা সম্পাদনাযোগ্য উপাদানে টাইপ করার সময় কীবোর্ড শর্টকাট নিষ্ক্রিয় থাকে।', // Keyboard shortcuts - undoAction: "পূর্বাবস্থায় ফেরান", - undoDescription: "শেষ ক্রিয়াটি পূর্বাবস্থায় ফেরান", - redoAction: "পুনরায় করুন", - redoDescription: "শেষ পূর্বাবস্থায় ফেরানো ক্রিয়া পুনরায় করুন", - redoAltAction: "পুনরায় করুন (বিকল্প)", - redoAltDescription: "পুনরায় করার জন্য বিকল্প শর্টকাট", - helpAction: "সহায়তা", - helpDescription: "কীবোর্ড শর্টকাট সহ সহায়তা ডায়ালগ খুলুন", - zoomInAction: "জুম ইন করুন", - zoomInShortcut: "মাউস হুইল উপরে", - zoomInDescription: "ক্যানভাসে জুম ইন করুন", - zoomOutAction: "জুম আউট করুন", - zoomOutShortcut: "মাউস হুইল নিচে", - zoomOutDescription: "ক্যানভাস থেকে জুম আউট করুন", - panCanvasAction: "ক্যানভাস প্যান করুন", - panCanvasShortcut: "বাম-ক্লিক + টেনে আনুন", - panCanvasDescription: "প্যান মোডে ক্যানভাস প্যান করুন", - contextMenuAction: "প্রসঙ্গ মেনু", - contextMenuShortcut: "ডান-ক্লিক", - contextMenuDescription: "আইটেম বা খালি স্থানের জন্য প্রসঙ্গ মেনু খুলুন", + undoAction: 'পূর্বাবস্থায় ফেরান', + undoDescription: 'শেষ ক্রিয়াটি পূর্বাবস্থায় ফেরান', + redoAction: 'পুনরায় করুন', + redoDescription: 'শেষ পূর্বাবস্থায় ফেরানো ক্রিয়া পুনরায় করুন', + redoAltAction: 'পুনরায় করুন (বিকল্প)', + redoAltDescription: 'পুনরায় করার জন্য বিকল্প শর্টকাট', + helpAction: 'সহায়তা', + helpDescription: 'কীবোর্ড শর্টকাট সহ সহায়তা ডায়ালগ খুলুন', + zoomInAction: 'জুম ইন করুন', + zoomInShortcut: 'মাউস হুইল উপরে', + zoomInDescription: 'ক্যানভাসে জুম ইন করুন', + zoomOutAction: 'জুম আউট করুন', + zoomOutShortcut: 'মাউস হুইল নিচে', + zoomOutDescription: 'ক্যানভাস থেকে জুম আউট করুন', + panCanvasAction: 'ক্যানভাস প্যান করুন', + panCanvasShortcut: 'বাম-ক্লিক + টেনে আনুন', + panCanvasDescription: 'প্যান মোডে ক্যানভাস প্যান করুন', + contextMenuAction: 'প্রসঙ্গ মেনু', + contextMenuShortcut: 'ডান-ক্লিক', + contextMenuDescription: 'আইটেম বা খালি স্থানের জন্য প্রসঙ্গ মেনু খুলুন', // Mouse interactions - selectToolAction: "নির্বাচন টুল", - selectToolShortcut: "নির্বাচন বোতামে ক্লিক করুন", - selectToolDescription: "নির্বাচন মোডে স্যুইচ করুন", - panToolAction: "প্যান টুল", - panToolShortcut: "প্যান বোতামে ক্লিক করুন", - panToolDescription: "ক্যানভাস সরানোর জন্য প্যান মোডে স্যুইচ করুন", - addItemAction: "আইটেম যোগ করুন", - addItemShortcut: "আইটেম যোগ করুন বোতামে ক্লিক করুন", - addItemDescription: "নতুন আইটেম যোগ করতে আইকন পিকার খুলুন", - drawRectangleAction: "আয়তক্ষেত্র আঁকুন", - drawRectangleShortcut: "আয়তক্ষেত্র বোতামে ক্লিক করুন", - drawRectangleDescription: "আয়তক্ষেত্র অঙ্কন মোডে স্যুইচ করুন", - createConnectorAction: "সংযোগকারী তৈরি করুন", - createConnectorShortcut: "সংযোগকারী বোতামে ক্লিক করুন", - createConnectorDescription: "সংযোগকারী মোডে স্যুইচ করুন", - addTextAction: "পাঠ্য যোগ করুন", - addTextShortcut: "পাঠ্য বোতামে ক্লিক করুন", - addTextDescription: "একটি নতুন টেক্সট বক্স তৈরি করুন" + selectToolAction: 'নির্বাচন টুল', + selectToolShortcut: 'নির্বাচন বোতামে ক্লিক করুন', + selectToolDescription: 'নির্বাচন মোডে স্যুইচ করুন', + panToolAction: 'প্যান টুল', + panToolShortcut: 'প্যান বোতামে ক্লিক করুন', + panToolDescription: 'ক্যানভাস সরানোর জন্য প্যান মোডে স্যুইচ করুন', + addItemAction: 'আইটেম যোগ করুন', + addItemShortcut: 'আইটেম যোগ করুন বোতামে ক্লিক করুন', + addItemDescription: 'নতুন আইটেম যোগ করতে আইকন পিকার খুলুন', + drawRectangleAction: 'আয়তক্ষেত্র আঁকুন', + drawRectangleShortcut: 'আয়তক্ষেত্র বোতামে ক্লিক করুন', + drawRectangleDescription: 'আয়তক্ষেত্র অঙ্কন মোডে স্যুইচ করুন', + createConnectorAction: 'সংযোগকারী তৈরি করুন', + createConnectorShortcut: 'সংযোগকারী বোতামে ক্লিক করুন', + createConnectorDescription: 'সংযোগকারী মোডে স্যুইচ করুন', + addTextAction: 'পাঠ্য যোগ করুন', + addTextShortcut: 'পাঠ্য বোতামে ক্লিক করুন', + addTextDescription: 'একটি নতুন টেক্সট বক্স তৈরি করুন' }, connectorHintTooltip: { - tipCreatingConnectors: "টিপ: সংযোগকারী তৈরি করা", - tipConnectorTools: "টিপ: সংযোগকারী টুল", - clickInstructionStart: "ক্লিক করুন", - clickInstructionMiddle: "প্রথম নোড বা পয়েন্টে, তারপর", - clickInstructionEnd: "দ্বিতীয় নোড বা পয়েন্টে একটি সংযোগ তৈরি করতে।", - nowClickTarget: "সংযোগ সম্পূর্ণ করতে এখন লক্ষ্যে ক্লিক করুন।", - dragStart: "টেনে আনুন", - dragEnd: "প্রথম নোড থেকে দ্বিতীয় নোডে একটি সংযোগ তৈরি করতে।", - rerouteStart: "একটি সংযোগকারী পুনর্নির্দেশ করতে,", - rerouteMiddle: "বাম-ক্লিক করুন", - rerouteEnd: "সংযোগকারী লাইনের সাথে যে কোনও পয়েন্টে এবং অ্যাঙ্কর পয়েন্ট তৈরি বা সরাতে টেনে আনুন।" + tipCreatingConnectors: 'টিপ: সংযোগকারী তৈরি করা', + tipConnectorTools: 'টিপ: সংযোগকারী টুল', + clickInstructionStart: 'ক্লিক করুন', + clickInstructionMiddle: 'প্রথম নোড বা পয়েন্টে, তারপর', + clickInstructionEnd: 'দ্বিতীয় নোড বা পয়েন্টে একটি সংযোগ তৈরি করতে।', + nowClickTarget: 'সংযোগ সম্পূর্ণ করতে এখন লক্ষ্যে ক্লিক করুন।', + dragStart: 'টেনে আনুন', + dragEnd: 'প্রথম নোড থেকে দ্বিতীয় নোডে একটি সংযোগ তৈরি করতে।', + rerouteStart: 'একটি সংযোগকারী পুনর্নির্দেশ করতে,', + rerouteMiddle: 'বাম-ক্লিক করুন', + rerouteEnd: + 'সংযোগকারী লাইনের সাথে যে কোনও পয়েন্টে এবং অ্যাঙ্কর পয়েন্ট তৈরি বা সরাতে টেনে আনুন।' }, lassoHintTooltip: { - tipLasso: "টিপ: ল্যাসো নির্বাচন", - tipFreehandLasso: "টিপ: ফ্রিহ্যান্ড ল্যাসো নির্বাচন", - lassoDragStart: "ক্লিক করুন এবং টেনে আনুন", - lassoDragEnd: "আপনি যে আইটেমগুলি নির্বাচন করতে চান তার চারপাশে একটি আয়তক্ষেত্রাকার নির্বাচন বক্স আঁকতে।", - freehandDragStart: "ক্লিক করুন এবং টেনে আনুন", - freehandDragMiddle: "একটি আঁকতে", - freehandDragEnd: "মুক্ত আকৃতি", - freehandComplete: "আইটেমগুলির চারপাশে। আকৃতির ভিতরের সমস্ত আইটেম নির্বাচন করতে ছেড়ে দিন।", - moveStart: "একবার নির্বাচিত হলে,", - moveMiddle: "নির্বাচনের ভিতরে ক্লিক করুন", - moveEnd: "এবং সমস্ত নির্বাচিত আইটেম একসাথে সরাতে টেনে আনুন।" + tipLasso: 'টিপ: ল্যাসো নির্বাচন', + tipFreehandLasso: 'টিপ: ফ্রিহ্যান্ড ল্যাসো নির্বাচন', + lassoDragStart: 'ক্লিক করুন এবং টেনে আনুন', + lassoDragEnd: + 'আপনি যে আইটেমগুলি নির্বাচন করতে চান তার চারপাশে একটি আয়তক্ষেত্রাকার নির্বাচন বক্স আঁকতে।', + freehandDragStart: 'ক্লিক করুন এবং টেনে আনুন', + freehandDragMiddle: 'একটি আঁকতে', + freehandDragEnd: 'মুক্ত আকৃতি', + freehandComplete: + 'আইটেমগুলির চারপাশে। আকৃতির ভিতরের সমস্ত আইটেম নির্বাচন করতে ছেড়ে দিন।', + moveStart: 'একবার নির্বাচিত হলে,', + moveMiddle: 'নির্বাচনের ভিতরে ক্লিক করুন', + moveEnd: 'এবং সমস্ত নির্বাচিত আইটেম একসাথে সরাতে টেনে আনুন।' }, importHintTooltip: { - title: "ডায়াগ্রাম আমদানি করুন", - instructionStart: "ডায়াগ্রাম আমদানি করতে, ক্লিক করুন", - menuButton: "মেনু বোতাম", - instructionMiddle: "(☰) উপরের বাম কোণে, তারপর নির্বাচন করুন", - openButton: "\"খুলুন\"", - instructionEnd: "আপনার ডায়াগ্রাম ফাইল লোড করতে।" + title: 'ডায়াগ্রাম আমদানি করুন', + instructionStart: 'ডায়াগ্রাম আমদানি করতে, ক্লিক করুন', + menuButton: 'মেনু বোতাম', + instructionMiddle: '(☰) উপরের বাম কোণে, তারপর নির্বাচন করুন', + openButton: '"খুলুন"', + instructionEnd: 'আপনার ডায়াগ্রাম ফাইল লোড করতে।' }, connectorRerouteTooltip: { - title: "টিপ: সংযোগকারী পুনর্নির্দেশ করুন", - instructionStart: "একবার আপনার সংযোগকারী স্থাপন করা হলে আপনি আপনার ইচ্ছামতো তাদের পুনর্নির্দেশ করতে পারেন।", - instructionSelect: "সংযোগকারী নির্বাচন করুন", - instructionMiddle: "প্রথমে, তারপর", - instructionClick: "সংযোগকারী পথে ক্লিক করুন", - instructionAnd: "এবং", - instructionDrag: "টেনে আনুন", - instructionEnd: "এটি পরিবর্তন করতে!" + title: 'টিপ: সংযোগকারী পুনর্নির্দেশ করুন', + instructionStart: + 'একবার আপনার সংযোগকারী স্থাপন করা হলে আপনি আপনার ইচ্ছামতো তাদের পুনর্নির্দেশ করতে পারেন।', + instructionSelect: 'সংযোগকারী নির্বাচন করুন', + instructionMiddle: 'প্রথমে, তারপর', + instructionClick: 'সংযোগকারী পথে ক্লিক করুন', + instructionAnd: 'এবং', + instructionDrag: 'টেনে আনুন', + instructionEnd: 'এটি পরিবর্তন করতে!' }, settings: { zoom: { - description: "মাউস হুইল ব্যবহার করার সময় জুম আচরণ কনফিগার করুন।", - zoomToCursor: "কার্সারে জুম করুন", - zoomToCursorDesc: "সক্রিয় থাকলে, মাউস কার্সার অবস্থানে কেন্দ্রীভূত জুম ইন/আউট। নিষ্ক্রিয় থাকলে, জুম ক্যানভাসে কেন্দ্রীভূত।" + description: 'মাউস হুইল ব্যবহার করার সময় জুম আচরণ কনফিগার করুন।', + zoomToCursor: 'কার্সারে জুম করুন', + zoomToCursorDesc: + 'সক্রিয় থাকলে, মাউস কার্সার অবস্থানে কেন্দ্রীভূত জুম ইন/আউট। নিষ্ক্রিয় থাকলে, জুম ক্যানভাসে কেন্দ্রীভূত।' }, hotkeys: { - title: "শর্টকাট সেটিংস", - profile: "শর্টকাট প্রোফাইল", - profileQwerty: "QWERTY (Q, W, E, R, T, Y)", - profileSmnrct: "SMNRCT (S, M, N, R, C, T)", - profileNone: "কোন শর্টকাট নেই", - tool: "টুল", - hotkey: "শর্টকাট", - toolSelect: "নির্বাচন করুন", - toolPan: "প্যান করুন", - toolAddItem: "আইটেম যোগ করুন", - toolRectangle: "আয়তক্ষেত্র", - toolConnector: "সংযোগকারী", - toolText: "পাঠ্য", - note: "নোট: টেক্সট ফিল্ডে টাইপ না করার সময় শর্টকাটগুলি কাজ করে" + title: 'শর্টকাট সেটিংস', + profile: 'শর্টকাট প্রোফাইল', + profileQwerty: 'QWERTY (Q, W, E, R, T, Y)', + profileSmnrct: 'SMNRCT (S, M, N, R, C, T)', + profileNone: 'কোন শর্টকাট নেই', + tool: 'টুল', + hotkey: 'শর্টকাট', + toolSelect: 'নির্বাচন করুন', + toolPan: 'প্যান করুন', + toolAddItem: 'আইটেম যোগ করুন', + toolRectangle: 'আয়তক্ষেত্র', + toolConnector: 'সংযোগকারী', + toolText: 'পাঠ্য', + note: 'নোট: টেক্সট ফিল্ডে টাইপ না করার সময় শর্টকাটগুলি কাজ করে' }, pan: { - title: "প্যান সেটিংস", - mousePanOptions: "মাউস প্যান বিকল্প", - emptyAreaClickPan: "খালি এলাকায় ক্লিক করুন এবং টেনে আনুন", - middleClickPan: "মধ্য ক্লিক করুন এবং টেনে আনুন", - rightClickPan: "ডান ক্লিক করুন এবং টেনে আনুন", - ctrlClickPan: "Ctrl + ক্লিক করুন এবং টেনে আনুন", - altClickPan: "Alt + ক্লিক করুন এবং টেনে আনুন", - keyboardPanOptions: "কীবোর্ড প্যান বিকল্প", - arrowKeys: "তীর কী", - wasdKeys: "WASD কী", - ijklKeys: "IJKL কী", - keyboardPanSpeed: "কীবোর্ড প্যান গতি", - note: "নোট: নিবেদিত প্যান টুলের পাশাপাশি প্যান বিকল্পগুলি কাজ করে" + title: 'প্যান সেটিংস', + mousePanOptions: 'মাউস প্যান বিকল্প', + emptyAreaClickPan: 'খালি এলাকায় ক্লিক করুন এবং টেনে আনুন', + middleClickPan: 'মধ্য ক্লিক করুন এবং টেনে আনুন', + rightClickPan: 'ডান ক্লিক করুন এবং টেনে আনুন', + ctrlClickPan: 'Ctrl + ক্লিক করুন এবং টেনে আনুন', + altClickPan: 'Alt + ক্লিক করুন এবং টেনে আনুন', + keyboardPanOptions: 'কীবোর্ড প্যান বিকল্প', + arrowKeys: 'তীর কী', + wasdKeys: 'WASD কী', + ijklKeys: 'IJKL কী', + keyboardPanSpeed: 'কীবোর্ড প্যান গতি', + note: 'নোট: নিবেদিত প্যান টুলের পাশাপাশি প্যান বিকল্পগুলি কাজ করে' }, connector: { - title: "সংযোগকারী সেটিংস", - connectionMode: "সংযোগ তৈরির মোড", - clickMode: "ক্লিক মোড (প্রস্তাবিত)", - clickModeDesc: "প্রথম নোডে ক্লিক করুন, তারপর একটি সংযোগ তৈরি করতে দ্বিতীয় নোডে ক্লিক করুন", - dragMode: "টেনে আনার মোড", - dragModeDesc: "প্রথম নোড থেকে দ্বিতীয় নোডে ক্লিক করুন এবং টেনে আনুন", - note: "নোট: আপনি যেকোনো সময় এই সেটিং পরিবর্তন করতে পারেন। সংযোগকারী টুল সক্রিয় থাকলে নির্বাচিত মোড ব্যবহার করা হবে।" + title: 'সংযোগকারী সেটিংস', + connectionMode: 'সংযোগ তৈরির মোড', + clickMode: 'ক্লিক মোড (প্রস্তাবিত)', + clickModeDesc: + 'প্রথম নোডে ক্লিক করুন, তারপর একটি সংযোগ তৈরি করতে দ্বিতীয় নোডে ক্লিক করুন', + dragMode: 'টেনে আনার মোড', + dragModeDesc: 'প্রথম নোড থেকে দ্বিতীয় নোডে ক্লিক করুন এবং টেনে আনুন', + note: 'নোট: আপনি যেকোনো সময় এই সেটিং পরিবর্তন করতে পারেন। সংযোগকারী টুল সক্রিয় থাকলে নির্বাচিত মোড ব্যবহার করা হবে।' }, iconPacks: { - title: "আইকন প্যাক ব্যবস্থাপনা", - lazyLoading: "লেজি লোডিং সক্ষম করুন", - lazyLoadingDesc: "দ্রুত স্টার্টআপের জন্য চাহিদা অনুযায়ী আইকন প্যাক লোড করুন", - availablePacks: "উপলব্ধ আইকন প্যাক", - coreIsoflow: "Core Isoflow (সর্বদা লোড)", - alwaysEnabled: "সর্বদা সক্রিয়", - awsPack: "AWS আইকন", - gcpPack: "Google Cloud আইকন", - azurePack: "Azure আইকন", - kubernetesPack: "Kubernetes আইকন", - loading: "লোড হচ্ছে...", - loaded: "লোড করা হয়েছে", - notLoaded: "লোড করা হয়নি", - iconCount: "{count} আইকন", - lazyLoadingDisabledNote: "লেজি লোডিং নিষ্ক্রিয়। সমস্ত আইকন প্যাক স্টার্টআপে লোড করা হয়।", - note: "আইকন প্যাকগুলি আপনার প্রয়োজন অনুসারে সক্রিয় বা নিষ্ক্রিয় করা যেতে পারে। নিষ্ক্রিয় প্যাকগুলি মেমরি ব্যবহার হ্রাস করবে এবং কর্মক্ষমতা উন্নত করবে।" + title: 'আইকন প্যাক ব্যবস্থাপনা', + lazyLoading: 'লেজি লোডিং সক্ষম করুন', + lazyLoadingDesc: + 'দ্রুত স্টার্টআপের জন্য চাহিদা অনুযায়ী আইকন প্যাক লোড করুন', + availablePacks: 'উপলব্ধ আইকন প্যাক', + coreIsoflow: 'Core Isoflow (সর্বদা লোড)', + alwaysEnabled: 'সর্বদা সক্রিয়', + awsPack: 'AWS আইকন', + gcpPack: 'Google Cloud আইকন', + azurePack: 'Azure আইকন', + kubernetesPack: 'Kubernetes আইকন', + loading: 'লোড হচ্ছে...', + loaded: 'লোড করা হয়েছে', + notLoaded: 'লোড করা হয়নি', + iconCount: '{count} আইকন', + lazyLoadingDisabledNote: + 'লেজি লোডিং নিষ্ক্রিয়। সমস্ত আইকন প্যাক স্টার্টআপে লোড করা হয়।', + note: 'আইকন প্যাকগুলি আপনার প্রয়োজন অনুসারে সক্রিয় বা নিষ্ক্রিয় করা যেতে পারে। নিষ্ক্রিয় প্যাকগুলি মেমরি ব্যবহার হ্রাস করবে এবং কর্মক্ষমতা উন্নত করবে।' } }, lazyLoadingWelcome: { - title: "নতুন বৈশিষ্ট্য: লেজি লোডিং!", - message: "হেই! জনপ্রিয় চাহিদার পরে, আমরা আইকনগুলির লেজি লোডিং প্রয়োগ করেছি, তাই এখন আপনি যদি অ-মানক আইকন প্যাক সক্ষম করতে চান তবে আপনি 'কনফিগারেশন' বিভাগে সেগুলি সক্ষম করতে পারেন।", - configPath: "হ্যামবার্গার আইকনে ক্লিক করুন", - configPath2: "কনফিগারেশন অ্যাক্সেস করতে উপরের বাম দিকে।", - canDisable: "আপনি চাইলে এই আচরণ নিষ্ক্রিয় করতে পারেন।", - signature: "-Stan" + title: 'নতুন বৈশিষ্ট্য: লেজি লোডিং!', + message: + "হেই! জনপ্রিয় চাহিদার পরে, আমরা আইকনগুলির লেজি লোডিং প্রয়োগ করেছি, তাই এখন আপনি যদি অ-মানক আইকন প্যাক সক্ষম করতে চান তবে আপনি 'কনফিগারেশন' বিভাগে সেগুলি সক্ষম করতে পারেন।", + configPath: 'হ্যামবার্গার আইকনে ক্লিক করুন', + configPath2: 'কনফিগারেশন অ্যাক্সেস করতে উপরের বাম দিকে।', + canDisable: 'আপনি চাইলে এই আচরণ নিষ্ক্রিয় করতে পারেন।', + signature: '-Stan' } }; diff --git a/packages/fossflow-lib/src/i18n/en-US.ts b/packages/fossflow-lib/src/i18n/en-US.ts index d66c004f..13eb4d34 100644 --- a/packages/fossflow-lib/src/i18n/en-US.ts +++ b/packages/fossflow-lib/src/i18n/en-US.ts @@ -2,187 +2,196 @@ import { LocaleProps } from '../types/isoflowProps'; const locale: LocaleProps = { common: { - exampleText: "This is an example text" + exampleText: 'This is an example text' }, mainMenu: { - undo: "Undo", - redo: "Redo", - open: "Open", - exportJson: "Export as JSON", - exportCompactJson: "Export as Compact JSON", - exportImage: "Export as image", - clearCanvas: "Clear the canvas", - settings: "Settings", - gitHub: "GitHub" + undo: 'Undo', + redo: 'Redo', + open: 'Open', + exportJson: 'Export as JSON', + exportCompactJson: 'Export as Compact JSON', + exportImage: 'Export as image', + clearCanvas: 'Clear the canvas', + settings: 'Settings', + gitHub: 'GitHub' }, helpDialog: { - title: "Keyboard Shortcuts & Help", - close: "Close", - keyboardShortcuts: "Keyboard Shortcuts", - mouseInteractions: "Mouse Interactions", - action: "Action", - shortcut: "Shortcut", - method: "Method", - description: "Description", - note: "Note:", - noteContent: "Keyboard shortcuts are disabled when typing in input fields, text areas, or content-editable elements to prevent conflicts.", + title: 'Keyboard Shortcuts & Help', + close: 'Close', + keyboardShortcuts: 'Keyboard Shortcuts', + mouseInteractions: 'Mouse Interactions', + action: 'Action', + shortcut: 'Shortcut', + method: 'Method', + description: 'Description', + note: 'Note:', + noteContent: + 'Keyboard shortcuts are disabled when typing in input fields, text areas, or content-editable elements to prevent conflicts.', // Keyboard shortcuts - undoAction: "Undo", - undoDescription: "Undo the last action", - redoAction: "Redo", - redoDescription: "Redo the last undone action", - redoAltAction: "Redo (Alternative)", - redoAltDescription: "Alternative redo shortcut", - helpAction: "Help", - helpDescription: "Open help dialog with keyboard shortcuts", - zoomInAction: "Zoom In", - zoomInShortcut: "Mouse Wheel Up", - zoomInDescription: "Zoom in on the canvas", - zoomOutAction: "Zoom Out", - zoomOutShortcut: "Mouse Wheel Down", - zoomOutDescription: "Zoom out from the canvas", - panCanvasAction: "Pan Canvas", - panCanvasShortcut: "Left-click + Drag", - panCanvasDescription: "Pan the canvas when in Pan mode", - contextMenuAction: "Context Menu", - contextMenuShortcut: "Right-click", - contextMenuDescription: "Open context menu for items or empty space", + undoAction: 'Undo', + undoDescription: 'Undo the last action', + redoAction: 'Redo', + redoDescription: 'Redo the last undone action', + redoAltAction: 'Redo (Alternative)', + redoAltDescription: 'Alternative redo shortcut', + helpAction: 'Help', + helpDescription: 'Open help dialog with keyboard shortcuts', + zoomInAction: 'Zoom In', + zoomInShortcut: 'Mouse Wheel Up', + zoomInDescription: 'Zoom in on the canvas', + zoomOutAction: 'Zoom Out', + zoomOutShortcut: 'Mouse Wheel Down', + zoomOutDescription: 'Zoom out from the canvas', + panCanvasAction: 'Pan Canvas', + panCanvasShortcut: 'Left-click + Drag', + panCanvasDescription: 'Pan the canvas when in Pan mode', + contextMenuAction: 'Context Menu', + contextMenuShortcut: 'Right-click', + contextMenuDescription: 'Open context menu for items or empty space', // Mouse interactions - selectToolAction: "Select Tool", - selectToolShortcut: "Click Select button", - selectToolDescription: "Switch to selection mode", - panToolAction: "Pan Tool", - panToolShortcut: "Click Pan button", - panToolDescription: "Switch to pan mode for moving canvas", - addItemAction: "Add Item", - addItemShortcut: "Click Add item button", - addItemDescription: "Open icon picker to add new items", - drawRectangleAction: "Draw Rectangle", - drawRectangleShortcut: "Click Rectangle button", - drawRectangleDescription: "Switch to rectangle drawing mode", - createConnectorAction: "Create Connector", - createConnectorShortcut: "Click Connector button", - createConnectorDescription: "Switch to connector mode", - addTextAction: "Add Text", - addTextShortcut: "Click Text button", - addTextDescription: "Create a new text box" + selectToolAction: 'Select Tool', + selectToolShortcut: 'Click Select button', + selectToolDescription: 'Switch to selection mode', + panToolAction: 'Pan Tool', + panToolShortcut: 'Click Pan button', + panToolDescription: 'Switch to pan mode for moving canvas', + addItemAction: 'Add Item', + addItemShortcut: 'Click Add item button', + addItemDescription: 'Open icon picker to add new items', + drawRectangleAction: 'Draw Rectangle', + drawRectangleShortcut: 'Click Rectangle button', + drawRectangleDescription: 'Switch to rectangle drawing mode', + createConnectorAction: 'Create Connector', + createConnectorShortcut: 'Click Connector button', + createConnectorDescription: 'Switch to connector mode', + addTextAction: 'Add Text', + addTextShortcut: 'Click Text button', + addTextDescription: 'Create a new text box' }, connectorHintTooltip: { - tipCreatingConnectors: "Tip: Creating Connectors", - tipConnectorTools: "Tip: Connector Tools", - clickInstructionStart: "Click", - clickInstructionMiddle: "on the first node or point, then", - clickInstructionEnd: "on the second node or point to create a connection.", - nowClickTarget: "Now click on the target to complete the connection.", - dragStart: "Drag", - dragEnd: "from the first node to the second node to create a connection.", - rerouteStart: "To reroute a connector,", - rerouteMiddle: "left-click", - rerouteEnd: "on any point along the connector line and drag to create or move anchor points." + tipCreatingConnectors: 'Tip: Creating Connectors', + tipConnectorTools: 'Tip: Connector Tools', + clickInstructionStart: 'Click', + clickInstructionMiddle: 'on the first node or point, then', + clickInstructionEnd: 'on the second node or point to create a connection.', + nowClickTarget: 'Now click on the target to complete the connection.', + dragStart: 'Drag', + dragEnd: 'from the first node to the second node to create a connection.', + rerouteStart: 'To reroute a connector,', + rerouteMiddle: 'left-click', + rerouteEnd: + 'on any point along the connector line and drag to create or move anchor points.' }, lassoHintTooltip: { - tipLasso: "Tip: Lasso Selection", - tipFreehandLasso: "Tip: Freehand Lasso Selection", - lassoDragStart: "Click and drag", - lassoDragEnd: "to draw a rectangular selection box around items you want to select.", - freehandDragStart: "Click and drag", - freehandDragMiddle: "to draw a", - freehandDragEnd: "freeform shape", - freehandComplete: "around items. Release to select all items inside the shape.", - moveStart: "Once selected,", - moveMiddle: "click inside the selection", - moveEnd: "and drag to move all selected items together." + tipLasso: 'Tip: Lasso Selection', + tipFreehandLasso: 'Tip: Freehand Lasso Selection', + lassoDragStart: 'Click and drag', + lassoDragEnd: + 'to draw a rectangular selection box around items you want to select.', + freehandDragStart: 'Click and drag', + freehandDragMiddle: 'to draw a', + freehandDragEnd: 'freeform shape', + freehandComplete: + 'around items. Release to select all items inside the shape.', + moveStart: 'Once selected,', + moveMiddle: 'click inside the selection', + moveEnd: 'and drag to move all selected items together.' }, importHintTooltip: { - title: "Import Diagrams", - instructionStart: "To import diagrams, click the", - menuButton: "menu button", - instructionMiddle: "(☰) in the top left corner, then select", - openButton: "\"Open\"", - instructionEnd: "to load your diagram files." + title: 'Import Diagrams', + instructionStart: 'To import diagrams, click the', + menuButton: 'menu button', + instructionMiddle: '(☰) in the top left corner, then select', + openButton: '"Open"', + instructionEnd: 'to load your diagram files.' }, connectorRerouteTooltip: { - title: "Tip: Reroute Connectors", - instructionStart: "Once your connectors are placed you can reroute them as you please.", - instructionSelect: "Select the connector", - instructionMiddle: "first, then", - instructionClick: "click on the connector path", - instructionAnd: "and", - instructionDrag: "drag", - instructionEnd: "to change it!" + title: 'Tip: Reroute Connectors', + instructionStart: + 'Once your connectors are placed you can reroute them as you please.', + instructionSelect: 'Select the connector', + instructionMiddle: 'first, then', + instructionClick: 'click on the connector path', + instructionAnd: 'and', + instructionDrag: 'drag', + instructionEnd: 'to change it!' }, settings: { zoom: { - description: "Configure zoom behavior when using the mouse wheel.", - zoomToCursor: "Zoom to Cursor", - zoomToCursorDesc: "When enabled, zoom in/out centered on the mouse cursor position. When disabled, zoom is centered on the canvas." + description: 'Configure zoom behavior when using the mouse wheel.', + zoomToCursor: 'Zoom to Cursor', + zoomToCursorDesc: + 'When enabled, zoom in/out centered on the mouse cursor position. When disabled, zoom is centered on the canvas.' }, hotkeys: { - title: "Hotkey Settings", - profile: "Hotkey Profile", - profileQwerty: "QWERTY (Q, W, E, R, T, Y)", - profileSmnrct: "SMNRCT (S, M, N, R, C, T)", - profileNone: "No Hotkeys", - tool: "Tool", - hotkey: "Hotkey", - toolSelect: "Select", - toolPan: "Pan", - toolAddItem: "Add Item", - toolRectangle: "Rectangle", - toolConnector: "Connector", - toolText: "Text", - note: "Note: Hotkeys work when not typing in text fields" + title: 'Hotkey Settings', + profile: 'Hotkey Profile', + profileQwerty: 'QWERTY (Q, W, E, R, T, Y)', + profileSmnrct: 'SMNRCT (S, M, N, R, C, T)', + profileNone: 'No Hotkeys', + tool: 'Tool', + hotkey: 'Hotkey', + toolSelect: 'Select', + toolPan: 'Pan', + toolAddItem: 'Add Item', + toolRectangle: 'Rectangle', + toolConnector: 'Connector', + toolText: 'Text', + note: 'Note: Hotkeys work when not typing in text fields' }, pan: { - title: "Pan Settings", - mousePanOptions: "Mouse Pan Options", - emptyAreaClickPan: "Click and drag on empty area", - middleClickPan: "Middle click and drag", - rightClickPan: "Right click and drag", - ctrlClickPan: "Ctrl + click and drag", - altClickPan: "Alt + click and drag", - keyboardPanOptions: "Keyboard Pan Options", - arrowKeys: "Arrow keys", - wasdKeys: "WASD keys", - ijklKeys: "IJKL keys", - keyboardPanSpeed: "Keyboard Pan Speed", - note: "Note: Pan options work in addition to the dedicated Pan tool" + title: 'Pan Settings', + mousePanOptions: 'Mouse Pan Options', + emptyAreaClickPan: 'Click and drag on empty area', + middleClickPan: 'Middle click and drag', + rightClickPan: 'Right click and drag', + ctrlClickPan: 'Ctrl + click and drag', + altClickPan: 'Alt + click and drag', + keyboardPanOptions: 'Keyboard Pan Options', + arrowKeys: 'Arrow keys', + wasdKeys: 'WASD keys', + ijklKeys: 'IJKL keys', + keyboardPanSpeed: 'Keyboard Pan Speed', + note: 'Note: Pan options work in addition to the dedicated Pan tool' }, connector: { - title: "Connector Settings", - connectionMode: "Connection Creation Mode", - clickMode: "Click Mode (Recommended)", - clickModeDesc: "Click the first node, then click the second node to create a connection", - dragMode: "Drag Mode", - dragModeDesc: "Click and drag from the first node to the second node", - note: "Note: You can change this setting at any time. The selected mode will be used when the Connector tool is active." + title: 'Connector Settings', + connectionMode: 'Connection Creation Mode', + clickMode: 'Click Mode (Recommended)', + clickModeDesc: + 'Click the first node, then click the second node to create a connection', + dragMode: 'Drag Mode', + dragModeDesc: 'Click and drag from the first node to the second node', + note: 'Note: You can change this setting at any time. The selected mode will be used when the Connector tool is active.' }, iconPacks: { - title: "Icon Pack Management", - lazyLoading: "Enable Lazy Loading", - lazyLoadingDesc: "Load icon packs on demand for faster startup", - availablePacks: "Available Icon Packs", - coreIsoflow: "Core Isoflow (Always Loaded)", - alwaysEnabled: "Always enabled", - awsPack: "AWS Icons", - gcpPack: "Google Cloud Icons", - azurePack: "Azure Icons", - kubernetesPack: "Kubernetes Icons", - loading: "Loading...", - loaded: "Loaded", - notLoaded: "Not loaded", - iconCount: "{count} icons", - lazyLoadingDisabledNote: "Lazy loading is disabled. All icon packs are loaded at startup.", - note: "Icon packs can be enabled or disabled based on your needs. Disabled packs will reduce memory usage and improve performance." + title: 'Icon Pack Management', + lazyLoading: 'Enable Lazy Loading', + lazyLoadingDesc: 'Load icon packs on demand for faster startup', + availablePacks: 'Available Icon Packs', + coreIsoflow: 'Core Isoflow (Always Loaded)', + alwaysEnabled: 'Always enabled', + awsPack: 'AWS Icons', + gcpPack: 'Google Cloud Icons', + azurePack: 'Azure Icons', + kubernetesPack: 'Kubernetes Icons', + loading: 'Loading...', + loaded: 'Loaded', + notLoaded: 'Not loaded', + iconCount: '{count} icons', + lazyLoadingDisabledNote: + 'Lazy loading is disabled. All icon packs are loaded at startup.', + note: 'Icon packs can be enabled or disabled based on your needs. Disabled packs will reduce memory usage and improve performance.' } }, lazyLoadingWelcome: { - title: "New Feature: Lazy Loading!", - message: "Hey! After popular demand, we have implemented Lazy Loading of icons, so now if you want to enable non-standard icon packs you can enable them in the 'Configuration' section.", - configPath: "Click on the Hamburger icon", - configPath2: "in the top left to access Configuration.", - canDisable: "You can disable this behaviour if you wish.", - signature: "-Stan" + title: 'New Feature: Lazy Loading!', + message: + "Hey! After popular demand, we have implemented Lazy Loading of icons, so now if you want to enable non-standard icon packs you can enable them in the 'Configuration' section.", + configPath: 'Click on the Hamburger icon', + configPath2: 'in the top left to access Configuration.', + canDisable: 'You can disable this behaviour if you wish.', + signature: '-Stan' } }; diff --git a/packages/fossflow-lib/src/i18n/es-ES.ts b/packages/fossflow-lib/src/i18n/es-ES.ts index bcafc165..9aa5a065 100644 --- a/packages/fossflow-lib/src/i18n/es-ES.ts +++ b/packages/fossflow-lib/src/i18n/es-ES.ts @@ -2,187 +2,203 @@ import { LocaleProps } from '../types/isoflowProps'; const locale: LocaleProps = { common: { - exampleText: "Este es un texto de ejemplo" + exampleText: 'Este es un texto de ejemplo' }, mainMenu: { - undo: "Deshacer", - redo: "Rehacer", - open: "Abrir", - exportJson: "Exportar como JSON", - exportCompactJson: "Exportar como JSON compacto", - exportImage: "Exportar como imagen", - clearCanvas: "Limpiar el lienzo", - settings: "Configuración", - gitHub: "GitHub" + undo: 'Deshacer', + redo: 'Rehacer', + open: 'Abrir', + exportJson: 'Exportar como JSON', + exportCompactJson: 'Exportar como JSON compacto', + exportImage: 'Exportar como imagen', + clearCanvas: 'Limpiar el lienzo', + settings: 'Configuración', + gitHub: 'GitHub' }, helpDialog: { - title: "Atajos de teclado y ayuda", - close: "Cerrar", - keyboardShortcuts: "Atajos de teclado", - mouseInteractions: "Interacciones del ratón", - action: "Acción", - shortcut: "Atajo", - method: "Método", - description: "Descripción", - note: "Nota:", - noteContent: "Los atajos de teclado se desactivan al escribir en campos de entrada, áreas de texto o elementos editables para evitar conflictos.", + title: 'Atajos de teclado y ayuda', + close: 'Cerrar', + keyboardShortcuts: 'Atajos de teclado', + mouseInteractions: 'Interacciones del ratón', + action: 'Acción', + shortcut: 'Atajo', + method: 'Método', + description: 'Descripción', + note: 'Nota:', + noteContent: + 'Los atajos de teclado se desactivan al escribir en campos de entrada, áreas de texto o elementos editables para evitar conflictos.', // Keyboard shortcuts - undoAction: "Deshacer", - undoDescription: "Deshacer la última acción", - redoAction: "Rehacer", - redoDescription: "Rehacer la última acción deshecha", - redoAltAction: "Rehacer (Alternativo)", - redoAltDescription: "Atajo alternativo para rehacer", - helpAction: "Ayuda", - helpDescription: "Abrir diálogo de ayuda con atajos de teclado", - zoomInAction: "Acercar", - zoomInShortcut: "Rueda del ratón hacia arriba", - zoomInDescription: "Acercar en el lienzo", - zoomOutAction: "Alejar", - zoomOutShortcut: "Rueda del ratón hacia abajo", - zoomOutDescription: "Alejar del lienzo", - panCanvasAction: "Desplazar lienzo", - panCanvasShortcut: "Clic izquierdo + Arrastrar", - panCanvasDescription: "Desplazar el lienzo en modo desplazamiento", - contextMenuAction: "Menú contextual", - contextMenuShortcut: "Clic derecho", - contextMenuDescription: "Abrir menú contextual para elementos o espacio vacío", + undoAction: 'Deshacer', + undoDescription: 'Deshacer la última acción', + redoAction: 'Rehacer', + redoDescription: 'Rehacer la última acción deshecha', + redoAltAction: 'Rehacer (Alternativo)', + redoAltDescription: 'Atajo alternativo para rehacer', + helpAction: 'Ayuda', + helpDescription: 'Abrir diálogo de ayuda con atajos de teclado', + zoomInAction: 'Acercar', + zoomInShortcut: 'Rueda del ratón hacia arriba', + zoomInDescription: 'Acercar en el lienzo', + zoomOutAction: 'Alejar', + zoomOutShortcut: 'Rueda del ratón hacia abajo', + zoomOutDescription: 'Alejar del lienzo', + panCanvasAction: 'Desplazar lienzo', + panCanvasShortcut: 'Clic izquierdo + Arrastrar', + panCanvasDescription: 'Desplazar el lienzo en modo desplazamiento', + contextMenuAction: 'Menú contextual', + contextMenuShortcut: 'Clic derecho', + contextMenuDescription: + 'Abrir menú contextual para elementos o espacio vacío', // Mouse interactions - selectToolAction: "Herramienta de selección", - selectToolShortcut: "Clic en botón Seleccionar", - selectToolDescription: "Cambiar al modo de selección", - panToolAction: "Herramienta de desplazamiento", - panToolShortcut: "Clic en botón Desplazar", - panToolDescription: "Cambiar al modo de desplazamiento para mover el lienzo", - addItemAction: "Añadir elemento", - addItemShortcut: "Clic en botón Añadir elemento", - addItemDescription: "Abrir selector de iconos para añadir nuevos elementos", - drawRectangleAction: "Dibujar rectángulo", - drawRectangleShortcut: "Clic en botón Rectángulo", - drawRectangleDescription: "Cambiar al modo de dibujo de rectángulos", - createConnectorAction: "Crear conector", - createConnectorShortcut: "Clic en botón Conector", - createConnectorDescription: "Cambiar al modo de conector", - addTextAction: "Añadir texto", - addTextShortcut: "Clic en botón Texto", - addTextDescription: "Crear un nuevo cuadro de texto" + selectToolAction: 'Herramienta de selección', + selectToolShortcut: 'Clic en botón Seleccionar', + selectToolDescription: 'Cambiar al modo de selección', + panToolAction: 'Herramienta de desplazamiento', + panToolShortcut: 'Clic en botón Desplazar', + panToolDescription: + 'Cambiar al modo de desplazamiento para mover el lienzo', + addItemAction: 'Añadir elemento', + addItemShortcut: 'Clic en botón Añadir elemento', + addItemDescription: 'Abrir selector de iconos para añadir nuevos elementos', + drawRectangleAction: 'Dibujar rectángulo', + drawRectangleShortcut: 'Clic en botón Rectángulo', + drawRectangleDescription: 'Cambiar al modo de dibujo de rectángulos', + createConnectorAction: 'Crear conector', + createConnectorShortcut: 'Clic en botón Conector', + createConnectorDescription: 'Cambiar al modo de conector', + addTextAction: 'Añadir texto', + addTextShortcut: 'Clic en botón Texto', + addTextDescription: 'Crear un nuevo cuadro de texto' }, connectorHintTooltip: { - tipCreatingConnectors: "Consejo: Crear conectores", - tipConnectorTools: "Consejo: Herramientas de conectores", - clickInstructionStart: "Haz clic", - clickInstructionMiddle: "en el primer nodo o punto, luego", - clickInstructionEnd: "en el segundo nodo o punto para crear una conexión.", - nowClickTarget: "Ahora haz clic en el objetivo para completar la conexión.", - dragStart: "Arrastra", - dragEnd: "desde el primer nodo al segundo nodo para crear una conexión.", - rerouteStart: "Para cambiar la ruta de un conector,", - rerouteMiddle: "haz clic izquierdo", - rerouteEnd: "en cualquier punto a lo largo de la línea del conector y arrastra para crear o mover puntos de anclaje." + tipCreatingConnectors: 'Consejo: Crear conectores', + tipConnectorTools: 'Consejo: Herramientas de conectores', + clickInstructionStart: 'Haz clic', + clickInstructionMiddle: 'en el primer nodo o punto, luego', + clickInstructionEnd: 'en el segundo nodo o punto para crear una conexión.', + nowClickTarget: 'Ahora haz clic en el objetivo para completar la conexión.', + dragStart: 'Arrastra', + dragEnd: 'desde el primer nodo al segundo nodo para crear una conexión.', + rerouteStart: 'Para cambiar la ruta de un conector,', + rerouteMiddle: 'haz clic izquierdo', + rerouteEnd: + 'en cualquier punto a lo largo de la línea del conector y arrastra para crear o mover puntos de anclaje.' }, lassoHintTooltip: { - tipLasso: "Consejo: Selección de lazo", - tipFreehandLasso: "Consejo: Selección de lazo libre", - lassoDragStart: "Haz clic y arrastra", - lassoDragEnd: "para dibujar un cuadro de selección rectangular alrededor de los elementos que deseas seleccionar.", - freehandDragStart: "Haz clic y arrastra", - freehandDragMiddle: "para dibujar una", - freehandDragEnd: "forma libre", - freehandComplete: "alrededor de los elementos. Suelta para seleccionar todos los elementos dentro de la forma.", - moveStart: "Una vez seleccionados,", - moveMiddle: "haz clic dentro de la selección", - moveEnd: "y arrastra para mover todos los elementos seleccionados juntos." + tipLasso: 'Consejo: Selección de lazo', + tipFreehandLasso: 'Consejo: Selección de lazo libre', + lassoDragStart: 'Haz clic y arrastra', + lassoDragEnd: + 'para dibujar un cuadro de selección rectangular alrededor de los elementos que deseas seleccionar.', + freehandDragStart: 'Haz clic y arrastra', + freehandDragMiddle: 'para dibujar una', + freehandDragEnd: 'forma libre', + freehandComplete: + 'alrededor de los elementos. Suelta para seleccionar todos los elementos dentro de la forma.', + moveStart: 'Una vez seleccionados,', + moveMiddle: 'haz clic dentro de la selección', + moveEnd: 'y arrastra para mover todos los elementos seleccionados juntos.' }, importHintTooltip: { - title: "Importar diagramas", - instructionStart: "Para importar diagramas, haz clic en el", - menuButton: "botón de menú", - instructionMiddle: "(☰) en la esquina superior izquierda, luego selecciona", - openButton: "\"Abrir\"", - instructionEnd: "para cargar tus archivos de diagrama." + title: 'Importar diagramas', + instructionStart: 'Para importar diagramas, haz clic en el', + menuButton: 'botón de menú', + instructionMiddle: + '(☰) en la esquina superior izquierda, luego selecciona', + openButton: '"Abrir"', + instructionEnd: 'para cargar tus archivos de diagrama.' }, connectorRerouteTooltip: { - title: "Consejo: Cambiar ruta de conectores", - instructionStart: "Una vez que tus conectores estén colocados, puedes cambiar su ruta como desees.", - instructionSelect: "Selecciona el conector", - instructionMiddle: "primero, luego", - instructionClick: "haz clic en la ruta del conector", - instructionAnd: "y", - instructionDrag: "arrastra", - instructionEnd: "para cambiarlo!" + title: 'Consejo: Cambiar ruta de conectores', + instructionStart: + 'Una vez que tus conectores estén colocados, puedes cambiar su ruta como desees.', + instructionSelect: 'Selecciona el conector', + instructionMiddle: 'primero, luego', + instructionClick: 'haz clic en la ruta del conector', + instructionAnd: 'y', + instructionDrag: 'arrastra', + instructionEnd: 'para cambiarlo!' }, settings: { zoom: { - description: "Configura el comportamiento del zoom al usar la rueda del ratón.", - zoomToCursor: "Zoom al cursor", - zoomToCursorDesc: "Cuando está habilitado, el zoom se centra en la posición del cursor del ratón. Cuando está deshabilitado, el zoom se centra en el lienzo." + description: + 'Configura el comportamiento del zoom al usar la rueda del ratón.', + zoomToCursor: 'Zoom al cursor', + zoomToCursorDesc: + 'Cuando está habilitado, el zoom se centra en la posición del cursor del ratón. Cuando está deshabilitado, el zoom se centra en el lienzo.' }, hotkeys: { - title: "Configuración de atajos", - profile: "Perfil de atajos", - profileQwerty: "QWERTY (Q, W, E, R, T, Y)", - profileSmnrct: "SMNRCT (S, M, N, R, C, T)", - profileNone: "Sin atajos", - tool: "Herramienta", - hotkey: "Atajo", - toolSelect: "Seleccionar", - toolPan: "Desplazar", - toolAddItem: "Añadir elemento", - toolRectangle: "Rectángulo", - toolConnector: "Conector", - toolText: "Texto", - note: "Nota: Los atajos funcionan cuando no estás escribiendo en campos de texto" + title: 'Configuración de atajos', + profile: 'Perfil de atajos', + profileQwerty: 'QWERTY (Q, W, E, R, T, Y)', + profileSmnrct: 'SMNRCT (S, M, N, R, C, T)', + profileNone: 'Sin atajos', + tool: 'Herramienta', + hotkey: 'Atajo', + toolSelect: 'Seleccionar', + toolPan: 'Desplazar', + toolAddItem: 'Añadir elemento', + toolRectangle: 'Rectángulo', + toolConnector: 'Conector', + toolText: 'Texto', + note: 'Nota: Los atajos funcionan cuando no estás escribiendo en campos de texto' }, pan: { - title: "Configuración de desplazamiento", - mousePanOptions: "Opciones de desplazamiento con ratón", - emptyAreaClickPan: "Clic y arrastrar en área vacía", - middleClickPan: "Clic central y arrastrar", - rightClickPan: "Clic derecho y arrastrar", - ctrlClickPan: "Ctrl + clic y arrastrar", - altClickPan: "Alt + clic y arrastrar", - keyboardPanOptions: "Opciones de desplazamiento con teclado", - arrowKeys: "Teclas de flechas", - wasdKeys: "Teclas WASD", - ijklKeys: "Teclas IJKL", - keyboardPanSpeed: "Velocidad de desplazamiento con teclado", - note: "Nota: Las opciones de desplazamiento funcionan además de la herramienta de desplazamiento dedicada" + title: 'Configuración de desplazamiento', + mousePanOptions: 'Opciones de desplazamiento con ratón', + emptyAreaClickPan: 'Clic y arrastrar en área vacía', + middleClickPan: 'Clic central y arrastrar', + rightClickPan: 'Clic derecho y arrastrar', + ctrlClickPan: 'Ctrl + clic y arrastrar', + altClickPan: 'Alt + clic y arrastrar', + keyboardPanOptions: 'Opciones de desplazamiento con teclado', + arrowKeys: 'Teclas de flechas', + wasdKeys: 'Teclas WASD', + ijklKeys: 'Teclas IJKL', + keyboardPanSpeed: 'Velocidad de desplazamiento con teclado', + note: 'Nota: Las opciones de desplazamiento funcionan además de la herramienta de desplazamiento dedicada' }, connector: { - title: "Configuración de conectores", - connectionMode: "Modo de creación de conexiones", - clickMode: "Modo clic (Recomendado)", - clickModeDesc: "Haz clic en el primer nodo, luego haz clic en el segundo nodo para crear una conexión", - dragMode: "Modo arrastrar", - dragModeDesc: "Haz clic y arrastra desde el primer nodo hasta el segundo nodo", - note: "Nota: Puedes cambiar esta configuración en cualquier momento. El modo seleccionado se usará cuando la herramienta de conector esté activa." + title: 'Configuración de conectores', + connectionMode: 'Modo de creación de conexiones', + clickMode: 'Modo clic (Recomendado)', + clickModeDesc: + 'Haz clic en el primer nodo, luego haz clic en el segundo nodo para crear una conexión', + dragMode: 'Modo arrastrar', + dragModeDesc: + 'Haz clic y arrastra desde el primer nodo hasta el segundo nodo', + note: 'Nota: Puedes cambiar esta configuración en cualquier momento. El modo seleccionado se usará cuando la herramienta de conector esté activa.' }, iconPacks: { - title: "Gestión de Paquetes de Iconos", - lazyLoading: "Activar Carga Diferida", - lazyLoadingDesc: "Cargar paquetes de iconos bajo demanda para un inicio más rápido", - availablePacks: "Paquetes de Iconos Disponibles", - coreIsoflow: "Core Isoflow (Siempre Cargado)", - alwaysEnabled: "Siempre activado", - awsPack: "Iconos AWS", - gcpPack: "Iconos Google Cloud", - azurePack: "Iconos Azure", - kubernetesPack: "Iconos Kubernetes", - loading: "Cargando...", - loaded: "Cargado", - notLoaded: "No cargado", - iconCount: "{count} iconos", - lazyLoadingDisabledNote: "La carga diferida está desactivada. Todos los paquetes de iconos se cargan al iniciar.", - note: "Los paquetes de iconos se pueden activar o desactivar según tus necesidades. Los paquetes desactivados reducirán el uso de memoria y mejorarán el rendimiento." + title: 'Gestión de Paquetes de Iconos', + lazyLoading: 'Activar Carga Diferida', + lazyLoadingDesc: + 'Cargar paquetes de iconos bajo demanda para un inicio más rápido', + availablePacks: 'Paquetes de Iconos Disponibles', + coreIsoflow: 'Core Isoflow (Siempre Cargado)', + alwaysEnabled: 'Siempre activado', + awsPack: 'Iconos AWS', + gcpPack: 'Iconos Google Cloud', + azurePack: 'Iconos Azure', + kubernetesPack: 'Iconos Kubernetes', + loading: 'Cargando...', + loaded: 'Cargado', + notLoaded: 'No cargado', + iconCount: '{count} iconos', + lazyLoadingDisabledNote: + 'La carga diferida está desactivada. Todos los paquetes de iconos se cargan al iniciar.', + note: 'Los paquetes de iconos se pueden activar o desactivar según tus necesidades. Los paquetes desactivados reducirán el uso de memoria y mejorarán el rendimiento.' } }, lazyLoadingWelcome: { - title: "Nueva Funcionalidad: ¡Carga Diferida!", - message: "¡Hola! Después de la demanda popular, hemos implementado la Carga Diferida de iconos, así que ahora si quieres activar paquetes de iconos no estándar puedes activarlos en la sección 'Configuración'.", - configPath: "Haz clic en el icono de Hamburguesa", - configPath2: "en la esquina superior izquierda para acceder a la Configuración.", - canDisable: "Puedes desactivar este comportamiento si lo deseas.", - signature: "-Stan" + title: 'Nueva Funcionalidad: ¡Carga Diferida!', + message: + "¡Hola! Después de la demanda popular, hemos implementado la Carga Diferida de iconos, así que ahora si quieres activar paquetes de iconos no estándar puedes activarlos en la sección 'Configuración'.", + configPath: 'Haz clic en el icono de Hamburguesa', + configPath2: + 'en la esquina superior izquierda para acceder a la Configuración.', + canDisable: 'Puedes desactivar este comportamiento si lo deseas.', + signature: '-Stan' } }; diff --git a/packages/fossflow-lib/src/i18n/fr-FR.ts b/packages/fossflow-lib/src/i18n/fr-FR.ts index e1f35878..6be8a236 100644 --- a/packages/fossflow-lib/src/i18n/fr-FR.ts +++ b/packages/fossflow-lib/src/i18n/fr-FR.ts @@ -5,184 +5,200 @@ const locale: LocaleProps = { exampleText: "Ceci est un texte d'exemple" }, mainMenu: { - undo: "Annuler", - redo: "Refaire", - open: "Ouvrir", - exportJson: "Exporter en JSON", - exportCompactJson: "Exporter en JSON compact", - exportImage: "Exporter en image", - clearCanvas: "Effacer le canevas", - settings: "Paramètres", - gitHub: "GitHub" + undo: 'Annuler', + redo: 'Refaire', + open: 'Ouvrir', + exportJson: 'Exporter en JSON', + exportCompactJson: 'Exporter en JSON compact', + exportImage: 'Exporter en image', + clearCanvas: 'Effacer le canevas', + settings: 'Paramètres', + gitHub: 'GitHub' }, helpDialog: { - title: "Raccourcis clavier et aide", - close: "Fermer", - keyboardShortcuts: "Raccourcis clavier", - mouseInteractions: "Interactions de la souris", - action: "Action", - shortcut: "Raccourci", - method: "Méthode", - description: "Description", - note: "Remarque :", - noteContent: "Les raccourcis clavier sont désactivés lors de la saisie dans les champs de saisie, les zones de texte ou les éléments modifiables pour éviter les conflits.", + title: 'Raccourcis clavier et aide', + close: 'Fermer', + keyboardShortcuts: 'Raccourcis clavier', + mouseInteractions: 'Interactions de la souris', + action: 'Action', + shortcut: 'Raccourci', + method: 'Méthode', + description: 'Description', + note: 'Remarque :', + noteContent: + 'Les raccourcis clavier sont désactivés lors de la saisie dans les champs de saisie, les zones de texte ou les éléments modifiables pour éviter les conflits.', // Keyboard shortcuts - undoAction: "Annuler", - undoDescription: "Annuler la dernière action", - redoAction: "Refaire", - redoDescription: "Refaire la dernière action annulée", - redoAltAction: "Refaire (Alternatif)", - redoAltDescription: "Raccourci alternatif pour refaire", - helpAction: "Aide", - helpDescription: "Ouvrir la boîte de dialogue d'aide avec les raccourcis clavier", - zoomInAction: "Zoom avant", - zoomInShortcut: "Molette de la souris vers le haut", - zoomInDescription: "Effectuer un zoom avant sur le canevas", - zoomOutAction: "Zoom arrière", - zoomOutShortcut: "Molette de la souris vers le bas", - zoomOutDescription: "Effectuer un zoom arrière sur le canevas", - panCanvasAction: "Déplacer le canevas", - panCanvasShortcut: "Clic gauche + Glisser", - panCanvasDescription: "Déplacer le canevas en mode déplacement", - contextMenuAction: "Menu contextuel", - contextMenuShortcut: "Clic droit", - contextMenuDescription: "Ouvrir le menu contextuel pour les éléments ou l'espace vide", + undoAction: 'Annuler', + undoDescription: 'Annuler la dernière action', + redoAction: 'Refaire', + redoDescription: 'Refaire la dernière action annulée', + redoAltAction: 'Refaire (Alternatif)', + redoAltDescription: 'Raccourci alternatif pour refaire', + helpAction: 'Aide', + helpDescription: + "Ouvrir la boîte de dialogue d'aide avec les raccourcis clavier", + zoomInAction: 'Zoom avant', + zoomInShortcut: 'Molette de la souris vers le haut', + zoomInDescription: 'Effectuer un zoom avant sur le canevas', + zoomOutAction: 'Zoom arrière', + zoomOutShortcut: 'Molette de la souris vers le bas', + zoomOutDescription: 'Effectuer un zoom arrière sur le canevas', + panCanvasAction: 'Déplacer le canevas', + panCanvasShortcut: 'Clic gauche + Glisser', + panCanvasDescription: 'Déplacer le canevas en mode déplacement', + contextMenuAction: 'Menu contextuel', + contextMenuShortcut: 'Clic droit', + contextMenuDescription: + "Ouvrir le menu contextuel pour les éléments ou l'espace vide", // Mouse interactions - selectToolAction: "Outil de sélection", - selectToolShortcut: "Cliquer sur le bouton Sélectionner", - selectToolDescription: "Passer en mode sélection", - panToolAction: "Outil de déplacement", - panToolShortcut: "Cliquer sur le bouton Déplacer", - panToolDescription: "Passer en mode déplacement pour déplacer le canevas", - addItemAction: "Ajouter un élément", - addItemShortcut: "Cliquer sur le bouton Ajouter un élément", - addItemDescription: "Ouvrir le sélecteur d'icônes pour ajouter de nouveaux éléments", - drawRectangleAction: "Dessiner un rectangle", - drawRectangleShortcut: "Cliquer sur le bouton Rectangle", - drawRectangleDescription: "Passer en mode dessin de rectangles", - createConnectorAction: "Créer un connecteur", - createConnectorShortcut: "Cliquer sur le bouton Connecteur", - createConnectorDescription: "Passer en mode connecteur", - addTextAction: "Ajouter du texte", - addTextShortcut: "Cliquer sur le bouton Texte", - addTextDescription: "Créer une nouvelle zone de texte" + selectToolAction: 'Outil de sélection', + selectToolShortcut: 'Cliquer sur le bouton Sélectionner', + selectToolDescription: 'Passer en mode sélection', + panToolAction: 'Outil de déplacement', + panToolShortcut: 'Cliquer sur le bouton Déplacer', + panToolDescription: 'Passer en mode déplacement pour déplacer le canevas', + addItemAction: 'Ajouter un élément', + addItemShortcut: 'Cliquer sur le bouton Ajouter un élément', + addItemDescription: + "Ouvrir le sélecteur d'icônes pour ajouter de nouveaux éléments", + drawRectangleAction: 'Dessiner un rectangle', + drawRectangleShortcut: 'Cliquer sur le bouton Rectangle', + drawRectangleDescription: 'Passer en mode dessin de rectangles', + createConnectorAction: 'Créer un connecteur', + createConnectorShortcut: 'Cliquer sur le bouton Connecteur', + createConnectorDescription: 'Passer en mode connecteur', + addTextAction: 'Ajouter du texte', + addTextShortcut: 'Cliquer sur le bouton Texte', + addTextDescription: 'Créer une nouvelle zone de texte' }, connectorHintTooltip: { - tipCreatingConnectors: "Astuce : Créer des connecteurs", - tipConnectorTools: "Astuce : Outils de connecteurs", - clickInstructionStart: "Cliquez", - clickInstructionMiddle: "sur le premier nœud ou point, puis", - clickInstructionEnd: "sur le deuxième nœud ou point pour créer une connexion.", - nowClickTarget: "Cliquez maintenant sur la cible pour terminer la connexion.", - dragStart: "Glissez", - dragEnd: "du premier nœud au deuxième nœud pour créer une connexion.", - rerouteStart: "Pour réacheminer un connecteur,", - rerouteMiddle: "cliquez avec le bouton gauche", - rerouteEnd: "sur n'importe quel point le long de la ligne du connecteur et glissez pour créer ou déplacer des points d'ancrage." + tipCreatingConnectors: 'Astuce : Créer des connecteurs', + tipConnectorTools: 'Astuce : Outils de connecteurs', + clickInstructionStart: 'Cliquez', + clickInstructionMiddle: 'sur le premier nœud ou point, puis', + clickInstructionEnd: + 'sur le deuxième nœud ou point pour créer une connexion.', + nowClickTarget: + 'Cliquez maintenant sur la cible pour terminer la connexion.', + dragStart: 'Glissez', + dragEnd: 'du premier nœud au deuxième nœud pour créer une connexion.', + rerouteStart: 'Pour réacheminer un connecteur,', + rerouteMiddle: 'cliquez avec le bouton gauche', + rerouteEnd: + "sur n'importe quel point le long de la ligne du connecteur et glissez pour créer ou déplacer des points d'ancrage." }, lassoHintTooltip: { - tipLasso: "Astuce : Sélection au lasso", - tipFreehandLasso: "Astuce : Sélection au lasso libre", - lassoDragStart: "Cliquez et glissez", - lassoDragEnd: "pour dessiner une zone de sélection rectangulaire autour des éléments que vous souhaitez sélectionner.", - freehandDragStart: "Cliquez et glissez", - freehandDragMiddle: "pour dessiner une", - freehandDragEnd: "forme libre", - freehandComplete: "autour des éléments. Relâchez pour sélectionner tous les éléments à l'intérieur de la forme.", - moveStart: "Une fois sélectionnés,", + tipLasso: 'Astuce : Sélection au lasso', + tipFreehandLasso: 'Astuce : Sélection au lasso libre', + lassoDragStart: 'Cliquez et glissez', + lassoDragEnd: + 'pour dessiner une zone de sélection rectangulaire autour des éléments que vous souhaitez sélectionner.', + freehandDragStart: 'Cliquez et glissez', + freehandDragMiddle: 'pour dessiner une', + freehandDragEnd: 'forme libre', + freehandComplete: + "autour des éléments. Relâchez pour sélectionner tous les éléments à l'intérieur de la forme.", + moveStart: 'Une fois sélectionnés,', moveMiddle: "cliquez à l'intérieur de la sélection", - moveEnd: "et glissez pour déplacer tous les éléments sélectionnés ensemble." + moveEnd: 'et glissez pour déplacer tous les éléments sélectionnés ensemble.' }, importHintTooltip: { - title: "Importer des diagrammes", - instructionStart: "Pour importer des diagrammes, cliquez sur le", - menuButton: "bouton de menu", - instructionMiddle: "(☰) dans le coin supérieur gauche, puis sélectionnez", - openButton: "\"Ouvrir\"", - instructionEnd: "pour charger vos fichiers de diagramme." + title: 'Importer des diagrammes', + instructionStart: 'Pour importer des diagrammes, cliquez sur le', + menuButton: 'bouton de menu', + instructionMiddle: '(☰) dans le coin supérieur gauche, puis sélectionnez', + openButton: '"Ouvrir"', + instructionEnd: 'pour charger vos fichiers de diagramme.' }, connectorRerouteTooltip: { - title: "Astuce : Réacheminer les connecteurs", - instructionStart: "Une fois vos connecteurs placés, vous pouvez les réacheminer comme vous le souhaitez.", - instructionSelect: "Sélectionnez le connecteur", + title: 'Astuce : Réacheminer les connecteurs', + instructionStart: + 'Une fois vos connecteurs placés, vous pouvez les réacheminer comme vous le souhaitez.', + instructionSelect: 'Sélectionnez le connecteur', instructionMiddle: "d'abord, puis", - instructionClick: "cliquez sur le chemin du connecteur", - instructionAnd: "et", - instructionDrag: "glissez", - instructionEnd: "pour le modifier !" + instructionClick: 'cliquez sur le chemin du connecteur', + instructionAnd: 'et', + instructionDrag: 'glissez', + instructionEnd: 'pour le modifier !' }, settings: { zoom: { - description: "Configurer le comportement du zoom lors de l'utilisation de la molette de la souris.", - zoomToCursor: "Zoom sur le curseur", - zoomToCursorDesc: "Lorsqu'il est activé, le zoom est centré sur la position du curseur de la souris. Lorsqu'il est désactivé, le zoom est centré sur le canevas." + description: + "Configurer le comportement du zoom lors de l'utilisation de la molette de la souris.", + zoomToCursor: 'Zoom sur le curseur', + zoomToCursorDesc: + "Lorsqu'il est activé, le zoom est centré sur la position du curseur de la souris. Lorsqu'il est désactivé, le zoom est centré sur le canevas." }, hotkeys: { - title: "Paramètres des raccourcis", - profile: "Profil de raccourcis", - profileQwerty: "QWERTY (Q, W, E, R, T, Y)", - profileSmnrct: "SMNRCT (S, M, N, R, C, T)", - profileNone: "Aucun raccourci", - tool: "Outil", - hotkey: "Raccourci", - toolSelect: "Sélectionner", - toolPan: "Déplacer", - toolAddItem: "Ajouter un élément", - toolRectangle: "Rectangle", - toolConnector: "Connecteur", - toolText: "Texte", - note: "Remarque : Les raccourcis fonctionnent lorsque vous ne tapez pas dans des champs de texte" + title: 'Paramètres des raccourcis', + profile: 'Profil de raccourcis', + profileQwerty: 'QWERTY (Q, W, E, R, T, Y)', + profileSmnrct: 'SMNRCT (S, M, N, R, C, T)', + profileNone: 'Aucun raccourci', + tool: 'Outil', + hotkey: 'Raccourci', + toolSelect: 'Sélectionner', + toolPan: 'Déplacer', + toolAddItem: 'Ajouter un élément', + toolRectangle: 'Rectangle', + toolConnector: 'Connecteur', + toolText: 'Texte', + note: 'Remarque : Les raccourcis fonctionnent lorsque vous ne tapez pas dans des champs de texte' }, pan: { - title: "Paramètres de déplacement", - mousePanOptions: "Options de déplacement à la souris", - emptyAreaClickPan: "Cliquer et glisser sur une zone vide", - middleClickPan: "Clic du milieu et glisser", - rightClickPan: "Clic droit et glisser", - ctrlClickPan: "Ctrl + clic et glisser", - altClickPan: "Alt + clic et glisser", - keyboardPanOptions: "Options de déplacement au clavier", - arrowKeys: "Touches fléchées", - wasdKeys: "Touches WASD", - ijklKeys: "Touches IJKL", - keyboardPanSpeed: "Vitesse de déplacement au clavier", + title: 'Paramètres de déplacement', + mousePanOptions: 'Options de déplacement à la souris', + emptyAreaClickPan: 'Cliquer et glisser sur une zone vide', + middleClickPan: 'Clic du milieu et glisser', + rightClickPan: 'Clic droit et glisser', + ctrlClickPan: 'Ctrl + clic et glisser', + altClickPan: 'Alt + clic et glisser', + keyboardPanOptions: 'Options de déplacement au clavier', + arrowKeys: 'Touches fléchées', + wasdKeys: 'Touches WASD', + ijklKeys: 'Touches IJKL', + keyboardPanSpeed: 'Vitesse de déplacement au clavier', note: "Remarque : Les options de déplacement fonctionnent en plus de l'outil de déplacement dédié" }, connector: { - title: "Paramètres des connecteurs", - connectionMode: "Mode de création de connexion", - clickMode: "Mode clic (Recommandé)", - clickModeDesc: "Cliquez sur le premier nœud, puis cliquez sur le deuxième nœud pour créer une connexion", - dragMode: "Mode glisser", - dragModeDesc: "Cliquez et glissez du premier nœud au deuxième nœud", + title: 'Paramètres des connecteurs', + connectionMode: 'Mode de création de connexion', + clickMode: 'Mode clic (Recommandé)', + clickModeDesc: + 'Cliquez sur le premier nœud, puis cliquez sur le deuxième nœud pour créer une connexion', + dragMode: 'Mode glisser', + dragModeDesc: 'Cliquez et glissez du premier nœud au deuxième nœud', note: "Remarque : Vous pouvez modifier ce paramètre à tout moment. Le mode sélectionné sera utilisé lorsque l'outil de connecteur est actif." }, iconPacks: { title: "Gestion des Packs d'Icônes", - lazyLoading: "Activer le Chargement Paresseux", - lazyLoadingDesc: "Charger les packs d'icônes à la demande pour un démarrage plus rapide", + lazyLoading: 'Activer le Chargement Paresseux', + lazyLoadingDesc: + "Charger les packs d'icônes à la demande pour un démarrage plus rapide", availablePacks: "Packs d'Icônes Disponibles", - coreIsoflow: "Core Isoflow (Toujours Chargé)", - alwaysEnabled: "Toujours activé", - awsPack: "Icônes AWS", - gcpPack: "Icônes Google Cloud", - azurePack: "Icônes Azure", - kubernetesPack: "Icônes Kubernetes", - loading: "Chargement...", - loaded: "Chargé", - notLoaded: "Non chargé", - iconCount: "{count} icônes", - lazyLoadingDisabledNote: "Le chargement paresseux est désactivé. Tous les packs d'icônes sont chargés au démarrage.", + coreIsoflow: 'Core Isoflow (Toujours Chargé)', + alwaysEnabled: 'Toujours activé', + awsPack: 'Icônes AWS', + gcpPack: 'Icônes Google Cloud', + azurePack: 'Icônes Azure', + kubernetesPack: 'Icônes Kubernetes', + loading: 'Chargement...', + loaded: 'Chargé', + notLoaded: 'Non chargé', + iconCount: '{count} icônes', + lazyLoadingDisabledNote: + "Le chargement paresseux est désactivé. Tous les packs d'icônes sont chargés au démarrage.", note: "Les packs d'icônes peuvent être activés ou désactivés selon vos besoins. Les packs désactivés réduiront l'utilisation de la mémoire et amélioreront les performances." } }, lazyLoadingWelcome: { - title: "Nouvelle Fonctionnalité : Chargement Paresseux !", - message: "Salut ! Suite à une forte demande, nous avons implémenté le Chargement Paresseux des icônes, donc maintenant si vous voulez activer des packs d'icônes non standard, vous pouvez les activer dans la section 'Configuration'.", + title: 'Nouvelle Fonctionnalité : Chargement Paresseux !', + message: + "Salut ! Suite à une forte demande, nous avons implémenté le Chargement Paresseux des icônes, donc maintenant si vous voulez activer des packs d'icônes non standard, vous pouvez les activer dans la section 'Configuration'.", configPath: "Cliquez sur l'icône Hamburger", - configPath2: "en haut à gauche pour accéder à la Configuration.", - canDisable: "Vous pouvez désactiver ce comportement si vous le souhaitez.", - signature: "-Stan" + configPath2: 'en haut à gauche pour accéder à la Configuration.', + canDisable: 'Vous pouvez désactiver ce comportement si vous le souhaitez.', + signature: '-Stan' } }; diff --git a/packages/fossflow-lib/src/i18n/hi-IN.ts b/packages/fossflow-lib/src/i18n/hi-IN.ts index a8f4930f..607c4aff 100644 --- a/packages/fossflow-lib/src/i18n/hi-IN.ts +++ b/packages/fossflow-lib/src/i18n/hi-IN.ts @@ -2,187 +2,198 @@ import { LocaleProps } from '../types/isoflowProps'; const locale: LocaleProps = { common: { - exampleText: "यह एक उदाहरण पाठ है" + exampleText: 'यह एक उदाहरण पाठ है' }, mainMenu: { - undo: "पूर्ववत करें", - redo: "फिर से करें", - open: "खोलें", - exportJson: "JSON के रूप में निर्यात करें", - exportCompactJson: "संक्षिप्त JSON के रूप में निर्यात करें", - exportImage: "छवि के रूप में निर्यात करें", - clearCanvas: "कैनवास साफ़ करें", - settings: "सेटिंग्स", - gitHub: "GitHub" + undo: 'पूर्ववत करें', + redo: 'फिर से करें', + open: 'खोलें', + exportJson: 'JSON के रूप में निर्यात करें', + exportCompactJson: 'संक्षिप्त JSON के रूप में निर्यात करें', + exportImage: 'छवि के रूप में निर्यात करें', + clearCanvas: 'कैनवास साफ़ करें', + settings: 'सेटिंग्स', + gitHub: 'GitHub' }, helpDialog: { - title: "कीबोर्ड शॉर्टकट और सहायता", - close: "बंद करें", - keyboardShortcuts: "कीबोर्ड शॉर्टकट", - mouseInteractions: "माउस इंटरैक्शन", - action: "क्रिया", - shortcut: "शॉर्टकट", - method: "विधि", - description: "विवरण", - note: "नोट:", - noteContent: "टकराव से बचने के लिए इनपुट फ़ील्ड, टेक्स्ट एरिया या संपादन योग्य तत्वों में टाइप करते समय कीबोर्ड शॉर्टकट अक्षम हो जाते हैं।", + title: 'कीबोर्ड शॉर्टकट और सहायता', + close: 'बंद करें', + keyboardShortcuts: 'कीबोर्ड शॉर्टकट', + mouseInteractions: 'माउस इंटरैक्शन', + action: 'क्रिया', + shortcut: 'शॉर्टकट', + method: 'विधि', + description: 'विवरण', + note: 'नोट:', + noteContent: + 'टकराव से बचने के लिए इनपुट फ़ील्ड, टेक्स्ट एरिया या संपादन योग्य तत्वों में टाइप करते समय कीबोर्ड शॉर्टकट अक्षम हो जाते हैं।', // Keyboard shortcuts - undoAction: "पूर्ववत करें", - undoDescription: "अंतिम क्रिया को पूर्ववत करें", - redoAction: "फिर से करें", - redoDescription: "अंतिम पूर्ववत की गई क्रिया को फिर से करें", - redoAltAction: "फिर से करें (वैकल्पिक)", - redoAltDescription: "फिर से करने के लिए वैकल्पिक शॉर्टकट", - helpAction: "सहायता", - helpDescription: "कीबोर्ड शॉर्टकट के साथ सहायता संवाद खोलें", - zoomInAction: "ज़ूम इन करें", - zoomInShortcut: "माउस व्हील ऊपर", - zoomInDescription: "कैनवास पर ज़ूम इन करें", - zoomOutAction: "ज़ूम आउट करें", - zoomOutShortcut: "माउस व्हील नीचे", - zoomOutDescription: "कैनवास से ज़ूम आउट करें", - panCanvasAction: "कैनवास को पैन करें", - panCanvasShortcut: "बाएँ-क्लिक + ड्रैग", - panCanvasDescription: "पैन मोड में कैनवास को पैन करें", - contextMenuAction: "संदर्भ मेनू", - contextMenuShortcut: "राइट-क्लिक", - contextMenuDescription: "आइटम या खाली स्थान के लिए संदर्भ मेनू खोलें", + undoAction: 'पूर्ववत करें', + undoDescription: 'अंतिम क्रिया को पूर्ववत करें', + redoAction: 'फिर से करें', + redoDescription: 'अंतिम पूर्ववत की गई क्रिया को फिर से करें', + redoAltAction: 'फिर से करें (वैकल्पिक)', + redoAltDescription: 'फिर से करने के लिए वैकल्पिक शॉर्टकट', + helpAction: 'सहायता', + helpDescription: 'कीबोर्ड शॉर्टकट के साथ सहायता संवाद खोलें', + zoomInAction: 'ज़ूम इन करें', + zoomInShortcut: 'माउस व्हील ऊपर', + zoomInDescription: 'कैनवास पर ज़ूम इन करें', + zoomOutAction: 'ज़ूम आउट करें', + zoomOutShortcut: 'माउस व्हील नीचे', + zoomOutDescription: 'कैनवास से ज़ूम आउट करें', + panCanvasAction: 'कैनवास को पैन करें', + panCanvasShortcut: 'बाएँ-क्लिक + ड्रैग', + panCanvasDescription: 'पैन मोड में कैनवास को पैन करें', + contextMenuAction: 'संदर्भ मेनू', + contextMenuShortcut: 'राइट-क्लिक', + contextMenuDescription: 'आइटम या खाली स्थान के लिए संदर्भ मेनू खोलें', // Mouse interactions - selectToolAction: "चयन उपकरण", - selectToolShortcut: "चयन बटन क्लिक करें", - selectToolDescription: "चयन मोड पर स्विच करें", - panToolAction: "पैन उपकरण", - panToolShortcut: "पैन बटन क्लिक करें", - panToolDescription: "कैनवास को स्थानांतरित करने के लिए पैन मोड पर स्विच करें", - addItemAction: "आइटम जोड़ें", - addItemShortcut: "आइटम जोड़ें बटन क्लिक करें", - addItemDescription: "नए आइटम जोड़ने के लिए आइकन पिकर खोलें", - drawRectangleAction: "आयत बनाएं", - drawRectangleShortcut: "आयत बटन क्लिक करें", - drawRectangleDescription: "आयत ड्राइंग मोड पर स्विच करें", - createConnectorAction: "कनेक्टर बनाएं", - createConnectorShortcut: "कनेक्टर बटन क्लिक करें", - createConnectorDescription: "कनेक्टर मोड पर स्विच करें", - addTextAction: "टेक्स्ट जोड़ें", - addTextShortcut: "टेक्स्ट बटन क्लिक करें", - addTextDescription: "एक नया टेक्स्ट बॉक्स बनाएं" + selectToolAction: 'चयन उपकरण', + selectToolShortcut: 'चयन बटन क्लिक करें', + selectToolDescription: 'चयन मोड पर स्विच करें', + panToolAction: 'पैन उपकरण', + panToolShortcut: 'पैन बटन क्लिक करें', + panToolDescription: + 'कैनवास को स्थानांतरित करने के लिए पैन मोड पर स्विच करें', + addItemAction: 'आइटम जोड़ें', + addItemShortcut: 'आइटम जोड़ें बटन क्लिक करें', + addItemDescription: 'नए आइटम जोड़ने के लिए आइकन पिकर खोलें', + drawRectangleAction: 'आयत बनाएं', + drawRectangleShortcut: 'आयत बटन क्लिक करें', + drawRectangleDescription: 'आयत ड्राइंग मोड पर स्विच करें', + createConnectorAction: 'कनेक्टर बनाएं', + createConnectorShortcut: 'कनेक्टर बटन क्लिक करें', + createConnectorDescription: 'कनेक्टर मोड पर स्विच करें', + addTextAction: 'टेक्स्ट जोड़ें', + addTextShortcut: 'टेक्स्ट बटन क्लिक करें', + addTextDescription: 'एक नया टेक्स्ट बॉक्स बनाएं' }, connectorHintTooltip: { - tipCreatingConnectors: "टिप: कनेक्टर बनाना", - tipConnectorTools: "टिप: कनेक्टर उपकरण", - clickInstructionStart: "क्लिक करें", - clickInstructionMiddle: "पहले नोड या बिंदु पर, फिर", - clickInstructionEnd: "दूसरे नोड या बिंदु पर कनेक्शन बनाने के लिए।", - nowClickTarget: "अब कनेक्शन पूरा करने के लिए लक्ष्य पर क्लिक करें।", - dragStart: "ड्रैग करें", - dragEnd: "पहले नोड से दूसरे नोड तक कनेक्शन बनाने के लिए।", - rerouteStart: "कनेक्टर को पुनर्मार्गित करने के लिए,", - rerouteMiddle: "बाएँ-क्लिक करें", - rerouteEnd: "कनेक्टर लाइन के साथ किसी भी बिंदु पर और एंकर बिंदुओं को बनाने या स्थानांतरित करने के लिए ड्रैग करें।" + tipCreatingConnectors: 'टिप: कनेक्टर बनाना', + tipConnectorTools: 'टिप: कनेक्टर उपकरण', + clickInstructionStart: 'क्लिक करें', + clickInstructionMiddle: 'पहले नोड या बिंदु पर, फिर', + clickInstructionEnd: 'दूसरे नोड या बिंदु पर कनेक्शन बनाने के लिए।', + nowClickTarget: 'अब कनेक्शन पूरा करने के लिए लक्ष्य पर क्लिक करें।', + dragStart: 'ड्रैग करें', + dragEnd: 'पहले नोड से दूसरे नोड तक कनेक्शन बनाने के लिए।', + rerouteStart: 'कनेक्टर को पुनर्मार्गित करने के लिए,', + rerouteMiddle: 'बाएँ-क्लिक करें', + rerouteEnd: + 'कनेक्टर लाइन के साथ किसी भी बिंदु पर और एंकर बिंदुओं को बनाने या स्थानांतरित करने के लिए ड्रैग करें।' }, lassoHintTooltip: { - tipLasso: "टिप: लासो चयन", - tipFreehandLasso: "टिप: फ्रीहैंड लासो चयन", - lassoDragStart: "क्लिक करें और ड्रैग करें", - lassoDragEnd: "उन आइटम के चारों ओर एक आयताकार चयन बॉक्स बनाने के लिए जिन्हें आप चुनना चाहते हैं।", - freehandDragStart: "क्लिक करें और ड्रैग करें", - freehandDragMiddle: "एक बनाने के लिए", - freehandDragEnd: "मुक्त आकार", - freehandComplete: "आइटम के चारों ओर। आकार के अंदर सभी आइटम का चयन करने के लिए छोड़ें।", - moveStart: "एक बार चयनित होने पर,", - moveMiddle: "चयन के अंदर क्लिक करें", - moveEnd: "और सभी चयनित आइटम को एक साथ स्थानांतरित करने के लिए ड्रैग करें।" + tipLasso: 'टिप: लासो चयन', + tipFreehandLasso: 'टिप: फ्रीहैंड लासो चयन', + lassoDragStart: 'क्लिक करें और ड्रैग करें', + lassoDragEnd: + 'उन आइटम के चारों ओर एक आयताकार चयन बॉक्स बनाने के लिए जिन्हें आप चुनना चाहते हैं।', + freehandDragStart: 'क्लिक करें और ड्रैग करें', + freehandDragMiddle: 'एक बनाने के लिए', + freehandDragEnd: 'मुक्त आकार', + freehandComplete: + 'आइटम के चारों ओर। आकार के अंदर सभी आइटम का चयन करने के लिए छोड़ें।', + moveStart: 'एक बार चयनित होने पर,', + moveMiddle: 'चयन के अंदर क्लिक करें', + moveEnd: 'और सभी चयनित आइटम को एक साथ स्थानांतरित करने के लिए ड्रैग करें।' }, importHintTooltip: { - title: "आरेख आयात करें", - instructionStart: "आरेख आयात करने के लिए, क्लिक करें", - menuButton: "मेनू बटन", - instructionMiddle: "(☰) ऊपरी बाएँ कोने में, फिर चुनें", - openButton: "\"खोलें\"", - instructionEnd: "अपनी आरेख फ़ाइलें लोड करने के लिए।" + title: 'आरेख आयात करें', + instructionStart: 'आरेख आयात करने के लिए, क्लिक करें', + menuButton: 'मेनू बटन', + instructionMiddle: '(☰) ऊपरी बाएँ कोने में, फिर चुनें', + openButton: '"खोलें"', + instructionEnd: 'अपनी आरेख फ़ाइलें लोड करने के लिए।' }, connectorRerouteTooltip: { - title: "टिप: कनेक्टर्स को पुनर्मार्गित करें", - instructionStart: "एक बार आपके कनेक्टर्स स्थापित हो जाने के बाद आप उन्हें अपनी इच्छानुसार पुनर्मार्गित कर सकते हैं।", - instructionSelect: "कनेक्टर का चयन करें", - instructionMiddle: "पहले, फिर", - instructionClick: "कनेक्टर पथ पर क्लिक करें", - instructionAnd: "और", - instructionDrag: "ड्रैग करें", - instructionEnd: "इसे बदलने के लिए!" + title: 'टिप: कनेक्टर्स को पुनर्मार्गित करें', + instructionStart: + 'एक बार आपके कनेक्टर्स स्थापित हो जाने के बाद आप उन्हें अपनी इच्छानुसार पुनर्मार्गित कर सकते हैं।', + instructionSelect: 'कनेक्टर का चयन करें', + instructionMiddle: 'पहले, फिर', + instructionClick: 'कनेक्टर पथ पर क्लिक करें', + instructionAnd: 'और', + instructionDrag: 'ड्रैग करें', + instructionEnd: 'इसे बदलने के लिए!' }, settings: { zoom: { - description: "माउस व्हील का उपयोग करते समय ज़ूम व्यवहार को कॉन्फ़िगर करें।", - zoomToCursor: "कर्सर पर ज़ूम करें", - zoomToCursorDesc: "सक्षम होने पर, माउस कर्सर की स्थिति पर केंद्रित ज़ूम इन/आउट। अक्षम होने पर, ज़ूम कैनवास पर केंद्रित होता है।" + description: + 'माउस व्हील का उपयोग करते समय ज़ूम व्यवहार को कॉन्फ़िगर करें।', + zoomToCursor: 'कर्सर पर ज़ूम करें', + zoomToCursorDesc: + 'सक्षम होने पर, माउस कर्सर की स्थिति पर केंद्रित ज़ूम इन/आउट। अक्षम होने पर, ज़ूम कैनवास पर केंद्रित होता है।' }, hotkeys: { - title: "शॉर्टकट सेटिंग्स", - profile: "शॉर्टकट प्रोफ़ाइल", - profileQwerty: "QWERTY (Q, W, E, R, T, Y)", - profileSmnrct: "SMNRCT (S, M, N, R, C, T)", - profileNone: "कोई शॉर्टकट नहीं", - tool: "उपकरण", - hotkey: "शॉर्टकट", - toolSelect: "चयन करें", - toolPan: "पैन करें", - toolAddItem: "आइटम जोड़ें", - toolRectangle: "आयत", - toolConnector: "कनेक्टर", - toolText: "टेक्स्ट", - note: "नोट: टेक्स्ट फ़ील्ड में टाइप न करने पर शॉर्टकट काम करते हैं" + title: 'शॉर्टकट सेटिंग्स', + profile: 'शॉर्टकट प्रोफ़ाइल', + profileQwerty: 'QWERTY (Q, W, E, R, T, Y)', + profileSmnrct: 'SMNRCT (S, M, N, R, C, T)', + profileNone: 'कोई शॉर्टकट नहीं', + tool: 'उपकरण', + hotkey: 'शॉर्टकट', + toolSelect: 'चयन करें', + toolPan: 'पैन करें', + toolAddItem: 'आइटम जोड़ें', + toolRectangle: 'आयत', + toolConnector: 'कनेक्टर', + toolText: 'टेक्स्ट', + note: 'नोट: टेक्स्ट फ़ील्ड में टाइप न करने पर शॉर्टकट काम करते हैं' }, pan: { - title: "पैन सेटिंग्स", - mousePanOptions: "माउस पैन विकल्प", - emptyAreaClickPan: "खाली क्षेत्र पर क्लिक करें और ड्रैग करें", - middleClickPan: "मध्य क्लिक करें और ड्रैग करें", - rightClickPan: "राइट क्लिक करें और ड्रैग करें", - ctrlClickPan: "Ctrl + क्लिक करें और ड्रैग करें", - altClickPan: "Alt + क्लिक करें और ड्रैग करें", - keyboardPanOptions: "कीबोर्ड पैन विकल्प", - arrowKeys: "एरो कुंजी", - wasdKeys: "WASD कुंजी", - ijklKeys: "IJKL कुंजी", - keyboardPanSpeed: "कीबोर्ड पैन गति", - note: "नोट: समर्पित पैन उपकरण के अलावा पैन विकल्प काम करते हैं" + title: 'पैन सेटिंग्स', + mousePanOptions: 'माउस पैन विकल्प', + emptyAreaClickPan: 'खाली क्षेत्र पर क्लिक करें और ड्रैग करें', + middleClickPan: 'मध्य क्लिक करें और ड्रैग करें', + rightClickPan: 'राइट क्लिक करें और ड्रैग करें', + ctrlClickPan: 'Ctrl + क्लिक करें और ड्रैग करें', + altClickPan: 'Alt + क्लिक करें और ड्रैग करें', + keyboardPanOptions: 'कीबोर्ड पैन विकल्प', + arrowKeys: 'एरो कुंजी', + wasdKeys: 'WASD कुंजी', + ijklKeys: 'IJKL कुंजी', + keyboardPanSpeed: 'कीबोर्ड पैन गति', + note: 'नोट: समर्पित पैन उपकरण के अलावा पैन विकल्प काम करते हैं' }, connector: { - title: "कनेक्टर सेटिंग्स", - connectionMode: "कनेक्शन निर्माण मोड", - clickMode: "क्लिक मोड (अनुशंसित)", - clickModeDesc: "पहले नोड पर क्लिक करें, फिर कनेक्शन बनाने के लिए दूसरे नोड पर क्लिक करें", - dragMode: "ड्रैग मोड", - dragModeDesc: "पहले नोड से दूसरे नोड तक क्लिक करें और ड्रैग करें", - note: "नोट: आप किसी भी समय इस सेटिंग को बदल सकते हैं। जब कनेक्टर उपकरण सक्रिय होता है तो चयनित मोड का उपयोग किया जाएगा।" + title: 'कनेक्टर सेटिंग्स', + connectionMode: 'कनेक्शन निर्माण मोड', + clickMode: 'क्लिक मोड (अनुशंसित)', + clickModeDesc: + 'पहले नोड पर क्लिक करें, फिर कनेक्शन बनाने के लिए दूसरे नोड पर क्लिक करें', + dragMode: 'ड्रैग मोड', + dragModeDesc: 'पहले नोड से दूसरे नोड तक क्लिक करें और ड्रैग करें', + note: 'नोट: आप किसी भी समय इस सेटिंग को बदल सकते हैं। जब कनेक्टर उपकरण सक्रिय होता है तो चयनित मोड का उपयोग किया जाएगा।' }, iconPacks: { - title: "आइकन पैक प्रबंधन", - lazyLoading: "लेज़ी लोडिंग सक्षम करें", - lazyLoadingDesc: "तेज़ स्टार्टअप के लिए आवश्यकता पर आइकन पैक लोड करें", - availablePacks: "उपलब्ध आइकन पैक", - coreIsoflow: "Core Isoflow (हमेशा लोड)", - alwaysEnabled: "हमेशा सक्षम", - awsPack: "AWS आइकन", - gcpPack: "Google Cloud आइकन", - azurePack: "Azure आइकन", - kubernetesPack: "Kubernetes आइकन", - loading: "लोड हो रहा है...", - loaded: "लोड किया गया", - notLoaded: "लोड नहीं किया गया", - iconCount: "{count} आइकन", - lazyLoadingDisabledNote: "लेज़ी लोडिंग अक्षम है। सभी आइकन पैक स्टार्टअप पर लोड किए जाते हैं।", - note: "आइकन पैक आपकी आवश्यकताओं के आधार पर सक्षम या अक्षम किए जा सकते हैं। अक्षम पैक मेमोरी उपयोग को कम करेंगे और प्रदर्शन में सुधार करेंगे।" + title: 'आइकन पैक प्रबंधन', + lazyLoading: 'लेज़ी लोडिंग सक्षम करें', + lazyLoadingDesc: 'तेज़ स्टार्टअप के लिए आवश्यकता पर आइकन पैक लोड करें', + availablePacks: 'उपलब्ध आइकन पैक', + coreIsoflow: 'Core Isoflow (हमेशा लोड)', + alwaysEnabled: 'हमेशा सक्षम', + awsPack: 'AWS आइकन', + gcpPack: 'Google Cloud आइकन', + azurePack: 'Azure आइकन', + kubernetesPack: 'Kubernetes आइकन', + loading: 'लोड हो रहा है...', + loaded: 'लोड किया गया', + notLoaded: 'लोड नहीं किया गया', + iconCount: '{count} आइकन', + lazyLoadingDisabledNote: + 'लेज़ी लोडिंग अक्षम है। सभी आइकन पैक स्टार्टअप पर लोड किए जाते हैं।', + note: 'आइकन पैक आपकी आवश्यकताओं के आधार पर सक्षम या अक्षम किए जा सकते हैं। अक्षम पैक मेमोरी उपयोग को कम करेंगे और प्रदर्शन में सुधार करेंगे।' } }, lazyLoadingWelcome: { - title: "नई सुविधा: लेज़ी लोडिंग!", - message: "अरे! लोकप्रिय मांग के बाद, हमने आइकन की लेज़ी लोडिंग लागू की है, इसलिए अब यदि आप गैर-मानक आइकन पैक सक्षम करना चाहते हैं तो आप उन्हें 'कॉन्फ़िगरेशन' अनुभाग में सक्षम कर सकते हैं।", - configPath: "हैमबर्गर आइकन पर क्लिक करें", - configPath2: "कॉन्फ़िगरेशन तक पहुंचने के लिए ऊपरी बाएं में।", - canDisable: "यदि आप चाहें तो आप इस व्यवहार को अक्षम कर सकते हैं।", - signature: "-Stan" + title: 'नई सुविधा: लेज़ी लोडिंग!', + message: + "अरे! लोकप्रिय मांग के बाद, हमने आइकन की लेज़ी लोडिंग लागू की है, इसलिए अब यदि आप गैर-मानक आइकन पैक सक्षम करना चाहते हैं तो आप उन्हें 'कॉन्फ़िगरेशन' अनुभाग में सक्षम कर सकते हैं।", + configPath: 'हैमबर्गर आइकन पर क्लिक करें', + configPath2: 'कॉन्फ़िगरेशन तक पहुंचने के लिए ऊपरी बाएं में।', + canDisable: 'यदि आप चाहें तो आप इस व्यवहार को अक्षम कर सकते हैं।', + signature: '-Stan' } }; diff --git a/packages/fossflow-lib/src/i18n/index.ts b/packages/fossflow-lib/src/i18n/index.ts index 57c84ea2..f89047a9 100644 --- a/packages/fossflow-lib/src/i18n/index.ts +++ b/packages/fossflow-lib/src/i18n/index.ts @@ -9,15 +9,15 @@ import ruRU from './ru-RU'; import plPL from './pl-PL'; const locales = { - 'en-US': enUS, - 'zh-CN': zhCN, - 'es-ES': esES, - 'pt-BR': ptBR, - 'fr-FR': frFR, - 'hi-IN': hiIN, - 'bn-BD': bnBD, - 'ru-RU': ruRU, - 'pl-PL': plPL + 'en-US': enUS, + 'zh-CN': zhCN, + 'es-ES': esES, + 'pt-BR': ptBR, + 'fr-FR': frFR, + 'hi-IN': hiIN, + 'bn-BD': bnBD, + 'ru-RU': ruRU, + 'pl-PL': plPL }; export default locales; diff --git a/packages/fossflow-lib/src/i18n/it-IT.ts b/packages/fossflow-lib/src/i18n/it-IT.ts index 43216f83..c83a4621 100644 --- a/packages/fossflow-lib/src/i18n/it-IT.ts +++ b/packages/fossflow-lib/src/i18n/it-IT.ts @@ -2,187 +2,201 @@ import { LocaleProps } from '../types/isoflowProps'; const locale: LocaleProps = { common: { - exampleText: "Questo è un testo di esempio" + exampleText: 'Questo è un testo di esempio' }, mainMenu: { - undo: "Annulla", - redo: "Ripeti", - open: "Apri", - exportJson: "Esporta come JSON", - exportCompactJson: "Esporta come JSON compatto", - exportImage: "Esporta come immagine", - clearCanvas: "Pulisci la tela", - settings: "Impostazioni", - gitHub: "GitHub" + undo: 'Annulla', + redo: 'Ripeti', + open: 'Apri', + exportJson: 'Esporta come JSON', + exportCompactJson: 'Esporta come JSON compatto', + exportImage: 'Esporta come immagine', + clearCanvas: 'Pulisci la tela', + settings: 'Impostazioni', + gitHub: 'GitHub' }, helpDialog: { - title: "Scorciatoie da tastiera e aiuto", - close: "Chiudi", - keyboardShortcuts: "Scorciatoie da tastiera", - mouseInteractions: "Interazioni del mouse", - action: "Azione", - shortcut: "Scorciatoia", - method: "Metodo", - description: "Descrizione", - note: "Nota:", - noteContent: "Le scorciatoie da tastiera sono disattivate durante la digitazione in campi di testo o elementi modificabili per evitare conflitti.", + title: 'Scorciatoie da tastiera e aiuto', + close: 'Chiudi', + keyboardShortcuts: 'Scorciatoie da tastiera', + mouseInteractions: 'Interazioni del mouse', + action: 'Azione', + shortcut: 'Scorciatoia', + method: 'Metodo', + description: 'Descrizione', + note: 'Nota:', + noteContent: + 'Le scorciatoie da tastiera sono disattivate durante la digitazione in campi di testo o elementi modificabili per evitare conflitti.', // Keyboard shortcuts - undoAction: "Annulla", + undoAction: 'Annulla', undoDescription: "Annulla l'ultima azione", - redoAction: "Ripeti", + redoAction: 'Ripeti', redoDescription: "Ripeti l'ultima azione annullata", - redoAltAction: "Ripeti (Alternativa)", - redoAltDescription: "Scorciatoia alternativa per ripetere", - helpAction: "Aiuto", - helpDescription: "Apri la finestra di aiuto con le scorciatoie da tastiera", - zoomInAction: "Ingrandisci", - zoomInShortcut: "Rotella del mouse su", - zoomInDescription: "Ingrandisci la tela", - zoomOutAction: "Rimpicciolisci", - zoomOutShortcut: "Rotella del mouse giù", - zoomOutDescription: "Rimpicciolisci la tela", - panCanvasAction: "Sposta la tela", - panCanvasShortcut: "Clic sinistro + trascina", - panCanvasDescription: "Muovi la tela in modalità panoramica", - contextMenuAction: "Menu contestuale", - contextMenuShortcut: "Tasto destro", - contextMenuDescription: "Apri il menu contestuale per elementi o spazio vuoto", + redoAltAction: 'Ripeti (Alternativa)', + redoAltDescription: 'Scorciatoia alternativa per ripetere', + helpAction: 'Aiuto', + helpDescription: 'Apri la finestra di aiuto con le scorciatoie da tastiera', + zoomInAction: 'Ingrandisci', + zoomInShortcut: 'Rotella del mouse su', + zoomInDescription: 'Ingrandisci la tela', + zoomOutAction: 'Rimpicciolisci', + zoomOutShortcut: 'Rotella del mouse giù', + zoomOutDescription: 'Rimpicciolisci la tela', + panCanvasAction: 'Sposta la tela', + panCanvasShortcut: 'Clic sinistro + trascina', + panCanvasDescription: 'Muovi la tela in modalità panoramica', + contextMenuAction: 'Menu contestuale', + contextMenuShortcut: 'Tasto destro', + contextMenuDescription: + 'Apri il menu contestuale per elementi o spazio vuoto', // Mouse interactions - selectToolAction: "Strumento Selezione", - selectToolShortcut: "Clicca il pulsante Selezione", - selectToolDescription: "Passa alla modalità selezione", - panToolAction: "Strumento Panoramica", - panToolShortcut: "Clicca il pulsante Panoramica", - panToolDescription: "Passa alla modalità panoramica per spostare la tela", - addItemAction: "Aggiungi elemento", - addItemShortcut: "Clicca il pulsante Aggiungi elemento", - addItemDescription: "Apri il selettore di icone per aggiungere nuovi elementi", - drawRectangleAction: "Disegna rettangolo", - drawRectangleShortcut: "Clicca il pulsante Rettangolo", - drawRectangleDescription: "Passa alla modalità disegno rettangolo", - createConnectorAction: "Crea connettore", - createConnectorShortcut: "Clicca il pulsante Connettore", - createConnectorDescription: "Passa alla modalità connettore", - addTextAction: "Aggiungi testo", - addTextShortcut: "Clicca il pulsante Testo", - addTextDescription: "Crea una nuova casella di testo" + selectToolAction: 'Strumento Selezione', + selectToolShortcut: 'Clicca il pulsante Selezione', + selectToolDescription: 'Passa alla modalità selezione', + panToolAction: 'Strumento Panoramica', + panToolShortcut: 'Clicca il pulsante Panoramica', + panToolDescription: 'Passa alla modalità panoramica per spostare la tela', + addItemAction: 'Aggiungi elemento', + addItemShortcut: 'Clicca il pulsante Aggiungi elemento', + addItemDescription: + 'Apri il selettore di icone per aggiungere nuovi elementi', + drawRectangleAction: 'Disegna rettangolo', + drawRectangleShortcut: 'Clicca il pulsante Rettangolo', + drawRectangleDescription: 'Passa alla modalità disegno rettangolo', + createConnectorAction: 'Crea connettore', + createConnectorShortcut: 'Clicca il pulsante Connettore', + createConnectorDescription: 'Passa alla modalità connettore', + addTextAction: 'Aggiungi testo', + addTextShortcut: 'Clicca il pulsante Testo', + addTextDescription: 'Crea una nuova casella di testo' }, connectorHintTooltip: { - tipCreatingConnectors: "Suggerimento: Creazione connettori", - tipConnectorTools: "Suggerimento: Strumenti connettore", - clickInstructionStart: "Clicca", - clickInstructionMiddle: "sul primo nodo o punto, poi", - clickInstructionEnd: "sul secondo nodo o punto per creare una connessione.", + tipCreatingConnectors: 'Suggerimento: Creazione connettori', + tipConnectorTools: 'Suggerimento: Strumenti connettore', + clickInstructionStart: 'Clicca', + clickInstructionMiddle: 'sul primo nodo o punto, poi', + clickInstructionEnd: 'sul secondo nodo o punto per creare una connessione.', nowClickTarget: "Ora clicca sull'obiettivo per completare la connessione.", - dragStart: "Trascina", - dragEnd: "dal primo nodo al secondo nodo per creare una connessione.", - rerouteStart: "Per riorientare un connettore,", - rerouteMiddle: "clicca con il tasto sinistro", - rerouteEnd: "su un punto qualsiasi lungo la linea del connettore e trascina per creare o spostare i punti di ancoraggio." + dragStart: 'Trascina', + dragEnd: 'dal primo nodo al secondo nodo per creare una connessione.', + rerouteStart: 'Per riorientare un connettore,', + rerouteMiddle: 'clicca con il tasto sinistro', + rerouteEnd: + 'su un punto qualsiasi lungo la linea del connettore e trascina per creare o spostare i punti di ancoraggio.' }, lassoHintTooltip: { - tipLasso: "Suggerimento: Selezione Lasso", - tipFreehandLasso: "Suggerimento: Selezione Lasso a mano libera", - lassoDragStart: "Clicca e trascina", - lassoDragEnd: "per disegnare un riquadro di selezione rettangolare attorno agli elementi da selezionare.", - freehandDragStart: "Clicca e trascina", - freehandDragMiddle: "per disegnare una", - freehandDragEnd: "forma libera", - freehandComplete: "attorno agli elementi. Rilascia per selezionare tutti gli elementi all'interno della forma.", - moveStart: "Una volta selezionati,", + tipLasso: 'Suggerimento: Selezione Lasso', + tipFreehandLasso: 'Suggerimento: Selezione Lasso a mano libera', + lassoDragStart: 'Clicca e trascina', + lassoDragEnd: + 'per disegnare un riquadro di selezione rettangolare attorno agli elementi da selezionare.', + freehandDragStart: 'Clicca e trascina', + freehandDragMiddle: 'per disegnare una', + freehandDragEnd: 'forma libera', + freehandComplete: + "attorno agli elementi. Rilascia per selezionare tutti gli elementi all'interno della forma.", + moveStart: 'Una volta selezionati,', moveMiddle: "clicca all'interno della selezione", - moveEnd: "e trascina per muovere tutti gli elementi selezionati insieme." + moveEnd: 'e trascina per muovere tutti gli elementi selezionati insieme.' }, importHintTooltip: { - title: "Importa diagrammi", - instructionStart: "Per importare diagrammi, clicca sul", - menuButton: "pulsante del menu", - instructionMiddle: "(☰) in alto a sinistra, poi seleziona", - openButton: "\"Apri\"", - instructionEnd: "per caricare i tuoi file di diagramma." + title: 'Importa diagrammi', + instructionStart: 'Per importare diagrammi, clicca sul', + menuButton: 'pulsante del menu', + instructionMiddle: '(☰) in alto a sinistra, poi seleziona', + openButton: '"Apri"', + instructionEnd: 'per caricare i tuoi file di diagramma.' }, connectorRerouteTooltip: { - title: "Suggerimento: Riorienta connettori", - instructionStart: "Una volta posizionati i connettori, puoi riorientarli come preferisci.", - instructionSelect: "Seleziona prima il connettore,", - instructionMiddle: "poi", - instructionClick: "clicca sul percorso del connettore", - instructionAnd: "e", - instructionDrag: "trascina", - instructionEnd: "per modificarlo!" + title: 'Suggerimento: Riorienta connettori', + instructionStart: + 'Una volta posizionati i connettori, puoi riorientarli come preferisci.', + instructionSelect: 'Seleziona prima il connettore,', + instructionMiddle: 'poi', + instructionClick: 'clicca sul percorso del connettore', + instructionAnd: 'e', + instructionDrag: 'trascina', + instructionEnd: 'per modificarlo!' }, settings: { zoom: { - description: "Configura il comportamento dello zoom quando si usa la rotella del mouse.", - zoomToCursor: "Zoom sul cursore", - zoomToCursorDesc: "Se abilitato, ingrandisci o riduci centrando sul cursore del mouse. Se disabilitato, lo zoom è centrato sulla tela." + description: + 'Configura il comportamento dello zoom quando si usa la rotella del mouse.', + zoomToCursor: 'Zoom sul cursore', + zoomToCursorDesc: + 'Se abilitato, ingrandisci o riduci centrando sul cursore del mouse. Se disabilitato, lo zoom è centrato sulla tela.' }, hotkeys: { - title: "Impostazioni scorciatoie", - profile: "Profilo scorciatoie", - profileQwerty: "QWERTY (Q, W, E, R, T, Y)", - profileSmnrct: "SMNRCT (S, M, N, R, C, T)", - profileNone: "Nessuna scorciatoia", - tool: "Strumento", - hotkey: "Scorciatoia", - toolSelect: "Seleziona", - toolPan: "Panoramica", - toolAddItem: "Aggiungi elemento", - toolRectangle: "Rettangolo", - toolConnector: "Connettore", - toolText: "Testo", - note: "Nota: Le scorciatoie funzionano quando non stai digitando nei campi di testo" + title: 'Impostazioni scorciatoie', + profile: 'Profilo scorciatoie', + profileQwerty: 'QWERTY (Q, W, E, R, T, Y)', + profileSmnrct: 'SMNRCT (S, M, N, R, C, T)', + profileNone: 'Nessuna scorciatoia', + tool: 'Strumento', + hotkey: 'Scorciatoia', + toolSelect: 'Seleziona', + toolPan: 'Panoramica', + toolAddItem: 'Aggiungi elemento', + toolRectangle: 'Rettangolo', + toolConnector: 'Connettore', + toolText: 'Testo', + note: 'Nota: Le scorciatoie funzionano quando non stai digitando nei campi di testo' }, pan: { - title: "Impostazioni Panoramica", - mousePanOptions: "Opzioni panoramica con mouse", + title: 'Impostazioni Panoramica', + mousePanOptions: 'Opzioni panoramica con mouse', emptyAreaClickPan: "Clicca e trascina su un'area vuota", - middleClickPan: "Clic centrale e trascina", - rightClickPan: "Clic destro e trascina", - ctrlClickPan: "Ctrl + clic e trascina", - altClickPan: "Alt + clic e trascina", - keyboardPanOptions: "Opzioni panoramica con tastiera", - arrowKeys: "Tasti freccia", - wasdKeys: "Tasti WASD", - ijklKeys: "Tasti IJKL", - keyboardPanSpeed: "Velocità panoramica tastiera", - note: "Nota: Le opzioni di panoramica funzionano insieme allo strumento Panoramica dedicato" + middleClickPan: 'Clic centrale e trascina', + rightClickPan: 'Clic destro e trascina', + ctrlClickPan: 'Ctrl + clic e trascina', + altClickPan: 'Alt + clic e trascina', + keyboardPanOptions: 'Opzioni panoramica con tastiera', + arrowKeys: 'Tasti freccia', + wasdKeys: 'Tasti WASD', + ijklKeys: 'Tasti IJKL', + keyboardPanSpeed: 'Velocità panoramica tastiera', + note: 'Nota: Le opzioni di panoramica funzionano insieme allo strumento Panoramica dedicato' }, connector: { - title: "Impostazioni Connettore", - connectionMode: "Modalità creazione connessione", - clickMode: "Modalità clic (consigliata)", - clickModeDesc: "Clicca sul primo nodo, poi sul secondo per creare una connessione", - dragMode: "Modalità trascinamento", - dragModeDesc: "Clicca e trascina dal primo nodo al secondo per creare una connessione", - note: "Nota: Puoi modificare questa impostazione in qualsiasi momento. La modalità selezionata verrà usata quando lo strumento Connettore è attivo." + title: 'Impostazioni Connettore', + connectionMode: 'Modalità creazione connessione', + clickMode: 'Modalità clic (consigliata)', + clickModeDesc: + 'Clicca sul primo nodo, poi sul secondo per creare una connessione', + dragMode: 'Modalità trascinamento', + dragModeDesc: + 'Clicca e trascina dal primo nodo al secondo per creare una connessione', + note: 'Nota: Puoi modificare questa impostazione in qualsiasi momento. La modalità selezionata verrà usata quando lo strumento Connettore è attivo.' }, iconPacks: { - title: "Gestione pacchetti di icone", - lazyLoading: "Abilita caricamento ritardato (Lazy Loading)", - lazyLoadingDesc: "Carica i pacchetti di icone su richiesta per un avvio più rapido", - availablePacks: "Pacchetti di icone disponibili", - coreIsoflow: "Isoflow di base (sempre caricato)", - alwaysEnabled: "Sempre abilitato", - awsPack: "Icone AWS", - gcpPack: "Icone Google Cloud", - azurePack: "Icone Azure", - kubernetesPack: "Icone Kubernetes", - loading: "Caricamento...", - loaded: "Caricato", - notLoaded: "Non caricato", - iconCount: "{count} icone", - lazyLoadingDisabledNote: "Il caricamento ritardato è disabilitato. Tutti i pacchetti di icone vengono caricati all'avvio.", + title: 'Gestione pacchetti di icone', + lazyLoading: 'Abilita caricamento ritardato (Lazy Loading)', + lazyLoadingDesc: + 'Carica i pacchetti di icone su richiesta per un avvio più rapido', + availablePacks: 'Pacchetti di icone disponibili', + coreIsoflow: 'Isoflow di base (sempre caricato)', + alwaysEnabled: 'Sempre abilitato', + awsPack: 'Icone AWS', + gcpPack: 'Icone Google Cloud', + azurePack: 'Icone Azure', + kubernetesPack: 'Icone Kubernetes', + loading: 'Caricamento...', + loaded: 'Caricato', + notLoaded: 'Non caricato', + iconCount: '{count} icone', + lazyLoadingDisabledNote: + "Il caricamento ritardato è disabilitato. Tutti i pacchetti di icone vengono caricati all'avvio.", note: "I pacchetti di icone possono essere abilitati o disabilitati in base alle tue esigenze. I pacchetti disabilitati riducono l'uso di memoria e migliorano le prestazioni." } }, lazyLoadingWelcome: { - title: "Nuova funzione: Lazy Loading!", - message: "Ciao! Su grande richiesta, abbiamo implementato il caricamento ritardato (Lazy Loading) delle icone. Ora, se desideri abilitare pacchetti di icone non standard, puoi farlo nella sezione 'Configurazione'.", + title: 'Nuova funzione: Lazy Loading!', + message: + "Ciao! Su grande richiesta, abbiamo implementato il caricamento ritardato (Lazy Loading) delle icone. Ora, se desideri abilitare pacchetti di icone non standard, puoi farlo nella sezione 'Configurazione'.", configPath: "Clicca sull'icona dell'hamburger", - configPath2: "in alto a sinistra per accedere alla Configurazione.", - canDisable: "Puoi disattivare questo comportamento se lo desideri.", - signature: "-Stan" + configPath2: 'in alto a sinistra per accedere alla Configurazione.', + canDisable: 'Puoi disattivare questo comportamento se lo desideri.', + signature: '-Stan' } }; diff --git a/packages/fossflow-lib/src/i18n/pl-PL.ts b/packages/fossflow-lib/src/i18n/pl-PL.ts index fc4a46b1..57752554 100644 --- a/packages/fossflow-lib/src/i18n/pl-PL.ts +++ b/packages/fossflow-lib/src/i18n/pl-PL.ts @@ -2,187 +2,204 @@ import { LocaleProps } from '../types/isoflowProps'; const locale: LocaleProps = { common: { - exampleText: "To jest przykładowy tekst" + exampleText: 'To jest przykładowy tekst' }, mainMenu: { - undo: "Cofnij", - redo: "Ponów", - open: "Otwórz", - exportJson: "Eksportuj do JSON", - exportCompactJson: "Eksportuj jako kompaktowy JSON", - exportImage: "Eksportuj do obrazu", - clearCanvas: "Wyczyść obszar roboczy", - settings: "Ustawienia", - gitHub: "GitHub" + undo: 'Cofnij', + redo: 'Ponów', + open: 'Otwórz', + exportJson: 'Eksportuj do JSON', + exportCompactJson: 'Eksportuj jako kompaktowy JSON', + exportImage: 'Eksportuj do obrazu', + clearCanvas: 'Wyczyść obszar roboczy', + settings: 'Ustawienia', + gitHub: 'GitHub' }, helpDialog: { - title: "Skróty klawiaturowe i Pomoc", - close: "Zamknij", - keyboardShortcuts: "Skróty klawiaturowe", - mouseInteractions: "Interakcje myszy", - action: "Operacja", - shortcut: "Skrót", - method: "Metoda", - description: "Opis", - note: "Uwagi:", - noteContent: "Skróty klawiaturowe są wyłączone podczas wpisywania danych w polach wprowadzania danych, obszarach tekstowych lub elementach z edytowalną treścią, aby zapobiec konfliktom.", + title: 'Skróty klawiaturowe i Pomoc', + close: 'Zamknij', + keyboardShortcuts: 'Skróty klawiaturowe', + mouseInteractions: 'Interakcje myszy', + action: 'Operacja', + shortcut: 'Skrót', + method: 'Metoda', + description: 'Opis', + note: 'Uwagi:', + noteContent: + 'Skróty klawiaturowe są wyłączone podczas wpisywania danych w polach wprowadzania danych, obszarach tekstowych lub elementach z edytowalną treścią, aby zapobiec konfliktom.', // Keyboard shortcuts - undoAction: "Cofnij", - undoDescription: "Cofnij do ostatniej operacji", - redoAction: "Powtórz", - redoDescription: "Ponów ostatnia operację", - redoAltAction: "Powtórz (alternatywa)", - redoAltDescription: "Alternatywny skrót do ponownego wykonania", - helpAction: "Pomoc", - helpDescription: "Otwórz okno dialogowe pomocy za pomocą skrótów klawiaturowych", - zoomInAction: "Powiększ", - zoomInShortcut: "Kółko myszy w górę", - zoomInDescription: "Powiększ obszar roboczy", - zoomOutAction: "Pomniejsz", - zoomOutShortcut: "Kółko muszy w dół", - zoomOutDescription: "Pomniejsz obszar roboczy", - panCanvasAction: "Przesuwanie obszaru roboczego", - panCanvasShortcut: "Kliknij lewym przyciskiem myszy + przeciągnij", - panCanvasDescription: "Przesuwaj obszar roboczy w trybie przesuwania", - contextMenuAction: "Menu kontekstowe", - contextMenuShortcut: "Prawy przycisk myszy", - contextMenuDescription: "Otwórz menu kontekstowe dla elementów lub pustej przestrzeni", + undoAction: 'Cofnij', + undoDescription: 'Cofnij do ostatniej operacji', + redoAction: 'Powtórz', + redoDescription: 'Ponów ostatnia operację', + redoAltAction: 'Powtórz (alternatywa)', + redoAltDescription: 'Alternatywny skrót do ponownego wykonania', + helpAction: 'Pomoc', + helpDescription: + 'Otwórz okno dialogowe pomocy za pomocą skrótów klawiaturowych', + zoomInAction: 'Powiększ', + zoomInShortcut: 'Kółko myszy w górę', + zoomInDescription: 'Powiększ obszar roboczy', + zoomOutAction: 'Pomniejsz', + zoomOutShortcut: 'Kółko muszy w dół', + zoomOutDescription: 'Pomniejsz obszar roboczy', + panCanvasAction: 'Przesuwanie obszaru roboczego', + panCanvasShortcut: 'Kliknij lewym przyciskiem myszy + przeciągnij', + panCanvasDescription: 'Przesuwaj obszar roboczy w trybie przesuwania', + contextMenuAction: 'Menu kontekstowe', + contextMenuShortcut: 'Prawy przycisk myszy', + contextMenuDescription: + 'Otwórz menu kontekstowe dla elementów lub pustej przestrzeni', // Mouse interactions - selectToolAction: "Wybierz narzędzie", - selectToolShortcut: "Kliknij przycisk Wybierz", - selectToolDescription: "Przejdź do trybu wyboru", - panToolAction: "Narzędzie przesuwania", - panToolShortcut: "Kliknij przycisk „Przesuwania”", - panToolDescription: "Przejdź do trybu przesuwania, aby przesuwać obszar roboczy", - addItemAction: "Dodaj element", - addItemShortcut: "Kliknij przycisk Dodaj element", - addItemDescription: "Otwórz narzędzie do wyboru opcji, aby dodać nowe elementy.", - drawRectangleAction: "Narysuj prostokąt", - drawRectangleShortcut: "Kliknij przycisk Prostokąt", - drawRectangleDescription: "Przejdź do trybu rysowania prostokątów", - createConnectorAction: "Stwórz połączenie", - createConnectorShortcut: "Kliknij przycisk Połączenie", - createConnectorDescription: "Przełącz do trybu połączenia", - addTextAction: "Dodaj Tekst", - addTextShortcut: "Kliknij przycisk Tekst", - addTextDescription: "Utwórz nowe pole tekstowe" + selectToolAction: 'Wybierz narzędzie', + selectToolShortcut: 'Kliknij przycisk Wybierz', + selectToolDescription: 'Przejdź do trybu wyboru', + panToolAction: 'Narzędzie przesuwania', + panToolShortcut: 'Kliknij przycisk „Przesuwania”', + panToolDescription: + 'Przejdź do trybu przesuwania, aby przesuwać obszar roboczy', + addItemAction: 'Dodaj element', + addItemShortcut: 'Kliknij przycisk Dodaj element', + addItemDescription: + 'Otwórz narzędzie do wyboru opcji, aby dodać nowe elementy.', + drawRectangleAction: 'Narysuj prostokąt', + drawRectangleShortcut: 'Kliknij przycisk Prostokąt', + drawRectangleDescription: 'Przejdź do trybu rysowania prostokątów', + createConnectorAction: 'Stwórz połączenie', + createConnectorShortcut: 'Kliknij przycisk Połączenie', + createConnectorDescription: 'Przełącz do trybu połączenia', + addTextAction: 'Dodaj Tekst', + addTextShortcut: 'Kliknij przycisk Tekst', + addTextDescription: 'Utwórz nowe pole tekstowe' }, connectorHintTooltip: { - tipCreatingConnectors: "Wskazówka: Tworzenie połączeń", - tipConnectorTools: "Wskazówka: Narzędzia do połączeń", - clickInstructionStart: "Kliknij", - clickInstructionMiddle: "w pierwszym węźle lub punkcie, a następnie", - clickInstructionEnd: "na drugim węźle lub punkcie, aby utworzyć połączenie.", - nowClickTarget: "Teraz kliknij na cel, aby zakończyć połączenie.", - dragStart: "Przeciagnij", - dragEnd: "od pierwszego węzła do drugiego węzła, aby utworzyć połączenie.", - rerouteStart: "Aby zmienić trasę połączenia,", - rerouteMiddle: "prawy przycisk myszy", - rerouteEnd: "w dowolnym miejscu wzdłuż linii łącznika i przeciągnij, aby utworzyć lub przenieść punkty kotwiczenia." + tipCreatingConnectors: 'Wskazówka: Tworzenie połączeń', + tipConnectorTools: 'Wskazówka: Narzędzia do połączeń', + clickInstructionStart: 'Kliknij', + clickInstructionMiddle: 'w pierwszym węźle lub punkcie, a następnie', + clickInstructionEnd: + 'na drugim węźle lub punkcie, aby utworzyć połączenie.', + nowClickTarget: 'Teraz kliknij na cel, aby zakończyć połączenie.', + dragStart: 'Przeciagnij', + dragEnd: 'od pierwszego węzła do drugiego węzła, aby utworzyć połączenie.', + rerouteStart: 'Aby zmienić trasę połączenia,', + rerouteMiddle: 'prawy przycisk myszy', + rerouteEnd: + 'w dowolnym miejscu wzdłuż linii łącznika i przeciągnij, aby utworzyć lub przenieść punkty kotwiczenia.' }, lassoHintTooltip: { - tipLasso: "Wskazówka: Zaznaczanie za pomocą narzędzia Lasso", - tipFreehandLasso: "Wskazówka: Zaznaczanie narzędziem Lasso z wolnej ręki", - lassoDragStart: "Kliknij i przeciągnij", - lassoDragEnd: "aby narysować prostokątne pole wyboru wokół elementów, które chcesz zaznaczyć.", - freehandDragStart: "Kliknij i przeciągnij", - freehandDragMiddle: "aby rysować", - freehandDragEnd: "dowolny kształt", - freehandComplete: "wokół elementów. Zwolnij, aby zaznaczyć wszystkie elementy wewnątrz kształtu.", - moveStart: "Po wybraniu", - moveMiddle: "kliknij wewnątrz zaznaczenia,", - moveEnd: "i przeciągnij, aby przenieść wszystkie zaznaczone elementy razem." + tipLasso: 'Wskazówka: Zaznaczanie za pomocą narzędzia Lasso', + tipFreehandLasso: 'Wskazówka: Zaznaczanie narzędziem Lasso z wolnej ręki', + lassoDragStart: 'Kliknij i przeciągnij', + lassoDragEnd: + 'aby narysować prostokątne pole wyboru wokół elementów, które chcesz zaznaczyć.', + freehandDragStart: 'Kliknij i przeciągnij', + freehandDragMiddle: 'aby rysować', + freehandDragEnd: 'dowolny kształt', + freehandComplete: + 'wokół elementów. Zwolnij, aby zaznaczyć wszystkie elementy wewnątrz kształtu.', + moveStart: 'Po wybraniu', + moveMiddle: 'kliknij wewnątrz zaznaczenia,', + moveEnd: 'i przeciągnij, aby przenieść wszystkie zaznaczone elementy razem.' }, importHintTooltip: { - title: "Importuj Diagramy", - instructionStart: "Aby zaimportować diagramy, kliknij przycisk", - menuButton: "Przycisk menu", - instructionMiddle: "(☰) w lewym górnym rogu, a następnie wybierz", - openButton: "\"Otwórz\"", - instructionEnd: "aby załadować pliki diagramów." + title: 'Importuj Diagramy', + instructionStart: 'Aby zaimportować diagramy, kliknij przycisk', + menuButton: 'Przycisk menu', + instructionMiddle: '(☰) w lewym górnym rogu, a następnie wybierz', + openButton: '"Otwórz"', + instructionEnd: 'aby załadować pliki diagramów.' }, connectorRerouteTooltip: { - title: "Wskazówka: Zmiana trasy połączenia", - instructionStart: "Po umieszczeniu połączenia można je dowolnie przekierowywać..", - instructionSelect: "Wybierz połączenie", - instructionMiddle: "następnie", - instructionClick: "kliknij na ścieżkę połączenia", - instructionAnd: "i", - instructionDrag: "przesuń", - instructionEnd: "aby zmienić!" + title: 'Wskazówka: Zmiana trasy połączenia', + instructionStart: + 'Po umieszczeniu połączenia można je dowolnie przekierowywać..', + instructionSelect: 'Wybierz połączenie', + instructionMiddle: 'następnie', + instructionClick: 'kliknij na ścieżkę połączenia', + instructionAnd: 'i', + instructionDrag: 'przesuń', + instructionEnd: 'aby zmienić!' }, settings: { zoom: { - description: "Skonfiguruj zachowanie powiększania podczas korzystania z kółka myszy.", - zoomToCursor: "Powiększ do kursora", - zoomToCursorDesc: "Po włączeniu funkcji powiększanie/pomniejszanie odbywa się w oparciu o położenie kursora myszy. Po wyłączeniu funkcji Powiększ do kursora odbywa się w oparciu o położenie obszaru roboczego." + description: + 'Skonfiguruj zachowanie powiększania podczas korzystania z kółka myszy.', + zoomToCursor: 'Powiększ do kursora', + zoomToCursorDesc: + 'Po włączeniu funkcji powiększanie/pomniejszanie odbywa się w oparciu o położenie kursora myszy. Po wyłączeniu funkcji Powiększ do kursora odbywa się w oparciu o położenie obszaru roboczego.' }, hotkeys: { - title: "Ustawienia skrótów klawiszowych", - profile: "Profil skrótów klawiszowych", - profileQwerty: "QWERTY (Q, W, E, R, T, Y)", - profileSmnrct: "SMNRCT (S, M, N, R, C, T)", - profileNone: "bez skrótów", - tool: "Narzędzie", - hotkey: "Skrót", - toolSelect: "Wybór", - toolPan: "Przesuwanie", - toolAddItem: "Dodaj element", - toolRectangle: "Prostokąt", - toolConnector: "Połączenia", - toolText: "Tekst", - note: "Uwaga: Skróty klawiszowe działają, gdy nie wpisujesz tekstu w polach tekstowych." + title: 'Ustawienia skrótów klawiszowych', + profile: 'Profil skrótów klawiszowych', + profileQwerty: 'QWERTY (Q, W, E, R, T, Y)', + profileSmnrct: 'SMNRCT (S, M, N, R, C, T)', + profileNone: 'bez skrótów', + tool: 'Narzędzie', + hotkey: 'Skrót', + toolSelect: 'Wybór', + toolPan: 'Przesuwanie', + toolAddItem: 'Dodaj element', + toolRectangle: 'Prostokąt', + toolConnector: 'Połączenia', + toolText: 'Tekst', + note: 'Uwaga: Skróty klawiszowe działają, gdy nie wpisujesz tekstu w polach tekstowych.' }, pan: { - title: "Ustawienia przesuwania", - mousePanOptions: "Opcje przesuwania myszą", - emptyAreaClickPan: "Kliknij i przesuń obszar", - middleClickPan: "Kliknij środkowym przyciskiem myszy i przeciągnij", - rightClickPan: "Kliknij prawym przyciskiem myszy i przeciągnij", - ctrlClickPan: "Ctrl + kliknij i przeciągnij", - altClickPan: "Alt + kliknij i przeciągnij", - keyboardPanOptions: "Opcje przesuwania klawiaturą", - arrowKeys: "Klawisze strzałek", - wasdKeys: "Klawisze WASD", - ijklKeys: "Klawisze IJKL", - keyboardPanSpeed: "Szybkość przesuwu klawiatury", - note: "Uwaga: Opcje przesuwania działają dodatkowo w stosunku do dedykowanego narzędzia przesuwania." + title: 'Ustawienia przesuwania', + mousePanOptions: 'Opcje przesuwania myszą', + emptyAreaClickPan: 'Kliknij i przesuń obszar', + middleClickPan: 'Kliknij środkowym przyciskiem myszy i przeciągnij', + rightClickPan: 'Kliknij prawym przyciskiem myszy i przeciągnij', + ctrlClickPan: 'Ctrl + kliknij i przeciągnij', + altClickPan: 'Alt + kliknij i przeciągnij', + keyboardPanOptions: 'Opcje przesuwania klawiaturą', + arrowKeys: 'Klawisze strzałek', + wasdKeys: 'Klawisze WASD', + ijklKeys: 'Klawisze IJKL', + keyboardPanSpeed: 'Szybkość przesuwu klawiatury', + note: 'Uwaga: Opcje przesuwania działają dodatkowo w stosunku do dedykowanego narzędzia przesuwania.' }, connector: { - title: "Ustawienia połączeń", - connectionMode: "Tryb tworzenia połączenia", - clickMode: "Tryb kliknięcia (zalecany)", - clickModeDesc: "Kliknij pierwszy węzeł, a następnie kliknij drugi węzeł, aby utworzyć połączenie.", - dragMode: "Tryb przeciągania", - dragModeDesc: "Kliknij i przeciągnij od pierwszego węzła do drugiego węzła.", - note: "Uwaga: To ustawienie można zmienić w dowolnym momencie. Wybrany tryb będzie używany, gdy narzędzie Połączeń jest aktywne.." + title: 'Ustawienia połączeń', + connectionMode: 'Tryb tworzenia połączenia', + clickMode: 'Tryb kliknięcia (zalecany)', + clickModeDesc: + 'Kliknij pierwszy węzeł, a następnie kliknij drugi węzeł, aby utworzyć połączenie.', + dragMode: 'Tryb przeciągania', + dragModeDesc: + 'Kliknij i przeciągnij od pierwszego węzła do drugiego węzła.', + note: 'Uwaga: To ustawienie można zmienić w dowolnym momencie. Wybrany tryb będzie używany, gdy narzędzie Połączeń jest aktywne..' }, iconPacks: { - title: "Zarządzanie pakietami ikon", - lazyLoading: "Włącz opóźnione ładowanie", - lazyLoadingDesc: "Wczytuj pakiety ikon na żądanie, aby przyspieszyć uruchamianie", - availablePacks: "Dostępne pakiety ikon", - coreIsoflow: "Core Isoflow (Zawsze wczytane)", - alwaysEnabled: "Zawsze włączone", - awsPack: "AWS Icons", - gcpPack: "Google Cloud Icons", - azurePack: "Azure Icons", - kubernetesPack: "Kubernetes Icons", - loading: "Wczytywanie...", - loaded: "Wczytane", - notLoaded: "Niewczytane", - iconCount: "{count} icon", - lazyLoadingDisabledNote: "Opóźnione ładowanie jest wyłączone. Wszystkie pakiety ikon są ładowane podczas uruchamiania.", - note: "Pakiety ikon można włączać lub wyłączać w zależności od potrzeb. Wyłączone pakiety zmniejszają zużycie pamięci i poprawiają wydajność." + title: 'Zarządzanie pakietami ikon', + lazyLoading: 'Włącz opóźnione ładowanie', + lazyLoadingDesc: + 'Wczytuj pakiety ikon na żądanie, aby przyspieszyć uruchamianie', + availablePacks: 'Dostępne pakiety ikon', + coreIsoflow: 'Core Isoflow (Zawsze wczytane)', + alwaysEnabled: 'Zawsze włączone', + awsPack: 'AWS Icons', + gcpPack: 'Google Cloud Icons', + azurePack: 'Azure Icons', + kubernetesPack: 'Kubernetes Icons', + loading: 'Wczytywanie...', + loaded: 'Wczytane', + notLoaded: 'Niewczytane', + iconCount: '{count} icon', + lazyLoadingDisabledNote: + 'Opóźnione ładowanie jest wyłączone. Wszystkie pakiety ikon są ładowane podczas uruchamiania.', + note: 'Pakiety ikon można włączać lub wyłączać w zależności od potrzeb. Wyłączone pakiety zmniejszają zużycie pamięci i poprawiają wydajność.' } }, lazyLoadingWelcome: { - title: "Nowa funkcja: Opóźnione ładowanie!", - message: "Hej! W odpowiedzi na liczne prośby wprowadziliśmy funkcję opóźnionego ładowania ikon, więc teraz, jeśli chcesz włączyć niestandardowe pakiety ikon, możesz to zrobić w sekcji „Ustawienia”.", - configPath: "Kliknij ikonę manu.", - configPath2: "w lewym górnym rogu, aby uzyskać dostęp do ustawień.", - canDisable: "Jeśli chcesz, możesz wyłączyć tę funkcję..", - signature: "-Stan" + title: 'Nowa funkcja: Opóźnione ładowanie!', + message: + 'Hej! W odpowiedzi na liczne prośby wprowadziliśmy funkcję opóźnionego ładowania ikon, więc teraz, jeśli chcesz włączyć niestandardowe pakiety ikon, możesz to zrobić w sekcji „Ustawienia”.', + configPath: 'Kliknij ikonę manu.', + configPath2: 'w lewym górnym rogu, aby uzyskać dostęp do ustawień.', + canDisable: 'Jeśli chcesz, możesz wyłączyć tę funkcję..', + signature: '-Stan' } }; diff --git a/packages/fossflow-lib/src/i18n/pt-BR.ts b/packages/fossflow-lib/src/i18n/pt-BR.ts index 05166742..d2befd87 100644 --- a/packages/fossflow-lib/src/i18n/pt-BR.ts +++ b/packages/fossflow-lib/src/i18n/pt-BR.ts @@ -2,187 +2,198 @@ import { LocaleProps } from '../types/isoflowProps'; const locale: LocaleProps = { common: { - exampleText: "Este é um texto de exemplo" + exampleText: 'Este é um texto de exemplo' }, mainMenu: { - undo: "Desfazer", - redo: "Refazer", - open: "Abrir", - exportJson: "Exportar como JSON", - exportCompactJson: "Exportar como JSON compacto", - exportImage: "Exportar como imagem", - clearCanvas: "Limpar a tela", - settings: "Configurações", - gitHub: "GitHub" + undo: 'Desfazer', + redo: 'Refazer', + open: 'Abrir', + exportJson: 'Exportar como JSON', + exportCompactJson: 'Exportar como JSON compacto', + exportImage: 'Exportar como imagem', + clearCanvas: 'Limpar a tela', + settings: 'Configurações', + gitHub: 'GitHub' }, helpDialog: { - title: "Atalhos de teclado e ajuda", - close: "Fechar", - keyboardShortcuts: "Atalhos de teclado", - mouseInteractions: "Interações do mouse", - action: "Ação", - shortcut: "Atalho", - method: "Método", - description: "Descrição", - note: "Nota:", - noteContent: "Os atalhos de teclado são desabilitados ao digitar em campos de entrada, áreas de texto ou elementos editáveis para evitar conflitos.", + title: 'Atalhos de teclado e ajuda', + close: 'Fechar', + keyboardShortcuts: 'Atalhos de teclado', + mouseInteractions: 'Interações do mouse', + action: 'Ação', + shortcut: 'Atalho', + method: 'Método', + description: 'Descrição', + note: 'Nota:', + noteContent: + 'Os atalhos de teclado são desabilitados ao digitar em campos de entrada, áreas de texto ou elementos editáveis para evitar conflitos.', // Keyboard shortcuts - undoAction: "Desfazer", - undoDescription: "Desfazer a última ação", - redoAction: "Refazer", - redoDescription: "Refazer a última ação desfeita", - redoAltAction: "Refazer (Alternativo)", - redoAltDescription: "Atalho alternativo para refazer", - helpAction: "Ajuda", - helpDescription: "Abrir diálogo de ajuda com atalhos de teclado", - zoomInAction: "Aumentar zoom", - zoomInShortcut: "Roda do mouse para cima", - zoomInDescription: "Aumentar o zoom na tela", - zoomOutAction: "Diminuir zoom", - zoomOutShortcut: "Roda do mouse para baixo", - zoomOutDescription: "Diminuir o zoom da tela", - panCanvasAction: "Mover tela", - panCanvasShortcut: "Clique esquerdo + Arrastar", - panCanvasDescription: "Mover a tela no modo de movimentação", - contextMenuAction: "Menu de contexto", - contextMenuShortcut: "Clique direito", - contextMenuDescription: "Abrir menu de contexto para itens ou espaço vazio", + undoAction: 'Desfazer', + undoDescription: 'Desfazer a última ação', + redoAction: 'Refazer', + redoDescription: 'Refazer a última ação desfeita', + redoAltAction: 'Refazer (Alternativo)', + redoAltDescription: 'Atalho alternativo para refazer', + helpAction: 'Ajuda', + helpDescription: 'Abrir diálogo de ajuda com atalhos de teclado', + zoomInAction: 'Aumentar zoom', + zoomInShortcut: 'Roda do mouse para cima', + zoomInDescription: 'Aumentar o zoom na tela', + zoomOutAction: 'Diminuir zoom', + zoomOutShortcut: 'Roda do mouse para baixo', + zoomOutDescription: 'Diminuir o zoom da tela', + panCanvasAction: 'Mover tela', + panCanvasShortcut: 'Clique esquerdo + Arrastar', + panCanvasDescription: 'Mover a tela no modo de movimentação', + contextMenuAction: 'Menu de contexto', + contextMenuShortcut: 'Clique direito', + contextMenuDescription: 'Abrir menu de contexto para itens ou espaço vazio', // Mouse interactions - selectToolAction: "Ferramenta de seleção", - selectToolShortcut: "Clique no botão Selecionar", - selectToolDescription: "Mudar para o modo de seleção", - panToolAction: "Ferramenta de movimentação", - panToolShortcut: "Clique no botão Mover", - panToolDescription: "Mudar para o modo de movimentação da tela", - addItemAction: "Adicionar item", - addItemShortcut: "Clique no botão Adicionar item", - addItemDescription: "Abrir seletor de ícones para adicionar novos itens", - drawRectangleAction: "Desenhar retângulo", - drawRectangleShortcut: "Clique no botão Retângulo", - drawRectangleDescription: "Mudar para o modo de desenho de retângulos", - createConnectorAction: "Criar conector", - createConnectorShortcut: "Clique no botão Conector", - createConnectorDescription: "Mudar para o modo de conector", - addTextAction: "Adicionar texto", - addTextShortcut: "Clique no botão Texto", - addTextDescription: "Criar uma nova caixa de texto" + selectToolAction: 'Ferramenta de seleção', + selectToolShortcut: 'Clique no botão Selecionar', + selectToolDescription: 'Mudar para o modo de seleção', + panToolAction: 'Ferramenta de movimentação', + panToolShortcut: 'Clique no botão Mover', + panToolDescription: 'Mudar para o modo de movimentação da tela', + addItemAction: 'Adicionar item', + addItemShortcut: 'Clique no botão Adicionar item', + addItemDescription: 'Abrir seletor de ícones para adicionar novos itens', + drawRectangleAction: 'Desenhar retângulo', + drawRectangleShortcut: 'Clique no botão Retângulo', + drawRectangleDescription: 'Mudar para o modo de desenho de retângulos', + createConnectorAction: 'Criar conector', + createConnectorShortcut: 'Clique no botão Conector', + createConnectorDescription: 'Mudar para o modo de conector', + addTextAction: 'Adicionar texto', + addTextShortcut: 'Clique no botão Texto', + addTextDescription: 'Criar uma nova caixa de texto' }, connectorHintTooltip: { - tipCreatingConnectors: "Dica: Criar conectores", - tipConnectorTools: "Dica: Ferramentas de conectores", - clickInstructionStart: "Clique", - clickInstructionMiddle: "no primeiro nó ou ponto, depois", - clickInstructionEnd: "no segundo nó ou ponto para criar uma conexão.", - nowClickTarget: "Agora clique no alvo para completar a conexão.", - dragStart: "Arraste", - dragEnd: "do primeiro nó ao segundo nó para criar uma conexão.", - rerouteStart: "Para redirecionar um conector,", - rerouteMiddle: "clique com o botão esquerdo", - rerouteEnd: "em qualquer ponto ao longo da linha do conector e arraste para criar ou mover pontos de ancoragem." + tipCreatingConnectors: 'Dica: Criar conectores', + tipConnectorTools: 'Dica: Ferramentas de conectores', + clickInstructionStart: 'Clique', + clickInstructionMiddle: 'no primeiro nó ou ponto, depois', + clickInstructionEnd: 'no segundo nó ou ponto para criar uma conexão.', + nowClickTarget: 'Agora clique no alvo para completar a conexão.', + dragStart: 'Arraste', + dragEnd: 'do primeiro nó ao segundo nó para criar uma conexão.', + rerouteStart: 'Para redirecionar um conector,', + rerouteMiddle: 'clique com o botão esquerdo', + rerouteEnd: + 'em qualquer ponto ao longo da linha do conector e arraste para criar ou mover pontos de ancoragem.' }, lassoHintTooltip: { - tipLasso: "Dica: Seleção com laço", - tipFreehandLasso: "Dica: Seleção com laço livre", - lassoDragStart: "Clique e arraste", - lassoDragEnd: "para desenhar uma caixa de seleção retangular ao redor dos itens que você deseja selecionar.", - freehandDragStart: "Clique e arraste", - freehandDragMiddle: "para desenhar uma", - freehandDragEnd: "forma livre", - freehandComplete: "ao redor dos itens. Solte para selecionar todos os itens dentro da forma.", - moveStart: "Uma vez selecionados,", - moveMiddle: "clique dentro da seleção", - moveEnd: "e arraste para mover todos os itens selecionados juntos." + tipLasso: 'Dica: Seleção com laço', + tipFreehandLasso: 'Dica: Seleção com laço livre', + lassoDragStart: 'Clique e arraste', + lassoDragEnd: + 'para desenhar uma caixa de seleção retangular ao redor dos itens que você deseja selecionar.', + freehandDragStart: 'Clique e arraste', + freehandDragMiddle: 'para desenhar uma', + freehandDragEnd: 'forma livre', + freehandComplete: + 'ao redor dos itens. Solte para selecionar todos os itens dentro da forma.', + moveStart: 'Uma vez selecionados,', + moveMiddle: 'clique dentro da seleção', + moveEnd: 'e arraste para mover todos os itens selecionados juntos.' }, importHintTooltip: { - title: "Importar diagramas", - instructionStart: "Para importar diagramas, clique no", - menuButton: "botão de menu", - instructionMiddle: "(☰) no canto superior esquerdo, depois selecione", - openButton: "\"Abrir\"", - instructionEnd: "para carregar seus arquivos de diagrama." + title: 'Importar diagramas', + instructionStart: 'Para importar diagramas, clique no', + menuButton: 'botão de menu', + instructionMiddle: '(☰) no canto superior esquerdo, depois selecione', + openButton: '"Abrir"', + instructionEnd: 'para carregar seus arquivos de diagrama.' }, connectorRerouteTooltip: { - title: "Dica: Redirecionar conectores", - instructionStart: "Uma vez que seus conectores estejam posicionados, você pode redirecioná-los como desejar.", - instructionSelect: "Selecione o conector", - instructionMiddle: "primeiro, depois", - instructionClick: "clique no caminho do conector", - instructionAnd: "e", - instructionDrag: "arraste", - instructionEnd: "para alterá-lo!" + title: 'Dica: Redirecionar conectores', + instructionStart: + 'Uma vez que seus conectores estejam posicionados, você pode redirecioná-los como desejar.', + instructionSelect: 'Selecione o conector', + instructionMiddle: 'primeiro, depois', + instructionClick: 'clique no caminho do conector', + instructionAnd: 'e', + instructionDrag: 'arraste', + instructionEnd: 'para alterá-lo!' }, settings: { zoom: { - description: "Configurar o comportamento do zoom ao usar a roda do mouse.", - zoomToCursor: "Zoom no cursor", - zoomToCursorDesc: "Quando habilitado, o zoom é centralizado na posição do cursor do mouse. Quando desabilitado, o zoom é centralizado na tela." + description: + 'Configurar o comportamento do zoom ao usar a roda do mouse.', + zoomToCursor: 'Zoom no cursor', + zoomToCursorDesc: + 'Quando habilitado, o zoom é centralizado na posição do cursor do mouse. Quando desabilitado, o zoom é centralizado na tela.' }, hotkeys: { - title: "Configurações de atalhos", - profile: "Perfil de atalhos", - profileQwerty: "QWERTY (Q, W, E, R, T, Y)", - profileSmnrct: "SMNRCT (S, M, N, R, C, T)", - profileNone: "Sem atalhos", - tool: "Ferramenta", - hotkey: "Atalho", - toolSelect: "Selecionar", - toolPan: "Mover", - toolAddItem: "Adicionar item", - toolRectangle: "Retângulo", - toolConnector: "Conector", - toolText: "Texto", - note: "Nota: Os atalhos funcionam quando você não está digitando em campos de texto" + title: 'Configurações de atalhos', + profile: 'Perfil de atalhos', + profileQwerty: 'QWERTY (Q, W, E, R, T, Y)', + profileSmnrct: 'SMNRCT (S, M, N, R, C, T)', + profileNone: 'Sem atalhos', + tool: 'Ferramenta', + hotkey: 'Atalho', + toolSelect: 'Selecionar', + toolPan: 'Mover', + toolAddItem: 'Adicionar item', + toolRectangle: 'Retângulo', + toolConnector: 'Conector', + toolText: 'Texto', + note: 'Nota: Os atalhos funcionam quando você não está digitando em campos de texto' }, pan: { - title: "Configurações de movimentação", - mousePanOptions: "Opções de movimentação com mouse", - emptyAreaClickPan: "Clicar e arrastar em área vazia", - middleClickPan: "Clicar com o botão do meio e arrastar", - rightClickPan: "Clicar com o botão direito e arrastar", - ctrlClickPan: "Ctrl + clicar e arrastar", - altClickPan: "Alt + clicar e arrastar", - keyboardPanOptions: "Opções de movimentação com teclado", - arrowKeys: "Teclas de seta", - wasdKeys: "Teclas WASD", - ijklKeys: "Teclas IJKL", - keyboardPanSpeed: "Velocidade de movimentação com teclado", - note: "Nota: As opções de movimentação funcionam além da ferramenta de movimentação dedicada" + title: 'Configurações de movimentação', + mousePanOptions: 'Opções de movimentação com mouse', + emptyAreaClickPan: 'Clicar e arrastar em área vazia', + middleClickPan: 'Clicar com o botão do meio e arrastar', + rightClickPan: 'Clicar com o botão direito e arrastar', + ctrlClickPan: 'Ctrl + clicar e arrastar', + altClickPan: 'Alt + clicar e arrastar', + keyboardPanOptions: 'Opções de movimentação com teclado', + arrowKeys: 'Teclas de seta', + wasdKeys: 'Teclas WASD', + ijklKeys: 'Teclas IJKL', + keyboardPanSpeed: 'Velocidade de movimentação com teclado', + note: 'Nota: As opções de movimentação funcionam além da ferramenta de movimentação dedicada' }, connector: { - title: "Configurações de conectores", - connectionMode: "Modo de criação de conexão", - clickMode: "Modo clique (Recomendado)", - clickModeDesc: "Clique no primeiro nó, depois clique no segundo nó para criar uma conexão", - dragMode: "Modo arrastar", - dragModeDesc: "Clique e arraste do primeiro nó ao segundo nó", - note: "Nota: Você pode alterar esta configuração a qualquer momento. O modo selecionado será usado quando a ferramenta de conector estiver ativa." + title: 'Configurações de conectores', + connectionMode: 'Modo de criação de conexão', + clickMode: 'Modo clique (Recomendado)', + clickModeDesc: + 'Clique no primeiro nó, depois clique no segundo nó para criar uma conexão', + dragMode: 'Modo arrastar', + dragModeDesc: 'Clique e arraste do primeiro nó ao segundo nó', + note: 'Nota: Você pode alterar esta configuração a qualquer momento. O modo selecionado será usado quando a ferramenta de conector estiver ativa.' }, iconPacks: { - title: "Gerenciamento de Pacotes de Ícones", - lazyLoading: "Ativar Carregamento Sob Demanda", - lazyLoadingDesc: "Carregar pacotes de ícones sob demanda para inicialização mais rápida", - availablePacks: "Pacotes de Ícones Disponíveis", - coreIsoflow: "Core Isoflow (Sempre Carregado)", - alwaysEnabled: "Sempre ativado", - awsPack: "Ícones AWS", - gcpPack: "Ícones Google Cloud", - azurePack: "Ícones Azure", - kubernetesPack: "Ícones Kubernetes", - loading: "Carregando...", - loaded: "Carregado", - notLoaded: "Não carregado", - iconCount: "{count} ícones", - lazyLoadingDisabledNote: "O carregamento sob demanda está desativado. Todos os pacotes de ícones são carregados na inicialização.", - note: "Os pacotes de ícones podem ser ativados ou desativados conforme suas necessidades. Pacotes desativados reduzirão o uso de memória e melhorarão o desempenho." + title: 'Gerenciamento de Pacotes de Ícones', + lazyLoading: 'Ativar Carregamento Sob Demanda', + lazyLoadingDesc: + 'Carregar pacotes de ícones sob demanda para inicialização mais rápida', + availablePacks: 'Pacotes de Ícones Disponíveis', + coreIsoflow: 'Core Isoflow (Sempre Carregado)', + alwaysEnabled: 'Sempre ativado', + awsPack: 'Ícones AWS', + gcpPack: 'Ícones Google Cloud', + azurePack: 'Ícones Azure', + kubernetesPack: 'Ícones Kubernetes', + loading: 'Carregando...', + loaded: 'Carregado', + notLoaded: 'Não carregado', + iconCount: '{count} ícones', + lazyLoadingDisabledNote: + 'O carregamento sob demanda está desativado. Todos os pacotes de ícones são carregados na inicialização.', + note: 'Os pacotes de ícones podem ser ativados ou desativados conforme suas necessidades. Pacotes desativados reduzirão o uso de memória e melhorarão o desempenho.' } }, lazyLoadingWelcome: { - title: "Novo Recurso: Carregamento Sob Demanda!", - message: "Ei! Após demanda popular, implementamos o Carregamento Sob Demanda de ícones, então agora se você quiser ativar pacotes de ícones não padrão, você pode ativá-los na seção 'Configuração'.", - configPath: "Clique no ícone do Menu", - configPath2: "no canto superior esquerdo para acessar a Configuração.", - canDisable: "Você pode desativar esse comportamento se desejar.", - signature: "-Stan" + title: 'Novo Recurso: Carregamento Sob Demanda!', + message: + "Ei! Após demanda popular, implementamos o Carregamento Sob Demanda de ícones, então agora se você quiser ativar pacotes de ícones não padrão, você pode ativá-los na seção 'Configuração'.", + configPath: 'Clique no ícone do Menu', + configPath2: 'no canto superior esquerdo para acessar a Configuração.', + canDisable: 'Você pode desativar esse comportamento se desejar.', + signature: '-Stan' } }; diff --git a/packages/fossflow-lib/src/i18n/ru-RU.ts b/packages/fossflow-lib/src/i18n/ru-RU.ts index 81fd0a54..8e453d6b 100644 --- a/packages/fossflow-lib/src/i18n/ru-RU.ts +++ b/packages/fossflow-lib/src/i18n/ru-RU.ts @@ -2,187 +2,199 @@ import { LocaleProps } from '../types/isoflowProps'; const locale: LocaleProps = { common: { - exampleText: "Это пример текста" + exampleText: 'Это пример текста' }, mainMenu: { - undo: "Отменить", - redo: "Повторить", - open: "Открыть", - exportJson: "Экспортировать как JSON", - exportCompactJson: "Экспортировать как компактный JSON", - exportImage: "Экспортировать как изображение", - clearCanvas: "Очистить холст", - settings: "Настройки", - gitHub: "GitHub" + undo: 'Отменить', + redo: 'Повторить', + open: 'Открыть', + exportJson: 'Экспортировать как JSON', + exportCompactJson: 'Экспортировать как компактный JSON', + exportImage: 'Экспортировать как изображение', + clearCanvas: 'Очистить холст', + settings: 'Настройки', + gitHub: 'GitHub' }, helpDialog: { - title: "Горячие клавиши и справка", - close: "Закрыть", - keyboardShortcuts: "Горячие клавиши", - mouseInteractions: "Взаимодействие с мышью", - action: "Действие", - shortcut: "Горячая клавиша", - method: "Метод", - description: "Описание", - note: "Примечание:", - noteContent: "Горячие клавиши отключены при вводе в полях ввода, текстовых областях или редактируемых элементах во избежание конфликтов.", + title: 'Горячие клавиши и справка', + close: 'Закрыть', + keyboardShortcuts: 'Горячие клавиши', + mouseInteractions: 'Взаимодействие с мышью', + action: 'Действие', + shortcut: 'Горячая клавиша', + method: 'Метод', + description: 'Описание', + note: 'Примечание:', + noteContent: + 'Горячие клавиши отключены при вводе в полях ввода, текстовых областях или редактируемых элементах во избежание конфликтов.', // Keyboard shortcuts - undoAction: "Отменить", - undoDescription: "Отменить последнее действие", - redoAction: "Повторить", - redoDescription: "Повторить последнее отмененное действие", - redoAltAction: "Повторить (альтернатива)", - redoAltDescription: "Альтернативная горячая клавиша для повтора", - helpAction: "Справка", - helpDescription: "Открыть диалог справки с горячими клавишами", - zoomInAction: "Увеличить", - zoomInShortcut: "Колесико мыши вверх", - zoomInDescription: "Увеличить масштаб холста", - zoomOutAction: "Уменьшить", - zoomOutShortcut: "Колесико мыши вниз", - zoomOutDescription: "Уменьшить масштаб холста", - panCanvasAction: "Переместить холст", - panCanvasShortcut: "Левая кнопка + перетаскивание", - panCanvasDescription: "Переместить холст в режиме перемещения", - contextMenuAction: "Контекстное меню", - contextMenuShortcut: "Правая кнопка мыши", - contextMenuDescription: "Открыть контекстное меню для элементов или пустого пространства", + undoAction: 'Отменить', + undoDescription: 'Отменить последнее действие', + redoAction: 'Повторить', + redoDescription: 'Повторить последнее отмененное действие', + redoAltAction: 'Повторить (альтернатива)', + redoAltDescription: 'Альтернативная горячая клавиша для повтора', + helpAction: 'Справка', + helpDescription: 'Открыть диалог справки с горячими клавишами', + zoomInAction: 'Увеличить', + zoomInShortcut: 'Колесико мыши вверх', + zoomInDescription: 'Увеличить масштаб холста', + zoomOutAction: 'Уменьшить', + zoomOutShortcut: 'Колесико мыши вниз', + zoomOutDescription: 'Уменьшить масштаб холста', + panCanvasAction: 'Переместить холст', + panCanvasShortcut: 'Левая кнопка + перетаскивание', + panCanvasDescription: 'Переместить холст в режиме перемещения', + contextMenuAction: 'Контекстное меню', + contextMenuShortcut: 'Правая кнопка мыши', + contextMenuDescription: + 'Открыть контекстное меню для элементов или пустого пространства', // Mouse interactions - selectToolAction: "Инструмент выделения", - selectToolShortcut: "Нажать кнопку Выделить", - selectToolDescription: "Переключиться в режим выделения", - panToolAction: "Инструмент перемещения", - panToolShortcut: "Нажать кнопку Переместить", - panToolDescription: "Переключиться в режим перемещения холста", - addItemAction: "Добавить элемент", - addItemShortcut: "Нажать кнопку Добавить элемент", - addItemDescription: "Открыть выбор иконок для добавления новых элементов", - drawRectangleAction: "Нарисовать прямоугольник", - drawRectangleShortcut: "Нажать кнопку Прямоугольник", - drawRectangleDescription: "Переключиться в режим рисования прямоугольников", - createConnectorAction: "Создать соединитель", - createConnectorShortcut: "Нажать кнопку Соединитель", - createConnectorDescription: "Переключиться в режим соединителя", - addTextAction: "Добавить текст", - addTextShortcut: "Нажать кнопку Текст", - addTextDescription: "Создать новое текстовое поле" + selectToolAction: 'Инструмент выделения', + selectToolShortcut: 'Нажать кнопку Выделить', + selectToolDescription: 'Переключиться в режим выделения', + panToolAction: 'Инструмент перемещения', + panToolShortcut: 'Нажать кнопку Переместить', + panToolDescription: 'Переключиться в режим перемещения холста', + addItemAction: 'Добавить элемент', + addItemShortcut: 'Нажать кнопку Добавить элемент', + addItemDescription: 'Открыть выбор иконок для добавления новых элементов', + drawRectangleAction: 'Нарисовать прямоугольник', + drawRectangleShortcut: 'Нажать кнопку Прямоугольник', + drawRectangleDescription: 'Переключиться в режим рисования прямоугольников', + createConnectorAction: 'Создать соединитель', + createConnectorShortcut: 'Нажать кнопку Соединитель', + createConnectorDescription: 'Переключиться в режим соединителя', + addTextAction: 'Добавить текст', + addTextShortcut: 'Нажать кнопку Текст', + addTextDescription: 'Создать новое текстовое поле' }, connectorHintTooltip: { - tipCreatingConnectors: "Совет: Создание соединителей", - tipConnectorTools: "Совет: Инструменты соединителей", - clickInstructionStart: "Нажмите", - clickInstructionMiddle: "на первый узел или точку, затем", - clickInstructionEnd: "на второй узел или точку, чтобы создать соединение.", - nowClickTarget: "Теперь нажмите на цель, чтобы завершить соединение.", - dragStart: "Перетащите", - dragEnd: "от первого узла ко второму узлу, чтобы создать соединение.", - rerouteStart: "Чтобы изменить маршрут соединителя,", - rerouteMiddle: "нажмите левой кнопкой", - rerouteEnd: "на любую точку вдоль линии соединителя и перетащите, чтобы создать или переместить опорные точки." + tipCreatingConnectors: 'Совет: Создание соединителей', + tipConnectorTools: 'Совет: Инструменты соединителей', + clickInstructionStart: 'Нажмите', + clickInstructionMiddle: 'на первый узел или точку, затем', + clickInstructionEnd: 'на второй узел или точку, чтобы создать соединение.', + nowClickTarget: 'Теперь нажмите на цель, чтобы завершить соединение.', + dragStart: 'Перетащите', + dragEnd: 'от первого узла ко второму узлу, чтобы создать соединение.', + rerouteStart: 'Чтобы изменить маршрут соединителя,', + rerouteMiddle: 'нажмите левой кнопкой', + rerouteEnd: + 'на любую точку вдоль линии соединителя и перетащите, чтобы создать или переместить опорные точки.' }, lassoHintTooltip: { - tipLasso: "Совет: Выделение лассо", - tipFreehandLasso: "Совет: Свободное выделение лассо", - lassoDragStart: "Нажмите и перетащите", - lassoDragEnd: "чтобы нарисовать прямоугольную область выделения вокруг элементов, которые вы хотите выбрать.", - freehandDragStart: "Нажмите и перетащите", - freehandDragMiddle: "чтобы нарисовать", - freehandDragEnd: "произвольную форму", - freehandComplete: "вокруг элементов. Отпустите, чтобы выбрать все элементы внутри формы.", - moveStart: "После выделения", - moveMiddle: "нажмите внутри выделения", - moveEnd: "и перетащите, чтобы переместить все выделенные элементы вместе." + tipLasso: 'Совет: Выделение лассо', + tipFreehandLasso: 'Совет: Свободное выделение лассо', + lassoDragStart: 'Нажмите и перетащите', + lassoDragEnd: + 'чтобы нарисовать прямоугольную область выделения вокруг элементов, которые вы хотите выбрать.', + freehandDragStart: 'Нажмите и перетащите', + freehandDragMiddle: 'чтобы нарисовать', + freehandDragEnd: 'произвольную форму', + freehandComplete: + 'вокруг элементов. Отпустите, чтобы выбрать все элементы внутри формы.', + moveStart: 'После выделения', + moveMiddle: 'нажмите внутри выделения', + moveEnd: 'и перетащите, чтобы переместить все выделенные элементы вместе.' }, importHintTooltip: { - title: "Импорт диаграмм", - instructionStart: "Чтобы импортировать диаграммы, нажмите", - menuButton: "кнопку меню", - instructionMiddle: "(☰) в верхнем левом углу, затем выберите", - openButton: "\"Открыть\"", - instructionEnd: "чтобы загрузить файлы диаграмм." + title: 'Импорт диаграмм', + instructionStart: 'Чтобы импортировать диаграммы, нажмите', + menuButton: 'кнопку меню', + instructionMiddle: '(☰) в верхнем левом углу, затем выберите', + openButton: '"Открыть"', + instructionEnd: 'чтобы загрузить файлы диаграмм.' }, connectorRerouteTooltip: { - title: "Совет: Изменение маршрута соединителей", - instructionStart: "После размещения соединителей вы можете изменить их маршрут по своему усмотрению.", - instructionSelect: "Выберите соединитель", - instructionMiddle: "сначала, затем", - instructionClick: "нажмите на путь соединителя", - instructionAnd: "и", - instructionDrag: "перетащите", - instructionEnd: "чтобы изменить его!" + title: 'Совет: Изменение маршрута соединителей', + instructionStart: + 'После размещения соединителей вы можете изменить их маршрут по своему усмотрению.', + instructionSelect: 'Выберите соединитель', + instructionMiddle: 'сначала, затем', + instructionClick: 'нажмите на путь соединителя', + instructionAnd: 'и', + instructionDrag: 'перетащите', + instructionEnd: 'чтобы изменить его!' }, settings: { zoom: { - description: "Настройте поведение масштабирования при использовании колесика мыши.", - zoomToCursor: "Масштабировать к курсору", - zoomToCursorDesc: "При включении масштабирование центрируется на позиции курсора мыши. При выключении масштабирование центрируется на холсте." + description: + 'Настройте поведение масштабирования при использовании колесика мыши.', + zoomToCursor: 'Масштабировать к курсору', + zoomToCursorDesc: + 'При включении масштабирование центрируется на позиции курсора мыши. При выключении масштабирование центрируется на холсте.' }, hotkeys: { - title: "Настройки горячих клавиш", - profile: "Профиль горячих клавиш", - profileQwerty: "QWERTY (Q, W, E, R, T, Y)", - profileSmnrct: "SMNRCT (S, M, N, R, C, T)", - profileNone: "Без горячих клавиш", - tool: "Инструмент", - hotkey: "Горячая клавиша", - toolSelect: "Выделить", - toolPan: "Переместить", - toolAddItem: "Добавить элемент", - toolRectangle: "Прямоугольник", - toolConnector: "Соединитель", - toolText: "Текст", - note: "Примечание: Горячие клавиши работают, когда вы не вводите текст в текстовых полях" + title: 'Настройки горячих клавиш', + profile: 'Профиль горячих клавиш', + profileQwerty: 'QWERTY (Q, W, E, R, T, Y)', + profileSmnrct: 'SMNRCT (S, M, N, R, C, T)', + profileNone: 'Без горячих клавиш', + tool: 'Инструмент', + hotkey: 'Горячая клавиша', + toolSelect: 'Выделить', + toolPan: 'Переместить', + toolAddItem: 'Добавить элемент', + toolRectangle: 'Прямоугольник', + toolConnector: 'Соединитель', + toolText: 'Текст', + note: 'Примечание: Горячие клавиши работают, когда вы не вводите текст в текстовых полях' }, pan: { - title: "Настройки перемещения", - mousePanOptions: "Параметры перемещения мышью", - emptyAreaClickPan: "Нажать и перетащить на пустой области", - middleClickPan: "Средняя кнопка и перетаскивание", - rightClickPan: "Правая кнопка и перетаскивание", - ctrlClickPan: "Ctrl + нажатие и перетаскивание", - altClickPan: "Alt + нажатие и перетаскивание", - keyboardPanOptions: "Параметры перемещения клавиатурой", - arrowKeys: "Клавиши стрелок", - wasdKeys: "Клавиши WASD", - ijklKeys: "Клавиши IJKL", - keyboardPanSpeed: "Скорость перемещения клавиатурой", - note: "Примечание: Параметры перемещения работают в дополнение к специальному инструменту перемещения" + title: 'Настройки перемещения', + mousePanOptions: 'Параметры перемещения мышью', + emptyAreaClickPan: 'Нажать и перетащить на пустой области', + middleClickPan: 'Средняя кнопка и перетаскивание', + rightClickPan: 'Правая кнопка и перетаскивание', + ctrlClickPan: 'Ctrl + нажатие и перетаскивание', + altClickPan: 'Alt + нажатие и перетаскивание', + keyboardPanOptions: 'Параметры перемещения клавиатурой', + arrowKeys: 'Клавиши стрелок', + wasdKeys: 'Клавиши WASD', + ijklKeys: 'Клавиши IJKL', + keyboardPanSpeed: 'Скорость перемещения клавиатурой', + note: 'Примечание: Параметры перемещения работают в дополнение к специальному инструменту перемещения' }, connector: { - title: "Настройки соединителя", - connectionMode: "Режим создания соединения", - clickMode: "Режим нажатия (рекомендуется)", - clickModeDesc: "Нажмите на первый узел, затем нажмите на второй узел, чтобы создать соединение", - dragMode: "Режим перетаскивания", - dragModeDesc: "Нажмите и перетащите от первого узла ко второму узлу", - note: "Примечание: Вы можете изменить эту настройку в любое время. Выбранный режим будет использоваться, когда инструмент соединителя активен." + title: 'Настройки соединителя', + connectionMode: 'Режим создания соединения', + clickMode: 'Режим нажатия (рекомендуется)', + clickModeDesc: + 'Нажмите на первый узел, затем нажмите на второй узел, чтобы создать соединение', + dragMode: 'Режим перетаскивания', + dragModeDesc: 'Нажмите и перетащите от первого узла ко второму узлу', + note: 'Примечание: Вы можете изменить эту настройку в любое время. Выбранный режим будет использоваться, когда инструмент соединителя активен.' }, iconPacks: { - title: "Управление Пакетами Иконок", - lazyLoading: "Включить Ленивую Загрузку", - lazyLoadingDesc: "Загружать пакеты иконок по требованию для более быстрого запуска", - availablePacks: "Доступные Пакеты Иконок", - coreIsoflow: "Core Isoflow (Всегда Загружен)", - alwaysEnabled: "Всегда включено", - awsPack: "Иконки AWS", - gcpPack: "Иконки Google Cloud", - azurePack: "Иконки Azure", - kubernetesPack: "Иконки Kubernetes", - loading: "Загрузка...", - loaded: "Загружено", - notLoaded: "Не загружено", - iconCount: "{count} иконок", - lazyLoadingDisabledNote: "Ленивая загрузка отключена. Все пакеты иконок загружаются при запуске.", - note: "Пакеты иконок могут быть включены или отключены в зависимости от ваших потребностей. Отключенные пакеты уменьшат использование памяти и улучшат производительность." + title: 'Управление Пакетами Иконок', + lazyLoading: 'Включить Ленивую Загрузку', + lazyLoadingDesc: + 'Загружать пакеты иконок по требованию для более быстрого запуска', + availablePacks: 'Доступные Пакеты Иконок', + coreIsoflow: 'Core Isoflow (Всегда Загружен)', + alwaysEnabled: 'Всегда включено', + awsPack: 'Иконки AWS', + gcpPack: 'Иконки Google Cloud', + azurePack: 'Иконки Azure', + kubernetesPack: 'Иконки Kubernetes', + loading: 'Загрузка...', + loaded: 'Загружено', + notLoaded: 'Не загружено', + iconCount: '{count} иконок', + lazyLoadingDisabledNote: + 'Ленивая загрузка отключена. Все пакеты иконок загружаются при запуске.', + note: 'Пакеты иконок могут быть включены или отключены в зависимости от ваших потребностей. Отключенные пакеты уменьшат использование памяти и улучшат производительность.' } }, lazyLoadingWelcome: { - title: "Новая Функция: Ленивая Загрузка!", - message: "Привет! По многочисленным просьбам мы реализовали Ленивую Загрузку иконок, поэтому теперь, если вы хотите включить нестандартные пакеты иконок, вы можете включить их в разделе 'Конфигурация'.", - configPath: "Нажмите на иконку Гамбургер", - configPath2: "в верхнем левом углу, чтобы получить доступ к Конфигурации.", - canDisable: "Вы можете отключить это поведение, если хотите.", - signature: "-Stan" + title: 'Новая Функция: Ленивая Загрузка!', + message: + "Привет! По многочисленным просьбам мы реализовали Ленивую Загрузку иконок, поэтому теперь, если вы хотите включить нестандартные пакеты иконок, вы можете включить их в разделе 'Конфигурация'.", + configPath: 'Нажмите на иконку Гамбургер', + configPath2: 'в верхнем левом углу, чтобы получить доступ к Конфигурации.', + canDisable: 'Вы можете отключить это поведение, если хотите.', + signature: '-Stan' } }; diff --git a/packages/fossflow-lib/src/i18n/zh-CN.ts b/packages/fossflow-lib/src/i18n/zh-CN.ts index 9bfd5f04..94be6d89 100644 --- a/packages/fossflow-lib/src/i18n/zh-CN.ts +++ b/packages/fossflow-lib/src/i18n/zh-CN.ts @@ -2,187 +2,190 @@ import { LocaleProps } from '../types/isoflowProps'; const locale: LocaleProps = { common: { - exampleText: "这是一段示例文本" + exampleText: '这是一段示例文本' }, mainMenu: { - undo: "撤销", - redo: "重做", - open: "打开", - exportJson: "导出为 JSON", - exportCompactJson: "导出为紧凑 JSON", - exportImage: "导出为图片", - clearCanvas: "清空画布", - settings: "设置", - gitHub: "GitHub" + undo: '撤销', + redo: '重做', + open: '打开', + exportJson: '导出为 JSON', + exportCompactJson: '导出为紧凑 JSON', + exportImage: '导出为图片', + clearCanvas: '清空画布', + settings: '设置', + gitHub: 'GitHub' }, helpDialog: { - title: "键盘快捷键和帮助", - close: "关闭", - keyboardShortcuts: "键盘快捷键", - mouseInteractions: "鼠标交互", - action: "操作", - shortcut: "快捷键", - method: "方法", - description: "描述", - note: "注意:", - noteContent: "在输入框、文本区域或可编辑内容元素中键入时,键盘快捷键会被禁用,以防止冲突。", + title: '键盘快捷键和帮助', + close: '关闭', + keyboardShortcuts: '键盘快捷键', + mouseInteractions: '鼠标交互', + action: '操作', + shortcut: '快捷键', + method: '方法', + description: '描述', + note: '注意:', + noteContent: + '在输入框、文本区域或可编辑内容元素中键入时,键盘快捷键会被禁用,以防止冲突。', // Keyboard shortcuts - undoAction: "撤销", - undoDescription: "撤销上一个操作", - redoAction: "重做", - redoDescription: "重做上一个撤销的操作", - redoAltAction: "重做(备选)", - redoAltDescription: "备选重做快捷键", - helpAction: "帮助", - helpDescription: "打开包含键盘快捷键的帮助对话框", - zoomInAction: "放大", - zoomInShortcut: "鼠标滚轮向上", - zoomInDescription: "放大画布", - zoomOutAction: "缩小", - zoomOutShortcut: "鼠标滚轮向下", - zoomOutDescription: "缩小画布", - panCanvasAction: "平移画布", - panCanvasShortcut: "左键拖拽", - panCanvasDescription: "在平移模式下移动画布", - contextMenuAction: "上下文菜单", - contextMenuShortcut: "右键点击", - contextMenuDescription: "为项目或空白区域打开上下文菜单", + undoAction: '撤销', + undoDescription: '撤销上一个操作', + redoAction: '重做', + redoDescription: '重做上一个撤销的操作', + redoAltAction: '重做(备选)', + redoAltDescription: '备选重做快捷键', + helpAction: '帮助', + helpDescription: '打开包含键盘快捷键的帮助对话框', + zoomInAction: '放大', + zoomInShortcut: '鼠标滚轮向上', + zoomInDescription: '放大画布', + zoomOutAction: '缩小', + zoomOutShortcut: '鼠标滚轮向下', + zoomOutDescription: '缩小画布', + panCanvasAction: '平移画布', + panCanvasShortcut: '左键拖拽', + panCanvasDescription: '在平移模式下移动画布', + contextMenuAction: '上下文菜单', + contextMenuShortcut: '右键点击', + contextMenuDescription: '为项目或空白区域打开上下文菜单', // Mouse interactions - selectToolAction: "选择工具", - selectToolShortcut: "点击选择按钮", - selectToolDescription: "切换到选择模式", - panToolAction: "平移工具", - panToolShortcut: "点击平移按钮", - panToolDescription: "切换到平移模式以移动画布", - addItemAction: "添加项目", - addItemShortcut: "点击添加项目按钮", - addItemDescription: "打开图标选择器以添加新项目", - drawRectangleAction: "绘制矩形", - drawRectangleShortcut: "点击矩形按钮", - drawRectangleDescription: "切换到矩形绘制模式", - createConnectorAction: "创建连接器", - createConnectorShortcut: "点击连接器按钮", - createConnectorDescription: "切换到连接器模式", - addTextAction: "添加文本", - addTextShortcut: "点击文本按钮", - addTextDescription: "创建新的文本框" + selectToolAction: '选择工具', + selectToolShortcut: '点击选择按钮', + selectToolDescription: '切换到选择模式', + panToolAction: '平移工具', + panToolShortcut: '点击平移按钮', + panToolDescription: '切换到平移模式以移动画布', + addItemAction: '添加项目', + addItemShortcut: '点击添加项目按钮', + addItemDescription: '打开图标选择器以添加新项目', + drawRectangleAction: '绘制矩形', + drawRectangleShortcut: '点击矩形按钮', + drawRectangleDescription: '切换到矩形绘制模式', + createConnectorAction: '创建连接器', + createConnectorShortcut: '点击连接器按钮', + createConnectorDescription: '切换到连接器模式', + addTextAction: '添加文本', + addTextShortcut: '点击文本按钮', + addTextDescription: '创建新的文本框' }, connectorHintTooltip: { - tipCreatingConnectors: "提示:创建连接器", - tipConnectorTools: "提示:连接器工具", - clickInstructionStart: "点击", - clickInstructionMiddle: "第一个节点或点,然后", - clickInstructionEnd: "第二个节点或点来创建连接。", - nowClickTarget: "现在点击目标以完成连接。", - dragStart: "拖拽", - dragEnd: "从第一个节点到第二个节点来创建连接。", - rerouteStart: "要重新规划连接器线路,请", - rerouteMiddle: "左键点击", - rerouteEnd: "连接器线上的任何点并拖拽以创建或移动锚点。" + tipCreatingConnectors: '提示:创建连接器', + tipConnectorTools: '提示:连接器工具', + clickInstructionStart: '点击', + clickInstructionMiddle: '第一个节点或点,然后', + clickInstructionEnd: '第二个节点或点来创建连接。', + nowClickTarget: '现在点击目标以完成连接。', + dragStart: '拖拽', + dragEnd: '从第一个节点到第二个节点来创建连接。', + rerouteStart: '要重新规划连接器线路,请', + rerouteMiddle: '左键点击', + rerouteEnd: '连接器线上的任何点并拖拽以创建或移动锚点。' }, lassoHintTooltip: { - tipLasso: "提示:套索选择", - tipFreehandLasso: "提示:自由套索选择", - lassoDragStart: "点击并拖拽", - lassoDragEnd: "以绘制矩形选择框来选中您想选择的项目。", - freehandDragStart: "点击并拖拽", - freehandDragMiddle: "以绘制", - freehandDragEnd: "自由形状", - freehandComplete: "围绕项目。释放以选择形状内的所有项目。", - moveStart: "选择后,", - moveMiddle: "在选择区域内点击", - moveEnd: "并拖拽以一起移动所有选中的项目。" + tipLasso: '提示:套索选择', + tipFreehandLasso: '提示:自由套索选择', + lassoDragStart: '点击并拖拽', + lassoDragEnd: '以绘制矩形选择框来选中您想选择的项目。', + freehandDragStart: '点击并拖拽', + freehandDragMiddle: '以绘制', + freehandDragEnd: '自由形状', + freehandComplete: '围绕项目。释放以选择形状内的所有项目。', + moveStart: '选择后,', + moveMiddle: '在选择区域内点击', + moveEnd: '并拖拽以一起移动所有选中的项目。' }, importHintTooltip: { - title: "导入图表", - instructionStart: "要导入图表,请点击左上角的", - menuButton: "菜单按钮", - instructionMiddle: "(☰),然后选择", - openButton: "\"打开\"", - instructionEnd: "来加载您的图表文件。" + title: '导入图表', + instructionStart: '要导入图表,请点击左上角的', + menuButton: '菜单按钮', + instructionMiddle: '(☰),然后选择', + openButton: '"打开"', + instructionEnd: '来加载您的图表文件。' }, connectorRerouteTooltip: { - title: "提示:重新规划连接器路径", - instructionStart: "连接器放置后,您可以随意重新规划路径。", - instructionSelect: "先选择连接器", - instructionMiddle: ",然后", - instructionClick: "点击连接器路径", - instructionAnd: "并", - instructionDrag: "拖拽", - instructionEnd: "即可更改!" + title: '提示:重新规划连接器路径', + instructionStart: '连接器放置后,您可以随意重新规划路径。', + instructionSelect: '先选择连接器', + instructionMiddle: ',然后', + instructionClick: '点击连接器路径', + instructionAnd: '并', + instructionDrag: '拖拽', + instructionEnd: '即可更改!' }, settings: { zoom: { - description: "配置使用鼠标滚轮时的缩放行为。", - zoomToCursor: "光标缩放", - zoomToCursorDesc: "启用时,以鼠标光标位置为中心进行缩放。禁用时,以画布中心进行缩放。" + description: '配置使用鼠标滚轮时的缩放行为。', + zoomToCursor: '光标缩放', + zoomToCursorDesc: + '启用时,以鼠标光标位置为中心进行缩放。禁用时,以画布中心进行缩放。' }, hotkeys: { - title: "快捷键设置", - profile: "快捷键配置", - profileQwerty: "QWERTY(Q、W、E、R、T、Y)", - profileSmnrct: "SMNRCT(S、M、N、R、C、T)", - profileNone: "无快捷键", - tool: "工具", - hotkey: "快捷键", - toolSelect: "选择", - toolPan: "平移", - toolAddItem: "添加项目", - toolRectangle: "矩形", - toolConnector: "连接器", - toolText: "文本", - note: "注意:在文本输入框中输入时快捷键不生效" + title: '快捷键设置', + profile: '快捷键配置', + profileQwerty: 'QWERTY(Q、W、E、R、T、Y)', + profileSmnrct: 'SMNRCT(S、M、N、R、C、T)', + profileNone: '无快捷键', + tool: '工具', + hotkey: '快捷键', + toolSelect: '选择', + toolPan: '平移', + toolAddItem: '添加项目', + toolRectangle: '矩形', + toolConnector: '连接器', + toolText: '文本', + note: '注意:在文本输入框中输入时快捷键不生效' }, pan: { - title: "平移设置", - mousePanOptions: "鼠标平移选项", - emptyAreaClickPan: "点击并拖拽空白区域", - middleClickPan: "中键点击并拖拽", - rightClickPan: "右键点击并拖拽", - ctrlClickPan: "Ctrl + 点击并拖拽", - altClickPan: "Alt + 点击并拖拽", - keyboardPanOptions: "键盘平移选项", - arrowKeys: "方向键", - wasdKeys: "WASD 键", - ijklKeys: "IJKL 键", - keyboardPanSpeed: "键盘平移速度", - note: "注意:平移选项可与专用的平移工具一起使用" + title: '平移设置', + mousePanOptions: '鼠标平移选项', + emptyAreaClickPan: '点击并拖拽空白区域', + middleClickPan: '中键点击并拖拽', + rightClickPan: '右键点击并拖拽', + ctrlClickPan: 'Ctrl + 点击并拖拽', + altClickPan: 'Alt + 点击并拖拽', + keyboardPanOptions: '键盘平移选项', + arrowKeys: '方向键', + wasdKeys: 'WASD 键', + ijklKeys: 'IJKL 键', + keyboardPanSpeed: '键盘平移速度', + note: '注意:平移选项可与专用的平移工具一起使用' }, connector: { - title: "连接器设置", - connectionMode: "连接创建模式", - clickMode: "点击模式(推荐)", - clickModeDesc: "先点击第一个节点,然后点击第二个节点来创建连接", - dragMode: "拖拽模式", - dragModeDesc: "从第一个节点点击并拖拽到第二个节点", - note: "注意:您可以随时更改此设置。所选模式将在连接器工具激活时使用。" + title: '连接器设置', + connectionMode: '连接创建模式', + clickMode: '点击模式(推荐)', + clickModeDesc: '先点击第一个节点,然后点击第二个节点来创建连接', + dragMode: '拖拽模式', + dragModeDesc: '从第一个节点点击并拖拽到第二个节点', + note: '注意:您可以随时更改此设置。所选模式将在连接器工具激活时使用。' }, iconPacks: { - title: "图标包管理", - lazyLoading: "启用延迟加载", - lazyLoadingDesc: "按需加载图标包以加快启动速度", - availablePacks: "可用图标包", - coreIsoflow: "核心 Isoflow(始终加载)", - alwaysEnabled: "始终启用", - awsPack: "AWS 图标", - gcpPack: "Google Cloud 图标", - azurePack: "Azure 图标", - kubernetesPack: "Kubernetes 图标", - loading: "加载中...", - loaded: "已加载", - notLoaded: "未加载", - iconCount: "{count} 个图标", - lazyLoadingDisabledNote: "延迟加载已禁用。所有图标包将在启动时加载。", - note: "可以根据需要启用或禁用图标包。禁用的图标包将减少内存使用并提高性能。" + title: '图标包管理', + lazyLoading: '启用延迟加载', + lazyLoadingDesc: '按需加载图标包以加快启动速度', + availablePacks: '可用图标包', + coreIsoflow: '核心 Isoflow(始终加载)', + alwaysEnabled: '始终启用', + awsPack: 'AWS 图标', + gcpPack: 'Google Cloud 图标', + azurePack: 'Azure 图标', + kubernetesPack: 'Kubernetes 图标', + loading: '加载中...', + loaded: '已加载', + notLoaded: '未加载', + iconCount: '{count} 个图标', + lazyLoadingDisabledNote: '延迟加载已禁用。所有图标包将在启动时加载。', + note: '可以根据需要启用或禁用图标包。禁用的图标包将减少内存使用并提高性能。' } }, lazyLoadingWelcome: { - title: "新功能:延迟加载!", - message: "嘿!应大家的要求,我们实现了图标的延迟加载功能,现在如果您想启用非标准图标包,可以在「配置」部分中启用它们。", - configPath: "点击左上角的汉堡菜单图标", - configPath2: "以访问配置。", - canDisable: "如果您愿意,可以禁用此行为。", - signature: "-Stan" + title: '新功能:延迟加载!', + message: + '嘿!应大家的要求,我们实现了图标的延迟加载功能,现在如果您想启用非标准图标包,可以在「配置」部分中启用它们。', + configPath: '点击左上角的汉堡菜单图标', + configPath2: '以访问配置。', + canDisable: '如果您愿意,可以禁用此行为。', + signature: '-Stan' } }; diff --git a/packages/fossflow-lib/src/interaction/modes/Connector.ts b/packages/fossflow-lib/src/interaction/modes/Connector.ts index 7e118168..fd583648 100644 --- a/packages/fossflow-lib/src/interaction/modes/Connector.ts +++ b/packages/fossflow-lib/src/interaction/modes/Connector.ts @@ -25,14 +25,17 @@ export const Connector: ModeActions = { // TypeScript type guard - we know mode is CONNECTOR type here const connectorMode = uiState.mode; - + // Only update connector position in drag mode or when connecting in click mode - if (uiState.connectorInteractionMode === 'drag' || connectorMode.isConnecting) { + if ( + uiState.connectorInteractionMode === 'drag' || + connectorMode.isConnecting + ) { // Try to find the connector - it might not exist yet - const connectorItem = (scene.currentView.connectors ?? []).find( - c => c.id === connectorMode.id - ); - + const connectorItem = (scene.currentView.connectors ?? []).find((c) => { + return c.id === connectorMode.id; + }); + // If connector doesn't exist yet, return early if (!connectorItem) { return; @@ -73,9 +76,10 @@ export const Connector: ModeActions = { // Click mode: handle first and second clicks if (!uiState.mode.startAnchor) { // First click: store the start position - const startAnchor = itemAtTile?.type === 'ITEM' - ? { itemId: itemAtTile.id } - : { tile: uiState.mouse.position.tile }; + const startAnchor = + itemAtTile?.type === 'ITEM' + ? { itemId: itemAtTile.id } + : { tile: uiState.mouse.position.tile }; // Create a connector but don't finalize it yet const newConnector: ConnectorI = { @@ -111,10 +115,10 @@ export const Connector: ModeActions = { const currentMode = uiState.mode; if (currentMode.id) { // Try to find the connector - it might not exist - const connector = (scene.currentView.connectors ?? []).find( - c => c.id === currentMode.id - ); - + const connector = (scene.currentView.connectors ?? []).find((c) => { + return c.id === currentMode.id; + }); + // If connector doesn't exist, reset mode and return if (!connector) { uiState.actions.setMode({ @@ -126,11 +130,14 @@ export const Connector: ModeActions = { }); return; } - + // Update the second anchor to the click position const newConnector = produce(connector, (draft) => { if (itemAtTile?.type === 'ITEM') { - draft.anchors[1] = { id: generateId(), ref: { item: itemAtTile.id } }; + draft.anchors[1] = { + id: generateId(), + ref: { item: itemAtTile.id } + }; } else { draft.anchors[1] = { id: generateId(), @@ -199,4 +206,4 @@ export const Connector: ModeActions = { } // Click mode handles completion in mousedown (second click) } -}; \ No newline at end of file +}; diff --git a/packages/fossflow-lib/src/interaction/modes/DragItems.ts b/packages/fossflow-lib/src/interaction/modes/DragItems.ts index 23ad8351..e1b41bf5 100644 --- a/packages/fossflow-lib/src/interaction/modes/DragItems.ts +++ b/packages/fossflow-lib/src/interaction/modes/DragItems.ts @@ -18,12 +18,16 @@ const dragItems = ( scene: ReturnType ) => { // Separate items from other draggable elements - const itemRefs = items.filter(item => item.type === 'ITEM'); - const otherRefs = items.filter(item => item.type !== 'ITEM'); + const itemRefs = items.filter((item) => { + return item.type === 'ITEM'; + }); + const otherRefs = items.filter((item) => { + return item.type !== 'ITEM'; + }); // If there are items being dragged, find nearest unoccupied tiles for them if (itemRefs.length > 0) { - const itemsWithTargets = itemRefs.map(item => { + const itemsWithTargets = itemRefs.map((item) => { const node = getItemByIdOrThrow(scene.items, item.id).value; return { id: item.id, @@ -35,7 +39,9 @@ const dragItems = ( const newTiles = findNearestUnoccupiedTilesForGroup( itemsWithTargets, scene, - itemRefs.map(item => item.id) // Exclude the items being dragged + itemRefs.map((item) => { + return item.id; + }) // Exclude the items being dragged ); // If we found valid positions for all items, move them @@ -45,9 +51,13 @@ const dragItems = ( // Chain state updates to avoid race conditions let currentState: State | undefined; itemRefs.forEach((item, index) => { - currentState = scene.updateViewItem(item.id, { - tile: newTiles[index] - }, currentState); + currentState = scene.updateViewItem( + item.id, + { + tile: newTiles[index] + }, + currentState + ); }); }); } diff --git a/packages/fossflow-lib/src/interaction/modes/FreehandLasso.ts b/packages/fossflow-lib/src/interaction/modes/FreehandLasso.ts index e5ddda74..8e8920f5 100644 --- a/packages/fossflow-lib/src/interaction/modes/FreehandLasso.ts +++ b/packages/fossflow-lib/src/interaction/modes/FreehandLasso.ts @@ -28,7 +28,9 @@ const getItemsInFreehandBounds = ( ]; // Rectangle is only selected if ALL corners are inside the polygon - const allCornersInside = corners.every(corner => isPointInPolygon(corner, pathTiles)); + const allCornersInside = corners.every((corner) => { + return isPointInPolygon(corner, pathTiles); + }); if (allCornersInside) { items.push({ type: 'RECTANGLE', id: rectangle.id }); @@ -47,7 +49,8 @@ const getItemsInFreehandBounds = ( export const FreehandLasso: ModeActions = { mousemove: ({ uiState, scene }) => { - if (uiState.mode.type !== 'FREEHAND_LASSO' || !uiState.mouse.mousedown) return; + if (uiState.mode.type !== 'FREEHAND_LASSO' || !uiState.mouse.mousedown) + return; // If user is dragging an existing selection, switch to DRAG_ITEMS mode if (uiState.mode.isDragging && uiState.mode.selection) { @@ -68,9 +71,11 @@ export const FreehandLasso: ModeActions = { if (draft.type === 'FREEHAND_LASSO') { // Add point to path if it's far enough from the last point (throttle) const lastPoint = draft.path[draft.path.length - 1]; - if (!lastPoint || - Math.abs(newScreenPoint.x - lastPoint.x) > 5 || - Math.abs(newScreenPoint.y - lastPoint.y) > 5) { + if ( + !lastPoint || + Math.abs(newScreenPoint.x - lastPoint.x) > 5 || + Math.abs(newScreenPoint.y - lastPoint.y) > 5 + ) { draft.path.push(newScreenPoint); } } diff --git a/packages/fossflow-lib/src/interaction/modes/Lasso.ts b/packages/fossflow-lib/src/interaction/modes/Lasso.ts index bf7b0db9..3a77b4eb 100644 --- a/packages/fossflow-lib/src/interaction/modes/Lasso.ts +++ b/packages/fossflow-lib/src/interaction/modes/Lasso.ts @@ -27,9 +27,9 @@ const getItemsInBounds = ( ]; // Rectangle is only selected if ALL corners are inside the bounds - const allCornersInside = corners.every(corner => - isWithinBounds(corner, [startTile, endTile]) - ); + const allCornersInside = corners.every((corner) => { + return isWithinBounds(corner, [startTile, endTile]); + }); if (allCornersInside) { items.push({ type: 'RECTANGLE', id: rectangle.id }); diff --git a/packages/fossflow-lib/src/interaction/modes/PlaceIcon.ts b/packages/fossflow-lib/src/interaction/modes/PlaceIcon.ts index 0a91a226..1477133f 100644 --- a/packages/fossflow-lib/src/interaction/modes/PlaceIcon.ts +++ b/packages/fossflow-lib/src/interaction/modes/PlaceIcon.ts @@ -1,6 +1,10 @@ import { produce } from 'immer'; import { ModeActions } from 'src/types'; -import { generateId, getItemAtTile, findNearestUnoccupiedTile } from 'src/utils'; +import { + generateId, + getItemAtTile, + findNearestUnoccupiedTile +} from 'src/utils'; import { VIEW_ITEM_DEFAULTS } from 'src/config'; export const PlaceIcon: ModeActions = { diff --git a/packages/fossflow-lib/src/interaction/useInteractionManager.ts b/packages/fossflow-lib/src/interaction/useInteractionManager.ts index c1a6496c..fba91214 100644 --- a/packages/fossflow-lib/src/interaction/useInteractionManager.ts +++ b/packages/fossflow-lib/src/interaction/useInteractionManager.ts @@ -3,7 +3,13 @@ import { useModelStore } from 'src/stores/modelStore'; import { useUiStateStore } from 'src/stores/uiStateStore'; import { ModeActions, State, SlimMouseEvent } from 'src/types'; import { DialogTypeEnum } from 'src/types/ui'; -import { getMouse, getItemAtTile, generateId, incrementZoom, decrementZoom } from 'src/utils'; +import { + getMouse, + getItemAtTile, + generateId, + incrementZoom, + decrementZoom +} from 'src/utils'; import { useResizeObserver } from 'src/hooks/useResizeObserver'; import { useScene } from 'src/hooks/useScene'; import { useHistory } from 'src/hooks/useHistory'; @@ -60,7 +66,10 @@ export const useInteractionManager = () => { const { size: rendererSize } = useResizeObserver(uiState.rendererEl); const { undo, redo, canUndo, canRedo } = useHistory(); const { createTextBox } = scene; - const { handleMouseDown: handlePanMouseDown, handleMouseUp: handlePanMouseUp } = usePanHandlers(); + const { + handleMouseDown: handlePanMouseDown, + handleMouseUp: handlePanMouseUp + } = usePanHandlers(); // Keyboard shortcuts for undo/redo useEffect(() => { @@ -81,8 +90,10 @@ export const useInteractionManager = () => { // Check if connection is in progress const isConnectionInProgress = - (uiState.connectorInteractionMode === 'click' && connectorMode.isConnecting) || - (uiState.connectorInteractionMode === 'drag' && connectorMode.id !== null); + (uiState.connectorInteractionMode === 'click' && + connectorMode.isConnecting) || + (uiState.connectorInteractionMode === 'drag' && + connectorMode.id !== null); if (isConnectionInProgress && connectorMode.id) { // Delete the temporary connector @@ -144,7 +155,12 @@ export const useInteractionManager = () => { const key = e.key.toLowerCase(); // Quick icon selection for selected node (when ItemControls is an ItemReference with type 'ITEM') - if (key === 'i' && uiState.itemControls && 'id' in uiState.itemControls && uiState.itemControls.type === 'ITEM') { + if ( + key === 'i' && + uiState.itemControls && + 'id' in uiState.itemControls && + uiState.itemControls.type === 'ITEM' + ) { e.preventDefault(); // Trigger icon change mode const event = new CustomEvent('quickIconChange'); @@ -211,7 +227,10 @@ export const useInteractionManager = () => { selection: null, isDragging: false }); - } else if (hotkeyMapping.freehandLasso && key === hotkeyMapping.freehandLasso) { + } else if ( + hotkeyMapping.freehandLasso && + key === hotkeyMapping.freehandLasso + ) { e.preventDefault(); uiState.actions.setMode({ type: 'FREEHAND_LASSO', @@ -227,7 +246,20 @@ export const useInteractionManager = () => { return () => { return window.removeEventListener('keydown', handleKeyDown); }; - }, [undo, redo, canUndo, canRedo, uiState.hotkeyProfile, uiState.actions, createTextBox, uiState.mouse.position.tile, scene, uiState.itemControls, uiState.mode, uiState.connectorInteractionMode]); + }, [ + undo, + redo, + canUndo, + canRedo, + uiState.hotkeyProfile, + uiState.actions, + createTextBox, + uiState.mouse.position.tile, + scene, + uiState.itemControls, + uiState.mode, + uiState.connectorInteractionMode + ]); const onMouseEvent = useCallback( (e: SlimMouseEvent) => { @@ -380,8 +412,10 @@ export const useInteractionManager = () => { // The point under the cursor in world space (before zoom) // World coordinates = (screen coordinates - scroll offset) / zoom - const worldX = (mouseRelativeToCenterX - uiState.scroll.position.x) / oldZoom; - const worldY = (mouseRelativeToCenterY - uiState.scroll.position.y) / oldZoom; + const worldX = + (mouseRelativeToCenterX - uiState.scroll.position.x) / oldZoom; + const worldY = + (mouseRelativeToCenterY - uiState.scroll.position.y) / oldZoom; // After zooming, to keep the same world point under the cursor: // screen coordinates = world coordinates * newZoom + scroll offset diff --git a/packages/fossflow-lib/src/interaction/usePanHandlers.ts b/packages/fossflow-lib/src/interaction/usePanHandlers.ts index a6e5969a..c8f44857 100644 --- a/packages/fossflow-lib/src/interaction/usePanHandlers.ts +++ b/packages/fossflow-lib/src/interaction/usePanHandlers.ts @@ -5,22 +5,27 @@ import { useScene } from 'src/hooks/useScene'; import { SlimMouseEvent } from 'src/types'; export const usePanHandlers = () => { - const uiState = useUiStateStore((state) => state); + const uiState = useUiStateStore((state) => { + return state; + }); const scene = useScene(); const isPanningRef = useRef(false); const panMethodRef = useRef(null); // Helper to start panning - const startPan = useCallback((method: string) => { - if (uiState.mode.type !== 'PAN') { - isPanningRef.current = true; - panMethodRef.current = method; - uiState.actions.setMode({ - type: 'PAN', - showCursor: false - }); - } - }, [uiState.mode.type, uiState.actions]); + const startPan = useCallback( + (method: string) => { + if (uiState.mode.type !== 'PAN') { + isPanningRef.current = true; + panMethodRef.current = method; + uiState.actions.setMode({ + type: 'PAN', + showCursor: false + }); + } + }, + [uiState.mode.type, uiState.actions] + ); // Helper to end panning const endPan = useCallback(() => { @@ -36,72 +41,81 @@ export const usePanHandlers = () => { }, [uiState.actions]); // Check if click is on empty area - const isEmptyArea = useCallback((e: SlimMouseEvent): boolean => { - if (!uiState.rendererEl || e.target !== uiState.rendererEl) return false; - - const itemAtTile = getItemAtTile({ - tile: uiState.mouse.position.tile, - scene - }); - - return !itemAtTile; - }, [uiState.rendererEl, uiState.mouse.position.tile, scene]); + const isEmptyArea = useCallback( + (e: SlimMouseEvent): boolean => { + if (!uiState.rendererEl || e.target !== uiState.rendererEl) return false; + + const itemAtTile = getItemAtTile({ + tile: uiState.mouse.position.tile, + scene + }); + + return !itemAtTile; + }, + [uiState.rendererEl, uiState.mouse.position.tile, scene] + ); // Enhanced mouse down handler - const handleMouseDown = useCallback((e: SlimMouseEvent): boolean => { - const panSettings = uiState.panSettings; - - // Check for the specific button that was pressed and only handle that one - // This fixes the issue where enabling both middle and right click causes neither to work - - // Middle click pan (button 1) - if (e.button === 1 && panSettings.middleClickPan) { - e.preventDefault(); - startPan('middle'); - return true; - } - - // Right click pan (button 2) - if (e.button === 2 && panSettings.rightClickPan) { - e.preventDefault(); - startPan('right'); - return true; - } - - // Left button (0) with modifiers or empty area - if (e.button === 0) { - // Ctrl + click pan - if (panSettings.ctrlClickPan && e.ctrlKey) { + const handleMouseDown = useCallback( + (e: SlimMouseEvent): boolean => { + const panSettings = uiState.panSettings; + + // Check for the specific button that was pressed and only handle that one + // This fixes the issue where enabling both middle and right click causes neither to work + + // Middle click pan (button 1) + if (e.button === 1 && panSettings.middleClickPan) { e.preventDefault(); - startPan('ctrl'); + startPan('middle'); return true; } - - // Alt + click pan - if (panSettings.altClickPan && e.altKey) { + + // Right click pan (button 2) + if (e.button === 2 && panSettings.rightClickPan) { e.preventDefault(); - startPan('alt'); + startPan('right'); return true; } - - // Empty area click pan - if (panSettings.emptyAreaClickPan && isEmptyArea(e)) { - startPan('empty'); - return true; + + // Left button (0) with modifiers or empty area + if (e.button === 0) { + // Ctrl + click pan + if (panSettings.ctrlClickPan && e.ctrlKey) { + e.preventDefault(); + startPan('ctrl'); + return true; + } + + // Alt + click pan + if (panSettings.altClickPan && e.altKey) { + e.preventDefault(); + startPan('alt'); + return true; + } + + // Empty area click pan + if (panSettings.emptyAreaClickPan && isEmptyArea(e)) { + startPan('empty'); + return true; + } } - } - - return false; - }, [uiState.panSettings, startPan, isEmptyArea]); + + return false; + }, + [uiState.panSettings, startPan, isEmptyArea] + ); // Enhanced mouse up handler - const handleMouseUp = useCallback((e: SlimMouseEvent): boolean => { - if (isPanningRef.current) { - endPan(); - return true; - } - return false; - }, [endPan]); + const handleMouseUp = useCallback( + (e: SlimMouseEvent): boolean => { + if (isPanningRef.current) { + endPan(); + return true; + } + return false; + }, + [endPan] + ); // Keyboard pan handler useEffect(() => { @@ -177,10 +191,10 @@ export const usePanHandlers = () => { // Apply pan if any movement if (dx !== 0 || dy !== 0) { - const newPosition = CoordsUtils.add( - uiState.scroll.position, - { x: dx, y: dy } - ); + const newPosition = CoordsUtils.add(uiState.scroll.position, { + x: dx, + y: dy + }); uiState.actions.setScroll({ position: newPosition, offset: uiState.scroll.offset @@ -189,7 +203,9 @@ export const usePanHandlers = () => { }; window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); + return () => { + return window.removeEventListener('keydown', handleKeyDown); + }; }, [uiState.panSettings, uiState.scroll, uiState.actions]); return { @@ -197,4 +213,4 @@ export const usePanHandlers = () => { handleMouseUp, isPanning: isPanningRef.current }; -}; \ No newline at end of file +}; diff --git a/packages/fossflow-lib/src/schemas/connector.ts b/packages/fossflow-lib/src/schemas/connector.ts index 9b15e706..b7d5db7e 100644 --- a/packages/fossflow-lib/src/schemas/connector.ts +++ b/packages/fossflow-lib/src/schemas/connector.ts @@ -2,7 +2,11 @@ import { z } from 'zod'; import { coords, id, constrainedStrings } from './common'; export const connectorStyleOptions = ['SOLID', 'DOTTED', 'DASHED'] as const; -export const connectorLineTypeOptions = ['SINGLE', 'DOUBLE', 'DOUBLE_WITH_CIRCLE'] as const; +export const connectorLineTypeOptions = [ + 'SINGLE', + 'DOUBLE', + 'DOUBLE_WITH_CIRCLE' +] as const; export const connectorLabelSchema = z.object({ id, diff --git a/packages/fossflow-lib/src/stores/localeStore.tsx b/packages/fossflow-lib/src/stores/localeStore.tsx index 435627a3..a9fcd81e 100644 --- a/packages/fossflow-lib/src/stores/localeStore.tsx +++ b/packages/fossflow-lib/src/stores/localeStore.tsx @@ -9,11 +9,12 @@ interface LocaleProviderProps { children: ReactNode; } -export const LocaleProvider: React.FC = ({ locale, children }) => { +export const LocaleProvider: React.FC = ({ + locale, + children +}) => { return ( - - {children} - + {children} ); }; @@ -41,11 +42,11 @@ export function useTranslation( namespace: K ): { t: (key: keyof LocaleProps[K]) => string; - }; +}; export function useTranslation(namespace?: K) { const locale = useLocale(); - + if (namespace) { // Return scoped translation function for specific namespace const namespaceData = locale[namespace]; @@ -59,7 +60,7 @@ export function useTranslation(namespace?: K) { const t = (key: NestedKeyOf): string => { const parts = key.split('.'); let current: any = locale; - + for (const part of parts) { if (current && typeof current === 'object' && part in current) { current = current[part]; @@ -67,7 +68,7 @@ export function useTranslation(namespace?: K) { return key; // Return key if path not found } } - + return typeof current === 'string' ? current : key; }; return { t }; diff --git a/packages/fossflow-lib/src/stores/reducers/__tests__/connector.test.ts b/packages/fossflow-lib/src/stores/reducers/__tests__/connector.test.ts index 1b8e2403..09e2e9f6 100644 --- a/packages/fossflow-lib/src/stores/reducers/__tests__/connector.test.ts +++ b/packages/fossflow-lib/src/stores/reducers/__tests__/connector.test.ts @@ -1,31 +1,38 @@ -import { - deleteConnector, - syncConnector, - updateConnector, - createConnector +import { + deleteConnector, + syncConnector, + updateConnector, + createConnector } from '../connector'; import { State, ViewReducerContext } from '../types'; import { Connector, View, Model, Scene } from 'src/types'; // Mock the utility functions -jest.mock('src/utils', () => ({ - getItemByIdOrThrow: jest.fn((items: any[], id: string) => { - const index = items.findIndex((item: any) => - (typeof item === 'object' && item.id === id) || item === id - ); - if (index === -1) { - throw new Error(`Item with id ${id} not found`); - } - return { value: items[index], index }; - }), - getConnectorPath: jest.fn(({ anchors }) => ({ - tiles: [{ x: 0, y: 0 }, { x: 1, y: 1 }], - rectangle: { - from: { x: anchors.from.x || 0, y: anchors.from.y || 0 }, - to: { x: anchors.to.x || 1, y: anchors.to.y || 1 } - } - })) -})); +jest.mock('src/utils', () => { + return { + getItemByIdOrThrow: jest.fn((items: any[], id: string) => { + const index = items.findIndex((item: any) => { + return (typeof item === 'object' && item.id === id) || item === id; + }); + if (index === -1) { + throw new Error(`Item with id ${id} not found`); + } + return { value: items[index], index }; + }), + getConnectorPath: jest.fn(() => { + return { + tiles: [ + { x: 0, y: 0 }, + { x: 1, y: 1 } + ], + rectangle: { + from: { x: 0, y: 0 }, + to: { x: 1, y: 1 } + } + }; + }) + }; +}); describe('connector reducer', () => { let mockState: State; @@ -35,15 +42,15 @@ describe('connector reducer', () => { beforeEach(() => { jest.clearAllMocks(); - + mockConnector = { id: 'connector1', - anchors: { - from: { id: 'item1', face: 'right', x: 0, y: 0 }, - to: { id: 'item2', face: 'left', x: 2, y: 0 } - }, - label: 'Test Connection', - lineType: 'solid', + anchors: [ + { id: 'from', ref: { item: 'item1' } }, + { id: 'to', ref: { item: 'item2' } } + ], + labels: [{ id: 'label1', text: 'Test Connection', position: 50 }], + lineType: 'SINGLE', color: 'color1' }; @@ -67,19 +74,14 @@ describe('connector reducer', () => { views: [mockView] }, scene: { - viewId: 'view1', - viewport: { x: 0, y: 0, zoom: 1 }, - grid: { enabled: true, size: 10, style: 'dots' }, connectors: { - 'connector1': { + connector1: { path: { tiles: [], rectangle: { from: { x: 0, y: 0 }, to: { x: 2, y: 0 } } } } }, - viewItems: {}, - rectangles: {}, textBoxes: {} } }; @@ -93,10 +95,10 @@ describe('connector reducer', () => { describe('deleteConnector', () => { it('should delete a connector from both model and scene', () => { const result = deleteConnector('connector1', mockContext); - + // Check connector is removed from model expect(result.model.views[0].connectors).toHaveLength(0); - + // Check connector is removed from scene by ID expect(result.scene.connectors['connector1']).toBeUndefined(); }); @@ -117,7 +119,7 @@ describe('connector reducer', () => { it('should handle empty connectors array gracefully', () => { mockState.model.views[0].connectors = []; mockState.scene.connectors = {}; - + expect(() => { deleteConnector('connector1', mockContext); }).toThrow('Item with id connector1 not found'); @@ -126,19 +128,22 @@ describe('connector reducer', () => { it('should not affect other connectors when deleting one', () => { const connector2: Connector = { id: 'connector2', - anchors: { - from: { id: 'item3', face: 'top' }, - to: { id: 'item4', face: 'bottom' } - } + anchors: [ + { id: 'from', ref: { item: 'item3' } }, + { id: 'to', ref: { item: 'item4' } } + ] }; - + mockState.model.views[0].connectors = [mockConnector, connector2]; - mockState.scene.connectors['connector2'] = { - path: { tiles: [], rectangle: { from: { x: 1, y: 1 }, to: { x: 2, y: 2 } } } + mockState.scene.connectors['connector2'] = { + path: { + tiles: [], + rectangle: { from: { x: 1, y: 1 }, to: { x: 2, y: 2 } } + } }; - + const result = deleteConnector('connector1', mockContext); - + expect(result.model.views[0].connectors).toHaveLength(1); expect(result.model.views[0].connectors![0].id).toBe('connector2'); expect(result.scene.connectors['connector2']).toBeDefined(); @@ -149,23 +154,29 @@ describe('connector reducer', () => { describe('syncConnector', () => { it('should sync connector path successfully', () => { const getConnectorPath = require('src/utils').getConnectorPath; - + // Clear previous calls and set up fresh mock getConnectorPath.mockClear(); getConnectorPath.mockReturnValue({ - tiles: [{ x: 0, y: 0 }, { x: 1, y: 1 }], + tiles: [ + { x: 0, y: 0 }, + { x: 1, y: 1 } + ], rectangle: { from: { x: 0, y: 0 }, to: { x: 2, y: 0 } } }); - + const result = syncConnector('connector1', mockContext); - + expect(getConnectorPath).toHaveBeenCalled(); - + expect(result.scene.connectors['connector1'].path).toEqual({ - tiles: [{ x: 0, y: 0 }, { x: 1, y: 1 }], + tiles: [ + { x: 0, y: 0 }, + { x: 1, y: 1 } + ], rectangle: { from: { x: 0, y: 0 }, to: { x: 2, y: 0 } @@ -178,9 +189,9 @@ describe('connector reducer', () => { getConnectorPath.mockImplementationOnce(() => { throw new Error('Path calculation failed'); }); - + const result = syncConnector('connector1', mockContext); - + // Should create empty path on error expect(result.scene.connectors['connector1'].path).toEqual({ tiles: [], @@ -198,13 +209,13 @@ describe('connector reducer', () => { }); it('should handle connectors with partial anchor data', () => { - mockConnector.anchors = { - from: { id: 'item1', face: 'right' }, - to: { id: 'item2', face: 'left' } - }; - + mockConnector.anchors = [ + { id: 'from', ref: { item: 'item1' } }, + { id: 'to', ref: { item: 'item2' } } + ]; + const result = syncConnector('connector1', mockContext); - + expect(result.scene.connectors['connector1'].path).toBeDefined(); }); }); @@ -213,30 +224,34 @@ describe('connector reducer', () => { it('should update connector properties', () => { const updates = { id: 'connector1', - label: 'Updated Connection', + description: 'Updated Connection', color: 'color2', - lineType: 'dashed' as const + lineType: 'DOUBLE' as const }; - + const result = updateConnector(updates, mockContext); - - expect(result.model.views[0].connectors![0].label).toBe('Updated Connection'); + + expect(result.model.views[0].connectors![0].description).toBe( + 'Updated Connection' + ); expect(result.model.views[0].connectors![0].color).toBe('color2'); - expect(result.model.views[0].connectors![0].lineType).toBe('dashed'); + expect(result.model.views[0].connectors![0].lineType).toBe('DOUBLE'); }); it('should sync connector when anchors are updated', () => { const updates = { id: 'connector1', - anchors: { - from: { id: 'item3', face: 'bottom' as const }, - to: { id: 'item4', face: 'top' as const } - } + anchors: [ + { id: 'from', ref: { item: 'item3' } }, + { id: 'to', ref: { item: 'item4' } } + ] }; - + const result = updateConnector(updates, mockContext); - - expect(result.model.views[0].connectors![0].anchors).toEqual(updates.anchors); + + expect(result.model.views[0].connectors![0].anchors).toEqual( + updates.anchors + ); // Verify sync was called by checking the path was updated expect(result.scene.connectors['connector1'].path).toBeDefined(); }); @@ -244,29 +259,35 @@ describe('connector reducer', () => { it('should not sync when anchors are not updated', () => { const getConnectorPath = require('src/utils').getConnectorPath; getConnectorPath.mockClear(); - + const updates = { id: 'connector1', label: 'Just a label update' }; - + updateConnector(updates, mockContext); - + // getConnectorPath should not be called when anchors aren't updated expect(getConnectorPath).not.toHaveBeenCalled(); }); it('should throw error when connector does not exist', () => { expect(() => { - updateConnector({ id: 'nonexistent', label: 'test' }, mockContext); + updateConnector( + { id: 'nonexistent', description: 'test' }, + mockContext + ); }).toThrow('Item with id nonexistent not found'); }); it('should handle empty connectors array', () => { mockState.model.views[0].connectors = undefined; - - const result = updateConnector({ id: 'connector1', label: 'test' }, mockContext); - + + const result = updateConnector( + { id: 'connector1', description: 'test' }, + mockContext + ); + // Should return state unchanged when connectors is undefined expect(result).toEqual(mockState); }); @@ -274,17 +295,25 @@ describe('connector reducer', () => { it('should preserve other connector properties when partially updating', () => { const updates = { id: 'connector1', - label: 'Partial Update' + description: 'Partial Update' }; - + const result = updateConnector(updates, mockContext); - + // Original properties should be preserved - expect(result.model.views[0].connectors![0].anchors).toEqual(mockConnector.anchors); - expect(result.model.views[0].connectors![0].color).toBe(mockConnector.color); - expect(result.model.views[0].connectors![0].lineType).toBe(mockConnector.lineType); + expect(result.model.views[0].connectors![0].anchors).toEqual( + mockConnector.anchors + ); + expect(result.model.views[0].connectors![0].color).toBe( + mockConnector.color + ); + expect(result.model.views[0].connectors![0].lineType).toBe( + mockConnector.lineType + ); // Updated property - expect(result.model.views[0].connectors![0].label).toBe('Partial Update'); + expect(result.model.views[0].connectors![0].description).toBe( + 'Partial Update' + ); }); }); @@ -292,20 +321,20 @@ describe('connector reducer', () => { it('should create a new connector', () => { const newConnector: Connector = { id: 'connector2', - anchors: { - from: { id: 'item5', face: 'right' }, - to: { id: 'item6', face: 'left' } - }, - label: 'New Connection' + anchors: [ + { id: 'from', ref: { item: 'item5' } }, + { id: 'to', ref: { item: 'item6' } } + ], + description: 'New Connection' }; - + const result = createConnector(newConnector, mockContext); - + // Should be added at the beginning (unshift) expect(result.model.views[0].connectors).toHaveLength(2); expect(result.model.views[0].connectors![0].id).toBe('connector2'); expect(result.model.views[0].connectors![1].id).toBe('connector1'); - + // Should sync the new connector expect(result.scene.connectors['connector2']).toBeDefined(); expect(result.scene.connectors['connector2'].path).toBeDefined(); @@ -313,17 +342,17 @@ describe('connector reducer', () => { it('should initialize connectors array if undefined', () => { mockState.model.views[0].connectors = undefined; - + const newConnector: Connector = { id: 'connector2', - anchors: { - from: { id: 'item5', face: 'right' }, - to: { id: 'item6', face: 'left' } - } + anchors: [ + { id: 'from', ref: { item: 'item5' } }, + { id: 'to', ref: { item: 'item6' } } + ] }; - + const result = createConnector(newConnector, mockContext); - + expect(result.model.views[0].connectors).toHaveLength(1); expect(result.model.views[0].connectors![0].id).toBe('connector2'); }); @@ -333,20 +362,20 @@ describe('connector reducer', () => { getConnectorPath.mockImplementationOnce(() => { throw new Error('Path calculation failed'); }); - + const newConnector: Connector = { id: 'connector2', - anchors: { - from: { id: 'item5', face: 'right' }, - to: { id: 'item6', face: 'left' } - } + anchors: [ + { id: 'from', ref: { item: 'item5' } }, + { id: 'to', ref: { item: 'item6' } } + ] }; - + const result = createConnector(newConnector, mockContext); - + // Connector should still be created expect(result.model.views[0].connectors).toHaveLength(2); - + // But with empty path expect(result.scene.connectors['connector2'].path).toEqual({ tiles: [], @@ -359,15 +388,15 @@ describe('connector reducer', () => { it('should throw error when view does not exist', () => { mockContext.viewId = 'nonexistent'; - + const newConnector: Connector = { id: 'connector2', - anchors: { - from: { id: 'item5', face: 'right' }, - to: { id: 'item6', face: 'left' } - } + anchors: [ + { id: 'from', ref: { item: 'item5' } }, + { id: 'to', ref: { item: 'item6' } } + ] }; - + expect(() => { createConnector(newConnector, mockContext); }).toThrow('Item with id nonexistent not found'); @@ -376,74 +405,92 @@ describe('connector reducer', () => { it('should create connector with all optional properties', () => { const newConnector: Connector = { id: 'connector2', - anchors: { - from: { id: 'item5', face: 'right' }, - to: { id: 'item6', face: 'left' } - }, - label: 'Full Connector', - lineType: 'dotted', + anchors: [ + { id: 'from', ref: { item: 'item5' } }, + { id: 'to', ref: { item: 'item6' } } + ], + description: 'Full Connector', + lineType: 'DOUBLE_WITH_CIRCLE', color: 'color3', - labels: ['Label1', 'Label2'] + labels: [ + { id: 'label1', text: 'Label1', position: 25 }, + { id: 'label2', text: 'Label2', position: 75 } + ] }; - + const result = createConnector(newConnector, mockContext); - + const created = result.model.views[0].connectors![0]; - expect(created.label).toBe('Full Connector'); - expect(created.lineType).toBe('dotted'); + expect(created.description).toBe('Full Connector'); + expect(created.lineType).toBe('DOTTED'); expect(created.color).toBe('color3'); - expect(created.labels).toEqual(['Label1', 'Label2']); + expect(created.labels).toEqual([ + { id: 'label1', text: 'Label1', position: 25 }, + { id: 'label2', text: 'Label2', position: 75 } + ]); }); }); describe('edge cases and state immutability', () => { it('should not mutate the original state', () => { const originalState = JSON.parse(JSON.stringify(mockState)); - + deleteConnector('connector1', mockContext); - + expect(mockState).toEqual(originalState); }); it('should handle multiple operations in sequence', () => { // Create - let result = createConnector({ - id: 'connector2', - anchors: { - from: { id: 'item3', face: 'top' }, - to: { id: 'item4', face: 'bottom' } - } - }, { ...mockContext, state: mockState }); - + let result = createConnector( + { + id: 'connector2', + anchors: [ + { id: 'from', ref: { item: 'item3' } }, + { id: 'to', ref: { item: 'item4' } } + ] + }, + { ...mockContext, state: mockState } + ); + // Update - result = updateConnector({ - id: 'connector2', - label: 'Updated' - }, { ...mockContext, state: result }); - + result = updateConnector( + { + id: 'connector2', + description: 'Updated' + }, + { ...mockContext, state: result } + ); + // Delete original result = deleteConnector('connector1', { ...mockContext, state: result }); - + expect(result.model.views[0].connectors).toHaveLength(1); expect(result.model.views[0].connectors![0].id).toBe('connector2'); - expect(result.model.views[0].connectors![0].label).toBe('Updated'); + expect(result.model.views[0].connectors![0].description).toBe('Updated'); }); it('should handle view with multiple connectors', () => { - const connectors: Connector[] = Array.from({ length: 5 }, (_, i) => ({ - id: `connector${i}`, - anchors: { - from: { id: `item${i}`, face: 'right' }, - to: { id: `item${i + 1}`, face: 'left' } - } - })); - + const connectors: Connector[] = Array.from({ length: 5 }, (_, i) => { + return { + id: `connector${i}`, + anchors: [ + { id: 'from', ref: { item: `item${i}` } }, + { id: 'to', ref: { item: `item${i + 1}` } } + ] + }; + }); + mockState.model.views[0].connectors = connectors; - + const result = deleteConnector('connector2', mockContext); - + expect(result.model.views[0].connectors).toHaveLength(4); - expect(result.model.views[0].connectors!.find(c => c.id === 'connector2')).toBeUndefined(); + expect( + result.model.views[0].connectors!.find((c) => { + return c.id === 'connector2'; + }) + ).toBeUndefined(); }); }); -}); \ No newline at end of file +}); diff --git a/packages/fossflow-lib/src/stores/reducers/__tests__/rectangle.test.ts b/packages/fossflow-lib/src/stores/reducers/__tests__/rectangle.test.ts index ec53a9bd..206dac44 100644 --- a/packages/fossflow-lib/src/stores/reducers/__tests__/rectangle.test.ts +++ b/packages/fossflow-lib/src/stores/reducers/__tests__/rectangle.test.ts @@ -7,17 +7,19 @@ import { State, ViewReducerContext } from '../types'; import { Rectangle, View } from 'src/types'; // Mock the utility functions -jest.mock('src/utils', () => ({ - getItemByIdOrThrow: jest.fn((items: any[], id: string) => { - const index = items.findIndex((item: any) => - (typeof item === 'object' && item.id === id) || item === id - ); - if (index === -1) { - throw new Error(`Item with id ${id} not found`); - } - return { value: items[index], index }; - }) -})); +jest.mock('src/utils', () => { + return { + getItemByIdOrThrow: jest.fn((items: any[], id: string) => { + const index = items.findIndex((item: any) => { + return (typeof item === 'object' && item.id === id) || item === id; + }); + if (index === -1) { + throw new Error(`Item with id ${id} not found`); + } + return { value: items[index], index }; + }) + }; +}); describe('rectangle reducer', () => { let mockState: State; @@ -27,17 +29,12 @@ describe('rectangle reducer', () => { beforeEach(() => { jest.clearAllMocks(); - + mockRectangle = { id: 'rect1', - position: { x: 0, y: 0 }, - size: { width: 100, height: 50 }, - color: 'color1', - borderColor: 'color2', - borderWidth: 2, - borderStyle: 'solid', - opacity: 1, - cornerRadius: 5 + from: { x: 0, y: 0 }, + to: { x: 100, y: 50 }, + color: 'color1' }; mockView = { @@ -60,11 +57,7 @@ describe('rectangle reducer', () => { views: [mockView] }, scene: { - viewId: 'view1', - viewport: { x: 0, y: 0, zoom: 1 }, - grid: { enabled: true, size: 10, style: 'dots' }, connectors: {}, - viewItems: {}, textBoxes: {} } }; @@ -79,16 +72,12 @@ describe('rectangle reducer', () => { it('should update rectangle properties', () => { const updates = { id: 'rect1', - size: { width: 200, height: 100 }, - color: 'color3', - opacity: 0.5 + color: 'color3' }; - + const result = updateRectangle(updates, mockContext); - - expect(result.model.views[0].rectangles![0].size).toEqual({ width: 200, height: 100 }); + expect(result.model.views[0].rectangles![0].color).toBe('color3'); - expect(result.model.views[0].rectangles![0].opacity).toBe(0.5); }); it('should preserve other properties when partially updating', () => { @@ -96,23 +85,26 @@ describe('rectangle reducer', () => { id: 'rect1', color: 'color4' }; - + const result = updateRectangle(updates, mockContext); - + // Original properties should be preserved - expect(result.model.views[0].rectangles![0].position).toEqual(mockRectangle.position); - expect(result.model.views[0].rectangles![0].size).toEqual(mockRectangle.size); - expect(result.model.views[0].rectangles![0].borderColor).toBe(mockRectangle.borderColor); - expect(result.model.views[0].rectangles![0].cornerRadius).toBe(mockRectangle.cornerRadius); + expect(result.model.views[0].rectangles![0].from).toEqual( + mockRectangle.from + ); + expect(result.model.views[0].rectangles![0].to).toEqual(mockRectangle.to); // Updated property expect(result.model.views[0].rectangles![0].color).toBe('color4'); }); it('should handle undefined rectangles array', () => { mockState.model.views[0].rectangles = undefined; - - const result = updateRectangle({ id: 'rect1', color: 'test' }, mockContext); - + + const result = updateRectangle( + { id: 'rect1', color: 'test' }, + mockContext + ); + // Should return state unchanged expect(result).toEqual(mockState); }); @@ -125,25 +117,21 @@ describe('rectangle reducer', () => { it('should throw error when view does not exist', () => { mockContext.viewId = 'nonexistent'; - + expect(() => { updateRectangle({ id: 'rect1', color: 'test' }, mockContext); }).toThrow('Item with id nonexistent not found'); }); - it('should update border properties', () => { + it('should update rectangle properties', () => { const updates = { id: 'rect1', - borderColor: 'color5', - borderWidth: 4, - borderStyle: 'dashed' as const + color: 'color5' }; - + const result = updateRectangle(updates, mockContext); - - expect(result.model.views[0].rectangles![0].borderColor).toBe('color5'); - expect(result.model.views[0].rectangles![0].borderWidth).toBe(4); - expect(result.model.views[0].rectangles![0].borderStyle).toBe('dashed'); + + expect(result.model.views[0].rectangles![0].color).toBe('color5'); }); }); @@ -151,13 +139,13 @@ describe('rectangle reducer', () => { it('should create a new rectangle', () => { const newRectangle: Rectangle = { id: 'rect2', - position: { x: 50, y: 50 }, - size: { width: 150, height: 75 }, + from: { x: 50, y: 50 }, + to: { x: 200, y: 125 }, color: 'color3' }; - + const result = createRectangle(newRectangle, mockContext); - + // Should be added at the beginning (unshift) expect(result.model.views[0].rectangles).toHaveLength(2); expect(result.model.views[0].rectangles![0].id).toBe('rect2'); @@ -166,15 +154,15 @@ describe('rectangle reducer', () => { it('should initialize rectangles array if undefined', () => { mockState.model.views[0].rectangles = undefined; - + const newRectangle: Rectangle = { id: 'rect2', - position: { x: 50, y: 50 }, - size: { width: 150, height: 75 } + from: { x: 50, y: 50 }, + to: { x: 200, y: 125 } }; - + const result = createRectangle(newRectangle, mockContext); - + expect(result.model.views[0].rectangles).toHaveLength(1); expect(result.model.views[0].rectangles![0].id).toBe('rect2'); }); @@ -182,48 +170,26 @@ describe('rectangle reducer', () => { it('should create rectangle with all properties', () => { const newRectangle: Rectangle = { id: 'rect2', - position: { x: 50, y: 50 }, - size: { width: 150, height: 75 }, - color: 'color6', - borderColor: 'color7', - borderWidth: 3, - borderStyle: 'dotted', - opacity: 0.8, - cornerRadius: 10, - shadow: { - offsetX: 2, - offsetY: 2, - blur: 4, - color: 'color8' - } + from: { x: 50, y: 50 }, + to: { x: 200, y: 125 }, + color: 'color6' }; - + const result = createRectangle(newRectangle, mockContext); - + const created = result.model.views[0].rectangles![0]; expect(created.color).toBe('color6'); - expect(created.borderColor).toBe('color7'); - expect(created.borderWidth).toBe(3); - expect(created.borderStyle).toBe('dotted'); - expect(created.opacity).toBe(0.8); - expect(created.cornerRadius).toBe(10); - expect(created.shadow).toEqual({ - offsetX: 2, - offsetY: 2, - blur: 4, - color: 'color8' - }); }); it('should throw error when view does not exist', () => { mockContext.viewId = 'nonexistent'; - + const newRectangle: Rectangle = { id: 'rect2', - position: { x: 50, y: 50 }, - size: { width: 150, height: 75 } + from: { x: 50, y: 50 }, + to: { x: 200, y: 125 } }; - + expect(() => { createRectangle(newRectangle, mockContext); }).toThrow('Item with id nonexistent not found'); @@ -234,12 +200,12 @@ describe('rectangle reducer', () => { // which ensures any necessary syncing happens const newRectangle: Rectangle = { id: 'rect2', - position: { x: 50, y: 50 }, - size: { width: 150, height: 75 } + from: { x: 50, y: 50 }, + to: { x: 200, y: 125 } }; - + const result = createRectangle(newRectangle, mockContext); - + // The rectangle should have all properties set expect(result.model.views[0].rectangles![0]).toMatchObject(newRectangle); }); @@ -248,10 +214,10 @@ describe('rectangle reducer', () => { describe('deleteRectangle', () => { it('should delete a rectangle from model', () => { const result = deleteRectangle('rect1', mockContext); - + // Check rectangle is removed from model expect(result.model.views[0].rectangles).toHaveLength(0); - + // Rectangles don't have scene data - only stored in model }); @@ -263,7 +229,7 @@ describe('rectangle reducer', () => { it('should throw error when view does not exist', () => { mockContext.viewId = 'nonexistent'; - + expect(() => { deleteRectangle('rect1', mockContext); }).toThrow('Item with id nonexistent not found'); @@ -271,7 +237,7 @@ describe('rectangle reducer', () => { it('should handle empty rectangles array', () => { mockState.model.views[0].rectangles = []; - + expect(() => { deleteRectangle('rect1', mockContext); }).toThrow('Item with id rect1 not found'); @@ -280,17 +246,17 @@ describe('rectangle reducer', () => { it('should not affect other rectangles when deleting one', () => { const rect2: Rectangle = { id: 'rect2', - position: { x: 100, y: 100 }, - size: { width: 80, height: 40 } + from: { x: 100, y: 100 }, + to: { x: 180, y: 140 } }; - + mockState.model.views[0].rectangles = [mockRectangle, rect2]; - + const result = deleteRectangle('rect1', mockContext); - + expect(result.model.views[0].rectangles).toHaveLength(1); expect(result.model.views[0].rectangles![0].id).toBe('rect2'); - + // Rectangles don't have scene data - only verify model is updated }); }); @@ -298,77 +264,73 @@ describe('rectangle reducer', () => { describe('edge cases and state immutability', () => { it('should not mutate the original state', () => { const originalState = JSON.parse(JSON.stringify(mockState)); - + deleteRectangle('rect1', mockContext); - + expect(mockState).toEqual(originalState); }); it('should handle multiple operations in sequence', () => { // Create - let result = createRectangle({ - id: 'rect2', - position: { x: 200, y: 200 }, - size: { width: 50, height: 50 } - }, { ...mockContext, state: mockState }); - + let result = createRectangle( + { + id: 'rect2', + from: { x: 200, y: 200 }, + to: { x: 250, y: 250 } + }, + { ...mockContext, state: mockState } + ); + // Update - result = updateRectangle({ - id: 'rect2', - color: 'updatedColor', - opacity: 0.7 - }, { ...mockContext, state: result }); - + result = updateRectangle( + { + id: 'rect2', + color: 'updatedColor' + }, + { ...mockContext, state: result } + ); + // Delete original result = deleteRectangle('rect1', { ...mockContext, state: result }); - + expect(result.model.views[0].rectangles).toHaveLength(1); expect(result.model.views[0].rectangles![0].id).toBe('rect2'); expect(result.model.views[0].rectangles![0].color).toBe('updatedColor'); - expect(result.model.views[0].rectangles![0].opacity).toBe(0.7); }); it('should handle view with multiple rectangles', () => { - const rectangles: Rectangle[] = Array.from({ length: 5 }, (_, i) => ({ - id: `rect${i}`, - position: { x: i * 20, y: i * 20 }, - size: { width: 100, height: 50 } - })); - + const rectangles: Rectangle[] = Array.from({ length: 5 }, (_, i) => { + return { + id: `rect${i}`, + from: { x: i * 20, y: i * 20 }, + to: { x: i * 20 + 100, y: i * 20 + 50 } + }; + }); + mockState.model.views[0].rectangles = rectangles; - + const result = deleteRectangle('rect2', mockContext); - + expect(result.model.views[0].rectangles).toHaveLength(4); - expect(result.model.views[0].rectangles!.find(r => r.id === 'rect2')).toBeUndefined(); + expect( + result.model.views[0].rectangles!.find((r) => { + return r.id === 'rect2'; + }) + ).toBeUndefined(); }); it('should handle rectangles with complex nested properties', () => { const complexRect: Rectangle = { id: 'rect2', - position: { x: 0, y: 0 }, - size: { width: 100, height: 100 }, - shadow: { - offsetX: 5, - offsetY: 5, - blur: 10, - color: 'shadowColor' - }, - gradient: { - type: 'linear', - angle: 45, - stops: [ - { offset: 0, color: 'color1' }, - { offset: 1, color: 'color2' } - ] - } + from: { x: 0, y: 0 }, + to: { x: 100, y: 100 } }; - + const result = createRectangle(complexRect, mockContext); - + const created = result.model.views[0].rectangles![0]; - expect(created.shadow).toEqual(complexRect.shadow); - expect(created.gradient).toEqual(complexRect.gradient); + expect(created.from).toEqual(complexRect.from); + expect(created.to).toEqual(complexRect.to); }); }); -}); \ No newline at end of file +}); diff --git a/packages/fossflow-lib/src/stores/reducers/__tests__/textBox.test.ts b/packages/fossflow-lib/src/stores/reducers/__tests__/textBox.test.ts index 50336087..01084584 100644 --- a/packages/fossflow-lib/src/stores/reducers/__tests__/textBox.test.ts +++ b/packages/fossflow-lib/src/stores/reducers/__tests__/textBox.test.ts @@ -8,21 +8,25 @@ import { State, ViewReducerContext } from '../types'; import { TextBox, View } from 'src/types'; // Mock the utility functions -jest.mock('src/utils', () => ({ - getItemByIdOrThrow: jest.fn((items: any[], id: string) => { - const index = items.findIndex((item: any) => - (typeof item === 'object' && item.id === id) || item === id - ); - if (index === -1) { - throw new Error(`Item with id ${id} not found`); - } - return { value: items[index], index }; - }), - getTextBoxDimensions: jest.fn((textBox: TextBox) => ({ - width: (textBox.content?.length || 0) * 10, - height: textBox.fontSize || 16 - })) -})); +jest.mock('src/utils', () => { + return { + getItemByIdOrThrow: jest.fn((items: any[], id: string) => { + const index = items.findIndex((item: any) => { + return (typeof item === 'object' && item.id === id) || item === id; + }); + if (index === -1) { + throw new Error(`Item with id ${id} not found`); + } + return { value: items[index], index }; + }), + getTextBoxDimensions: jest.fn((textBox: TextBox) => { + return { + width: (textBox.content?.length || 0) * 10, + height: textBox.fontSize || 16 + }; + }) + }; +}); describe('textBox reducer', () => { let mockState: State; @@ -32,16 +36,12 @@ describe('textBox reducer', () => { beforeEach(() => { jest.clearAllMocks(); - + mockTextBox = { id: 'textbox1', - position: { x: 10, y: 20 }, + tile: { x: 10, y: 20 }, content: 'Test Content', - fontSize: 14, - color: 'color1', - backgroundColor: 'color2', - borderColor: 'color3', - alignment: 'left' + fontSize: 14 }; mockView = { @@ -64,14 +64,9 @@ describe('textBox reducer', () => { views: [mockView] }, scene: { - viewId: 'view1', - viewport: { x: 0, y: 0, zoom: 1 }, - grid: { enabled: true, size: 10, style: 'dots' }, connectors: {}, - viewItems: {}, - rectangles: {}, textBoxes: { - 'textbox1': { + textbox1: { size: { width: 120, height: 14 } } } @@ -89,9 +84,9 @@ describe('textBox reducer', () => { const getTextBoxDimensions = require('src/utils').getTextBoxDimensions; getTextBoxDimensions.mockClear(); getTextBoxDimensions.mockReturnValue({ width: 150, height: 20 }); - + const result = syncTextBox('textbox1', mockContext); - + expect(getTextBoxDimensions).toHaveBeenCalled(); expect(result.scene.textBoxes['textbox1'].size).toEqual({ width: 150, @@ -107,17 +102,17 @@ describe('textBox reducer', () => { it('should handle empty textBoxes array', () => { mockState.model.views[0].textBoxes = []; - + expect(() => { syncTextBox('textbox1', mockContext); }).toThrow('Item with id textbox1 not found'); }); - it('should create scene entry if it doesn\'t exist', () => { + it("should create scene entry if it doesn't exist", () => { delete mockState.scene.textBoxes['textbox1']; - + const result = syncTextBox('textbox1', mockContext); - + expect(result.scene.textBoxes['textbox1']).toBeDefined(); expect(result.scene.textBoxes['textbox1'].size).toBeDefined(); }); @@ -128,28 +123,28 @@ describe('textBox reducer', () => { const updates = { id: 'textbox1', content: 'Updated Content', - fontSize: 18, - color: 'color4' + fontSize: 18 }; - + const result = updateTextBox(updates, mockContext); - - expect(result.model.views[0].textBoxes![0].content).toBe('Updated Content'); + + expect(result.model.views[0].textBoxes![0].content).toBe( + 'Updated Content' + ); expect(result.model.views[0].textBoxes![0].fontSize).toBe(18); - expect(result.model.views[0].textBoxes![0].color).toBe('color4'); }); it('should sync when content is updated', () => { const getTextBoxDimensions = require('src/utils').getTextBoxDimensions; getTextBoxDimensions.mockClear(); - + const updates = { id: 'textbox1', content: 'New Content That Is Longer' }; - + updateTextBox(updates, mockContext); - + // Should trigger sync because content changed expect(getTextBoxDimensions).toHaveBeenCalled(); }); @@ -157,14 +152,14 @@ describe('textBox reducer', () => { it('should sync when fontSize is updated', () => { const getTextBoxDimensions = require('src/utils').getTextBoxDimensions; getTextBoxDimensions.mockClear(); - + const updates = { id: 'textbox1', fontSize: 24 }; - + updateTextBox(updates, mockContext); - + // Should trigger sync because fontSize changed expect(getTextBoxDimensions).toHaveBeenCalled(); }); @@ -172,24 +167,25 @@ describe('textBox reducer', () => { it('should not sync when other properties are updated', () => { const getTextBoxDimensions = require('src/utils').getTextBoxDimensions; getTextBoxDimensions.mockClear(); - + const updates = { - id: 'textbox1', - color: 'color5', - backgroundColor: 'color6' + id: 'textbox1' }; - + updateTextBox(updates, mockContext); - + // Should NOT trigger sync for non-size-affecting properties expect(getTextBoxDimensions).not.toHaveBeenCalled(); }); it('should handle undefined textBoxes array', () => { mockState.model.views[0].textBoxes = undefined; - - const result = updateTextBox({ id: 'textbox1', content: 'test' }, mockContext); - + + const result = updateTextBox( + { id: 'textbox1', content: 'test' }, + mockContext + ); + // Should return state unchanged expect(result).toEqual(mockState); }); @@ -199,15 +195,20 @@ describe('textBox reducer', () => { id: 'textbox1', content: 'Partial Update' }; - + const result = updateTextBox(updates, mockContext); - + // Original properties should be preserved - expect(result.model.views[0].textBoxes![0].fontSize).toBe(mockTextBox.fontSize); - expect(result.model.views[0].textBoxes![0].color).toBe(mockTextBox.color); - expect(result.model.views[0].textBoxes![0].position).toEqual(mockTextBox.position); + expect(result.model.views[0].textBoxes![0].fontSize).toBe( + mockTextBox.fontSize + ); + expect(result.model.views[0].textBoxes![0].tile).toEqual( + mockTextBox.tile + ); // Updated property - expect(result.model.views[0].textBoxes![0].content).toBe('Partial Update'); + expect(result.model.views[0].textBoxes![0].content).toBe( + 'Partial Update' + ); }); it('should throw error when text box does not exist', () => { @@ -221,18 +222,18 @@ describe('textBox reducer', () => { it('should create a new text box', () => { const newTextBox: TextBox = { id: 'textbox2', - position: { x: 30, y: 40 }, + tile: { x: 30, y: 40 }, content: 'New Text Box', fontSize: 16 }; - + const result = createTextBox(newTextBox, mockContext); - + // Should be added at the beginning (unshift) expect(result.model.views[0].textBoxes).toHaveLength(2); expect(result.model.views[0].textBoxes![0].id).toBe('textbox2'); expect(result.model.views[0].textBoxes![1].id).toBe('textbox1'); - + // Should sync the new text box expect(result.scene.textBoxes['textbox2']).toBeDefined(); expect(result.scene.textBoxes['textbox2'].size).toBeDefined(); @@ -240,15 +241,15 @@ describe('textBox reducer', () => { it('should initialize textBoxes array if undefined', () => { mockState.model.views[0].textBoxes = undefined; - + const newTextBox: TextBox = { id: 'textbox2', - position: { x: 30, y: 40 }, + tile: { x: 30, y: 40 }, content: 'New Text Box' }; - + const result = createTextBox(newTextBox, mockContext); - + expect(result.model.views[0].textBoxes).toHaveLength(1); expect(result.model.views[0].textBoxes![0].id).toBe('textbox2'); }); @@ -256,39 +257,29 @@ describe('textBox reducer', () => { it('should create text box with all properties', () => { const newTextBox: TextBox = { id: 'textbox2', - position: { x: 30, y: 40 }, + tile: { x: 30, y: 40 }, content: 'Full Text Box', fontSize: 20, - color: 'color7', - backgroundColor: 'color8', - borderColor: 'color9', - alignment: 'center', - bold: true, - italic: true + orientation: 'X' }; - + const result = createTextBox(newTextBox, mockContext); - + const created = result.model.views[0].textBoxes![0]; expect(created.content).toBe('Full Text Box'); expect(created.fontSize).toBe(20); - expect(created.color).toBe('color7'); - expect(created.backgroundColor).toBe('color8'); - expect(created.borderColor).toBe('color9'); - expect(created.alignment).toBe('center'); - expect(created.bold).toBe(true); - expect(created.italic).toBe(true); + expect(created.orientation).toBe('X'); }); it('should throw error when view does not exist', () => { mockContext.viewId = 'nonexistent'; - + const newTextBox: TextBox = { id: 'textbox2', - position: { x: 30, y: 40 }, + tile: { x: 30, y: 40 }, content: 'New Text Box' }; - + expect(() => { createTextBox(newTextBox, mockContext); }).toThrow('Item with id nonexistent not found'); @@ -298,10 +289,10 @@ describe('textBox reducer', () => { describe('deleteTextBox', () => { it('should delete a text box from both model and scene', () => { const result = deleteTextBox('textbox1', mockContext); - + // Check text box is removed from model expect(result.model.views[0].textBoxes).toHaveLength(0); - + // Check text box is removed from scene expect(result.scene.textBoxes['textbox1']).toBeUndefined(); }); @@ -314,7 +305,7 @@ describe('textBox reducer', () => { it('should throw error when view does not exist', () => { mockContext.viewId = 'nonexistent'; - + expect(() => { deleteTextBox('textbox1', mockContext); }).toThrow('Item with id nonexistent not found'); @@ -322,7 +313,7 @@ describe('textBox reducer', () => { it('should handle empty textBoxes array', () => { mockState.model.views[0].textBoxes = []; - + expect(() => { deleteTextBox('textbox1', mockContext); }).toThrow('Item with id textbox1 not found'); @@ -331,20 +322,20 @@ describe('textBox reducer', () => { it('should not affect other text boxes when deleting one', () => { const textBox2: TextBox = { id: 'textbox2', - position: { x: 50, y: 60 }, + tile: { x: 50, y: 60 }, content: 'Second Text Box' }; - + mockState.model.views[0].textBoxes = [mockTextBox, textBox2]; - mockState.scene.textBoxes['textbox2'] = { + mockState.scene.textBoxes['textbox2'] = { size: { width: 150, height: 16 } }; - + const result = deleteTextBox('textbox1', mockContext); - + expect(result.model.views[0].textBoxes).toHaveLength(1); expect(result.model.views[0].textBoxes![0].id).toBe('textbox2'); - + // Verify proper scene cleanup expect(result.scene.textBoxes['textbox1']).toBeUndefined(); expect(result.scene.textBoxes['textbox2']).toBeDefined(); @@ -354,30 +345,36 @@ describe('textBox reducer', () => { describe('edge cases and state immutability', () => { it('should not mutate the original state', () => { const originalState = JSON.parse(JSON.stringify(mockState)); - + deleteTextBox('textbox1', mockContext); - + expect(mockState).toEqual(originalState); }); it('should handle multiple operations in sequence', () => { // Create - let result = createTextBox({ - id: 'textbox2', - position: { x: 100, y: 100 }, - content: 'New Box' - }, { ...mockContext, state: mockState }); - + let result = createTextBox( + { + id: 'textbox2', + tile: { x: 100, y: 100 }, + content: 'New Box' + }, + { ...mockContext, state: mockState } + ); + // Update - result = updateTextBox({ - id: 'textbox2', - content: 'Updated Box', - fontSize: 24 - }, { ...mockContext, state: result }); - + result = updateTextBox( + { + id: 'textbox2', + content: 'Updated Box', + fontSize: 24 + }, + { ...mockContext, state: result } + ); + // Delete original result = deleteTextBox('textbox1', { ...mockContext, state: result }); - + expect(result.model.views[0].textBoxes).toHaveLength(1); expect(result.model.views[0].textBoxes![0].id).toBe('textbox2'); expect(result.model.views[0].textBoxes![0].content).toBe('Updated Box'); @@ -385,18 +382,24 @@ describe('textBox reducer', () => { }); it('should handle view with multiple text boxes', () => { - const textBoxes: TextBox[] = Array.from({ length: 5 }, (_, i) => ({ - id: `textbox${i}`, - position: { x: i * 10, y: i * 10 }, - content: `Text ${i}` - })); - + const textBoxes: TextBox[] = Array.from({ length: 5 }, (_, i) => { + return { + id: `textbox${i}`, + tile: { x: i * 10, y: i * 10 }, + content: `Text ${i}` + }; + }); + mockState.model.views[0].textBoxes = textBoxes; - + const result = deleteTextBox('textbox2', mockContext); - + expect(result.model.views[0].textBoxes).toHaveLength(4); - expect(result.model.views[0].textBoxes!.find(t => t.id === 'textbox2')).toBeUndefined(); + expect( + result.model.views[0].textBoxes!.find((t) => { + return t.id === 'textbox2'; + }) + ).toBeUndefined(); }); }); -}); \ No newline at end of file +}); diff --git a/packages/fossflow-lib/src/stores/reducers/__tests__/viewItem.test.ts b/packages/fossflow-lib/src/stores/reducers/__tests__/viewItem.test.ts index bb3071f0..740e31aa 100644 --- a/packages/fossflow-lib/src/stores/reducers/__tests__/viewItem.test.ts +++ b/packages/fossflow-lib/src/stores/reducers/__tests__/viewItem.test.ts @@ -1,38 +1,46 @@ -import { - deleteViewItem, - updateViewItem, - createViewItem -} from '../viewItem'; +import { deleteViewItem, updateViewItem, createViewItem } from '../viewItem'; import { State, ViewReducerContext } from '../types'; import { ViewItem, View, Connector } from 'src/types'; // Mock the utility functions and reducers -jest.mock('src/utils', () => ({ - getItemByIdOrThrow: jest.fn((items: any[], id: string) => { - const index = items.findIndex((item: any) => - (typeof item === 'object' && item.id === id) || item === id - ); - if (index === -1) { - throw new Error(`Item with id ${id} not found`); - } - return { value: items[index], index }; - }), - getConnectorsByViewItem: jest.fn((viewItemId: string, connectors: Connector[]) => { - return connectors.filter(connector => - connector.anchors.some((anchor: any) => - anchor.ref?.item === viewItemId - ) - ); - }) -})); - -jest.mock('src/schemas/validation', () => ({ - validateView: jest.fn(() => []) -})); - -jest.mock('../view', () => ({ - view: jest.fn((params: any) => params.ctx.state) -})); +jest.mock('src/utils', () => { + return { + getItemByIdOrThrow: jest.fn((items: any[], id: string) => { + const index = items.findIndex((item: any) => { + return (typeof item === 'object' && item.id === id) || item === id; + }); + if (index === -1) { + throw new Error(`Item with id ${id} not found`); + } + return { value: items[index], index }; + }), + getConnectorsByViewItem: jest.fn( + (viewItemId: string, connectors: Connector[]) => { + return connectors.filter((connector) => { + return connector.anchors.some((anchor: any) => { + return anchor.ref?.item === viewItemId; + }); + }); + } + ) + }; +}); + +jest.mock('src/schemas/validation', () => { + return { + validateView: jest.fn(() => { + return []; + }) + }; +}); + +jest.mock('../view', () => { + return { + view: jest.fn((params: any) => { + return params.ctx.state; + }) + }; +}); describe('viewItem reducer', () => { let mockState: State; @@ -46,8 +54,7 @@ describe('viewItem reducer', () => { mockViewItem = { id: 'item1', - tile: { x: 0, y: 0 }, - size: { width: 100, height: 100 } + tile: { x: 0, y: 0 } }; mockConnector = { @@ -55,15 +62,11 @@ describe('viewItem reducer', () => { anchors: [ { id: 'anchor1', - ref: { item: 'item1' }, - face: 'right', - offset: 0 + ref: { item: 'item1' } }, { id: 'anchor2', - ref: { item: 'item2' }, - face: 'left', - offset: 0 + ref: { item: 'item2' } } ] }; @@ -88,19 +91,14 @@ describe('viewItem reducer', () => { views: [mockView] }, scene: { - viewId: 'view1', - viewport: { x: 0, y: 0, zoom: 1 }, - grid: { enabled: true, size: 10, style: 'dots' }, connectors: { - 'connector1': { + connector1: { path: { tiles: [], rectangle: { from: { x: 0, y: 0 }, to: { x: 1, y: 0 } } } } }, - viewItems: {}, - rectangles: {}, textBoxes: {} } }; @@ -117,7 +115,11 @@ describe('viewItem reducer', () => { // Check item is removed from model expect(result.model.views[0].items).toHaveLength(1); - expect(result.model.views[0].items.find(item => item.id === 'item1')).toBeUndefined(); + expect( + result.model.views[0].items.find((item) => { + return item.id === 'item1'; + }) + ).toBeUndefined(); // Check connectors referencing the item are removed expect(result.model.views[0].connectors).toHaveLength(0); @@ -125,25 +127,26 @@ describe('viewItem reducer', () => { }); it('should only remove connectors that reference the deleted item', () => { - const connector2: Connector = { + const connector2 = { id: 'connector2', anchors: [ { id: 'anchor3', - ref: { item: 'item2' }, - face: 'top' + ref: { item: 'item2' } }, { id: 'anchor4', - ref: { item: 'item3' }, - face: 'bottom' + ref: { item: 'item3' } } ] }; mockState.model.views[0].connectors = [mockConnector, connector2]; mockState.scene.connectors['connector2'] = { - path: { tiles: [], rectangle: { from: { x: 1, y: 1 }, to: { x: 2, y: 2 } } } + path: { + tiles: [], + rectangle: { from: { x: 1, y: 1 }, to: { x: 2, y: 2 } } + } }; const result = deleteViewItem('item1', mockContext); @@ -157,21 +160,21 @@ describe('viewItem reducer', () => { it('should handle deletion when no connectors reference the item', () => { // Create a connector that doesn't reference item1 - mockState.model.views[0].connectors = [{ - id: 'connector2', - anchors: [ - { - id: 'anchor3', - ref: { item: 'item2' }, - face: 'top' - }, - { - id: 'anchor4', - ref: { item: 'item3' }, - face: 'bottom' - } - ] - }]; + mockState.model.views[0].connectors = [ + { + id: 'connector2', + anchors: [ + { + id: 'anchor3', + ref: { item: 'item2' } + }, + { + id: 'anchor4', + ref: { item: 'item3' } + } + ] + } + ]; const result = deleteViewItem('item1', mockContext); @@ -208,18 +211,15 @@ describe('viewItem reducer', () => { anchors: [ { id: 'anchor5', - ref: { item: 'item1' }, - face: 'top' + ref: { item: 'item1' } }, { id: 'anchor6', - ref: { item: 'item1' }, - face: 'bottom' + ref: { item: 'item1' } }, { id: 'anchor7', - ref: { item: 'item2' }, - face: 'left' + ref: { item: 'item2' } } ] }; @@ -237,15 +237,15 @@ describe('viewItem reducer', () => { it('should update view item properties', () => { const updates = { id: 'item1', - tile: { x: 2, y: 2 }, - size: { width: 200, height: 200 } + tile: { x: 2, y: 2 } }; const result = updateViewItem(updates, mockContext); - const updatedItem = result.model.views[0].items.find(item => item.id === 'item1'); + const updatedItem = result.model.views[0].items.find((item) => { + return item.id === 'item1'; + }); expect(updatedItem?.tile).toEqual({ x: 2, y: 2 }); - expect(updatedItem?.size).toEqual({ width: 200, height: 200 }); }); it('should update connectors when item tile position changes', () => { @@ -257,12 +257,15 @@ describe('viewItem reducer', () => { const result = updateViewItem(updates, mockContext); // The item should be updated with new position - const updatedItem = result.model.views[0].items.find(item => item.id === 'item1'); + const updatedItem = result.model.views[0].items.find((item) => { + return item.id === 'item1'; + }); expect(updatedItem?.tile).toEqual({ x: 5, y: 5 }); // When tile changes, connectors that reference this item are updated // The mock implementation tracks that connectors referencing the item were found - const getConnectorsByViewItem = require('src/utils').getConnectorsByViewItem; + const getConnectorsByViewItem = + require('src/utils').getConnectorsByViewItem; expect(getConnectorsByViewItem).toHaveBeenCalled(); }); @@ -281,7 +284,7 @@ describe('viewItem reducer', () => { const updates = { id: 'item1', - size: { width: 150, height: 150 } + labelHeight: 20 }; updateViewItem(updates, mockContext); @@ -292,7 +295,10 @@ describe('viewItem reducer', () => { it('should throw error when item does not exist', () => { expect(() => { - updateViewItem({ id: 'nonexistent', tile: { x: 1, y: 1 } }, mockContext); + updateViewItem( + { id: 'nonexistent', tile: { x: 1, y: 1 } }, + mockContext + ); }).toThrow('Item with id nonexistent not found'); }); }); @@ -301,8 +307,7 @@ describe('viewItem reducer', () => { it('should create a new view item', () => { const newItem: ViewItem = { id: 'item3', - tile: { x: 3, y: 3 }, - size: { width: 100, height: 100 } + tile: { x: 3, y: 3 } }; const result = createViewItem(newItem, mockContext); @@ -351,23 +356,41 @@ describe('viewItem reducer', () => { it('should handle multiple operations in sequence', () => { // Create - let result = createViewItem({ - id: 'item3', - tile: { x: 2, y: 2 } - }, { ...mockContext, state: mockState }); + let result = createViewItem( + { + id: 'item3', + tile: { x: 2, y: 2 } + }, + { ...mockContext, state: mockState } + ); // Update - result = updateViewItem({ - id: 'item3', - size: { width: 150, height: 150 } - }, { ...mockContext, state: result }); + result = updateViewItem( + { + id: 'item3', + labelHeight: 25 + }, + { ...mockContext, state: result } + ); // Delete original result = deleteViewItem('item1', { ...mockContext, state: result }); - expect(result.model.views[0].items.find(item => item.id === 'item3')).toBeDefined(); - expect(result.model.views[0].items.find(item => item.id === 'item3')?.size).toEqual({ width: 150, height: 150 }); - expect(result.model.views[0].items.find(item => item.id === 'item1')).toBeUndefined(); + expect( + result.model.views[0].items.find((item) => { + return item.id === 'item3'; + }) + ).toBeDefined(); + expect( + result.model.views[0].items.find((item) => { + return item.id === 'item3'; + })?.labelHeight + ).toBe(25); + expect( + result.model.views[0].items.find((item) => { + return item.id === 'item1'; + }) + ).toBeUndefined(); // Connector referencing item1 should be removed expect(result.model.views[0].connectors).toHaveLength(0); @@ -382,4 +405,4 @@ describe('viewItem reducer', () => { expect(result.model.views[0].connectors).toHaveLength(0); }); }); -}); \ No newline at end of file +}); diff --git a/packages/fossflow-lib/src/stores/reducers/connector.ts b/packages/fossflow-lib/src/stores/reducers/connector.ts index e8902e3a..452fc73c 100644 --- a/packages/fossflow-lib/src/stores/reducers/connector.ts +++ b/packages/fossflow-lib/src/stores/reducers/connector.ts @@ -25,7 +25,7 @@ export const syncConnector = ( const newState = produce(state, (draft) => { const view = getItemByIdOrThrow(draft.model.views, viewId); const connector = getItemByIdOrThrow(view.value.connectors ?? [], id); - + // Skip validation - allow all connectors regardless of position try { const path = getConnectorPath({ @@ -36,14 +36,14 @@ export const syncConnector = ( draft.scene.connectors[connector.value.id] = { path }; } catch (error) { // Even if we can't get the path, keep the connector with an empty path - draft.scene.connectors[connector.value.id] = { - path: { - tiles: [], - rectangle: { - from: { x: 0, y: 0 }, - to: { x: 0, y: 0 } - } - } + draft.scene.connectors[connector.value.id] = { + path: { + tiles: [], + rectangle: { + from: { x: 0, y: 0 }, + to: { x: 0, y: 0 } + } + } }; } }); diff --git a/packages/fossflow-lib/src/stores/reducers/viewItem.ts b/packages/fossflow-lib/src/stores/reducers/viewItem.ts index 8a6f282b..a1cf9157 100644 --- a/packages/fossflow-lib/src/stores/reducers/viewItem.ts +++ b/packages/fossflow-lib/src/stores/reducers/viewItem.ts @@ -82,14 +82,20 @@ export const deleteViewItem = ( ); // Remove connectors that reference the deleted item - if (connectorsToDelete.length > 0 && draft.model.views[view.index].connectors) { - draft.model.views[view.index].connectors = - draft.model.views[view.index].connectors?.filter( - connector => !connectorsToDelete.some(c => c.id === connector.id) - ); + if ( + connectorsToDelete.length > 0 && + draft.model.views[view.index].connectors + ) { + draft.model.views[view.index].connectors = draft.model.views[ + view.index + ].connectors?.filter((connector) => { + return !connectorsToDelete.some((c) => { + return c.id === connector.id; + }); + }); // Also remove from scene - connectorsToDelete.forEach(connector => { + connectorsToDelete.forEach((connector) => { delete draft.scene.connectors[connector.id]; }); } diff --git a/packages/fossflow-lib/src/types/common.ts b/packages/fossflow-lib/src/types/common.ts index 44044705..d0e75a68 100644 --- a/packages/fossflow-lib/src/types/common.ts +++ b/packages/fossflow-lib/src/types/common.ts @@ -22,7 +22,16 @@ export type BoundingBox = [Coords, Coords, Coords, Coords]; export type SlimMouseEvent = Pick< MouseEvent, - 'clientX' | 'clientY' | 'target' | 'type' | 'preventDefault' | 'button' | 'ctrlKey' | 'altKey' | 'shiftKey' | 'metaKey' + | 'clientX' + | 'clientY' + | 'target' + | 'type' + | 'preventDefault' + | 'button' + | 'ctrlKey' + | 'altKey' + | 'shiftKey' + | 'metaKey' >; export const EditorModeEnum = { diff --git a/packages/fossflow-lib/src/types/dom-to-image-more.d.ts b/packages/fossflow-lib/src/types/dom-to-image-more.d.ts index 678d41ff..cd2671f4 100644 --- a/packages/fossflow-lib/src/types/dom-to-image-more.d.ts +++ b/packages/fossflow-lib/src/types/dom-to-image-more.d.ts @@ -17,7 +17,10 @@ declare module 'dom-to-image-more' { export function toJpeg(node: Node, options?: Options): Promise; export function toSvg(node: Node, options?: Options): Promise; export function toBlob(node: Node, options?: Options): Promise; - export function toPixelData(node: Node, options?: Options): Promise; + export function toPixelData( + node: Node, + options?: Options + ): Promise; const domtoimage: { toPng: typeof toPng; @@ -28,4 +31,4 @@ declare module 'dom-to-image-more' { }; export default domtoimage; -} \ No newline at end of file +} diff --git a/packages/fossflow-lib/src/types/ui.ts b/packages/fossflow-lib/src/types/ui.ts index 9d84dffa..46020106 100644 --- a/packages/fossflow-lib/src/types/ui.ts +++ b/packages/fossflow-lib/src/types/ui.ts @@ -44,7 +44,7 @@ export interface DragItemsMode { type: 'DRAG_ITEMS'; showCursor: boolean; items: ItemReference[]; - isInitialMovement: Boolean; + isInitialMovement: boolean; } export interface PanMode { @@ -160,7 +160,6 @@ export interface ContextMenu { tile: Coords; } - export type ConnectorInteractionMode = 'click' | 'drag'; export interface UiState { @@ -185,7 +184,6 @@ export interface UiState { connectorInteractionMode: ConnectorInteractionMode; expandLabels: boolean; iconPackManager: IconPackManagerProps | null; - } export interface UiStateActions { @@ -213,7 +211,6 @@ export interface UiStateActions { setConnectorInteractionMode: (mode: ConnectorInteractionMode) => void; setExpandLabels: (expand: boolean) => void; setIconPackManager: (iconPackManager: IconPackManagerProps | null) => void; - } export type UiStateStore = UiState & { diff --git a/packages/fossflow-lib/src/utils/exportOptions.ts b/packages/fossflow-lib/src/utils/exportOptions.ts index 3f29ffb6..0c3a761e 100644 --- a/packages/fossflow-lib/src/utils/exportOptions.ts +++ b/packages/fossflow-lib/src/utils/exportOptions.ts @@ -41,23 +41,39 @@ export const transformToCompactFormat = (model: Model) => { const { items, views, icons, title } = model; // Compact format: ultra-minimal for LLM generation - const compactItems = items.map((item, index) => [ - item.name.substring(0, 30), // Truncated name - item.icon || 'block', // Icon reference only (no base64) - item.description?.substring(0, 100) || '' // Truncated description - ]); + const compactItems = items.map((item, index) => { + return [ + item.name.substring(0, 30), // Truncated name + item.icon || 'block', // Icon reference only (no base64) + item.description?.substring(0, 100) || '' // Truncated description + ]; + }); const compactViews = views.map((view) => { const positions = view.items.map((viewItem) => { - const itemIndex = items.findIndex(item => item.id === viewItem.id); + const itemIndex = items.findIndex((item) => { + return item.id === viewItem.id; + }); return [itemIndex, viewItem.tile.x, viewItem.tile.y]; }); - const connections = view.connectors?.map((connector) => { - const fromIndex = items.findIndex(item => item.id === connector.anchors[0]?.ref.item); - const toIndex = items.findIndex(item => item.id === connector.anchors[connector.anchors.length - 1]?.ref.item); - return [fromIndex, toIndex]; - }).filter(conn => conn[0] !== -1 && conn[1] !== -1) || []; + const connections = + view.connectors + ?.map((connector) => { + const fromIndex = items.findIndex((item) => { + return item.id === connector.anchors[0]?.ref.item; + }); + const toIndex = items.findIndex((item) => { + return ( + item.id === + connector.anchors[connector.anchors.length - 1]?.ref.item + ); + }); + return [fromIndex, toIndex]; + }) + .filter((conn) => { + return conn[0] !== -1 && conn[1] !== -1; + }) || []; return [positions, connections]; }); @@ -74,12 +90,14 @@ export const transformFromCompactFormat = (compactModel: any): Model => { const { t, i, v, _ } = compactModel; // Restore from compact format - const fullItems = i.map((item: any, index: number) => ({ - id: `item_${index}`, - name: item[0], - icon: item[1], - description: item[2] || '' // Restore description if available - })); + const fullItems = i.map((item: any, index: number) => { + return { + id: `item_${index}`, + name: item[0], + icon: item[1], + description: item[2] || '' // Restore description if available + }; + }); // Resolve icons from the internal icon library const iconSet = new Set(); @@ -87,10 +105,12 @@ export const transformFromCompactFormat = (compactModel: any): Model => { if (item[1]) iconSet.add(item[1]); }); - const fullIcons = Array.from(iconSet).map(iconName => { + const fullIcons = Array.from(iconSet).map((iconName) => { // Find the icon in the available icons library - const existingIcon = availableIcons.find(icon => icon.id === iconName || icon.name === iconName); - + const existingIcon = availableIcons.find((icon) => { + return icon.id === iconName || icon.name === iconName; + }); + if (existingIcon) { // Use the existing icon data with proper URL return { @@ -129,8 +149,14 @@ export const transformFromCompactFormat = (compactModel: any): Model => { id: `conn_${viewIndex}_${connIndex}`, color: 'color1', anchors: [ - { id: `a_${viewIndex}_${connIndex}_0`, ref: { item: `item_${fromIndex}` } }, - { id: `a_${viewIndex}_${connIndex}_1`, ref: { item: `item_${toIndex}` } } + { + id: `a_${viewIndex}_${connIndex}_0`, + ref: { item: `item_${fromIndex}` } + }, + { + id: `a_${viewIndex}_${connIndex}_1`, + ref: { item: `item_${toIndex}` } + } ], width: 10, description: '', @@ -193,10 +219,13 @@ export const exportAsImage = async ( bgcolor, quality: 1.0, // Apply CSS transform for high-quality scaling - style: scale !== 1 ? { - transform: `scale(${scale})`, - transformOrigin: 'top left' - } : undefined + style: + scale !== 1 + ? { + transform: `scale(${scale})`, + transformOrigin: 'top left' + } + : undefined }; try { diff --git a/packages/fossflow-lib/src/utils/findNearestUnoccupiedTile.ts b/packages/fossflow-lib/src/utils/findNearestUnoccupiedTile.ts index 3c23c92a..cf6003e2 100644 --- a/packages/fossflow-lib/src/utils/findNearestUnoccupiedTile.ts +++ b/packages/fossflow-lib/src/utils/findNearestUnoccupiedTile.ts @@ -22,10 +22,10 @@ export const findNearestUnoccupiedTile = ( // Spiral search pattern: right, down, left, up const directions = [ - { x: 1, y: 0 }, // right - { x: 0, y: 1 }, // down - { x: -1, y: 0 }, // left - { x: 0, y: -1 } // up + { x: 1, y: 0 }, // right + { x: 0, y: 1 }, // down + { x: -1, y: 0 }, // left + { x: 0, y: -1 } // up ]; // Search in expanding rings around the target @@ -40,7 +40,7 @@ export const findNearestUnoccupiedTile = ( for (let side = 0; side < 4; side++) { const direction = directions[side]; const sideLength = distance * 2; - + for (let step = 0; step < sideLength; step++) { // Move to the next tile on this side of the ring currentTile = { @@ -78,7 +78,7 @@ export const findNearestUnoccupiedTilesForGroup = ( const occupiedTiles = new Set(); // Add existing items to occupied tiles (excluding the ones being moved) - scene.items.forEach(item => { + scene.items.forEach((item) => { if (!excludeIds.includes(item.id)) { occupiedTiles.add(`${item.tile.x},${item.tile.y}`); } @@ -105,10 +105,14 @@ export const findNearestUnoccupiedTilesForGroup = ( y: item.targetTile.y + dy }; const checkKey = `${checkTile.x},${checkTile.y}`; - + if (!occupiedTiles.has(checkKey)) { const itemAtTile = getItemAtTile({ tile: checkTile, scene }); - if (!itemAtTile || itemAtTile.type !== 'ITEM' || excludeIds.includes(itemAtTile.id)) { + if ( + !itemAtTile || + itemAtTile.type !== 'ITEM' || + excludeIds.includes(itemAtTile.id) + ) { foundTile = checkTile; break; } @@ -130,4 +134,4 @@ export const findNearestUnoccupiedTilesForGroup = ( } return result; -}; \ No newline at end of file +}; diff --git a/packages/fossflow-lib/src/utils/pointInPolygon.ts b/packages/fossflow-lib/src/utils/pointInPolygon.ts index 30081576..bb899a56 100644 --- a/packages/fossflow-lib/src/utils/pointInPolygon.ts +++ b/packages/fossflow-lib/src/utils/pointInPolygon.ts @@ -35,7 +35,9 @@ export const screenPathToTilePath = ( screenPath: Coords[], screenToIsoFn: (coords: Coords) => Coords ): Coords[] => { - return screenPath.map((point) => screenToIsoFn(point)); + return screenPath.map((point) => { + return screenToIsoFn(point); + }); }; /** diff --git a/packages/fossflow-lib/src/utils/renderer.ts b/packages/fossflow-lib/src/utils/renderer.ts index 58f52bea..43028955 100644 --- a/packages/fossflow-lib/src/utils/renderer.ts +++ b/packages/fossflow-lib/src/utils/renderer.ts @@ -718,18 +718,21 @@ export const getProjectBounds = ( }; export const getVisualBounds = (view: View, padding = 50) => { - let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity; - + let minX = Infinity, + maxX = -Infinity, + minY = Infinity, + maxY = -Infinity; + // Collect actual content positions and find extremes view.items.forEach((item) => { const pos = getTilePosition({ tile: item.tile }); const itemSize = 50; - minX = Math.min(minX, pos.x - itemSize/2); - maxX = Math.max(maxX, pos.x + itemSize/2); - minY = Math.min(minY, pos.y - itemSize/2); - maxY = Math.max(maxY, pos.y + itemSize/2); + minX = Math.min(minX, pos.x - itemSize / 2); + maxX = Math.max(maxX, pos.x + itemSize / 2); + minY = Math.min(minY, pos.y - itemSize / 2); + maxY = Math.max(maxY, pos.y + itemSize / 2); }); - + const connectors = view.connectors ?? []; connectors.forEach((connector) => { const path = getConnectorPath({ anchors: connector.anchors, view }); @@ -742,7 +745,7 @@ export const getVisualBounds = (view: View, padding = 50) => { maxY = Math.max(maxY, pos.y); }); }); - + const textBoxes = view.textBoxes ?? []; textBoxes.forEach((textBox) => { const pos = getTilePosition({ tile: textBox.tile }); @@ -753,7 +756,7 @@ export const getVisualBounds = (view: View, padding = 50) => { minY = Math.min(minY, pos.y, endPos.y); maxY = Math.max(maxY, pos.y, endPos.y); }); - + const rectangles = view.rectangles ?? []; rectangles.forEach((rectangle) => { const fromPos = getTilePosition({ tile: rectangle.from }); @@ -763,17 +766,17 @@ export const getVisualBounds = (view: View, padding = 50) => { minY = Math.min(minY, fromPos.y, toPos.y); maxY = Math.max(maxY, fromPos.y, toPos.y); }); - + if (minX === Infinity) { return { x: 0, y: 0, width: 200, height: 200 }; } - + // Create tight bounds around actual content extremes return { x: minX - padding, y: minY - padding, - width: (maxX - minX) + (padding * 2), - height: (maxY - minY) + (padding * 2) + width: maxX - minX + padding * 2, + height: maxY - minY + padding * 2 }; };