Skip to content

Commit 27df322

Browse files
author
NellowTCS
committed
bunch of stuff (i forgot to commit this from a week ago oops)
1 parent e269acc commit 27df322

61 files changed

Lines changed: 1177 additions & 3596 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Build/src/global.css

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
@import "@fontsource/poppins";
33
@import "@fontsource/jetbrains-mono";
44
@import "@fontsource/caveat";
5+
@import "./ui/theming/base-theme.css";
56

67
/* This root block is kept separate as these values do not change between light and dark mode */
78
/* prettier-ignore */
@@ -61,7 +62,9 @@
6162
--spacing-12: 3rem;
6263
--spacing-16: 4rem;
6364

64-
/* Miscellaneous */
65+
/* Layout Spacing */
66+
--spacing-layout: 15px;
67+
--player-art-size: 115px;
6568
--sidebar-width: 250px;
6669
--sidebar-collapsed-width: 40px;
6770

@@ -154,3 +157,48 @@ html {
154157
/* Sidebar is overlay on mobile, doesn't affect layout */
155158
}
156159
}
160+
161+
/* Structural patterns */
162+
@layer utilities {
163+
.search-wrapper {
164+
position: relative;
165+
}
166+
167+
.search-icon {
168+
position: absolute;
169+
left: var(--spacing-3);
170+
top: 50%;
171+
transform: translateY(-50%);
172+
pointer-events: none;
173+
}
174+
175+
.search-input {
176+
background: var(--surface-transparent-1);
177+
border: 1px solid var(--surface-transparent-2);
178+
color: var(--primary-foreground);
179+
padding-left: calc(var(--spacing-3) * 2 + 16px);
180+
border-radius: var(--radius);
181+
}
182+
183+
.search-input::placeholder {
184+
color: var(--primary-foreground);
185+
}
186+
187+
.search-input:focus {
188+
outline: var(--shadow);
189+
border-color: var(--primary-border-strong);
190+
box-shadow: 0 0 0 2px var(--primary-transparent-2);
191+
}
192+
193+
.action-button-lift {
194+
transition: all var(--animation-duration-fast) ease;
195+
}
196+
197+
.action-button-lift:hover {
198+
transform: translateY(-1px);
199+
}
200+
201+
.action-button-lift:active {
202+
transform: translateY(0);
203+
}
204+
}

Build/src/hooks/useFileHandler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import { createLogger } from "../helpers/logger";
1010
const logger = createLogger("useFileHandler");
1111

1212
export function useFileHandler(
13-
addSong: (song: Track) => Promise<void>,
13+
addSong: (song: Track, file?: File) => Promise<void>,
1414
t: any,
1515
importFiles: (
1616
files: File[],
17-
addSong: (song: Track) => Promise<void>,
17+
addSong: (song: Track, file?: File) => Promise<void>,
1818
t: any,
1919
) => Promise<void>,
2020
isInitialized?: boolean,

Build/src/hooks/useKomorebi.ts

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { IAudioBackend } from "../platform/audio";
1010
import { HTMLAudioBackend } from "../platform/audio/backends/HTMLBackend";
1111
import { LibraryManager } from "../platform/library/library";
1212
import { libraryPersistence } from "../platform/library/persistence";
13+
import { trackStorage } from "../platform/storage/trackStorage";
1314
import { SettingsManager } from "../platform/settings/settings";
1415
import { createLogger } from "../helpers/logger";
1516

@@ -178,11 +179,23 @@ export function useKomorebi(
178179
engine.on("loading", handleLoading);
179180
engine.on("ready", handleReady);
180181

182+
const handleLibraryChange = () => {
183+
setState(engine.getState());
184+
};
185+
library.on("songadded", handleLibraryChange);
186+
library.on("songremoved", handleLibraryChange);
187+
library.on("playlistadded", handleLibraryChange);
188+
library.on("playlistremoved", handleLibraryChange);
189+
library.on("favoritechanged", handleLibraryChange);
190+
181191
const loadLibrary = async () => {
182192
try {
183193
const savedLibrary = await libraryPersistence.loadFullLibrary();
184194
if (savedLibrary) {
185-
savedLibrary.songs.forEach((song) => library.addSong(song));
195+
for (const song of savedLibrary.songs) {
196+
const reconstructed = await trackStorage.reconstructUrl(song);
197+
library.addSong(reconstructed);
198+
}
186199
savedLibrary.playlists.forEach((playlist) => {
187200
if ("songs" in playlist) {
188201
library.addPlaylist(playlist);
@@ -211,6 +224,11 @@ export function useKomorebi(
211224
engine.off("error", handleError);
212225
engine.off("loading", handleLoading);
213226
engine.off("ready", handleReady);
227+
library.off("songadded", handleLibraryChange);
228+
library.off("songremoved", handleLibraryChange);
229+
library.off("playlistadded", handleLibraryChange);
230+
library.off("playlistremoved", handleLibraryChange);
231+
library.off("favoritechanged", handleLibraryChange);
214232
backendRef.current?.dispose();
215233
};
216234
}, []);
@@ -289,15 +307,34 @@ export function useKomorebi(
289307
}, []);
290308

291309
const playSong = useCallback(async (song: Track, playlist?: Playlist) => {
310+
console.log("[playSong] called", { song: song?.title, songUrl: song?.url, playlist: playlist?.name });
292311
setError(null);
293312
const engine = engineRef.current;
294-
if (!engine) return;
313+
console.log("[playSong] engine:", engine);
314+
if (!engine) {
315+
console.log("[playSong] no engine!");
316+
return;
317+
}
318+
319+
const trackToPlay = await trackStorage.reconstructUrl(song);
320+
console.log("[playSong] reconstructed url:", trackToPlay.url);
295321

296322
if (playlist) {
297323
engine.setPlaylist(playlist);
298324
}
299-
engine.load(song, playlist);
325+
engine.load(trackToPlay, playlist);
326+
327+
await new Promise<void>((resolve) => {
328+
const handleReady = () => {
329+
engine.off("ready", handleReady);
330+
resolve();
331+
};
332+
engine.on("ready", handleReady);
333+
});
334+
335+
console.log("[playSong] calling engine.play()");
300336
await engine.play();
337+
console.log("[playSong] play() completed");
301338
}, []);
302339

303340
const seek = useCallback((time: number) => {
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import { useRef, useEffect, useState, useCallback, RefObject } from "react";
2+
import { logger } from "../helpers/logger";
3+
import {
4+
getVisualizer,
5+
getAvailableVisualizers,
6+
VisualizerType,
7+
clearVisualizerState,
8+
} from "../platform/visualizers";
9+
10+
interface UseVisualizerCanvasProps {
11+
analyserNode?: AnalyserNode | null;
12+
isPlaying: boolean;
13+
canvasRef: RefObject<HTMLCanvasElement | null>;
14+
}
15+
16+
export const useVisualizerCanvas = ({
17+
analyserNode,
18+
isPlaying,
19+
canvasRef,
20+
}: UseVisualizerCanvasProps) => {
21+
const DEFAULT_VISUALIZER_KEY = "oceanwaves";
22+
const animationFrameId = useRef<number | null>(null);
23+
const dataArrayRef = useRef<Uint8Array | null>(null);
24+
25+
const [availableVisualizers, setAvailableVisualizers] = useState<string[]>([]);
26+
const [loadedVisualizerNames, setLoadedVisualizerNames] = useState<Map<string, string>>(new Map());
27+
const [selectedVisualizerKey, setSelectedVisualizerKey] = useState<string>("");
28+
const [selectedVisualizer, setSelectedVisualizer] = useState<VisualizerType | null>(null);
29+
const [visualizerSettings, setVisualizerSettings] = useState<Record<string, any>>({});
30+
const [isLoading, setIsLoading] = useState(false);
31+
32+
// Initialize available visualizers
33+
useEffect(() => {
34+
const visualizers = getAvailableVisualizers();
35+
setAvailableVisualizers(visualizers);
36+
37+
Promise.all(
38+
visualizers.map(async (key) => {
39+
const visualizer = await getVisualizer(key);
40+
return { key, name: visualizer?.name || key };
41+
}),
42+
).then((results) => {
43+
setLoadedVisualizerNames(new Map(results.map(({ key, name }) => [key, name])));
44+
});
45+
46+
if (visualizers.length > 0 && !selectedVisualizerKey) {
47+
setSelectedVisualizerKey(
48+
visualizers.includes(DEFAULT_VISUALIZER_KEY) ? DEFAULT_VISUALIZER_KEY : visualizers[0],
49+
);
50+
}
51+
}, [selectedVisualizerKey]);
52+
53+
// Load selected visualizer
54+
useEffect(() => {
55+
if (!selectedVisualizerKey) return;
56+
57+
setIsLoading(true);
58+
getVisualizer(selectedVisualizerKey)
59+
.then((visualizer) => {
60+
setSelectedVisualizer(visualizer);
61+
setIsLoading(false);
62+
63+
if (visualizer?.settingsConfig) {
64+
setVisualizerSettings(
65+
Object.entries(visualizer.settingsConfig).reduce((acc, [key, config]) => {
66+
acc[key] = config.default;
67+
return acc;
68+
}, {} as Record<string, any>),
69+
);
70+
}
71+
})
72+
.catch((error) => {
73+
logger.error("Failed to load visualizer", { error: error instanceof Error ? error.message : "" });
74+
setIsLoading(false);
75+
});
76+
}, [selectedVisualizerKey]);
77+
78+
// Handle drawing
79+
const draw = useCallback(() => {
80+
if (!analyserNode || !canvasRef.current || !selectedVisualizer) return;
81+
const canvas = canvasRef.current;
82+
const ctx = canvas.getContext("2d");
83+
if (!ctx) return;
84+
85+
const bufferLength = analyserNode.frequencyBinCount;
86+
if (!dataArrayRef.current || dataArrayRef.current.length !== bufferLength) {
87+
dataArrayRef.current = new Uint8Array(bufferLength);
88+
}
89+
90+
selectedVisualizer.draw(
91+
analyserNode,
92+
canvas,
93+
ctx,
94+
bufferLength,
95+
dataArrayRef.current,
96+
selectedVisualizer.dataType,
97+
visualizerSettings,
98+
);
99+
100+
animationFrameId.current = requestAnimationFrame(draw);
101+
}, [analyserNode, selectedVisualizer, visualizerSettings, canvasRef]);
102+
103+
// Animation lifecycle
104+
useEffect(() => {
105+
if (isPlaying && analyserNode && selectedVisualizer) {
106+
animationFrameId.current = requestAnimationFrame(draw);
107+
} else {
108+
if (animationFrameId.current) cancelAnimationFrame(animationFrameId.current);
109+
}
110+
return () => {
111+
if (animationFrameId.current) cancelAnimationFrame(animationFrameId.current);
112+
};
113+
}, [isPlaying, analyserNode, selectedVisualizer, draw]);
114+
115+
// Resize handling
116+
useEffect(() => {
117+
const canvas = canvasRef.current;
118+
if (!canvas) return;
119+
const resizeObserver = new ResizeObserver(() => {
120+
const { width, height } = canvas.getBoundingClientRect();
121+
canvas.width = width;
122+
canvas.height = height;
123+
});
124+
resizeObserver.observe(canvas);
125+
return () => resizeObserver.disconnect();
126+
}, [canvasRef]);
127+
128+
// Global cleanup
129+
useEffect(() => () => clearVisualizerState(), []);
130+
131+
return {
132+
availableVisualizers,
133+
loadedVisualizerNames,
134+
selectedVisualizerKey,
135+
selectedVisualizer,
136+
setSelectedVisualizerKey,
137+
visualizerSettings,
138+
setVisualizerSettings,
139+
isLoading,
140+
};
141+
};

0 commit comments

Comments
 (0)