diff --git a/.github/workflows/Deploy Vite to GitHub Pages.yml b/.github/workflows/Deploy Vite to GitHub Pages.yml index dccf6fc..e4fb9a5 100644 --- a/.github/workflows/Deploy Vite to GitHub Pages.yml +++ b/.github/workflows/Deploy Vite to GitHub Pages.yml @@ -48,4 +48,4 @@ jobs: path: './dist' - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 \ No newline at end of file + uses: actions/deploy-pages@v4 diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..759232e --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "semi": false, + "singleQuote": true, + "arrowParens": "avoid" +} diff --git a/.repoignore b/.repoignore new file mode 100644 index 0000000..b6cdf57 --- /dev/null +++ b/.repoignore @@ -0,0 +1,25 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local +*.tsbuildinfo + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/eslint.config.js b/eslint.config.js index 092408a..f76201c 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -3,12 +3,17 @@ import globals from 'globals' import reactHooks from 'eslint-plugin-react-hooks' import reactRefresh from 'eslint-plugin-react-refresh' import tseslint from 'typescript-eslint' +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended' export default tseslint.config( { ignores: ['dist'] }, { - extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + ...tseslint.configs.recommended, + eslintPluginPrettierRecommended, + ], + files: ['**/*.{js,jsx,ts,tsx}'], languageOptions: { ecmaVersion: 2020, globals: globals.browser, @@ -23,6 +28,8 @@ export default tseslint.config( 'warn', { allowConstantExport: true }, ], + semi: ['error', 'never'], + 'no-unused-vars': ['error', 'never'], }, }, ) diff --git a/package-lock.json b/package-lock.json index 5fd416a..8c954c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,10 +26,13 @@ "@vitejs/plugin-react": "^4.3.1", "autoprefixer": "^10.4.20", "eslint": "^9.9.0", + "eslint-config-prettier": "^9.1.0", "eslint-plugin-react-hooks": "^5.1.0-rc.0", "eslint-plugin-react-refresh": "^0.4.9", "globals": "^15.9.0", + "husky": "^9.1.6", "postcss": "^8.4.47", + "prettier": "^3.3.3", "tailwindcss": "^3.4.13", "typescript": "^5.5.3", "typescript-eslint": "^8.0.1", @@ -3080,6 +3083,19 @@ } } }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, "node_modules/eslint-plugin-react-hooks": { "version": "5.1.0-rc-fb9a90fa48-20240614", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0-rc-fb9a90fa48-20240614.tgz", @@ -3589,6 +3605,22 @@ "node": ">= 0.4" } }, + "node_modules/husky": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.6.tgz", + "integrity": "sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4375,6 +4407,22 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index 61a5f52..f8bdbe9 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "format": "prettier --write ." }, "dependencies": { "@radix-ui/react-dialog": "^1.1.1", @@ -28,10 +29,13 @@ "@vitejs/plugin-react": "^4.3.1", "autoprefixer": "^10.4.20", "eslint": "^9.9.0", + "eslint-config-prettier": "^9.1.0", "eslint-plugin-react-hooks": "^5.1.0-rc.0", "eslint-plugin-react-refresh": "^0.4.9", "globals": "^15.9.0", + "husky": "^9.1.6", "postcss": "^8.4.47", + "prettier": "^3.3.3", "tailwindcss": "^3.4.13", "typescript": "^5.5.3", "typescript-eslint": "^8.0.1", diff --git a/src/App.tsx b/src/App.tsx index 47cc21a..3f5b3ef 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,19 +1,19 @@ // src/App.tsx -import { useState, useEffect, useRef } from "react"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; -import * as Dialog from "@radix-ui/react-dialog"; -import { useAudioDeviceStore } from "./hooks/useAudioDeviceStore"; -import DeviceNode from "./components/DeviceNode"; -import ConnectionLine from "./components/ConnectionLine"; -import DarkModeToggle from "./components/DarkModeToggle"; -import AddDeviceForm from "./components/AddDeviceForm"; -import { Device, Connection } from "./types/devices"; -import { DOT_SIZE, GRID_SIZE } from "./constants/grid"; -import EditDeviceForm from "./components/EditDeviceForm"; +import { useState, useEffect, useRef } from 'react' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { ReactQueryDevtools } from '@tanstack/react-query-devtools' +import * as Dialog from '@radix-ui/react-dialog' +import { useAudioDeviceStore } from './hooks/useAudioDeviceStore' +import DeviceNode from './components/DeviceNode' +import ConnectionLine from './components/ConnectionLine' +import DarkModeToggle from './components/DarkModeToggle' +import AddDeviceForm from './components/AddDeviceForm' +import { Device, Connection } from './types/devices' +import { DOT_SIZE, GRID_SIZE } from './constants/grid' +import EditDeviceForm from './components/EditDeviceForm' -const queryClient = new QueryClient(); +const queryClient = new QueryClient() function AudioDeviceArrangerApp() { const { @@ -23,64 +23,64 @@ function AudioDeviceArrangerApp() { updateDevicePosition, updateDevice, deleteDevice, - } = useAudioDeviceStore(); + } = useAudioDeviceStore() const [isDarkMode, setIsDarkMode] = useState(() => { - const saved = localStorage.getItem("darkMode"); - return saved ? JSON.parse(saved) : false; - }); + const saved = localStorage.getItem('darkMode') + return saved ? JSON.parse(saved) : false + }) - const [gridSize, setGridSize] = useState({ width: 0, height: 0 }); - const gridContainerRef = useRef(null); + const [gridSize, setGridSize] = useState({ width: 0, height: 0 }) + const gridContainerRef = useRef(null) - const [editingDevice, setEditingDevice] = useState(null); - const [connections, setConnections] = useState([]); + const [editingDevice, setEditingDevice] = useState(null) + const [connections, setConnections] = useState([]) const [connectingFrom, setConnectingFrom] = useState<{ - deviceId: string; - portId: string; - } | null>(null); + deviceId: string + portId: string + } | null>(null) const [tempConnection, setTempConnection] = useState<{ - x: number; - y: number; - } | null>(null); + x: number + y: number + } | null>(null) - const handleAddCustomDevice = (deviceData: Omit) => { + const handleAddCustomDevice = (deviceData: Omit) => { addDevice({ ...deviceData, id: `device-${Date.now()}`, - }); - }; + }) + } const handleEditDevice = (device: Device) => { - setEditingDevice(device); - }; + setEditingDevice(device) + } const handleUpdateDevice = (updatedDevice: Device) => { - updateDevice(updatedDevice); - setEditingDevice(null); - }; + updateDevice(updatedDevice) + setEditingDevice(null) + } const handleDeleteDevice = (id: string) => { - deleteDevice(id); + deleteDevice(id) setConnections( connections.filter( - (conn) => conn.sourceDeviceId !== id && conn.targetDeviceId !== id - ) - ); - }; + conn => conn.sourceDeviceId !== id && conn.targetDeviceId !== id, + ), + ) + } const handlePortClick = ( deviceId: string, portId: string, isOutput: boolean, - event: React.MouseEvent + event: React.MouseEvent, ) => { if (connectingFrom) { if (isOutput || connectingFrom.deviceId === deviceId) { // Can't connect output to output or to the same device - setConnectingFrom(null); - setTempConnection(null); - return; + setConnectingFrom(null) + setTempConnection(null) + return } // Complete the connection const newConnection: Connection = { @@ -89,89 +89,89 @@ function AudioDeviceArrangerApp() { sourcePortId: connectingFrom.portId, targetDeviceId: deviceId, targetPortId: portId, - }; - setConnections([...connections, newConnection]); - setConnectingFrom(null); - setTempConnection(null); + } + setConnections([...connections, newConnection]) + setConnectingFrom(null) + setTempConnection(null) } else if (isOutput) { // Start a new connection from an output port - setConnectingFrom({ deviceId, portId }); - setTempConnection({ x: event.clientX, y: event.clientY }); + setConnectingFrom({ deviceId, portId }) + setTempConnection({ x: event.clientX, y: event.clientY }) } - }; + } useEffect(() => { - localStorage.setItem("darkMode", JSON.stringify(isDarkMode)); + localStorage.setItem('darkMode', JSON.stringify(isDarkMode)) if (isDarkMode) { - document.documentElement.classList.add("dark"); + document.documentElement.classList.add('dark') } else { - document.documentElement.classList.remove("dark"); + document.documentElement.classList.remove('dark') } - }, [isDarkMode]); + }, [isDarkMode]) useEffect(() => { const updateGridSize = () => { if (gridContainerRef.current) { - const toolbarWidth = 256; // Adjust if needed (w-64 = 16rem = 256px) + const toolbarWidth = 256 // Adjust if needed (w-64 = 16rem = 256px) setGridSize({ width: window.innerWidth - toolbarWidth, height: gridContainerRef.current.clientHeight, - }); + }) } - }; + } - updateGridSize(); - window.addEventListener("resize", updateGridSize); - return () => window.removeEventListener("resize", updateGridSize); - }, []); + updateGridSize() + window.addEventListener('resize', updateGridSize) + return () => window.removeEventListener('resize', updateGridSize) + }, []) - const toggleDarkMode = () => setIsDarkMode(!isDarkMode); + const toggleDarkMode = () => setIsDarkMode(!isDarkMode) const handleGridExpansion = (newPosition: { x: number; y: number }) => { - setGridSize((prevSize) => ({ + setGridSize(prevSize => ({ width: Math.max(prevSize.width, newPosition.x + GRID_SIZE), height: Math.max(prevSize.height, newPosition.y + GRID_SIZE), - })); - }; + })) + } const handleMouseMove = (event: React.MouseEvent) => { if (connectingFrom) { - setTempConnection({ x: event.clientX, y: event.clientY }); + setTempConnection({ x: event.clientX, y: event.clientY }) } - }; + } const handleMouseUp = () => { if (connectingFrom) { - setConnectingFrom(null); - setTempConnection(null); + setConnectingFrom(null) + setTempConnection(null) } - }; + } if (isLoading) return (
Loading...
- ); + ) const dotMatrixStyle = { backgroundImage: ` - radial-gradient(circle, ${isDarkMode ? "#4a5568" : "#e0e0e0"} ${ - DOT_SIZE / 2 - }px, transparent ${DOT_SIZE / 2}px) + radial-gradient(circle, ${isDarkMode ? '#4a5568' : '#e0e0e0'} ${ + DOT_SIZE / 2 + }px, transparent ${DOT_SIZE / 2}px) `, backgroundSize: `${GRID_SIZE}px ${GRID_SIZE}px`, backgroundPosition: `-${DOT_SIZE / 2}px -${DOT_SIZE / 2}px`, width: `${Math.max(gridSize.width, window.innerWidth - 256)}px`, height: `${Math.max(gridSize.height, window.innerHeight - 64)}px`, - }; + } return (
{/* Toolbar */} !open && setEditingDevice(null)} + onOpenChange={open => !open && setEditingDevice(null)} >

@@ -221,14 +221,14 @@ function AudioDeviceArrangerApp() { className="border border-gray-300 dark:border-gray-600 relative transition-colors" style={dotMatrixStyle} > - {connections.map((connection) => { + {connections.map(connection => { const sourceDevice = devices.find( - (d) => d.id === connection.sourceDeviceId - ); + d => d.id === connection.sourceDeviceId, + ) const targetDevice = devices.find( - (d) => d.id === connection.targetDeviceId - ); - if (!sourceDevice || !targetDevice) return null; + d => d.id === connection.targetDeviceId, + ) + if (!sourceDevice || !targetDevice) return null return ( - ); + ) })} {connectingFrom && tempConnection && ( d.id === connectingFrom.deviceId)! - .position.x + + devices.find(d => d.id === connectingFrom.deviceId)!.position + .x + GRID_SIZE * 1.5 } startY={ - devices.find((d) => d.id === connectingFrom.deviceId)! - .position.y + + devices.find(d => d.id === connectingFrom.deviceId)!.position + .y + GRID_SIZE * 1.5 } endX={tempConnection.x} @@ -257,13 +257,13 @@ function AudioDeviceArrangerApp() { isTemp={true} /> )} - {devices.map((device) => ( + {devices.map(device => ( { - updateDevicePosition({ id, position }); - handleGridExpansion(position); + updateDevicePosition({ id, position }) + handleGridExpansion(position) }} onEdit={handleEditDevice} onDelete={handleDeleteDevice} @@ -277,7 +277,7 @@ function AudioDeviceArrangerApp() {

- ); + ) } function App() { @@ -286,7 +286,7 @@ function App() { - ); + ) } -export default App; +export default App diff --git a/src/components/AddDeviceForm.tsx b/src/components/AddDeviceForm.tsx index 3ed9caa..a0f6f2d 100644 --- a/src/components/AddDeviceForm.tsx +++ b/src/components/AddDeviceForm.tsx @@ -1,17 +1,17 @@ -import React, { useState } from "react"; -import { Device, Port } from "../types/devices"; -import * as Form from "@radix-ui/react-form"; +import React, { useState } from 'react' +import { Device, Port } from '../types/devices' +import * as Form from '@radix-ui/react-form' interface AddDeviceFormProps { - onAddDevice: (device: Omit) => void; + onAddDevice: (device: Omit) => void } const AddDeviceForm: React.FC = ({ onAddDevice }) => { - const [name, setName] = useState(""); - const [type, setType] = useState("synthesizer"); - const [gridSize, setGridSize] = useState(100); - const [inputs, setInputs] = useState([]); - const [outputs, setOutputs] = useState([]); + const [name, setName] = useState('') + const [type, setType] = useState('synthesizer') + const [gridSize, setGridSize] = useState(100) + const [inputs, setInputs] = useState([]) + const [outputs, setOutputs] = useState([]) const handleAddDevice = () => { onAddDevice({ @@ -23,8 +23,8 @@ const AddDeviceForm: React.FC = ({ onAddDevice }) => { inputs: [], outputs: [], position: { x: 0, y: 0 }, - }); - }; + }) + } const handleAddInput = () => { setInputs([ @@ -32,10 +32,10 @@ const AddDeviceForm: React.FC = ({ onAddDevice }) => { { id: `input-${Date.now()}`, name: `Input ${inputs.length + 1}`, - type: "input", + type: 'input', }, - ]); - }; + ]) + } const handleAddOutput = () => { setOutputs([ @@ -43,10 +43,10 @@ const AddDeviceForm: React.FC = ({ onAddDevice }) => { { id: `output-${Date.now()}`, name: `Output ${outputs.length + 1}`, - type: "output", + type: 'output', }, - ]); - }; + ]) + } return ( @@ -58,7 +58,7 @@ const AddDeviceForm: React.FC = ({ onAddDevice }) => { setName(e.target.value)} + onChange={e => setName(e.target.value)} className="p-2 border border-gray-300 dark:border-gray-700 rounded-md bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-gray-100" /> @@ -71,7 +71,7 @@ const AddDeviceForm: React.FC = ({ onAddDevice }) => { setType(e.target.value)} + onChange={e => setType(e.target.value)} className="p-2 border border-gray-300 dark:border-gray-700 rounded-md bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-gray-100" /> @@ -84,7 +84,7 @@ const AddDeviceForm: React.FC = ({ onAddDevice }) => { setGridSize(Number(e.target.value))} + onChange={e => setGridSize(Number(e.target.value))} className="p-2 border border-gray-300 dark:border-gray-700 rounded-md bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-gray-100" /> @@ -111,7 +111,7 @@ const AddDeviceForm: React.FC = ({ onAddDevice }) => {

Inputs

- {inputs.map((input) => ( + {inputs.map(input => (
= ({ onAddDevice }) => {

Outputs

- {outputs.map((output) => ( + {outputs.map(output => (
= ({ onAddDevice }) => {
- ); -}; + ) +} -export default AddDeviceForm; +export default AddDeviceForm diff --git a/src/components/ConnectionLine.tsx b/src/components/ConnectionLine.tsx index 23d4378..afe4b8c 100644 --- a/src/components/ConnectionLine.tsx +++ b/src/components/ConnectionLine.tsx @@ -1,13 +1,13 @@ // src/components/ConnectionLine.tsx -import React from "react"; +import React from 'react' interface ConnectionLineProps { - startX: number; - startY: number; - endX: number; - endY: number; - isTemp?: boolean; + startX: number + startY: number + endX: number + endY: number + isTemp?: boolean } const ConnectionLine: React.FC = ({ @@ -18,31 +18,31 @@ const ConnectionLine: React.FC = ({ isTemp = false, }) => { // Calculate control points for a quadratic bezier curve - const midX = (startX + endX) / 2; - const midY = (startY + endY) / 2; - const controlX = midX; - const controlY = midY - Math.abs(endY - startY) / 2; + const midX = (startX + endX) / 2 + const midY = (startY + endY) / 2 + const controlX = midX + const controlY = midY - Math.abs(endY - startY) / 2 return ( - ); -}; + ) +} -export default ConnectionLine; +export default ConnectionLine diff --git a/src/components/DarkModeToggle.tsx b/src/components/DarkModeToggle.tsx index b8113bd..4916395 100644 --- a/src/components/DarkModeToggle.tsx +++ b/src/components/DarkModeToggle.tsx @@ -1,10 +1,10 @@ // src/components/DarkModeToggle.tsx -import React from "react"; +import React from 'react' interface DarkModeToggleProps { - isDarkMode: boolean; - toggleDarkMode: () => void; + isDarkMode: boolean + toggleDarkMode: () => void } const DarkModeToggle: React.FC = ({ @@ -48,7 +48,7 @@ const DarkModeToggle: React.FC = ({ )} - ); -}; + ) +} -export default DarkModeToggle; +export default DarkModeToggle diff --git a/src/components/DeviceNode.tsx b/src/components/DeviceNode.tsx index 011fdc6..bd5b90b 100644 --- a/src/components/DeviceNode.tsx +++ b/src/components/DeviceNode.tsx @@ -1,24 +1,24 @@ // src/components/DeviceNode.tsx -import React, { useRef, useEffect, useState, useCallback } from "react"; -import * as Tooltip from "@radix-ui/react-tooltip"; -import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; -import { Device, Port } from "../types/devices"; +import React, { useRef, useEffect, useState, useCallback } from 'react' +import * as Tooltip from '@radix-ui/react-tooltip' +import * as DropdownMenu from '@radix-ui/react-dropdown-menu' +import { Device, Port } from '../types/devices' interface DeviceNodeProps { - device: Device; - onMove: (id: string, position: { x: number; y: number }) => void; - onEdit: (device: Device) => void; - onDelete: (id: string) => void; + device: Device + onMove: (id: string, position: { x: number; y: number }) => void + onEdit: (device: Device) => void + onDelete: (id: string) => void onPortClick: ( deviceId: string, portId: string, isOutput: boolean, - event: React.MouseEvent - ) => void; - isConnecting: boolean; - gridWidth: number; - gridHeight: number; + event: React.MouseEvent, + ) => void + isConnecting: boolean + gridWidth: number + gridHeight: number } const DeviceNode: React.FC = ({ @@ -31,83 +31,83 @@ const DeviceNode: React.FC = ({ gridWidth, gridHeight, }) => { - const nodeRef = useRef(null); - const [position, setPosition] = useState(device.position); - const [isDragging, setIsDragging] = useState(false); - const dragStartRef = useRef({ x: 0, y: 0 }); - const [nodeSize, setNodeSize] = useState({ width: 0, height: 0 }); + const nodeRef = useRef(null) + const [position, setPosition] = useState(device.position) + const [isDragging, setIsDragging] = useState(false) + const dragStartRef = useRef({ x: 0, y: 0 }) + const [nodeSize, setNodeSize] = useState({ width: 0, height: 0 }) - console.log("inputs", gridWidth, gridHeight, nodeSize); + console.log('inputs', gridWidth, gridHeight, nodeSize) useEffect(() => { - setPosition(device.position); - }, [device.position]); + setPosition(device.position) + }, [device.position]) useEffect(() => { if (nodeRef.current) { setNodeSize({ width: nodeRef.current.offsetWidth, height: nodeRef.current.offsetHeight, - }); + }) } - }, []); + }, []) const snapToGrid = useCallback( (x: number, y: number) => { - const snappedX = Math.round(x / device.gridSize) * device.gridSize; - const snappedY = Math.round(y / device.gridSize) * device.gridSize; - return { x: snappedX, y: snappedY }; + const snappedX = Math.round(x / device.gridSize) * device.gridSize + const snappedY = Math.round(y / device.gridSize) * device.gridSize + return { x: snappedX, y: snappedY } }, - [device.gridSize] - ); + [device.gridSize], + ) useEffect(() => { const handleMouseMove = (e: MouseEvent) => { - if (!isDragging) return; + if (!isDragging) return - const dx = e.clientX - dragStartRef.current.x; - const dy = e.clientY - dragStartRef.current.y; + const dx = e.clientX - dragStartRef.current.x + const dy = e.clientY - dragStartRef.current.y - setPosition((prevPos) => ({ + setPosition(prevPos => ({ x: prevPos.x + dx, y: prevPos.y + dy, - })); + })) - dragStartRef.current = { x: e.clientX, y: e.clientY }; - }; + dragStartRef.current = { x: e.clientX, y: e.clientY } + } const handleMouseUp = () => { if (isDragging) { - setIsDragging(false); - const snappedPosition = snapToGrid(position.x, position.y); - setPosition(snappedPosition); - onMove(device.id, snappedPosition); + setIsDragging(false) + const snappedPosition = snapToGrid(position.x, position.y) + setPosition(snappedPosition) + onMove(device.id, snappedPosition) } - }; + } if (isDragging) { - window.addEventListener("mousemove", handleMouseMove); - window.addEventListener("mouseup", handleMouseUp); + window.addEventListener('mousemove', handleMouseMove) + window.addEventListener('mouseup', handleMouseUp) } return () => { - window.removeEventListener("mousemove", handleMouseMove); - window.removeEventListener("mouseup", handleMouseUp); - }; - }, [isDragging, device.id, onMove, position, snapToGrid]); + window.removeEventListener('mousemove', handleMouseMove) + window.removeEventListener('mouseup', handleMouseUp) + } + }, [isDragging, device.id, onMove, position, snapToGrid]) const handleMouseDown = (e: React.MouseEvent) => { - e.preventDefault(); - setIsDragging(true); - dragStartRef.current = { x: e.clientX, y: e.clientY }; - }; + e.preventDefault() + setIsDragging(true) + dragStartRef.current = { x: e.clientX, y: e.clientY } + } const renderPorts = (ports: Port[], isOutput: boolean) => ( -
+

- {isOutput ? "Outputs" : "Inputs"} + {isOutput ? 'Outputs' : 'Inputs'}

- {ports.map((port) => ( + {ports.map(port => (
= ({ ${ isConnecting ? isOutput - ? "hover:bg-green-200 dark:hover:bg-green-700" - : "hover:bg-blue-200 dark:hover:bg-blue-700" - : "" + ? 'hover:bg-green-200 dark:hover:bg-green-700' + : 'hover:bg-blue-200 dark:hover:bg-blue-700' + : '' } ${ isOutput - ? "text-green-600 dark:text-green-400" - : "text-blue-600 dark:text-blue-400" + ? 'text-green-600 dark:text-green-400' + : 'text-blue-600 dark:text-blue-400' } p-1 rounded transition-colors `} - onClick={(e) => { - e.stopPropagation(); - onPortClick(device.id, port.id, isOutput, e); + onClick={e => { + e.stopPropagation() + onPortClick(device.id, port.id, isOutput, e) }} > {port.name}
))}
- ); + ) return ( @@ -147,7 +147,7 @@ const DeviceNode: React.FC = ({ style={{ left: `${position.x}px`, top: `${position.y}px`, - cursor: isDragging ? "grabbing" : "grab", + cursor: isDragging ? 'grabbing' : 'grab', width: `${device.gridSize * 3}px`, height: `${device.gridSize * 3}px`, }} @@ -162,7 +162,7 @@ const DeviceNode: React.FC = ({ {renderPorts(device.inputs, false)} {renderPorts(device.outputs, true)}
- Grid: ({Math.round(position.x / device.gridSize)},{" "} + Grid: ({Math.round(position.x / device.gridSize)},{' '} {Math.round(position.y / device.gridSize)})
@@ -199,7 +199,7 @@ const DeviceNode: React.FC = ({
- ); -}; + ) +} -export default DeviceNode; +export default DeviceNode diff --git a/src/components/EditDeviceForm.tsx b/src/components/EditDeviceForm.tsx index 326b266..d353008 100644 --- a/src/components/EditDeviceForm.tsx +++ b/src/components/EditDeviceForm.tsx @@ -1,12 +1,12 @@ // src/components/EditDeviceForm.tsx -import React, { useState } from "react"; -import { Device, Port } from "../types/devices"; +import React, { useState } from 'react' +import { Device, Port } from '../types/devices' interface EditDeviceFormProps { - device: Device; - onUpdateDevice: (updatedDevice: Device) => void; - onCancel: () => void; + device: Device + onUpdateDevice: (updatedDevice: Device) => void + onCancel: () => void } const EditDeviceForm: React.FC = ({ @@ -14,11 +14,11 @@ const EditDeviceForm: React.FC = ({ onUpdateDevice, onCancel, }) => { - const [name, setName] = useState(device.name); - const [type, setType] = useState(device.type); - const [gridSize, setGridSize] = useState(device.gridSize); - const [inputs, setInputs] = useState(device.inputs); - const [outputs, setOutputs] = useState(device.outputs); + const [name, setName] = useState(device.name) + const [type, setType] = useState(device.type) + const [gridSize, setGridSize] = useState(device.gridSize) + const [inputs, setInputs] = useState(device.inputs) + const [outputs, setOutputs] = useState(device.outputs) const handleAddInput = () => { setInputs([ @@ -26,10 +26,10 @@ const EditDeviceForm: React.FC = ({ { id: `input-${Date.now()}`, name: `Input ${inputs.length + 1}`, - type: "input", + type: 'input', }, - ]); - }; + ]) + } const handleAddOutput = () => { setOutputs([ @@ -37,13 +37,13 @@ const EditDeviceForm: React.FC = ({ { id: `output-${Date.now()}`, name: `Output ${outputs.length + 1}`, - type: "output", + type: 'output', }, - ]); - }; + ]) + } const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); + e.preventDefault() onUpdateDevice({ ...device, name, @@ -51,8 +51,8 @@ const EditDeviceForm: React.FC = ({ gridSize, inputs, outputs, - }); - }; + }) + } return (
@@ -67,7 +67,7 @@ const EditDeviceForm: React.FC = ({ type="text" id="name" value={name} - onChange={(e) => setName(e.target.value)} + onChange={e => setName(e.target.value)} required className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" /> @@ -83,7 +83,7 @@ const EditDeviceForm: React.FC = ({ type="text" id="type" value={type} - onChange={(e) => setType(e.target.value)} + onChange={e => setType(e.target.value)} required className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" /> @@ -99,7 +99,7 @@ const EditDeviceForm: React.FC = ({ type="number" id="gridSize" value={gridSize} - onChange={(e) => setGridSize(Number(e.target.value))} + onChange={e => setGridSize(Number(e.target.value))} required min="100" max="2000" @@ -146,7 +146,7 @@ const EditDeviceForm: React.FC = ({
- ); -}; + ) +} -export default EditDeviceForm; +export default EditDeviceForm diff --git a/src/constants/grid.ts b/src/constants/grid.ts index ce6eb32..0dcfa81 100644 --- a/src/constants/grid.ts +++ b/src/constants/grid.ts @@ -1,5 +1,5 @@ // src/constants/grid.ts -export const GRID_SIZE = 20; // Size of each grid cell in pixels -export const DOT_SIZE = 2; // Size of each dot in pixels -export const SNAP_THRESHOLD = GRID_SIZE / 2; // Threshold for snapping to grid +export const GRID_SIZE = 20 // Size of each grid cell in pixels +export const DOT_SIZE = 2 // Size of each dot in pixels +export const SNAP_THRESHOLD = GRID_SIZE / 2 // Threshold for snapping to grid diff --git a/src/hooks/useAudioDeviceStore.ts b/src/hooks/useAudioDeviceStore.ts index 38a281c..9f14d95 100644 --- a/src/hooks/useAudioDeviceStore.ts +++ b/src/hooks/useAudioDeviceStore.ts @@ -1,136 +1,136 @@ /* eslint-disable prefer-const */ // src/hooks/useAudioDeviceStore.ts -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import { Device, Connection } from "../types/devices"; -import { GRID_SIZE } from "../constants/grid"; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' +import { Device, Connection } from '../types/devices' +import { GRID_SIZE } from '../constants/grid' -const DEVICES_KEY = ["devices"]; -const CONNECTIONS_KEY = ["connections"]; -const STORAGE_KEY = "audioDeviceArrangerState"; +const DEVICES_KEY = ['devices'] +const CONNECTIONS_KEY = ['connections'] +const STORAGE_KEY = 'audioDeviceArrangerState' // Load initial state from localStorage const loadInitialState = (): { - devices: Device[]; - connections: Connection[]; + devices: Device[] + connections: Connection[] } => { - const savedState = localStorage.getItem(STORAGE_KEY); + const savedState = localStorage.getItem(STORAGE_KEY) if (savedState) { - return JSON.parse(savedState); + return JSON.parse(savedState) } - return { devices: [], connections: [] }; -}; + return { devices: [], connections: [] } +} -let { devices, connections } = loadInitialState(); +let { devices, connections } = loadInitialState() // Save state to localStorage const saveState = () => { - localStorage.setItem(STORAGE_KEY, JSON.stringify({ devices, connections })); -}; + localStorage.setItem(STORAGE_KEY, JSON.stringify({ devices, connections })) +} // Simulated API functions -const getDevices = () => Promise.resolve(devices); -const getConnections = () => Promise.resolve(connections); +const getDevices = () => Promise.resolve(devices) +const getConnections = () => Promise.resolve(connections) const addDevice = (device: Device) => { const snappedPosition = { x: Math.round(device.position.x / GRID_SIZE) * GRID_SIZE, y: Math.round(device.position.y / GRID_SIZE) * GRID_SIZE, - }; - const newDevice = { ...device, position: snappedPosition }; - devices.push(newDevice); - saveState(); - return Promise.resolve(newDevice); -}; + } + const newDevice = { ...device, position: snappedPosition } + devices.push(newDevice) + saveState() + return Promise.resolve(newDevice) +} const updateDevicePosition = ( id: string, - position: { x: number; y: number } + position: { x: number; y: number }, ) => { - devices = devices.map((d) => (d.id === id ? { ...d, position } : d)); - saveState(); - return Promise.resolve(devices.find((d) => d.id === id)); -}; + devices = devices.map(d => (d.id === id ? { ...d, position } : d)) + saveState() + return Promise.resolve(devices.find(d => d.id === id)) +} const addConnection = (connection: Connection) => { - connections.push(connection); - saveState(); - return Promise.resolve(connection); -}; + connections.push(connection) + saveState() + return Promise.resolve(connection) +} export function useAudioDeviceStore() { - const queryClient = useQueryClient(); + const queryClient = useQueryClient() const devicesQuery = useQuery({ queryKey: DEVICES_KEY, queryFn: getDevices, - }); + }) const connectionsQuery = useQuery({ queryKey: CONNECTIONS_KEY, queryFn: getConnections, - }); + }) const addDeviceMutation = useMutation({ mutationFn: addDevice, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: DEVICES_KEY }); + queryClient.invalidateQueries({ queryKey: DEVICES_KEY }) }, - }); + }) const updateDeviceMutation = useMutation({ mutationFn: (updatedDevice: Device) => { - devices = devices.map((d) => - d.id === updatedDevice.id ? updatedDevice : d - ); - saveState(); - return Promise.resolve(updatedDevice); + devices = devices.map(d => + d.id === updatedDevice.id ? updatedDevice : d, + ) + saveState() + return Promise.resolve(updatedDevice) }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: DEVICES_KEY }); + queryClient.invalidateQueries({ queryKey: DEVICES_KEY }) }, - }); + }) const deleteDeviceMutation = useMutation({ mutationFn: (id: string) => { - devices = devices.filter((d) => d.id !== id); - saveState(); - return Promise.resolve(); + devices = devices.filter(d => d.id !== id) + saveState() + return Promise.resolve() }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: DEVICES_KEY }); + queryClient.invalidateQueries({ queryKey: DEVICES_KEY }) }, - }); + }) const updateDevicePositionMutation = useMutation({ mutationFn: ({ id, position, }: { - id: string; - position: { x: number; y: number }; + id: string + position: { x: number; y: number } }) => updateDevicePosition(id, position), onMutate: async ({ id, position }) => { - await queryClient.cancelQueries({ queryKey: DEVICES_KEY }); - const previousDevices = queryClient.getQueryData(DEVICES_KEY); - queryClient.setQueryData(DEVICES_KEY, (old) => - old ? old.map((d) => (d.id === id ? { ...d, position } : d)) : [] - ); - return { previousDevices }; + await queryClient.cancelQueries({ queryKey: DEVICES_KEY }) + const previousDevices = queryClient.getQueryData(DEVICES_KEY) + queryClient.setQueryData(DEVICES_KEY, old => + old ? old.map(d => (d.id === id ? { ...d, position } : d)) : [], + ) + return { previousDevices } }, onError: (err, newTodo, context) => { - console.log("Error updating device position", err, newTodo, context); - queryClient.setQueryData(DEVICES_KEY, context?.previousDevices); + console.log('Error updating device position', err, newTodo, context) + queryClient.setQueryData(DEVICES_KEY, context?.previousDevices) }, onSettled: () => { - queryClient.invalidateQueries({ queryKey: DEVICES_KEY }); + queryClient.invalidateQueries({ queryKey: DEVICES_KEY }) }, - }); + }) const addConnectionMutation = useMutation({ mutationFn: addConnection, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: CONNECTIONS_KEY }); + queryClient.invalidateQueries({ queryKey: CONNECTIONS_KEY }) }, - }); + }) return { devices: devicesQuery.data || [], @@ -141,5 +141,5 @@ export function useAudioDeviceStore() { addConnection: addConnectionMutation.mutate, updateDevice: updateDeviceMutation.mutate, deleteDevice: deleteDeviceMutation.mutate, - }; + } } diff --git a/src/types/devices.ts b/src/types/devices.ts index ed23071..ff71bb9 100644 --- a/src/types/devices.ts +++ b/src/types/devices.ts @@ -1,25 +1,25 @@ // src/types/devices.ts export interface Port { - id: string; - name: string; - type: "input" | "output"; + id: string + name: string + type: 'input' | 'output' } export interface Device { - id: string; - name: string; - type: string; - gridSize: number; - inputs: Port[]; - outputs: Port[]; - position: { x: number; y: number }; + id: string + name: string + type: string + gridSize: number + inputs: Port[] + outputs: Port[] + position: { x: number; y: number } } export interface Connection { - id: string; - sourceDeviceId: string; - sourcePortId: string; - targetDeviceId: string; - targetPortId: string; + id: string + sourceDeviceId: string + sourcePortId: string + targetDeviceId: string + targetPortId: string } diff --git a/tailwind.config.js b/tailwind.config.js index 5c23f8f..4b354cc 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,8 +1,6 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: [ - "./src/**/*.{js,jsx,ts,tsx}", - ], + content: ['./src/**/*.{js,jsx,ts,tsx}'], darkMode: 'class', // This enables dark mode theme: { extend: {}, diff --git a/vite.config.ts b/vite.config.ts index 57c29da..b52d317 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,8 +1,8 @@ -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react"; +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' // https://vitejs.dev/config/ export default defineConfig({ - base: "/audio-hardware-configurator/", + base: '/audio-hardware-configurator/', plugins: [react()], -}); +})