From 5cdfd1e1b0c9a0245a0c623cd4a5742ddb339abe Mon Sep 17 00:00:00 2001 From: fireandicefrog Date: Tue, 19 Aug 2025 14:26:49 +1200 Subject: [PATCH 1/4] chore: added custom controls --- string-art-demo/src/App.tsx | 47 +----- .../StringArtConfigSection.tsx | 90 +++++++++++ .../src/components/StringArtConfig/index.ts | 0 .../StringArtConfig/useStringArt.ts | 127 +++++++++++++++ string-art-demo/src/hooks/useStringArt.ts | 152 ------------------ .../src/interfaces/stringArtConfig.ts | 17 ++ string-art-demo/src/main.tsx | 5 +- .../src/workers/stringArtWorker.ts | 26 +-- 8 files changed, 251 insertions(+), 213 deletions(-) create mode 100644 string-art-demo/src/components/StringArtConfig/StringArtConfigSection.tsx create mode 100644 string-art-demo/src/components/StringArtConfig/index.ts create mode 100644 string-art-demo/src/components/StringArtConfig/useStringArt.ts delete mode 100644 string-art-demo/src/hooks/useStringArt.ts create mode 100644 string-art-demo/src/interfaces/stringArtConfig.ts diff --git a/string-art-demo/src/App.tsx b/string-art-demo/src/App.tsx index 634f1c8..16f4e3e 100644 --- a/string-art-demo/src/App.tsx +++ b/string-art-demo/src/App.tsx @@ -1,19 +1,18 @@ import { useState, useCallback } from 'react'; import { ImageUploader } from './components/ImageUploader'; import { StringArtCanvas } from './components/StringArtCanvas'; -import { useStringArt, type StringArtConfig, type ProgressInfo } from './hooks/useStringArt'; import './App.css'; +import StringArtConfigSection from './components/StringArtConfig/StringArtConfigSection'; +import { useStringArt, type ProgressInfo } from './components/StringArtConfig/useStringArt'; function App() { - const { wasmModule, isLoading, error, generateStringArt, presets } = useStringArt(); const [imageData, setImageData] = useState(null); const [imageUrl, setImageUrl] = useState(''); const [isGenerating, setIsGenerating] = useState(false); const [currentPath, setCurrentPath] = useState([]); const [nailCoords, setNailCoords] = useState>([]); const [progress, setProgress] = useState(null); - const [config, setConfig] = useState(presets.balanced()); - + const { generateStringArt, isLoading, error, settings } = useStringArt(); const handleImageSelected = useCallback((data: Uint8Array, url: string) => { setImageData(data); setImageUrl(url); @@ -22,8 +21,8 @@ function App() { setNailCoords([]); // Clear until we generate the string art }, []); - const handleStartGeneration = useCallback(async () => { - if (!imageData || !wasmModule) return; + const handleStartGeneration = async () => { + if (!imageData) return; setIsGenerating(true); setCurrentPath([]); @@ -41,7 +40,7 @@ function App() { setNailCoords(coords); }; - const result = await generateStringArt(imageData, config, onProgress, onNailCoords); + const result = await generateStringArt(imageData, onProgress, onNailCoords); if (result.path) { setCurrentPath(result.path); } @@ -54,11 +53,7 @@ function App() { } finally { setIsGenerating(false); } - }, [imageData, wasmModule, config, generateStringArt]); - - const handlePresetChange = useCallback((presetName: 'fast' | 'balanced' | 'highQuality') => { - setConfig(presets[presetName]()); - }, [presets]); + }; if (isLoading) { return ( @@ -98,32 +93,7 @@ function App() { {imageData && (
-
-

Quality Presets

-
- - - -
-
+
- Current: Nail {progress.current_nail} → {progress.next_nail} Score: {progress.score.toFixed(1)}
diff --git a/string-art-demo/src/components/StringArtConfig/StringArtConfigSection.tsx b/string-art-demo/src/components/StringArtConfig/StringArtConfigSection.tsx new file mode 100644 index 0000000..0d88271 --- /dev/null +++ b/string-art-demo/src/components/StringArtConfig/StringArtConfigSection.tsx @@ -0,0 +1,90 @@ +import { useEffect, useState, type RefObject } from "react"; +import { type StringArtConfig } from "./useStringArt"; + +const Slider = ({ + title, + settingsRef, + index, +}: { + title: string; + settingsRef: RefObject; + index: keyof StringArtConfig; +}) => { + const minMaxVals: Record< + keyof Omit< + StringArtConfig, + | "extract_subject" + | "remove_shadows" + | "preserve_eyes" + | "preserve_negative_space" + >, + [number, number, number] + > = { + //min, max, start + image_size: [500, 2000, 500], + line_darkness: [25, 300, 100], + max_lines: [800, 5000, 1000], + min_improvement_score: [0, 100, 15], + negative_space_penalty: [0, 100, 0], + negative_space_threshold: [0, 100, 0], + num_nails: [360, 1440, 360], + progress_frequency: [200, 500, 200], + }; + + const [val, setVal] = useState("0"); + const dontRender = !index || !Object.keys(minMaxVals).includes(index as string) + const [min, max, start] = dontRender? [0,0,0] : minMaxVals[index as keyof typeof minMaxVals]; + + useEffect(() => { + setVal(String(start)) + }, [start]) + + if (dontRender) return null; + if (!settingsRef?.current) return null; + return ( +
+ { + (settingsRef.current[index] as unknown) = target.value; + setVal(target.value); + }} + /> + +
+ ); +}; + +const StringArtConfigSection = ({ + settings, +}: { + settings: RefObject; +}) => { + if (!settings.current) { + return null; + } + return ( +
+

String Art Configuration

+ {(Object.keys(settings.current) as (keyof StringArtConfig)[]).map( + (key) => { + return ( + + ); + } + )} +
+ ); +}; + +export default StringArtConfigSection; diff --git a/string-art-demo/src/components/StringArtConfig/index.ts b/string-art-demo/src/components/StringArtConfig/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/string-art-demo/src/components/StringArtConfig/useStringArt.ts b/string-art-demo/src/components/StringArtConfig/useStringArt.ts new file mode 100644 index 0000000..b0f9b55 --- /dev/null +++ b/string-art-demo/src/components/StringArtConfig/useStringArt.ts @@ -0,0 +1,127 @@ +import { + useState, + useEffect, + useCallback, + useRef, +} from "react"; +import type { StringArtConfig } from "../../interfaces/stringArtConfig"; +import init, { + StringArtWasm, + WasmStringArtConfig, + test_wasm, + get_version, + ProgressInfo, +} from "../../wasm/string_art_rust_impl"; + +interface WasmModule { + StringArtWasm: typeof StringArtWasm; + WasmStringArtConfig: typeof WasmStringArtConfig; + test_wasm: typeof test_wasm; + get_version: typeof get_version; +} + +export const useStringArt = () => { + const [, setWasmModule] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const settings = useRef({ + num_nails: 500, + image_size: 500, + extract_subject: false, + remove_shadows: false, + preserve_eyes: true, + preserve_negative_space: false, + negative_space_penalty: 5, + negative_space_threshold: 0.5, + max_lines: 1000, + line_darkness: 50, + min_improvement_score: 15, + progress_frequency: 300, + } as StringArtConfig); + + useEffect(() => { + const loadWasm = async () => { + try { + setIsLoading(true); + setError(null); + + // Initialize the WASM module + await init(); + + // Create the WASM interface + const wasmInterface: WasmModule = { + StringArtWasm, + WasmStringArtConfig, + test_wasm, + get_version, + }; + + setWasmModule(wasmInterface); + console.log("WASM module loaded successfully:", test_wasm()); + } catch (err) { + console.error("Failed to load WASM module:", err); + setError( + err instanceof Error ? err.message : "Failed to load WASM module" + ); + } + setTimeout(() => setIsLoading(false), 1000); + }; + + loadWasm(); + }, [setIsLoading, setError, setWasmModule]); + + const workerRef = useRef(null); + + useEffect(() => { + // Initialize the service worker + workerRef.current = new Worker( + new URL("../workers/stringArtWorker.ts", import.meta.url), + { type: "module" } + ); + return () => { + workerRef.current?.terminate(); + }; + }, []); + + const generateStringArt = useCallback( + async ( + imageData: Uint8Array, + onProgress: (progress: ProgressInfo) => void, + onNailCoords?: (coords: Array<[number, number]>) => void + ): Promise<{ + path: number[] | null; + nailCoords: Array<[number, number]>; + }> => { + if (!workerRef.current) { + throw new Error("Service worker not initialized"); + } + + return new Promise((resolve, reject) => { + workerRef.current!.onmessage = (event) => { + const { type, data } = event.data; + + if (type === "nailCoords" && onNailCoords) { + onNailCoords(data); + } else if (type === "progress") { + onProgress(data); + } else if (type === "complete") { + resolve({ path: data, nailCoords: [] }); + } else if (type === "error") { + reject(new Error(data)); + } + }; + + workerRef.current!.postMessage({ + imageData, + config: settings.current, + wasmModuleUrl: "../wasm/string_art_rust_impl.js", + }); + }); + }, + [settings] + ); + + return { isLoading, error, generateStringArt, settings }; +}; + +export type { StringArtConfig, ProgressInfo }; diff --git a/string-art-demo/src/hooks/useStringArt.ts b/string-art-demo/src/hooks/useStringArt.ts deleted file mode 100644 index ee6d4cf..0000000 --- a/string-art-demo/src/hooks/useStringArt.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { useState, useEffect, useCallback, useRef } from 'react'; -import init, { - StringArtWasm, - WasmStringArtConfig, - test_wasm, - get_version -} from '../wasm/string_art_rust_impl.js'; - -interface StringArtConfig { - num_nails: number; - image_size: number; - extract_subject: boolean; - remove_shadows: boolean; - preserve_eyes: boolean; - preserve_negative_space: boolean; - negative_space_penalty: number; - negative_space_threshold: number; -} - -interface ProgressInfo { - lines_completed: number; - total_lines: number; - current_nail: number; - next_nail: number; - score: number; - completion_percent: number; - path_segment: [number, number]; - current_path: number[]; // Added current_path to match the updated callback -} - -interface WasmModule { - StringArtWasm: typeof StringArtWasm; - WasmStringArtConfig: typeof WasmStringArtConfig; - test_wasm: typeof test_wasm; - get_version: typeof get_version; -} - -export interface UseStringArtReturn { - wasmModule: WasmModule | null; - isLoading: boolean; - error: string | null; - generateStringArt: ( - imageData: Uint8Array, - config: StringArtConfig, - onProgress: (progress: ProgressInfo) => void, - onNailCoords?: (coords: Array<[number, number]>) => void - ) => Promise<{ path: number[] | null; nailCoords: Array<[number, number]> }>; - presets: { - fast: () => StringArtConfig; - balanced: () => StringArtConfig; - highQuality: () => StringArtConfig; - }; -} - -export const useStringArt = (): UseStringArtReturn => { - const [wasmModule, setWasmModule] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - const loadWasm = async () => { - try { - setIsLoading(true); - setError(null); - - // Initialize the WASM module - await init(); - - // Create the WASM interface - const wasmInterface: WasmModule = { - StringArtWasm, - WasmStringArtConfig, - test_wasm, - get_version, - }; - - setWasmModule(wasmInterface); - console.log('WASM module loaded successfully:', test_wasm()); - - } catch (err) { - console.error('Failed to load WASM module:', err); - setError(err instanceof Error ? err.message : 'Failed to load WASM module'); - } finally { - setIsLoading(false); - } - }; - - loadWasm(); - }, []); - - const workerRef = useRef(null); - - useEffect(() => { - // Initialize the service worker - workerRef.current = new Worker(new URL('../workers/stringArtWorker.ts', import.meta.url), { type: 'module' }); - return () => { - workerRef.current?.terminate(); - }; - }, []); - - const generateStringArt = useCallback( - async ( - imageData: Uint8Array, - config: StringArtConfig, - onProgress: (progress: ProgressInfo) => void, - onNailCoords?: (coords: Array<[number, number]>) => void - ): Promise<{ path: number[] | null; nailCoords: Array<[number, number]> }> => { - if (!workerRef.current) { - throw new Error('Service worker not initialized'); - } - - return new Promise((resolve, reject) => { - workerRef.current!.onmessage = (event) => { - const { type, data } = event.data; - - if (type === 'nailCoords' && onNailCoords) { - onNailCoords(data); - } else if (type === 'progress') { - onProgress(data); - } else if (type === 'complete') { - resolve({ path: data, nailCoords: [] }); - } else if (type === 'error') { - reject(new Error(data)); - } - }; - - workerRef.current!.postMessage({ - imageData, - config, - wasmModuleUrl: '../wasm/string_art_rust_impl.js', - }); - }); - }, - [] -); - - const presets = { - fast: () => ({ num_nails: 360, image_size: 500, extract_subject: true, remove_shadows: true, preserve_eyes: true, preserve_negative_space: true, negative_space_penalty: 5, negative_space_threshold: 5 }), - balanced: () => ({ num_nails: 720, image_size: 1000, extract_subject: true, remove_shadows: true, preserve_eyes: true, preserve_negative_space: true, negative_space_penalty: 10, negative_space_threshold: 10 }), - highQuality: () => ({ num_nails: 1440, image_size: 2000, extract_subject: true, remove_shadows: true, preserve_eyes: true, preserve_negative_space: true, negative_space_penalty: 15, negative_space_threshold: 20 }), - }; - - return { - wasmModule, - isLoading, - error, - generateStringArt, - presets, - }; -}; - -export type { StringArtConfig, ProgressInfo }; diff --git a/string-art-demo/src/interfaces/stringArtConfig.ts b/string-art-demo/src/interfaces/stringArtConfig.ts new file mode 100644 index 0000000..f50e27e --- /dev/null +++ b/string-art-demo/src/interfaces/stringArtConfig.ts @@ -0,0 +1,17 @@ +import type { WasmStringArtConfig } from "../wasm/string_art_rust_impl"; + +export type StringArtConfig = Pick & { + max_lines: number, + line_darkness: number, + min_improvement_score: number, + progress_frequency: number, +} \ No newline at end of file diff --git a/string-art-demo/src/main.tsx b/string-art-demo/src/main.tsx index bef5202..9d93057 100644 --- a/string-art-demo/src/main.tsx +++ b/string-art-demo/src/main.tsx @@ -1,10 +1,7 @@ -import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' import App from './App.tsx' createRoot(document.getElementById('root')!).render( - - - , + , ) diff --git a/string-art-demo/src/workers/stringArtWorker.ts b/string-art-demo/src/workers/stringArtWorker.ts index 3855a14..2e5a554 100644 --- a/string-art-demo/src/workers/stringArtWorker.ts +++ b/string-art-demo/src/workers/stringArtWorker.ts @@ -1,23 +1,13 @@ -import init, { StringArtWasm, WasmStringArtConfig } from '../wasm/string_art_rust_impl.js'; +import type { StringArtConfig } from '../interfaces/stringArtConfig.js'; +import init, { ProgressInfo, StringArtWasm, WasmStringArtConfig } from '../wasm/string_art_rust_impl.js'; interface WorkerMessage { imageData: Uint8Array; - config: WasmStringArtConfig; -} - -interface ProgressInfo { - lines_completed: number; - total_lines: number; - current_nail: number; - next_nail: number; - score: number; - completion_percent: number; - path_segment: [number, number]; - current_path: number[]; + config: StringArtConfig; } self.onmessage = async (event: MessageEvent) => { - const { imageData, config } = event.data; + const { imageData, config } = event.data as { imageData: Uint8Array, config: StringArtConfig}; try { // Initialize the WASM module @@ -40,10 +30,10 @@ self.onmessage = async (event: MessageEvent) => { // Generate the string art path with progress updates const path = await generator.generate_path_streaming_with_frequency( - 2000, // max_lines - 50.0, // line_darkness - 15.0, // min_improvement_score - 300, // progress_frequency + config.max_lines, // max_lines + config.line_darkness, // line_darkness + config.min_improvement_score, // min_improvement_score + config.progress_frequency, // progress_frequency (progress: ProgressInfo) => { const { current_path } = progress; self.postMessage({ type: 'progress', data: { ...progress, current_path } }); From c665a8b6918286bd785c53e032673c17721d76f8 Mon Sep 17 00:00:00 2001 From: fireandicefrog Date: Tue, 19 Aug 2025 14:27:23 +1200 Subject: [PATCH 2/4] chore: moved string art hook to main page --- string-art-demo/src/App.tsx | 2 +- .../src/components/StringArtConfig/StringArtConfigSection.tsx | 2 +- .../src/{components/StringArtConfig => }/useStringArt.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename string-art-demo/src/{components/StringArtConfig => }/useStringArt.ts (96%) diff --git a/string-art-demo/src/App.tsx b/string-art-demo/src/App.tsx index 16f4e3e..bba8017 100644 --- a/string-art-demo/src/App.tsx +++ b/string-art-demo/src/App.tsx @@ -3,7 +3,7 @@ import { ImageUploader } from './components/ImageUploader'; import { StringArtCanvas } from './components/StringArtCanvas'; import './App.css'; import StringArtConfigSection from './components/StringArtConfig/StringArtConfigSection'; -import { useStringArt, type ProgressInfo } from './components/StringArtConfig/useStringArt'; +import { useStringArt, type ProgressInfo } from './useStringArt'; function App() { const [imageData, setImageData] = useState(null); diff --git a/string-art-demo/src/components/StringArtConfig/StringArtConfigSection.tsx b/string-art-demo/src/components/StringArtConfig/StringArtConfigSection.tsx index 0d88271..27f20a7 100644 --- a/string-art-demo/src/components/StringArtConfig/StringArtConfigSection.tsx +++ b/string-art-demo/src/components/StringArtConfig/StringArtConfigSection.tsx @@ -1,5 +1,5 @@ import { useEffect, useState, type RefObject } from "react"; -import { type StringArtConfig } from "./useStringArt"; +import { type StringArtConfig } from "../../useStringArt"; const Slider = ({ title, diff --git a/string-art-demo/src/components/StringArtConfig/useStringArt.ts b/string-art-demo/src/useStringArt.ts similarity index 96% rename from string-art-demo/src/components/StringArtConfig/useStringArt.ts rename to string-art-demo/src/useStringArt.ts index b0f9b55..185a636 100644 --- a/string-art-demo/src/components/StringArtConfig/useStringArt.ts +++ b/string-art-demo/src/useStringArt.ts @@ -4,14 +4,14 @@ import { useCallback, useRef, } from "react"; -import type { StringArtConfig } from "../../interfaces/stringArtConfig"; +import type { StringArtConfig } from "./interfaces/stringArtConfig"; import init, { StringArtWasm, WasmStringArtConfig, test_wasm, get_version, ProgressInfo, -} from "../../wasm/string_art_rust_impl"; +} from "./wasm/string_art_rust_impl"; interface WasmModule { StringArtWasm: typeof StringArtWasm; From df26a3d1e23db04df618289868b81d6afcd5154a Mon Sep 17 00:00:00 2001 From: fireandicefrog Date: Tue, 19 Aug 2025 14:32:19 +1200 Subject: [PATCH 3/4] chore: added steps to the configuration system --- .../StringArtConfigSection.tsx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/string-art-demo/src/components/StringArtConfig/StringArtConfigSection.tsx b/string-art-demo/src/components/StringArtConfig/StringArtConfigSection.tsx index 27f20a7..ff6ea67 100644 --- a/string-art-demo/src/components/StringArtConfig/StringArtConfigSection.tsx +++ b/string-art-demo/src/components/StringArtConfig/StringArtConfigSection.tsx @@ -18,22 +18,22 @@ const Slider = ({ | "preserve_eyes" | "preserve_negative_space" >, - [number, number, number] + [number, number, number, number] > = { //min, max, start - image_size: [500, 2000, 500], - line_darkness: [25, 300, 100], - max_lines: [800, 5000, 1000], - min_improvement_score: [0, 100, 15], - negative_space_penalty: [0, 100, 0], - negative_space_threshold: [0, 100, 0], - num_nails: [360, 1440, 360], - progress_frequency: [200, 500, 200], + image_size: [500, 2000, 500, 100], + line_darkness: [25, 300, 100, 5], + max_lines: [800, 5000, 1000, 100], + min_improvement_score: [0, 100, 15, 1], + negative_space_penalty: [0, 100, 0, 1], + negative_space_threshold: [0, 100, 0, 1], + num_nails: [360, 1440, 360, 360/4], + progress_frequency: [200, 500, 200, 50], }; const [val, setVal] = useState("0"); const dontRender = !index || !Object.keys(minMaxVals).includes(index as string) - const [min, max, start] = dontRender? [0,0,0] : minMaxVals[index as keyof typeof minMaxVals]; + const [min, max, start, step] = dontRender? [0,0,0,0] : minMaxVals[index as keyof typeof minMaxVals]; useEffect(() => { setVal(String(start)) @@ -50,6 +50,7 @@ const Slider = ({ name="volume" min={min} max={max} + step={step} onChange={({ target }) => { (settingsRef.current[index] as unknown) = target.value; setVal(target.value); From ee9d394cab867deaf18ca14e444afb42f128658c Mon Sep 17 00:00:00 2001 From: fireandicefrog Date: Tue, 19 Aug 2025 15:09:37 +1200 Subject: [PATCH 4/4] chore: tidied up styling --- string-art-demo/src/App.css | 8 +- string-art-demo/src/App.tsx | 5 +- .../src/components/StringArtCanvas.tsx | 74 +++++++++++-------- string-art-demo/src/useStringArt.ts | 6 +- 4 files changed, 52 insertions(+), 41 deletions(-) diff --git a/string-art-demo/src/App.css b/string-art-demo/src/App.css index 27d7713..b0313c4 100644 --- a/string-art-demo/src/App.css +++ b/string-art-demo/src/App.css @@ -1,10 +1,11 @@ .app { min-height: 100vh; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - padding: 1rem; + width: 100vw; + background: linear-gradient(0deg, #8f91ff 0%, #7476ff 100%); } .app-header { + padding-top: 1rem; text-align: center; color: white; margin-bottom: 2rem; @@ -47,6 +48,8 @@ background: white; border-radius: 8px; padding: 1.5rem; + display: flex; + flex-direction: column; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } @@ -89,6 +92,7 @@ } .generate-button { + margin-top: 15px; width: 100%; padding: 1rem; background: #28a745; diff --git a/string-art-demo/src/App.tsx b/string-art-demo/src/App.tsx index bba8017..18d1e8e 100644 --- a/string-art-demo/src/App.tsx +++ b/string-art-demo/src/App.tsx @@ -4,7 +4,6 @@ import { StringArtCanvas } from './components/StringArtCanvas'; import './App.css'; import StringArtConfigSection from './components/StringArtConfig/StringArtConfigSection'; import { useStringArt, type ProgressInfo } from './useStringArt'; - function App() { const [imageData, setImageData] = useState(null); const [imageUrl, setImageUrl] = useState(''); @@ -42,7 +41,7 @@ function App() { const result = await generateStringArt(imageData, onProgress, onNailCoords); if (result.path) { - setCurrentPath(result.path); + setCurrentPath((prevPath) => [...prevPath, ...(result.path || [])]); } if (result.nailCoords.length > 0) { setNailCoords(result.nailCoords); @@ -94,7 +93,7 @@ function App() { {imageData && (
- +
); -}; \ No newline at end of file +}; diff --git a/string-art-demo/src/useStringArt.ts b/string-art-demo/src/useStringArt.ts index 185a636..853f5b9 100644 --- a/string-art-demo/src/useStringArt.ts +++ b/string-art-demo/src/useStringArt.ts @@ -12,6 +12,7 @@ import init, { get_version, ProgressInfo, } from "./wasm/string_art_rust_impl"; +import workerUrl from "./workers/stringArtWorker?url" interface WasmModule { StringArtWasm: typeof StringArtWasm; @@ -74,10 +75,7 @@ export const useStringArt = () => { useEffect(() => { // Initialize the service worker - workerRef.current = new Worker( - new URL("../workers/stringArtWorker.ts", import.meta.url), - { type: "module" } - ); + workerRef.current = new Worker(workerUrl, { type: "module" }); return () => { workerRef.current?.terminate(); };