From a76cb021c7f21199fa53ab3aeec773e098962e19 Mon Sep 17 00:00:00 2001 From: skratchdot Date: Tue, 8 Sep 2026 14:51:40 -0400 Subject: [PATCH 1/2] Reorganize source around components and domain modules Replace the workspace feature hierarchy with components/playground, shared components/dialogs, and non-React controllers. Rename workspace APIs and selectors to playground/player terminology while preserving lifecycle and playback behavior. Keep page content directly in TanStack routes, move the document shell under components/layout, and retain browser-only lazy loading for the interactive screen. Keep shared state in place, consolidate generic helpers under utils, and update shadcn aliases. Move the sort worker and Timbre adapter into their domain folders and audio previews into audio. Update imports, regression tests, and architecture/development documentation. Verified lint, formatting, spelling, TypeScript, 360 unit tests, production prerendering, and 37 browser tests. Existing bundle-size warnings remain. --- components.json | 4 +- cspell.config.ts | 4 +- docs/architecture.md | 38 ++-- docs/development.md | 5 +- docs/migration-handoff.md | 24 +-- docs/modernization.md | 10 +- .../runtime => audio}/envelope-diagram.ts | 2 +- .../runtime => audio}/string-preview.ts | 0 src/{vendor.mjs => audio/timbre.mjs} | 0 .../dialogs/algorithm-dialog.tsx | 18 +- .../dialogs}/create-code-editor.mjs | 0 .../dialogs/midi-dialog.tsx | 6 +- .../dialogs/player-dialog.tsx} | 2 +- .../layout}/site-document.tsx | 6 +- src/components/option-button.tsx | 2 +- .../playground/browser-playground.tsx} | 12 +- .../playground}/filtered-options.tsx | 0 .../playground}/playback-controls.tsx | 6 +- .../playground/player-section.tsx} | 4 +- .../playground}/setting-range.tsx | 0 .../playground}/settings-controls.tsx | 8 +- .../playground}/sort-sidebar.tsx | 2 +- .../playground/sorting-playground.tsx} | 14 +- .../playground}/waveform-controls.tsx | 8 +- src/components/text-link.tsx | 2 +- .../connect-audio-settings.ts | 4 +- .../connect-playback-settings.ts | 6 +- .../connect-sort-settings.ts | 4 +- .../create-helpers.mjs | 2 +- .../create-player.mjs} | 14 +- .../create-playground.mjs} | 26 +-- src/controllers/playground-types.ts | 3 + .../workspace/runtime/workspace-types.ts | 3 - src/generators/patterns/almost-sorted.ts | 4 +- src/generators/patterns/few-unique.ts | 2 +- src/generators/patterns/random-unique.ts | 2 +- src/pages/about.tsx | 53 ----- src/pages/api.tsx | 185 ----------------- src/pages/home.tsx | 37 ---- src/routes/__root.tsx | 2 +- src/routes/about.tsx | 54 ++++- src/routes/api.tsx | 186 +++++++++++++++++- src/routes/index.tsx | 38 +++- src/{ => sorting}/worker.mjs | 2 +- src/{lib/utils.ts => utils/cn.ts} | 0 src/{utilities => utils}/random.ts | 0 src/{utilities => utils}/shuffle.ts | 0 src/{utilities => utils}/swap.ts | 0 test/audio-settings-connection.test.mjs | 2 +- test/browser/site.spec.mjs | 18 +- test/playback-settings-connection.test.mjs | 2 +- test/scales.test.mjs | 2 +- test/settings.test.mjs | 6 +- test/sort-settings-connection.test.mjs | 2 +- test/string-preview.test.mjs | 2 +- test/ui.test.mjs | 18 +- test/utilities.test.mjs | 6 +- test/waveforms.test.mjs | 9 +- 58 files changed, 440 insertions(+), 431 deletions(-) rename src/{features/workspace/runtime => audio}/envelope-diagram.ts (96%) rename src/{features/workspace/runtime => audio}/string-preview.ts (100%) rename src/{vendor.mjs => audio/timbre.mjs} (100%) rename src/{features/workspace => components}/dialogs/algorithm-dialog.tsx (90%) rename src/{features/workspace/runtime => components/dialogs}/create-code-editor.mjs (100%) rename src/{features/workspace => components}/dialogs/midi-dialog.tsx (92%) rename src/{features/workspace/dialogs/workspace-dialog.tsx => components/dialogs/player-dialog.tsx} (97%) rename src/{pages => components/layout}/site-document.tsx (85%) rename src/{features/workspace/browser-workspace.tsx => components/playground/browser-playground.tsx} (74%) rename src/{features/workspace/components => components/playground}/filtered-options.tsx (100%) rename src/{features/workspace/components => components/playground}/playback-controls.tsx (94%) rename src/{features/workspace/components/workspace-section.tsx => components/playground/player-section.tsx} (90%) rename src/{features/workspace/components => components/playground}/setting-range.tsx (100%) rename src/{features/workspace/components => components/playground}/settings-controls.tsx (96%) rename src/{features/workspace/components => components/playground}/sort-sidebar.tsx (98%) rename src/{features/workspace/components/workspace.tsx => components/playground/sorting-playground.tsx} (93%) rename src/{features/workspace/components => components/playground}/waveform-controls.tsx (93%) rename src/{features/workspace/runtime => controllers}/connect-audio-settings.ts (92%) rename src/{features/workspace/runtime => controllers}/connect-playback-settings.ts (88%) rename src/{features/workspace/runtime => controllers}/connect-sort-settings.ts (92%) rename src/{features/workspace/runtime => controllers}/create-helpers.mjs (91%) rename src/{features/workspace/runtime/create-workspace-player.mjs => controllers/create-player.mjs} (89%) rename src/{features/workspace/runtime/create-workspace.mjs => controllers/create-playground.mjs} (89%) create mode 100644 src/controllers/playground-types.ts delete mode 100644 src/features/workspace/runtime/workspace-types.ts delete mode 100644 src/pages/about.tsx delete mode 100644 src/pages/api.tsx delete mode 100644 src/pages/home.tsx rename src/{ => sorting}/worker.mjs (59%) rename src/{lib/utils.ts => utils/cn.ts} (100%) rename src/{utilities => utils}/random.ts (100%) rename src/{utilities => utils}/shuffle.ts (100%) rename src/{utilities => utils}/swap.ts (100%) diff --git a/components.json b/components.json index 42acebc..1904d1b 100644 --- a/components.json +++ b/components.json @@ -14,9 +14,9 @@ "rtl": false, "aliases": { "components": "@/components", - "utils": "@/lib/utils", + "utils": "@/utils/cn", "ui": "@/components/ui", - "lib": "@/lib", + "lib": "@/utils", "hooks": "@/hooks" }, "menuColor": "default", diff --git a/cspell.config.ts b/cspell.config.ts index 2d34690..a5d7d2e 100644 --- a/cspell.config.ts +++ b/cspell.config.ts @@ -14,8 +14,8 @@ export default defineConfig({ "src/midi/scales.ts", ], dictionaryDefinitions: [ - { name: "project", path: "./.cspell/project.txt", addWords: true }, - { name: "music", path: "./.cspell/music.txt" }, + { name: "project", path: ".cspell/project.txt", addWords: true }, + { name: "music", path: ".cspell/music.txt" }, ], dictionaries: ["project", "music"], overrides: [ diff --git a/docs/architecture.md b/docs/architecture.md index d61a65c..36e78c4 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -2,11 +2,25 @@ Modules in `src/` use lowercase, hyphen-separated filenames. `components/ui/` contains shadcn Base UI primitives; `components/layout/` contains the header and footer. -`features/workspace/` separates components, dialogs, and runtime coordination. +`components/playground/` contains the interactive screen and its controls; +`components/dialogs/` contains the algorithm editor and MIDI export dialogs. `sorting/` contains the engine and request handling, and -`sorting/algorithms/` only algorithm implementations. Generators, utilities, -MIDI support, and visualizations each have their own directory. The client entry, -worker entry, and vendor bridge stay at the top level. `@/` aliases `src/`. +`sorting/algorithms/` only algorithm implementations. Generators, +MIDI support, and visualizations each have their own directory. The worker lives in +`sorting/worker.mjs`; the Timbre adapter lives in `audio/timbre.mjs`. `@/` aliases `src/`. + +`utils/` contains small, stateless, domain-independent helpers: `cn.ts` combines +CSS classes; `random.ts`, `shuffle.ts`, and `swap.ts` handle numbers and arrays. +There is no separate `utilities/` folder or catch-all `utils.ts` file. shadcn's +`utils` alias points to `@/utils/cn` so newly generated components use the same helper. +Domain-specific code stays with its domain rather than accumulating in `utils/`. + +The sorting playground is the interactive data/sort screen, including its editor +and playback controls. React presentation lives under `components/`; +`controllers/` connects the sorting, audio, state, and visualization modules. Shared +settings and atoms remain in `state/`; component-local state stays in its component. A player +is one data or sort panel; the playground coordinates both. The older “workspace” +name did not describe a separate domain concept. TypeScript is introduced incrementally alongside `.mjs` modules. Vite handles bundling; `pnpm run typecheck` checks `.ts` and `.tsx` application modules separately. @@ -18,30 +32,30 @@ one level above, separate from the implementations they register. ## UI -TanStack Start routes in `src/routes/` render Home, About, and API through the -shared document in `src/pages/`. Public URLs are `/audio-sort/`, `/audio-sort/about`, and `/audio-sort/api`; +TanStack Start routes in `src/routes/` contain Home, About, and API page content and use +the shared document in `src/components/layout/`. Public URLs are `/audio-sort/`, `/audio-sort/about`, and `/audio-sort/api`; prerendering emits `index.html`, `about/index.html`, and `api/index.html`. TanStack links provide client navigation and ordinary anchor fallbacks without JavaScript. Audio and editor dependencies are dynamically imported by Home's effect, never evaluated during server prerendering. About/API remain usable without JavaScript. `src/client.tsx` hydrates the document. -[`browser-workspace.tsx`](../src/features/workspace/browser-workspace.tsx) owns the browser workspace lifecycle with an application-scoped +[`browser-playground.tsx`](../src/components/playground/browser-playground.tsx) owns the browser playground lifecycle with an application-scoped vanilla Jotai store. React owns settings, tabs, transport controls, counters, sliders, and dialogs. Shared buttons, links, and option controls use Tailwind classes. `styles/globals.css` holds the shadcn theme and document defaults; `styles/visualizations.css` styles D3-owned SVG children. There is no `site.css`. -The light theme uses sky accents and neutral surfaces. Workspace layout uses one +The light theme uses sky accents and neutral surfaces. Playground layout uses one threshold (`lg`, 1024px): stacked below it, side-by-side above it. Chart heights are fluid, bounded with `clamp()`, without height-specific media queries. -[`create-workspace.mjs`](../src/features/workspace/runtime/create-workspace.mjs) coordinates workers, +[`create-playground.mjs`](../src/controllers/create-playground.mjs) coordinates workers, data generation, settings subscriptions, soundfont preloading, and player lifetime. React reads its playback snapshots through `useSyncExternalStore`. Settings and custom algorithms are read directly from Jotai; there is no mirrored settings cache. Audio clocks and nodes remain outside React and Jotai. -[`create-workspace-player.mjs`](../src/features/workspace/runtime/create-workspace-player.mjs) owns +[`create-player.mjs`](../src/controllers/create-player.mjs) owns the contents of its D3 SVG and delegates transport and synthesis to `audio/create-transport.ts` and `audio/create-timbre-audio.mjs`. React renders the surrounding controls and an empty SVG host, never chart children. @@ -65,11 +79,11 @@ initialization does not depend on measuring thumb widths. MIDI export uses shadc Cached-page suspension disconnects runtime effects, pauses audio, cancels workers and pending resumes, and closes dialogs. Returning reconnects effects without automatically playing. React continues to represent the same Jotai store. -Non-cached exits and Home effect cleanup unmount the workspace, dispose owned resources, and +Non-cached exits and Home effect cleanup unmount the playground, dispose owned resources, and release native pointer listeners. Fresh runtime instances can reuse the store. The shared AudioContext stays library-owned. -All third-party JavaScript uses package imports. `vendor.mjs` only re-exports the +All third-party JavaScript uses package imports. `audio/timbre.mjs` only re-exports the pinned Timbre browser entry; there are no classic script tags or `public/js` files. D3 imports remain scoped. Sample audio is fetched and decoded by first-party modules; see [the audio boundary](audio-dependencies.md). diff --git a/docs/development.md b/docs/development.md index 51c5dc9..1b8dd60 100644 --- a/docs/development.md +++ b/docs/development.md @@ -42,7 +42,8 @@ styles live in `src/styles/visualizations.css`. Add primitives with `pnpm dlx shadcn@latest add `. `components.json` selects Base UI, the Nova preset, neutral base colors, and Lucide icons. Keep shared -primitives in `src/components/ui/` and workspace behavior in `src/features/workspace/`. +primitives in `src/components/ui/`, screen controls in `src/components/playground/`, +and dialogs in `src/components/dialogs/`. Non-React coordination lives in `src/controllers/`. Use `lg:` for the application's stacked/side-by-side layout; avoid adding extra width tiers. ## Checks @@ -59,7 +60,7 @@ TypeScript can infer imported JavaScript modules (`allowJs`), but `checkJs` stay off until those modules are migrated. Worker payloads are still checked at runtime. Vendor and generated files are excluded from linting and formatting. Oxfmt formats -source CSS and TSX alongside JavaScript. `src/pages/site-document.tsx` owns the +source CSS and TSX alongside JavaScript. `src/components/layout/site-document.tsx` owns the shared document, header, and footer. Start generates `src/route-tree.gen.ts` from `src/routes/`; commit that file but do not edit it manually. diff --git a/docs/migration-handoff.md b/docs/migration-handoff.md index 212135f..74a209e 100644 --- a/docs/migration-handoff.md +++ b/docs/migration-handoff.md @@ -11,12 +11,12 @@ ## Completed UI migration -PR #47 migrated the whole workspace; PR #48 polished icons, hover states, and typography: +PR #47 migrated the whole playground; PR #48 polished icons, hover states, and typography: -- The workspace uses a reusable vanilla Jotai store. -- `ui/workspace.tsx` assembles settings, playback controls, and native dialogs. -- `ui/create-workspace.mjs` owns worker/data coordination and audio subscriptions. -- `ui/create-workspace-player.mjs` bridges the existing transport/audio modules and D3. +- The playground uses a reusable vanilla Jotai store. +- `components/playground/sorting-playground.tsx` assembles settings, playback controls, and dialogs. +- `controllers/create-playground.mjs` owns worker/data coordination and audio subscriptions. +- `controllers/create-player.mjs` bridges the existing transport/audio modules and D3. - React owns controls; D3 owns SVG children; audio draws the waveform canvas. - Settings/algorithm overrides stay in Jotai. Playback snapshots use `useSyncExternalStore`. - Native ranges replace plugin sliders; native dialogs handle editing and MIDI export. @@ -34,12 +34,12 @@ the old JSONP/MP3 extensions. No new license-output logic is included. ## Completed TanStack Start shell Eleventy and Liquid are replaced by TanStack Start file routes and React pages. -The existing workspace, runtime, and visual design are retained. +The existing playground, runtime, and visual design are retained. - `src/routes/` defines the three pages; Start generates `src/route-tree.gen.ts`. -- `src/pages/site-document.tsx` renders the shared header/footer and document. -- `src/pages/home.tsx` dynamically imports `src/features/workspace/browser-workspace.tsx` after hydration. - It renders the workspace within Start's React root; runtime lifecycle cleanup +- `src/components/layout/site-document.tsx` renders the shared header/footer and document. +- `src/routes/index.tsx` dynamically imports `src/components/playground/browser-playground.tsx` after hydration. + It renders the playground within Start's React root; runtime lifecycle cleanup handles navigation away, cached pages, and remounts. Audio/editor modules never execute during prerendering. - Public routes are `/audio-sort/`, `/audio-sort/about`, and `/audio-sort/api`. Per the user's updated preference, @@ -55,7 +55,7 @@ The existing workspace, runtime, and visual design are retained. per-page output configuration. Link crawling stays disabled because it duplicates base-prefixed URLs in the Pages build. - Tests cover clean URLs/reloads, client navigation, no-JavaScript content, - hydration errors, lazy-load recovery, and the existing workspace regressions. + hydration errors, lazy-load recovery, and the existing playground regressions. - Adding Vite's raw-import types exposed an existing `getFunctionBody` signature mismatch; it now explicitly accepts the source strings it already handled. @@ -66,10 +66,10 @@ or sorting APIs. It uses shadcn's Base UI Nova primitives, retains Lucide and th system font, and maps the light theme to Tailwind sky/neutral colors. - `src/components/ui/`: shared primitives; `components/layout/`: header/footer. -- `src/features/workspace/`: components, separate dialogs, and runtime coordination. +- `src/components/playground/` and `src/components/dialogs/`: controls and dialogs; `src/controllers/`: runtime coordination. - `src/styles/globals.css`: theme/document defaults; `visualizations.css`: D3 state styles. - `site.css` and the `tw:` prefix are removed. Buttons and links own their Tailwind classes. -- Two workspace layouts use one 1024px threshold; chart heights are fluid. +- Two playground layouts use one 1024px threshold; chart heights are fluid. - Tab panels stay mounted for Ace/canvas lifetime. Slider thumbs use center alignment to avoid hidden-panel measurement. Base UI handles dialog focus and dismissal. diff --git a/docs/modernization.md b/docs/modernization.md index a24d4a0..9e61512 100644 --- a/docs/modernization.md +++ b/docs/modernization.md @@ -88,7 +88,7 @@ JSONP/MP3/soundfont scripts are removed. The [audio dependency audit](audio-depe records sample-host verification and the Timbre package compatibility checks. Timbre now uses the pinned `14.11.25` browser entry with its Node-only dependencies excluded. The obsolete local bundles, map, and Flash asset are removed. -Phase 6 now replaces the complete workspace in PR #47, not a sequence of islands. +Phase 6 now replaces the complete playground in PR #47, not a sequence of islands. React owns all controls and dialogs, uses the existing Jotai store, and subscribes to playback snapshots from a separate runtime. D3 and audio retain their owned SVG/canvas hosts. The legacy controller, plugin sliders, Bootstrap CSS/JavaScript, @@ -100,12 +100,12 @@ Public routes are `/audio-sort/`, `/audio-sort/about`, and `/audio-sort/api`, wi Static prerendering emits directory index files into `dist/audio-sort/`; build-time server files remain in `.tanstack/` and are not deployed. Development, production, preview, and browser tests share the same base and build configuration. -Home imports the existing workspace after hydration; About/API need no audio or -editor runtime. Client navigation unmounts the workspace when leaving Home. +Home imports the existing playground after hydration; About/API need no audio or +editor runtime. Client navigation unmounts the playground when leaving Home. The current UI refinement adds shadcn Base UI (Nova preset) before phase 8. -Shared primitives live in `components/ui/`; workspace components, dialogs, and -runtime modules live in `features/workspace/`. Tailwind component classes replace +Shared primitives live in `components/ui/`; playground components, dialogs, and +runtime modules live in `components/playground/`, `components/dialogs/`, and `controllers/`, respectively. Tailwind component classes replace `site.css`, with sky/neutral theme tokens and a small D3 stylesheet. One `lg` threshold separates stacked and side-by-side layouts; chart heights use `clamp()`. Review the larger controls, dialog behavior, and laptop/mobile layouts before merging. diff --git a/src/features/workspace/runtime/envelope-diagram.ts b/src/audio/envelope-diagram.ts similarity index 96% rename from src/features/workspace/runtime/envelope-diagram.ts rename to src/audio/envelope-diagram.ts index 92e97f7..be4acd6 100644 --- a/src/features/workspace/runtime/envelope-diagram.ts +++ b/src/audio/envelope-diagram.ts @@ -1,4 +1,4 @@ -import type { Envelope, EnvelopeKey } from "../../../state/envelope.ts"; +import type { Envelope, EnvelopeKey } from "../state/envelope.ts"; export function formatEnvelopeValue(key: EnvelopeKey, value: number): string { if (key === "s") return `${Math.round(value * 100)}%`; diff --git a/src/features/workspace/runtime/string-preview.ts b/src/audio/string-preview.ts similarity index 100% rename from src/features/workspace/runtime/string-preview.ts rename to src/audio/string-preview.ts diff --git a/src/vendor.mjs b/src/audio/timbre.mjs similarity index 100% rename from src/vendor.mjs rename to src/audio/timbre.mjs diff --git a/src/features/workspace/dialogs/algorithm-dialog.tsx b/src/components/dialogs/algorithm-dialog.tsx similarity index 90% rename from src/features/workspace/dialogs/algorithm-dialog.tsx rename to src/components/dialogs/algorithm-dialog.tsx index 084d3be..f78d439 100644 --- a/src/features/workspace/dialogs/algorithm-dialog.tsx +++ b/src/components/dialogs/algorithm-dialog.tsx @@ -1,18 +1,18 @@ import { Button } from "@/components/ui/button"; import { useEffect, useRef, useState } from "react"; import { useAtomValue } from "jotai"; -import { settingsAtom } from "../../../state/settings.ts"; +import { settingsAtom } from "../../state/settings.ts"; import { algorithmCatalogAtom, addAlgorithmAtom, editAlgorithmAtom, -} from "../../../state/algorithm-overrides.ts"; -import { algorithms } from "../../../sorting/algorithm-registry.mjs"; -import { sources } from "../../../sorting/algorithm-sources.mjs"; -import { getFunctionBody } from "../../../sorting/sort-requests.ts"; -import type { createCodeEditor } from "../runtime/create-code-editor.mjs"; -import type { Props } from "../runtime/workspace-types.ts"; -import { WorkspaceDialog as Dialog } from "./workspace-dialog.tsx"; +} from "../../state/algorithm-overrides.ts"; +import { algorithms } from "../../sorting/algorithm-registry.mjs"; +import { sources } from "../../sorting/algorithm-sources.mjs"; +import { getFunctionBody } from "../../sorting/sort-requests.ts"; +import type { createCodeEditor } from "./create-code-editor.mjs"; +import type { Props } from "../../controllers/playground-types.ts"; +import { PlayerDialog as Dialog } from "./player-dialog.tsx"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; import { Input } from "@/components/ui/input"; export function AlgorithmDialog({ @@ -32,7 +32,7 @@ export function AlgorithmDialog({ useEffect(() => { let cancelled = false; let instance: ReturnType | undefined; - void import("../runtime/create-code-editor.mjs") + void import("./create-code-editor.mjs") .then(({ createCodeEditor }) => { if (cancelled) return; instance = createCodeEditor(host.current!); diff --git a/src/features/workspace/runtime/create-code-editor.mjs b/src/components/dialogs/create-code-editor.mjs similarity index 100% rename from src/features/workspace/runtime/create-code-editor.mjs rename to src/components/dialogs/create-code-editor.mjs diff --git a/src/features/workspace/dialogs/midi-dialog.tsx b/src/components/dialogs/midi-dialog.tsx similarity index 92% rename from src/features/workspace/dialogs/midi-dialog.tsx rename to src/components/dialogs/midi-dialog.tsx index f30cbb9..ec0160d 100644 --- a/src/features/workspace/dialogs/midi-dialog.tsx +++ b/src/components/dialogs/midi-dialog.tsx @@ -2,9 +2,9 @@ import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { NativeSelect } from "@/components/ui/native-select"; -import { instruments } from "../../../midi/instruments.ts"; -import { WorkspaceDialog as Dialog } from "./workspace-dialog.tsx"; -import type { Props, PlayerId } from "../runtime/workspace-types.ts"; +import { instruments } from "../../midi/instruments.ts"; +import { PlayerDialog as Dialog } from "./player-dialog.tsx"; +import type { Props, PlayerId } from "../../controllers/playground-types.ts"; export function MidiDialog({ runtime, id, diff --git a/src/features/workspace/dialogs/workspace-dialog.tsx b/src/components/dialogs/player-dialog.tsx similarity index 97% rename from src/features/workspace/dialogs/workspace-dialog.tsx rename to src/components/dialogs/player-dialog.tsx index 69fecc9..7f30890 100644 --- a/src/features/workspace/dialogs/workspace-dialog.tsx +++ b/src/components/dialogs/player-dialog.tsx @@ -8,7 +8,7 @@ import { } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; -export function WorkspaceDialog({ +export function PlayerDialog({ id, title, children, diff --git a/src/pages/site-document.tsx b/src/components/layout/site-document.tsx similarity index 85% rename from src/pages/site-document.tsx rename to src/components/layout/site-document.tsx index 13a4054..e8c49a7 100644 --- a/src/pages/site-document.tsx +++ b/src/components/layout/site-document.tsx @@ -1,8 +1,8 @@ import type { ReactNode } from "react"; import { HeadContent, Scripts } from "@tanstack/react-router"; -import { Header } from "../components/layout/header.tsx"; -import { Footer } from "../components/layout/footer.tsx"; -import stylesheet from "../styles/globals.css?url"; +import { Header } from "./header.tsx"; +import { Footer } from "./footer.tsx"; +import stylesheet from "../../styles/globals.css?url"; export function SiteDocument({ children }: { children: ReactNode }) { return ( diff --git a/src/components/option-button.tsx b/src/components/option-button.tsx index 15f94e0..f341763 100644 --- a/src/components/option-button.tsx +++ b/src/components/option-button.tsx @@ -1,5 +1,5 @@ import { Button } from "@/components/ui/button"; -import { cn } from "@/lib/utils"; +import { cn } from "@/utils/cn"; import type { ComponentProps } from "react"; export function OptionButton({ className, ...props }: ComponentProps) { diff --git a/src/features/workspace/browser-workspace.tsx b/src/components/playground/browser-playground.tsx similarity index 74% rename from src/features/workspace/browser-workspace.tsx rename to src/components/playground/browser-playground.tsx index c4278d3..442d548 100644 --- a/src/features/workspace/browser-workspace.tsx +++ b/src/components/playground/browser-playground.tsx @@ -1,17 +1,17 @@ import { useEffect, useState } from "react"; import { createStore } from "jotai/vanilla"; -import { Workspace } from "./components/workspace.tsx"; -import { createWorkspace } from "./runtime/create-workspace.mjs"; +import { SortingPlayground } from "./sorting-playground.tsx"; +import { createPlayground } from "../../controllers/create-playground.mjs"; const store = createStore(); -type Runtime = ReturnType; +type Runtime = ReturnType; -export function BrowserWorkspace() { +export function BrowserPlayground() { const [runtime, setRuntime] = useState(null); useEffect(() => { let current: Runtime | null = null; const mount = () => { - current = createWorkspace(store); + current = createPlayground(store); setRuntime(current); }; const onPageHide = (event: PageTransitionEvent) => { @@ -36,5 +36,5 @@ export function BrowserWorkspace() { current?.destroy(); }; }, []); - return runtime ? : null; + return runtime ? : null; } diff --git a/src/features/workspace/components/filtered-options.tsx b/src/components/playground/filtered-options.tsx similarity index 100% rename from src/features/workspace/components/filtered-options.tsx rename to src/components/playground/filtered-options.tsx diff --git a/src/features/workspace/components/playback-controls.tsx b/src/components/playground/playback-controls.tsx similarity index 94% rename from src/features/workspace/components/playback-controls.tsx rename to src/components/playground/playback-controls.tsx index 8a26514..1842319 100644 --- a/src/features/workspace/components/playback-controls.tsx +++ b/src/components/playground/playback-controls.tsx @@ -3,10 +3,10 @@ import { Toggle } from "@/components/ui/toggle"; import { ValueSlider } from "@/components/value-slider"; import { useSyncExternalStore } from "react"; import { useAtomValue } from "jotai"; -import { playbackPreferencesAtom } from "../../../state/playback-preferences.ts"; -import type { Props, PlayerId } from "../runtime/workspace-types.ts"; +import { playbackPreferencesAtom } from "../../state/playback-preferences.ts"; +import type { Props, PlayerId } from "../../controllers/playground-types.ts"; import { FastForward, Rewind, SkipBack, SkipForward, Square, RotateCcw } from "lucide-react"; -import { ControlIcon } from "../../../components/control-icon.tsx"; +import { ControlIcon } from "../control-icon.tsx"; export function Counters({ runtime }: Props) { const state = useSyncExternalStore(runtime.subscribe, () => runtime.getSnapshot().sort); diff --git a/src/features/workspace/components/workspace-section.tsx b/src/components/playground/player-section.tsx similarity index 90% rename from src/features/workspace/components/workspace-section.tsx rename to src/components/playground/player-section.tsx index 9697754..f0ba8b2 100644 --- a/src/features/workspace/components/workspace-section.tsx +++ b/src/components/playground/player-section.tsx @@ -2,9 +2,9 @@ import type { ReactNode } from "react"; import { Download } from "lucide-react"; import { Button } from "@/components/ui/button"; import { ControlIcon } from "@/components/control-icon"; -import type { PlayerId } from "../runtime/workspace-types"; +import type { PlayerId } from "../../controllers/playground-types"; -export function WorkspaceSection({ +export function PlayerSection({ id, title, description, diff --git a/src/features/workspace/components/setting-range.tsx b/src/components/playground/setting-range.tsx similarity index 100% rename from src/features/workspace/components/setting-range.tsx rename to src/components/playground/setting-range.tsx diff --git a/src/features/workspace/components/settings-controls.tsx b/src/components/playground/settings-controls.tsx similarity index 96% rename from src/features/workspace/components/settings-controls.tsx rename to src/components/playground/settings-controls.tsx index 4f6ad16..88862a0 100644 --- a/src/features/workspace/components/settings-controls.tsx +++ b/src/components/playground/settings-controls.tsx @@ -4,11 +4,11 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; import { Button } from "@/components/ui/button"; import { useState } from "react"; import { useAtomValue, useSetAtom } from "jotai"; -import { settingsAtom, updateSettingAtom, defaults } from "../../../state/settings.ts"; -import { scales } from "../../../midi/scales.ts"; -import { instruments } from "../../../midi/instruments.ts"; +import { settingsAtom, updateSettingAtom, defaults } from "../../state/settings.ts"; +import { scales } from "../../midi/scales.ts"; +import { instruments } from "../../midi/instruments.ts"; import { WaveformControls } from "./waveform-controls.tsx"; -import type { Props } from "../runtime/workspace-types.ts"; +import type { Props } from "../../controllers/playground-types.ts"; const scaleOptions = Object.entries(scales) .sort( ([, a], [, b]) => diff --git a/src/features/workspace/components/sort-sidebar.tsx b/src/components/playground/sort-sidebar.tsx similarity index 98% rename from src/features/workspace/components/sort-sidebar.tsx rename to src/components/playground/sort-sidebar.tsx index 76f08bd..e03ad5d 100644 --- a/src/features/workspace/components/sort-sidebar.tsx +++ b/src/components/playground/sort-sidebar.tsx @@ -8,7 +8,7 @@ import { OptionButton } from "@/components/option-button"; import { ControlIcon } from "@/components/control-icon"; import { Info, CirclePlus, ChartNoAxesColumnIncreasing, List } from "lucide-react"; import { Counters } from "./playback-controls"; -import type { Props } from "../runtime/workspace-types"; +import type { Props } from "../../controllers/playground-types"; export function SortSidebar({ runtime, onDialog: setModal, diff --git a/src/features/workspace/components/workspace.tsx b/src/components/playground/sorting-playground.tsx similarity index 93% rename from src/features/workspace/components/workspace.tsx rename to src/components/playground/sorting-playground.tsx index 978da58..b36bebc 100644 --- a/src/features/workspace/components/workspace.tsx +++ b/src/components/playground/sorting-playground.tsx @@ -1,4 +1,4 @@ -import { WorkspaceSection } from "./workspace-section"; +import { PlayerSection } from "./player-section"; import { SortSidebar } from "./sort-sidebar"; import { Button } from "@/components/ui/button"; import { useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react"; @@ -6,9 +6,9 @@ import { Settings } from "./settings-controls.tsx"; import { Transport, Scrubber } from "./playback-controls.tsx"; import { AlgorithmDialog } from "../dialogs/algorithm-dialog.tsx"; import { MidiDialog } from "../dialogs/midi-dialog.tsx"; -import type { Props, PlayerId } from "../runtime/workspace-types.ts"; +import type { Props, PlayerId } from "../../controllers/playground-types.ts"; type Modal = "sort" | "add-algorithm" | "midi-export" | null; -export function Workspace({ runtime }: Props) { +export function SortingPlayground({ runtime }: Props) { const error = useSyncExternalStore(runtime.subscribe, () => runtime.getSnapshot().error); const suspended = useSyncExternalStore(runtime.subscribe, () => runtime.getSnapshot().suspended); const base = useRef(null); @@ -33,7 +33,7 @@ export function Workspace({ runtime }: Props) { const close = () => setModal(null); return ( <> - - - + - + {!suspended && modal === "sort" && ( )} diff --git a/src/features/workspace/components/waveform-controls.tsx b/src/components/playground/waveform-controls.tsx similarity index 93% rename from src/features/workspace/components/waveform-controls.tsx rename to src/components/playground/waveform-controls.tsx index 4410f74..bd8becf 100644 --- a/src/features/workspace/components/waveform-controls.tsx +++ b/src/components/playground/waveform-controls.tsx @@ -4,10 +4,10 @@ import { useLayoutEffect, useRef } from "react"; import { Field } from "@base-ui/react/field"; import { useAtomValue, useSetAtom } from "jotai"; import type { createStore } from "jotai/vanilla"; -import { settingsAtom, updateSettingAtom } from "../../../state/settings.ts"; -import { envelopeAtom, updateEnvelopeAtom, type EnvelopeKey } from "../../../state/envelope.ts"; -import { waveformDefaults, type WaveformId } from "../../../state/waveforms.ts"; -import { drawEnvelopeDiagram, formatEnvelopeValue } from "../runtime/envelope-diagram.ts"; +import { settingsAtom, updateSettingAtom } from "../../state/settings.ts"; +import { envelopeAtom, updateEnvelopeAtom, type EnvelopeKey } from "../../state/envelope.ts"; +import { waveformDefaults, type WaveformId } from "../../state/waveforms.ts"; +import { drawEnvelopeDiagram, formatEnvelopeValue } from "../../audio/envelope-diagram.ts"; type Store = ReturnType; const controls: ReadonlyArray<{ diff --git a/src/components/text-link.tsx b/src/components/text-link.tsx index 7236a63..07fc5e4 100644 --- a/src/components/text-link.tsx +++ b/src/components/text-link.tsx @@ -1,6 +1,6 @@ import { createLink } from "@tanstack/react-router"; import type { ComponentProps } from "react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/utils/cn"; export function TextLink({ className, ...props }: ComponentProps<"a">) { return ( diff --git a/src/features/workspace/runtime/connect-audio-settings.ts b/src/controllers/connect-audio-settings.ts similarity index 92% rename from src/features/workspace/runtime/connect-audio-settings.ts rename to src/controllers/connect-audio-settings.ts index a826e64..c5a4a32 100644 --- a/src/features/workspace/runtime/connect-audio-settings.ts +++ b/src/controllers/connect-audio-settings.ts @@ -1,6 +1,6 @@ import type { createStore } from "jotai/vanilla"; -import { settingsAtom, type Settings } from "../../../state/settings.ts"; -import { selectedWaveformAtom, type Waveform } from "../../../state/waveforms.ts"; +import { settingsAtom, type Settings } from "../state/settings.ts"; +import { selectedWaveformAtom, type Waveform } from "../state/waveforms.ts"; type Effects = { render: (settings: Settings, waveform: Waveform) => void; diff --git a/src/features/workspace/runtime/connect-playback-settings.ts b/src/controllers/connect-playback-settings.ts similarity index 88% rename from src/features/workspace/runtime/connect-playback-settings.ts rename to src/controllers/connect-playback-settings.ts index 6a00663..05a6c03 100644 --- a/src/features/workspace/runtime/connect-playback-settings.ts +++ b/src/controllers/connect-playback-settings.ts @@ -1,7 +1,7 @@ import type { createStore } from "jotai/vanilla"; -import { settingsAtom } from "../../../state/settings.ts"; -import { waveformDefaults } from "../../../state/waveforms.ts"; -import { playbackPreferencesAtom } from "../../../state/playback-preferences.ts"; +import { settingsAtom } from "../state/settings.ts"; +import { waveformDefaults } from "../state/waveforms.ts"; +import { playbackPreferencesAtom } from "../state/playback-preferences.ts"; type Store = ReturnType; type Preferences = ReturnType; diff --git a/src/features/workspace/runtime/connect-sort-settings.ts b/src/controllers/connect-sort-settings.ts similarity index 92% rename from src/features/workspace/runtime/connect-sort-settings.ts rename to src/controllers/connect-sort-settings.ts index ddadb08..f382227 100644 --- a/src/features/workspace/runtime/connect-sort-settings.ts +++ b/src/controllers/connect-sort-settings.ts @@ -1,6 +1,6 @@ import type { createStore } from "jotai/vanilla"; -import { settingsAtom } from "../../../state/settings.ts"; -import { algorithmCatalogAtom } from "../../../state/algorithm-overrides.ts"; +import { settingsAtom } from "../state/settings.ts"; +import { algorithmCatalogAtom } from "../state/algorithm-overrides.ts"; type Catalog = ReturnType; type Effects = { diff --git a/src/features/workspace/runtime/create-helpers.mjs b/src/controllers/create-helpers.mjs similarity index 91% rename from src/features/workspace/runtime/create-helpers.mjs rename to src/controllers/create-helpers.mjs index 3d113fa..9fc8693 100644 --- a/src/features/workspace/runtime/create-helpers.mjs +++ b/src/controllers/create-helpers.mjs @@ -1,4 +1,4 @@ -import { scales } from "../../../midi/scales.ts"; +import { scales } from "../midi/scales.ts"; export function createHelpers(settings, dependencies = { scales }) { return { diff --git a/src/features/workspace/runtime/create-workspace-player.mjs b/src/controllers/create-player.mjs similarity index 89% rename from src/features/workspace/runtime/create-workspace-player.mjs rename to src/controllers/create-player.mjs index 913b69f..86ca5e9 100644 --- a/src/features/workspace/runtime/create-workspace-player.mjs +++ b/src/controllers/create-player.mjs @@ -1,13 +1,13 @@ -import { createTimbreAudio } from "../../../audio/create-timbre-audio.mjs"; -import { createTransport } from "../../../audio/create-transport.ts"; -import { timbre } from "../../../vendor.mjs"; +import { createTimbreAudio } from "../audio/create-timbre-audio.mjs"; +import { createTransport } from "../audio/create-transport.ts"; +import { timbre } from "../audio/timbre.mjs"; import { select } from "d3-selection"; -import { visualizations } from "../../../visualizations/visualization-registry.mjs"; -import { createMidiBytes } from "../../../midi/create-midi-bytes.mjs"; -import { drawStringPreview } from "./string-preview.ts"; +import { visualizations } from "../visualizations/visualization-registry.mjs"; +import { createMidiBytes } from "../midi/create-midi-bytes.mjs"; +import { drawStringPreview } from "../audio/string-preview.ts"; // Owns only the D3 SVG contents and audio resources. React owns all controls. -export function createWorkspacePlayer({ +export function createPlayer({ svg, settings, getMidiNumber, diff --git a/src/features/workspace/runtime/create-workspace.mjs b/src/controllers/create-playground.mjs similarity index 89% rename from src/features/workspace/runtime/create-workspace.mjs rename to src/controllers/create-playground.mjs index c770a0c..efec220 100644 --- a/src/features/workspace/runtime/create-workspace.mjs +++ b/src/controllers/create-playground.mjs @@ -2,19 +2,19 @@ import { createStore } from "jotai/vanilla"; import { min, max } from "d3-array"; import { scaleLinear } from "d3-scale"; import { saveAs } from "file-saver"; -import { timbre } from "../../../vendor.mjs"; -import { createTimbreSoundfont } from "../../../audio/create-timbre-soundfont.mjs"; +import { timbre } from "../audio/timbre.mjs"; +import { createTimbreSoundfont } from "../audio/create-timbre-soundfont.mjs"; import { createHelpers } from "./create-helpers.mjs"; -import { createWorkspacePlayer } from "./create-workspace-player.mjs"; +import { createPlayer } from "./create-player.mjs"; import { connectAudioSettings } from "./connect-audio-settings.ts"; import { connectPlaybackSettings } from "./connect-playback-settings.ts"; import { connectSortSettings } from "./connect-sort-settings.ts"; -import { generators } from "../../../generators/generator-registry.ts"; -import { defaults, settingsAtom, updateSettingAtom } from "../../../state/settings.ts"; -import { selectedWaveformAtom } from "../../../state/waveforms.ts"; -import { algorithmCatalogAtom } from "../../../state/algorithm-overrides.ts"; -import { playbackPreferencesAtom, toggleLoopAtom } from "../../../state/playback-preferences.ts"; -import { createSortRequest, runSortRequest } from "../../../sorting/sort-requests.ts"; +import { generators } from "../generators/generator-registry.ts"; +import { defaults, settingsAtom, updateSettingAtom } from "../state/settings.ts"; +import { selectedWaveformAtom } from "../state/waveforms.ts"; +import { algorithmCatalogAtom } from "../state/algorithm-overrides.ts"; +import { playbackPreferencesAtom, toggleLoopAtom } from "../state/playback-preferences.ts"; +import { createSortRequest, runSortRequest } from "../sorting/sort-requests.ts"; const emptyPlayer = { position: 0, @@ -26,7 +26,7 @@ const emptyPlayer = { playing: false, }; -export function createWorkspace(store = createStore()) { +export function createPlayground(store = createStore()) { let snapshot = { base: emptyPlayer, sort: emptyPlayer, error: "", suspended: false }; const listeners = new Set(); const publish = (update) => { @@ -129,7 +129,7 @@ export function createWorkspace(store = createStore()) { } return; } - worker = new Worker(new URL("../../../worker.mjs", import.meta.url), { type: "module" }); + worker = new Worker(new URL("../sorting/worker.mjs", import.meta.url), { type: "module" }); const key = workerKey; worker.addEventListener("message", (event) => accept(event.data)); worker.addEventListener("error", (event) => accept({ key, error: event.message })); @@ -205,13 +205,13 @@ export function createWorkspace(store = createStore()) { }, getSnapshot: () => snapshot, mount(elements) { - if (destroyed) throw new Error("Cannot mount a destroyed workspace"); + if (destroyed) throw new Error("Cannot mount a destroyed playground"); canvas = elements.canvas; soundfont = createTimbreSoundfont(timbre); players = {}; try { for (const id of ["base", "sort"]) { - players[id] = createWorkspacePlayer({ + players[id] = createPlayer({ svg: elements[id], settings, getMidiNumber: helper.getMidiNumber, diff --git a/src/controllers/playground-types.ts b/src/controllers/playground-types.ts new file mode 100644 index 0000000..526a14c --- /dev/null +++ b/src/controllers/playground-types.ts @@ -0,0 +1,3 @@ +import type { createPlayground } from "./create-playground.mjs"; +export type PlayerId = "base" | "sort"; +export type Props = { runtime: ReturnType }; diff --git a/src/features/workspace/runtime/workspace-types.ts b/src/features/workspace/runtime/workspace-types.ts deleted file mode 100644 index 07af59d..0000000 --- a/src/features/workspace/runtime/workspace-types.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { createWorkspace } from "./create-workspace.mjs"; -export type PlayerId = "base" | "sort"; -export type Props = { runtime: ReturnType }; diff --git a/src/generators/patterns/almost-sorted.ts b/src/generators/patterns/almost-sorted.ts index 1de9c14..6ed2e13 100644 --- a/src/generators/patterns/almost-sorted.ts +++ b/src/generators/patterns/almost-sorted.ts @@ -1,6 +1,6 @@ import sorted from "./sorted.ts"; -import random from "../../utilities/random.ts"; -import swap from "../../utilities/swap.ts"; +import random from "../../utils/random.ts"; +import swap from "../../utils/swap.ts"; const almostSortedFactor = 2; diff --git a/src/generators/patterns/few-unique.ts b/src/generators/patterns/few-unique.ts index c3985e4..a7a2105 100644 --- a/src/generators/patterns/few-unique.ts +++ b/src/generators/patterns/few-unique.ts @@ -1,4 +1,4 @@ -import shuffle from "../../utilities/shuffle.ts"; +import shuffle from "../../utils/shuffle.ts"; const fewUniqueSize = 4; diff --git a/src/generators/patterns/random-unique.ts b/src/generators/patterns/random-unique.ts index 49bfa49..f09e5a1 100644 --- a/src/generators/patterns/random-unique.ts +++ b/src/generators/patterns/random-unique.ts @@ -1,4 +1,4 @@ -import shuffle from "../../utilities/shuffle.ts"; +import shuffle from "../../utils/shuffle.ts"; import sorted from "./sorted.ts"; export default function randomUnique(size: number): number[] { diff --git a/src/pages/about.tsx b/src/pages/about.tsx deleted file mode 100644 index 15ada1d..0000000 --- a/src/pages/about.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { buttonVariants } from "@/components/ui/button"; -import { TextLink } from "@/components/text-link"; -import { Link } from "@tanstack/react-router"; - -export function About() { - return ( -
-
-

Audio Sort

-

This app was created as a way to "hear" what sorting algorithms sound like.

-

- To accomplish this, it uses a long list of{" "} - libraries - , and some of the more recent browser features like{" "} - - Web Workers - - . -

-

- I plan on adding a few more features, and getting a longer list of default sorting - algorithms to choose from. You can currently tweak the existing algorithms, (or add your - own) to hear what small changes sound like. -

-

- If you have feature requests (or find a bug), please{" "} - - log an issue at Github - - . You can also leave{" "} - - comments on the Project Page - -

-

- - Algorithm API - -     - - Start Sorting - -

-
-
- ); -} diff --git a/src/pages/api.tsx b/src/pages/api.tsx deleted file mode 100644 index 2df44e5..0000000 --- a/src/pages/api.tsx +++ /dev/null @@ -1,185 +0,0 @@ -import { AppLink as Link } from "@/components/text-link"; - -export function Api() { - return ( -
-

Algorithm API

- -

- You can add/edit your own javascript algorithm by clicking the "Add Algorithm" button at the - bottom-left side of the main page. -

- -

- When writing your algorithm, you have access to the AS (Audio Sort) global - object. The methods on that object are described in the table below. The AS{" "} - object gives you access to the current array, allows you to set visualization markers, and - lets you "play" certain array elements. -

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
MethodDescriptionMarker ColorMarker Level
AS.length()Gets the length of the array.N/AN/A
AS.size()Gets the length of the array.N/AN/A
AS.get(index) - Gets the sortItem object with the given array index. This sortItem can be passed to - other AS method calls. - N/AN/A
AS.play(item1, ..., itemN) - Will play the given items. Items can be indexes or array items returned via AS.get() - calls. - N/AN/A
AS.mark(item1, ..., itemN) - Will mark the given items. Items can be indexes or array items returned via AS.get() - calls. - -
 
-
1
AS.lt(itemOne, itemTwo) - Returns true if itemOne is less than itemTwo. Item can be an array index, or an - array item (returned via an AS.get() call). - -
 
-
2
AS.lte(itemOne, itemTwo) - Returns true if itemOne is less than or equal to itemTwo. Item can be an array - index, or an array item (returned via an AS.get() call). - -
 
-
2
AS.gt(itemOne, itemTwo) - Returns true if itemOne is greater than itemTwo. Item can be an array index, or an - array item (returned via an AS.get() call). - -
 
-
2
AS.gte(itemOne, itemTwo) - Returns true if itemOne is greater than or equal to itemTwo. Item can be an array - index, or an array item (returned via an AS.get() call). - -
 
-
2
AS.eq(itemOne, itemTwo) - Returns true if itemOne is equal to itemTwo. Item can be an array index, or an array - item (returned via an AS.get() call). - -
 
-
2
AS.neq(itemOne, itemTwo) - Returns true if itemOne is not equal to itemTwo. Item can be an array index, or an - array item (returned via an AS.get() call). - -
 
-
2
AS.swap(itemOne, itemTwo)Will swap the positions of the two items. -
 
-
3
N/A - Items that were swapped in the last iteration, will show up below the "swapped" - items. They appear as green circles, while items that are getting ready to be - swapped show up as yellow circles. - -
 
-
4
AS.highlight(item1, ..., itemN) - Will highlight the given items. Highlighted items stay highlighted until the next - AS.highlight() call, or until AS.clearHighlight() is called. - -
 
-
5
AS.clearHighlight()Clears the currently highlighted items.N/AN/A
-
-
- ); -} diff --git a/src/pages/home.tsx b/src/pages/home.tsx deleted file mode 100644 index 22cb839..0000000 --- a/src/pages/home.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { useEffect, useState } from "react"; -import type { ComponentType } from "react"; - -export function Home() { - const [Workspace, setWorkspace] = useState(null); - const [failed, setFailed] = useState(false); - useEffect(() => { - let cancelled = false; - // Audio/editor code is browser-only; never evaluate it during prerendering. - void import("../features/workspace/browser-workspace.tsx") - .then(({ BrowserWorkspace }) => { - if (!cancelled) setWorkspace(() => BrowserWorkspace); - }) - .catch(() => { - if (!cancelled) setFailed(true); - }); - return () => { - cancelled = true; - }; - }, []); - - return ( - <> -
{Workspace && }
- {failed && ( -

- Unable to load the workspace. Please reload the page to try again. -

- )} - - - ); -} diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index b8233d0..0a36680 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -1,4 +1,4 @@ import { createRootRoute } from "@tanstack/react-router"; -import { SiteDocument } from "../pages/site-document"; +import { SiteDocument } from "../components/layout/site-document"; export const Route = createRootRoute({ shellComponent: SiteDocument }); diff --git a/src/routes/about.tsx b/src/routes/about.tsx index e389876..2b42383 100644 --- a/src/routes/about.tsx +++ b/src/routes/about.tsx @@ -1,4 +1,56 @@ import { createFileRoute } from "@tanstack/react-router"; -import { About } from "../pages/about"; +import { buttonVariants } from "@/components/ui/button"; +import { TextLink } from "@/components/text-link"; +import { Link } from "@tanstack/react-router"; + +function About() { + return ( +
+
+

Audio Sort

+

This app was created as a way to "hear" what sorting algorithms sound like.

+

+ To accomplish this, it uses a long list of{" "} + libraries + , and some of the more recent browser features like{" "} + + Web Workers + + . +

+

+ I plan on adding a few more features, and getting a longer list of default sorting + algorithms to choose from. You can currently tweak the existing algorithms, (or add your + own) to hear what small changes sound like. +

+

+ If you have feature requests (or find a bug), please{" "} + + log an issue at Github + + . You can also leave{" "} + + comments on the Project Page + +

+

+ + Algorithm API + +     + + Start Sorting + +

+
+
+ ); +} export const Route = createFileRoute("/about")({ component: About }); diff --git a/src/routes/api.tsx b/src/routes/api.tsx index ccaa2c3..7fd037a 100644 --- a/src/routes/api.tsx +++ b/src/routes/api.tsx @@ -1,4 +1,188 @@ import { createFileRoute } from "@tanstack/react-router"; -import { Api } from "../pages/api"; +import { AppLink as Link } from "@/components/text-link"; + +function Api() { + return ( +
+

Algorithm API

+ +

+ You can add/edit your own javascript algorithm by clicking the "Add Algorithm" button at the + bottom-left side of the main page. +

+ +

+ When writing your algorithm, you have access to the AS (Audio Sort) global + object. The methods on that object are described in the table below. The AS{" "} + object gives you access to the current array, allows you to set visualization markers, and + lets you "play" certain array elements. +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodDescriptionMarker ColorMarker Level
AS.length()Gets the length of the array.N/AN/A
AS.size()Gets the length of the array.N/AN/A
AS.get(index) + Gets the sortItem object with the given array index. This sortItem can be passed to + other AS method calls. + N/AN/A
AS.play(item1, ..., itemN) + Will play the given items. Items can be indexes or array items returned via AS.get() + calls. + N/AN/A
AS.mark(item1, ..., itemN) + Will mark the given items. Items can be indexes or array items returned via AS.get() + calls. + +
 
+
1
AS.lt(itemOne, itemTwo) + Returns true if itemOne is less than itemTwo. Item can be an array index, or an + array item (returned via an AS.get() call). + +
 
+
2
AS.lte(itemOne, itemTwo) + Returns true if itemOne is less than or equal to itemTwo. Item can be an array + index, or an array item (returned via an AS.get() call). + +
 
+
2
AS.gt(itemOne, itemTwo) + Returns true if itemOne is greater than itemTwo. Item can be an array index, or an + array item (returned via an AS.get() call). + +
 
+
2
AS.gte(itemOne, itemTwo) + Returns true if itemOne is greater than or equal to itemTwo. Item can be an array + index, or an array item (returned via an AS.get() call). + +
 
+
2
AS.eq(itemOne, itemTwo) + Returns true if itemOne is equal to itemTwo. Item can be an array index, or an array + item (returned via an AS.get() call). + +
 
+
2
AS.neq(itemOne, itemTwo) + Returns true if itemOne is not equal to itemTwo. Item can be an array index, or an + array item (returned via an AS.get() call). + +
 
+
2
AS.swap(itemOne, itemTwo)Will swap the positions of the two items. +
 
+
3
N/A + Items that were swapped in the last iteration, will show up below the "swapped" + items. They appear as green circles, while items that are getting ready to be + swapped show up as yellow circles. + +
 
+
4
AS.highlight(item1, ..., itemN) + Will highlight the given items. Highlighted items stay highlighted until the next + AS.highlight() call, or until AS.clearHighlight() is called. + +
 
+
5
AS.clearHighlight()Clears the currently highlighted items.N/AN/A
+
+
+ ); +} export const Route = createFileRoute("/api")({ component: Api }); diff --git a/src/routes/index.tsx b/src/routes/index.tsx index f35eb35..c64885b 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -1,4 +1,40 @@ import { createFileRoute } from "@tanstack/react-router"; -import { Home } from "../pages/home"; +import { useEffect, useState } from "react"; +import type { ComponentType } from "react"; + +function Home() { + const [Playground, setPlayground] = useState(null); + const [failed, setFailed] = useState(false); + useEffect(() => { + let cancelled = false; + // Audio/editor code is browser-only; never evaluate it during prerendering. + void import("../components/playground/browser-playground.tsx") + .then(({ BrowserPlayground }) => { + if (!cancelled) setPlayground(() => BrowserPlayground); + }) + .catch(() => { + if (!cancelled) setFailed(true); + }); + return () => { + cancelled = true; + }; + }, []); + + return ( + <> +
{Playground && }
+ {failed && ( +

+ Unable to load the playground. Please reload the page to try again. +

+ )} + + + ); +} export const Route = createFileRoute("/")({ component: Home }); diff --git a/src/worker.mjs b/src/sorting/worker.mjs similarity index 59% rename from src/worker.mjs rename to src/sorting/worker.mjs index e06733c..d70a88c 100644 --- a/src/worker.mjs +++ b/src/sorting/worker.mjs @@ -1,4 +1,4 @@ -import { handleSortRequest } from "./sorting/sort-requests.ts"; +import { handleSortRequest } from "./sort-requests.ts"; globalThis.onmessage = ({ data }) => { globalThis.postMessage(handleSortRequest(data)); diff --git a/src/lib/utils.ts b/src/utils/cn.ts similarity index 100% rename from src/lib/utils.ts rename to src/utils/cn.ts diff --git a/src/utilities/random.ts b/src/utils/random.ts similarity index 100% rename from src/utilities/random.ts rename to src/utils/random.ts diff --git a/src/utilities/shuffle.ts b/src/utils/shuffle.ts similarity index 100% rename from src/utilities/shuffle.ts rename to src/utils/shuffle.ts diff --git a/src/utilities/swap.ts b/src/utils/swap.ts similarity index 100% rename from src/utilities/swap.ts rename to src/utils/swap.ts diff --git a/test/audio-settings-connection.test.mjs b/test/audio-settings-connection.test.mjs index 53a9db0..be45ce6 100644 --- a/test/audio-settings-connection.test.mjs +++ b/test/audio-settings-connection.test.mjs @@ -1,6 +1,6 @@ import { expect, test, vi } from "vitest"; import { createStore } from "jotai/vanilla"; -import { connectAudioSettings } from "../src/features/workspace/runtime/connect-audio-settings.ts"; +import { connectAudioSettings } from "../src/controllers/connect-audio-settings.ts"; import { updateSettingAtom } from "../src/state/settings.ts"; import { updateEnvelopeAtom } from "../src/state/envelope.ts"; diff --git a/test/browser/site.spec.mjs b/test/browser/site.spec.mjs index fa1ebac..3ce62f8 100644 --- a/test/browser/site.spec.mjs +++ b/test/browser/site.spec.mjs @@ -317,7 +317,7 @@ test("built UI loads and algorithm IDs execute in the bundled worker", async ({ await page.goto("./"); const sortWorker = await workerReady; await expect(page.locator("#wrapper > #header")).toHaveCount(1); - await expect(page.locator("#workspace > #base-section")).toHaveCount(1); + await expect(page.locator("#playground > #base-section")).toHaveCount(1); await expect(page.locator("body > #footer")).toHaveCount(1); const workerURL = sortWorker.url(); expect(await sortWorker.evaluate(() => Object.hasOwn(globalThis, "AS"))).toBe(false); @@ -722,7 +722,7 @@ test("teardown clears owned resources and repeated remounts do not duplicate UI await page.evaluate(() => globalThis.sortWorkers.every((worker) => worker.wasTerminated)), ).toBe(true); expect(await page.evaluate(() => typeof globalThis.jQuery)).toBe("undefined"); - await expect(page.locator("#workspace > *")).toHaveCount(0); + await expect(page.locator("#playground > *")).toHaveCount(0); await page.evaluate(() => globalThis.dispatchEvent(new globalThis.PageTransitionEvent("pageshow", { persisted: true })), ); @@ -993,7 +993,7 @@ test("the page scrolls only when its content exceeds the viewport", async ({ pag } }); -test("charts size fluidly with two workspace layouts", async ({ page }) => { +test("charts size fluidly with two playground layouts", async ({ page }) => { await page.goto("./"); for (const [width, height, chartHeight] of [ [1440, 900, "216px"], @@ -1109,7 +1109,7 @@ test("clean routes hydrate, navigate, and reload without a server", async ({ page.on("console", (message) => { if (message.type() === "error") errors.push(message.text()); }); - for (const path of ["", "?view=sort#workspace"]) { + for (const path of ["", "?view=sort#playground"]) { const url = new URL(path, baseURL).href; expect((await page.goto(url)).status()).toBe(200); await expect(page.locator("#base-svg rect")).toHaveCount(12); @@ -1124,7 +1124,7 @@ test("clean routes hydrate, navigate, and reload without a server", async ({ await expect(page).toHaveURL(new URL("about", baseURL).href); await expect(page.locator("#about")).toBeVisible(); await page.waitForLoadState("networkidle"); - expect(await page.locator("#workspace").count()).toBe(0); + expect(await page.locator("#playground").count()).toBe(0); expect(await page.evaluate(() => globalThis.navigationWitness)).toBe(true); expect( await page.evaluate(() => globalThis.sortWorkers.every((worker) => worker.wasTerminated)), @@ -1163,11 +1163,11 @@ test("prerendered pages and navigation remain readable without JavaScript", asyn } }); -test("a failed workspace download shows a recoverable error", async ({ page }) => { - await page.route(/\/assets\/browser-workspace-[^/]+\.js$/, (route) => route.abort()); +test("a failed playground download shows a recoverable error", async ({ page }) => { + await page.route(/\/assets\/browser-playground-[^/]+\.js$/, (route) => route.abort()); await page.goto("./"); - await expect(page.getByRole("alert")).toContainText("Unable to load the workspace"); - await page.unroute(/\/assets\/browser-workspace-[^/]+\.js$/); + await expect(page.getByRole("alert")).toContainText("Unable to load the playground"); + await page.unroute(/\/assets\/browser-playground-[^/]+\.js$/); await page.reload(); await expect(page.locator("#base-svg rect")).toHaveCount(12); await expect(page.getByRole("alert")).toHaveCount(0); diff --git a/test/playback-settings-connection.test.mjs b/test/playback-settings-connection.test.mjs index 5e3c00c..5e3d262 100644 --- a/test/playback-settings-connection.test.mjs +++ b/test/playback-settings-connection.test.mjs @@ -1,6 +1,6 @@ import { expect, test, vi } from "vitest"; import { createStore } from "jotai/vanilla"; -import { connectPlaybackSettings } from "../src/features/workspace/runtime/connect-playback-settings.ts"; +import { connectPlaybackSettings } from "../src/controllers/connect-playback-settings.ts"; import { updateSettingAtom } from "../src/state/settings.ts"; import { toggleAutoPlayAtom, toggleLoopAtom } from "../src/state/playback-preferences.ts"; diff --git a/test/scales.test.mjs b/test/scales.test.mjs index de5da23..9def83d 100644 --- a/test/scales.test.mjs +++ b/test/scales.test.mjs @@ -1,7 +1,7 @@ import { createHash } from "node:crypto"; import { expect, test } from "vitest"; import { scales } from "../src/midi/scales.ts"; -import { createHelpers } from "../src/features/workspace/runtime/create-helpers.mjs"; +import { createHelpers } from "../src/controllers/create-helpers.mjs"; test("preserves all scale data extracted from the bundled subcollider 0.1.0", () => { // Baseline computed from ScaleInfo.names()/at() in the original bundle. diff --git a/test/settings.test.mjs b/test/settings.test.mjs index d1cbdc6..bffd0c7 100644 --- a/test/settings.test.mjs +++ b/test/settings.test.mjs @@ -1,7 +1,7 @@ import { expect, test, vi } from "vitest"; import { createStore } from "jotai/vanilla"; import { defaults, settingsAtom, updateSettingAtom } from "../src/state/settings.ts"; -import { createWorkspace } from "../src/features/workspace/runtime/create-workspace.mjs"; +import { createPlayground } from "../src/controllers/create-playground.mjs"; test("settings retain all nine existing defaults", () => { const store = createStore(); @@ -41,8 +41,8 @@ test("stores are isolated, snapshots immutable, and updates notify only on chang test("runtime audio settings getters read the current store, including falsy values and fallbacks", () => { const store = createStore(); - const controller = createWorkspace(store).settings; - const independent = createWorkspace().settings; + const controller = createPlayground(store).settings; + const independent = createPlayground().settings; for (const [key, value] of Object.entries({ volume: 0, tempo: 120, diff --git a/test/sort-settings-connection.test.mjs b/test/sort-settings-connection.test.mjs index 0350b9c..a5ae55f 100644 --- a/test/sort-settings-connection.test.mjs +++ b/test/sort-settings-connection.test.mjs @@ -1,6 +1,6 @@ import { expect, test, vi } from "vitest"; import { createStore } from "jotai/vanilla"; -import { connectSortSettings } from "../src/features/workspace/runtime/connect-sort-settings.ts"; +import { connectSortSettings } from "../src/controllers/connect-sort-settings.ts"; import { updateSettingAtom } from "../src/state/settings.ts"; import { addAlgorithmAtom, editAlgorithmAtom } from "../src/state/algorithm-overrides.ts"; diff --git a/test/string-preview.test.mjs b/test/string-preview.test.mjs index c25e961..635f56e 100644 --- a/test/string-preview.test.mjs +++ b/test/string-preview.test.mjs @@ -1,5 +1,5 @@ import { expect, test } from "vitest"; -import { getStringPreviewSamples } from "../src/features/workspace/runtime/string-preview.ts"; +import { getStringPreviewSamples } from "../src/audio/string-preview.ts"; test("string illustration is deterministic, bounded, oscillating, and decaying", () => { const samples = getStringPreviewSamples(); diff --git a/test/ui.test.mjs b/test/ui.test.mjs index c16b069..b2095bd 100644 --- a/test/ui.test.mjs +++ b/test/ui.test.mjs @@ -1,17 +1,17 @@ import { expect, test } from "vitest"; -import { createWorkspace } from "../src/features/workspace/runtime/create-workspace.mjs"; -import { createHelpers } from "../src/features/workspace/runtime/create-helpers.mjs"; -import { createWorkspacePlayer } from "../src/features/workspace/runtime/create-workspace-player.mjs"; +import { createPlayground } from "../src/controllers/create-playground.mjs"; +import { createHelpers } from "../src/controllers/create-helpers.mjs"; +import { createPlayer } from "../src/controllers/create-player.mjs"; import { visualizations } from "../src/visualizations/visualization-registry.mjs"; test("UI modules import without DOM initialization or first-party globals", () => { for (const name of ["A", "visualization"]) expect(Object.hasOwn(globalThis, name)).toBe(false); - const first = createWorkspace(); - const second = createWorkspace(); + const first = createPlayground(); + const second = createPlayground(); expect(first).not.toBe(second); expect(typeof first.mount).toBe("function"); expect(first.settings.getSelected("unknown", "fallback")).toBe("fallback"); - expect(typeof createWorkspacePlayer).toBe("function"); + expect(typeof createPlayer).toBe("function"); expect(Object.keys(visualizations).sort()).toEqual(["bar", "flat"]); for (const name of ["A", "visualization"]) expect(Object.hasOwn(globalThis, name)).toBe(false); }); @@ -33,10 +33,10 @@ test("MIDI helper reads current settings from its injected controller", () => { expect(helpers.getMidiNumber(4)).toBe(72); }); -test("destroying an unmounted workspace is safe and prevents reuse", () => { - const controller = createWorkspace(); +test("destroying an unmounted playground is safe and prevents reuse", () => { + const controller = createPlayground(); controller.destroy(); controller.destroy(); controller.resume(); - expect(() => controller.mount({})).toThrow("destroyed workspace"); + expect(() => controller.mount({})).toThrow("destroyed playground"); }); diff --git a/test/utilities.test.mjs b/test/utilities.test.mjs index 8ca61ce..c7a2851 100644 --- a/test/utilities.test.mjs +++ b/test/utilities.test.mjs @@ -1,7 +1,7 @@ import { expect, test, vi } from "vitest"; -import random from "../src/utilities/random.ts"; -import shuffle from "../src/utilities/shuffle.ts"; -import swap from "../src/utilities/swap.ts"; +import random from "../src/utils/random.ts"; +import shuffle from "../src/utils/shuffle.ts"; +import swap from "../src/utils/swap.ts"; test("random includes both integer endpoints", () => { const rng = vi.spyOn(Math, "random").mockReturnValue(0); diff --git a/test/waveforms.test.mjs b/test/waveforms.test.mjs index 872b284..27146e5 100644 --- a/test/waveforms.test.mjs +++ b/test/waveforms.test.mjs @@ -3,11 +3,8 @@ import { createStore } from "jotai/vanilla"; import { waveformDefaults, selectedWaveformAtom } from "../src/state/waveforms.ts"; import { envelopeDefaults, envelopeAtom, updateEnvelopeAtom } from "../src/state/envelope.ts"; import { updateSettingAtom } from "../src/state/settings.ts"; -import { createWorkspace } from "../src/features/workspace/runtime/create-workspace.mjs"; -import { - getEnvelopePoints, - formatEnvelopeValue, -} from "../src/features/workspace/runtime/envelope-diagram.ts"; +import { createPlayground } from "../src/controllers/create-playground.mjs"; +import { getEnvelopePoints, formatEnvelopeValue } from "../src/audio/envelope-diagram.ts"; test("all eight generators use the shared defaults, including string", () => { const store = createStore(); @@ -69,7 +66,7 @@ test.each([ test("controller getters combine the current generator with the shared envelope", () => { const store = createStore(); - const controller = createWorkspace(store).settings; + const controller = createPlayground(store).settings; store.set(updateEnvelopeAtom, { key: "a", value: 100 }); store.set(updateSettingAtom, { key: "waveform", value: "sin" }); expect(controller.getSelectedWaveformInfo()).toMatchObject({ gen: "OscGen", a: 100 }); From 1e821ebde9455fe336e36d35c16b880e7e2dec27 Mon Sep 17 00:00:00 2001 From: skratchdot Date: Tue, 8 Sep 2026 16:37:15 -0400 Subject: [PATCH 2/2] Use the verbose utilities directory name Rename utils to utilities and update imports, shadcn aliases, and architecture documentation. Verified the full check pipeline, including 360 unit tests and production build. --- components.json | 4 ++-- docs/architecture.md | 8 ++++---- src/components/option-button.tsx | 2 +- src/components/text-link.tsx | 2 +- src/generators/patterns/almost-sorted.ts | 4 ++-- src/generators/patterns/few-unique.ts | 2 +- src/generators/patterns/random-unique.ts | 2 +- src/{utils => utilities}/cn.ts | 0 src/{utils => utilities}/random.ts | 0 src/{utils => utilities}/shuffle.ts | 0 src/{utils => utilities}/swap.ts | 0 test/utilities.test.mjs | 6 +++--- 12 files changed, 15 insertions(+), 15 deletions(-) rename src/{utils => utilities}/cn.ts (100%) rename src/{utils => utilities}/random.ts (100%) rename src/{utils => utilities}/shuffle.ts (100%) rename src/{utils => utilities}/swap.ts (100%) diff --git a/components.json b/components.json index 1904d1b..e569f26 100644 --- a/components.json +++ b/components.json @@ -14,9 +14,9 @@ "rtl": false, "aliases": { "components": "@/components", - "utils": "@/utils/cn", + "utils": "@/utilities/cn", "ui": "@/components/ui", - "lib": "@/utils", + "lib": "@/utilities", "hooks": "@/hooks" }, "menuColor": "default", diff --git a/docs/architecture.md b/docs/architecture.md index 36e78c4..0e6f393 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -9,11 +9,11 @@ shadcn Base UI primitives; `components/layout/` contains the header and footer. MIDI support, and visualizations each have their own directory. The worker lives in `sorting/worker.mjs`; the Timbre adapter lives in `audio/timbre.mjs`. `@/` aliases `src/`. -`utils/` contains small, stateless, domain-independent helpers: `cn.ts` combines +`utilities/` contains small, stateless, domain-independent helpers: `cn.ts` combines CSS classes; `random.ts`, `shuffle.ts`, and `swap.ts` handle numbers and arrays. -There is no separate `utilities/` folder or catch-all `utils.ts` file. shadcn's -`utils` alias points to `@/utils/cn` so newly generated components use the same helper. -Domain-specific code stays with its domain rather than accumulating in `utils/`. +There is no separate `lib/` folder or catch-all `utils.ts` file. shadcn's +`utils` alias points to `@/utilities/cn` so newly generated components use the same helper. +Domain-specific code stays with its domain rather than accumulating in `utilities/`. The sorting playground is the interactive data/sort screen, including its editor and playback controls. React presentation lives under `components/`; diff --git a/src/components/option-button.tsx b/src/components/option-button.tsx index f341763..d97db85 100644 --- a/src/components/option-button.tsx +++ b/src/components/option-button.tsx @@ -1,5 +1,5 @@ import { Button } from "@/components/ui/button"; -import { cn } from "@/utils/cn"; +import { cn } from "@/utilities/cn"; import type { ComponentProps } from "react"; export function OptionButton({ className, ...props }: ComponentProps) { diff --git a/src/components/text-link.tsx b/src/components/text-link.tsx index 07fc5e4..2fa7c20 100644 --- a/src/components/text-link.tsx +++ b/src/components/text-link.tsx @@ -1,6 +1,6 @@ import { createLink } from "@tanstack/react-router"; import type { ComponentProps } from "react"; -import { cn } from "@/utils/cn"; +import { cn } from "@/utilities/cn"; export function TextLink({ className, ...props }: ComponentProps<"a">) { return ( diff --git a/src/generators/patterns/almost-sorted.ts b/src/generators/patterns/almost-sorted.ts index 6ed2e13..1de9c14 100644 --- a/src/generators/patterns/almost-sorted.ts +++ b/src/generators/patterns/almost-sorted.ts @@ -1,6 +1,6 @@ import sorted from "./sorted.ts"; -import random from "../../utils/random.ts"; -import swap from "../../utils/swap.ts"; +import random from "../../utilities/random.ts"; +import swap from "../../utilities/swap.ts"; const almostSortedFactor = 2; diff --git a/src/generators/patterns/few-unique.ts b/src/generators/patterns/few-unique.ts index a7a2105..c3985e4 100644 --- a/src/generators/patterns/few-unique.ts +++ b/src/generators/patterns/few-unique.ts @@ -1,4 +1,4 @@ -import shuffle from "../../utils/shuffle.ts"; +import shuffle from "../../utilities/shuffle.ts"; const fewUniqueSize = 4; diff --git a/src/generators/patterns/random-unique.ts b/src/generators/patterns/random-unique.ts index f09e5a1..49bfa49 100644 --- a/src/generators/patterns/random-unique.ts +++ b/src/generators/patterns/random-unique.ts @@ -1,4 +1,4 @@ -import shuffle from "../../utils/shuffle.ts"; +import shuffle from "../../utilities/shuffle.ts"; import sorted from "./sorted.ts"; export default function randomUnique(size: number): number[] { diff --git a/src/utils/cn.ts b/src/utilities/cn.ts similarity index 100% rename from src/utils/cn.ts rename to src/utilities/cn.ts diff --git a/src/utils/random.ts b/src/utilities/random.ts similarity index 100% rename from src/utils/random.ts rename to src/utilities/random.ts diff --git a/src/utils/shuffle.ts b/src/utilities/shuffle.ts similarity index 100% rename from src/utils/shuffle.ts rename to src/utilities/shuffle.ts diff --git a/src/utils/swap.ts b/src/utilities/swap.ts similarity index 100% rename from src/utils/swap.ts rename to src/utilities/swap.ts diff --git a/test/utilities.test.mjs b/test/utilities.test.mjs index c7a2851..8ca61ce 100644 --- a/test/utilities.test.mjs +++ b/test/utilities.test.mjs @@ -1,7 +1,7 @@ import { expect, test, vi } from "vitest"; -import random from "../src/utils/random.ts"; -import shuffle from "../src/utils/shuffle.ts"; -import swap from "../src/utils/swap.ts"; +import random from "../src/utilities/random.ts"; +import shuffle from "../src/utilities/shuffle.ts"; +import swap from "../src/utilities/swap.ts"; test("random includes both integer endpoints", () => { const rng = vi.spyOn(Math, "random").mockReturnValue(0);