From f2268e0243bb60ab2b6506a53ebc8faf85b1a22d Mon Sep 17 00:00:00 2001 From: Glockosu Date: Sat, 19 Oct 2024 20:29:55 -0600 Subject: [PATCH 1/9] quick control changes --- .../components/song/song-detail/SongControls.tsx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/renderer/src/components/song/song-detail/SongControls.tsx b/src/renderer/src/components/song/song-detail/SongControls.tsx index 88206ea..febc3b9 100644 --- a/src/renderer/src/components/song/song-detail/SongControls.tsx +++ b/src/renderer/src/components/song/song-detail/SongControls.tsx @@ -25,7 +25,12 @@ import { } from "lucide-solid"; import { Component, createEffect, createSignal, Match, Show, Switch } from "solid-js"; -const SongControls: Component = () => { +// Add a prop to accept the averageColor +type SongControlsProps = { + averageColor: string; +}; + +const SongControls: Component = (props) => { const [disable, setDisable] = createSignal(isSongUndefined(song())); const [playHint, setPlayHint] = createSignal(""); @@ -43,8 +48,8 @@ const SongControls: Component = () => { createEffect(() => setDisable(isSongUndefined(song()))); return ( -
- +
+
); }; -const ProgressBar = () => { +const ProgressBar = (props: { averageColor: string }) => { const currentValue = createMemo(() => { return timestamp() / (duration() !== 0 ? duration() : 1); }); @@ -51,6 +55,7 @@ const ProgressBar = () => { onValueStart={handleSeekStart} onValueCommit={handleSeekEnd} animate + style={{ "--bar-fill-color": props.averageColor }} // Use dynamic color for progress bar > From 7ec75af7916c0940359860547756651cb6b60a74 Mon Sep 17 00:00:00 2001 From: Glockosu Date: Sat, 19 Oct 2024 20:35:05 -0600 Subject: [PATCH 3/9] songcolors added --- .../src/components/song/SongColor.tsx | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 src/renderer/src/components/song/SongColor.tsx diff --git a/src/renderer/src/components/song/SongColor.tsx b/src/renderer/src/components/song/SongColor.tsx new file mode 100644 index 0000000..8167adb --- /dev/null +++ b/src/renderer/src/components/song/SongColor.tsx @@ -0,0 +1,106 @@ +import { createSignal } from "solid-js"; +import { extractColors } from "extract-colors"; +import { darken, lighten, getContrast, parseToHsl } from "polished"; +import { setGradientColors } from "../Gradient"; // Import the setGradientColors function + +// Minimum contrast ratio for readability (WCAG recommends at least 4.5:1 for normal text) +const MIN_CONTRAST_RATIO = 4.5; + +// Minimum vibrancy to ensure visually appealing colors +const MIN_VIBRANCY_THRESHOLD = 0.3; + +// Weight factors to prioritize dominant, vibrant colors +const VIBRANCY_WEIGHT = 0.7; +const AREA_WEIGHT = 0.3; + +export function useSongColor() { + const [averageColor, setAverageColor] = createSignal("white"); + + const handleImageLoad = (imageElement: HTMLImageElement) => { + const src = imageElement.src; + + // Extract colors from the image + extractColors(src) + .then((colors) => { + // Filter out colors that are too dark or too light + const validColors = colors.filter((color) => { + const hsl = parseToHsl(color.hex); + return hsl.lightness > 0.1 && hsl.lightness < 0.9; // Exclude very dark or very light colors + }); + + // Sort colors based on a combination of vibrancy and area (dominance) + const sortedColors = validColors.sort((a, b) => { + const vibrancyA = getVibrancy(parseToHsl(a.hex)); + const vibrancyB = getVibrancy(parseToHsl(b.hex)); + + // Calculate a weighted score based on vibrancy and area + const scoreA = vibrancyA * VIBRANCY_WEIGHT + a.area * AREA_WEIGHT; + const scoreB = vibrancyB * VIBRANCY_WEIGHT + b.area * AREA_WEIGHT; + + return scoreB - scoreA; // Sort descending by score + }); + + // Find the first valid color that meets both vibrancy and contrast threshold + let selectedColor = sortedColors[0].hex; + for (const color of sortedColors) { + const contrast = getContrast(color.hex, "#FFFFFF"); + if (contrast >= MIN_CONTRAST_RATIO && getVibrancy(parseToHsl(color.hex)) >= MIN_VIBRANCY_THRESHOLD) { + selectedColor = color.hex; + break; // Pick the first valid color and exit loop + } + } + + // If no vibrant color hits the threshold, use improveContrast to adjust + const contrast = getContrast(selectedColor, "#FFFFFF"); + if (contrast < MIN_CONTRAST_RATIO) { + selectedColor = improveContrast(selectedColor); + } + + // Set the gradient colors for top and bottom + setGradientColors({ + top: selectedColor, // Set the selected color as the top + bottom: lighten(0.2, selectedColor), // Set a lighter shade as the bottom + }); + + const rgb = hexToRgb(selectedColor); + + document.documentElement.style.setProperty( + "--extracted-color-rgb", + `${rgb.r}, ${rgb.g}, ${rgb.b}` + ); + setAverageColor(selectedColor); + }) + .catch((err) => { + console.error("Error extracting color:", err); + setAverageColor("gray"); + }); + }; + + return { averageColor, handleImageLoad }; +} + +// Helper function to improve contrast by darkening the color +function improveContrast(color: string): string { + let darkenedColor = color; + let contrast = getContrast(darkenedColor, "#FFFFFF"); + for (let i = 0; i < 7 && contrast < MIN_CONTRAST_RATIO; i++) { + darkenedColor = darken(0.1, darkenedColor); // Darken the color by 10% + contrast = getContrast(darkenedColor, "#FFFFFF"); + } + + return contrast >= MIN_CONTRAST_RATIO ? darkenedColor : "gray"; +} + +// Helper function to convert hex to RGB +function hexToRgb(hex: string) { + const bigint = parseInt(hex.slice(1), 16); + const r = (bigint >> 16) & 255; + const g = (bigint >> 8) & 255; + const b = bigint & 255; + return { r, g, b }; +} + +// Helper function to calculate vibrancy based on saturation and lightness +function getVibrancy(hsl: { hue: number; saturation: number; lightness: number }): number { + return hsl.saturation * (1 - Math.abs(hsl.lightness - 0.5) * 2); +} From 525378096eb63e2bac84482b4b31b520125b8076 Mon Sep 17 00:00:00 2001 From: Glockosu Date: Sat, 19 Oct 2024 20:36:40 -0600 Subject: [PATCH 4/9] songitem changes --- src/renderer/src/components/song/song-item/SongItem.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/renderer/src/components/song/song-item/SongItem.tsx b/src/renderer/src/components/song/song-item/SongItem.tsx index f25ebc9..c80fbee 100644 --- a/src/renderer/src/components/song/song-item/SongItem.tsx +++ b/src/renderer/src/components/song/song-item/SongItem.tsx @@ -5,6 +5,7 @@ import SongImage from "../SongImage"; import { ignoreClickInContextMenu } from "../context-menu/SongContextMenu"; import { song as selectedSong } from "../song.utils"; import { Component, createSignal, onMount } from "solid-js"; +import { useSongColor } from "../SongColor"; // Import the color hook type SongItemProps = { song: Song; @@ -29,6 +30,8 @@ const SongItem: Component = ({ const [, setCoords] = createSignal<[number, number]>([0, 0], { equals: false }); let item: HTMLDivElement | undefined; + const { averageColor, handleImageLoad } = useSongColor(); // Use the color extraction + const showMenu = (evt: MouseEvent) => { if (children === undefined) { showSignal[1](false); @@ -66,6 +69,7 @@ const SongItem: Component = ({ ref={item} data-url={song.bg} onContextMenu={showMenu} + style={{ backgroundColor: averageColor() }} // Correctly using camelCase for backgroundColor > = ({ }} src={song.bg} group={group} + onImageLoad={handleImageLoad} // Pass the image load handler for color extraction />
From a3b4bdb98331b1d4eb75464d1fb65dff267fb491 Mon Sep 17 00:00:00 2001 From: Glockosu Date: Sun, 20 Oct 2024 05:17:07 -0600 Subject: [PATCH 5/9] Fix color preloading and type mismatches in SongItem and SongDetail components --- package-lock.json | 34 ++++ package.json | 2 + .../src/components/song/SongColor.tsx | 106 ----------- .../src/components/song/colorExtractor.tsx | 170 ++++++++++++++++++ .../song/song-detail/SongControls.tsx | 6 +- .../song/song-detail/SongDetail.tsx | 36 ++-- .../components/song/song-item/SongItem.tsx | 38 ++-- .../src/scenes/main-scene/MainScene.tsx | 2 +- src/renderer/src/scenes/main-scene/styles.css | 11 ++ 9 files changed, 263 insertions(+), 142 deletions(-) delete mode 100644 src/renderer/src/components/song/SongColor.tsx create mode 100644 src/renderer/src/components/song/colorExtractor.tsx diff --git a/package-lock.json b/package-lock.json index c2ee705..f575973 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,11 +17,13 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "electron-updater": "^6.3.9", + "extract-colors": "^4.1.0", "fastest-levenshtein": "^1.0.16", "get-audio-duration": "^4.0.1", "graceful-fs": "^4.2.11", "lucide-solid": "^0.452.0", "node-addon-api": "^8.2.1", + "polished": "^4.3.1", "sharp": "^0.33.5", "solid-focus-trap": "^0.1.7", "tailwind-merge": "^2.5.3" @@ -485,6 +487,17 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", + "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz", @@ -5471,6 +5484,11 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/extract-colors": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/extract-colors/-/extract-colors-4.1.0.tgz", + "integrity": "sha512-BWZxUwpYra1G91rnq/xxuhVNkkbixdi74xdlebo6744lXYx8SUsOMdFU9FQGoVJZpEmcXC9dXS3lc0/8WyNVkw==" + }, "node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -7929,6 +7947,17 @@ "node": ">=10.4.0" } }, + "node_modules/polished": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz", + "integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==", + "dependencies": { + "@babel/runtime": "^7.17.8" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/postcss": { "version": "8.4.47", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", @@ -8423,6 +8452,11 @@ "node": ">=8.10.0" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", diff --git a/package.json b/package.json index 54d56f0..2b2a868 100644 --- a/package.json +++ b/package.json @@ -32,11 +32,13 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "electron-updater": "^6.3.9", + "extract-colors": "^4.1.0", "fastest-levenshtein": "^1.0.16", "get-audio-duration": "^4.0.1", "graceful-fs": "^4.2.11", "lucide-solid": "^0.452.0", "node-addon-api": "^8.2.1", + "polished": "^4.3.1", "sharp": "^0.33.5", "solid-focus-trap": "^0.1.7", "tailwind-merge": "^2.5.3" diff --git a/src/renderer/src/components/song/SongColor.tsx b/src/renderer/src/components/song/SongColor.tsx deleted file mode 100644 index 8167adb..0000000 --- a/src/renderer/src/components/song/SongColor.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { createSignal } from "solid-js"; -import { extractColors } from "extract-colors"; -import { darken, lighten, getContrast, parseToHsl } from "polished"; -import { setGradientColors } from "../Gradient"; // Import the setGradientColors function - -// Minimum contrast ratio for readability (WCAG recommends at least 4.5:1 for normal text) -const MIN_CONTRAST_RATIO = 4.5; - -// Minimum vibrancy to ensure visually appealing colors -const MIN_VIBRANCY_THRESHOLD = 0.3; - -// Weight factors to prioritize dominant, vibrant colors -const VIBRANCY_WEIGHT = 0.7; -const AREA_WEIGHT = 0.3; - -export function useSongColor() { - const [averageColor, setAverageColor] = createSignal("white"); - - const handleImageLoad = (imageElement: HTMLImageElement) => { - const src = imageElement.src; - - // Extract colors from the image - extractColors(src) - .then((colors) => { - // Filter out colors that are too dark or too light - const validColors = colors.filter((color) => { - const hsl = parseToHsl(color.hex); - return hsl.lightness > 0.1 && hsl.lightness < 0.9; // Exclude very dark or very light colors - }); - - // Sort colors based on a combination of vibrancy and area (dominance) - const sortedColors = validColors.sort((a, b) => { - const vibrancyA = getVibrancy(parseToHsl(a.hex)); - const vibrancyB = getVibrancy(parseToHsl(b.hex)); - - // Calculate a weighted score based on vibrancy and area - const scoreA = vibrancyA * VIBRANCY_WEIGHT + a.area * AREA_WEIGHT; - const scoreB = vibrancyB * VIBRANCY_WEIGHT + b.area * AREA_WEIGHT; - - return scoreB - scoreA; // Sort descending by score - }); - - // Find the first valid color that meets both vibrancy and contrast threshold - let selectedColor = sortedColors[0].hex; - for (const color of sortedColors) { - const contrast = getContrast(color.hex, "#FFFFFF"); - if (contrast >= MIN_CONTRAST_RATIO && getVibrancy(parseToHsl(color.hex)) >= MIN_VIBRANCY_THRESHOLD) { - selectedColor = color.hex; - break; // Pick the first valid color and exit loop - } - } - - // If no vibrant color hits the threshold, use improveContrast to adjust - const contrast = getContrast(selectedColor, "#FFFFFF"); - if (contrast < MIN_CONTRAST_RATIO) { - selectedColor = improveContrast(selectedColor); - } - - // Set the gradient colors for top and bottom - setGradientColors({ - top: selectedColor, // Set the selected color as the top - bottom: lighten(0.2, selectedColor), // Set a lighter shade as the bottom - }); - - const rgb = hexToRgb(selectedColor); - - document.documentElement.style.setProperty( - "--extracted-color-rgb", - `${rgb.r}, ${rgb.g}, ${rgb.b}` - ); - setAverageColor(selectedColor); - }) - .catch((err) => { - console.error("Error extracting color:", err); - setAverageColor("gray"); - }); - }; - - return { averageColor, handleImageLoad }; -} - -// Helper function to improve contrast by darkening the color -function improveContrast(color: string): string { - let darkenedColor = color; - let contrast = getContrast(darkenedColor, "#FFFFFF"); - for (let i = 0; i < 7 && contrast < MIN_CONTRAST_RATIO; i++) { - darkenedColor = darken(0.1, darkenedColor); // Darken the color by 10% - contrast = getContrast(darkenedColor, "#FFFFFF"); - } - - return contrast >= MIN_CONTRAST_RATIO ? darkenedColor : "gray"; -} - -// Helper function to convert hex to RGB -function hexToRgb(hex: string) { - const bigint = parseInt(hex.slice(1), 16); - const r = (bigint >> 16) & 255; - const g = (bigint >> 8) & 255; - const b = bigint & 255; - return { r, g, b }; -} - -// Helper function to calculate vibrancy based on saturation and lightness -function getVibrancy(hsl: { hue: number; saturation: number; lightness: number }): number { - return hsl.saturation * (1 - Math.abs(hsl.lightness - 0.5) * 2); -} diff --git a/src/renderer/src/components/song/colorExtractor.tsx b/src/renderer/src/components/song/colorExtractor.tsx new file mode 100644 index 0000000..a0e2373 --- /dev/null +++ b/src/renderer/src/components/song/colorExtractor.tsx @@ -0,0 +1,170 @@ +import { Accessor, createSignal, Setter } from "solid-js"; +import { extractColors } from "extract-colors"; +import { lighten, darken, getContrast, parseToHsl, hslToColorString } from "polished"; +import { setGradientColors } from "../Gradient"; + +export type Song = { + title: string; + artist: string; + bg?: string; + duration: number; + path: string; + color?: string; // Optional color property to store extracted color +}; + +const MIN_CONTRAST_RATIO = 4.5; +const MIN_VIBRANCY_THRESHOLD = 0.3; +const VIBRANCY_WEIGHT = 0.7; +const AREA_WEIGHT = 0.3; + +const colorCache = new Map< + string, + { color: string; signal: Accessor; setter: Setter } +>(); + +export function useColorExtractor() { + const extractColorFromImage = (song: Song): Accessor => { + const songId = song.path; + + // Check the cache first + if (colorCache.has(songId)) { + const cached = colorCache.get(songId)!; + song.color = cached.color; // Ensure song.color is set + document.documentElement.style.setProperty('--extracted-color-rgb', hexToRgb(cached.color)); + return cached.signal; + } + + // Create a signal for this song's color + const [songColor, setSongColor] = createSignal("gray"); + + // Store in cache immediately with default color + colorCache.set(songId, { color: "gray", signal: songColor, setter: setSongColor }); + + if (!song.bg) { + console.error("No background image found for color extraction."); + setSongColor("gray"); + return songColor; + } + + const img = new Image(); + img.crossOrigin = "Anonymous"; + img.src = song.bg; + + img.onload = () => { + extractColors(img.src) + .then((colors) => { + const validColors = colors.filter((color) => { + const hsl = parseToHsl(color.hex); + return hsl.lightness > 0.1 && hsl.lightness < 0.9; + }); + + const sortedColors = validColors.sort((a, b) => { + const vibrancyA = getVibrancy(parseToHsl(a.hex)); + const vibrancyB = getVibrancy(parseToHsl(b.hex)); + const scoreA = vibrancyA * VIBRANCY_WEIGHT + a.area * AREA_WEIGHT; + const scoreB = vibrancyB * VIBRANCY_WEIGHT + b.area * AREA_WEIGHT; + return scoreB - scoreA; + }); + + let selectedColor = sortedColors[0]?.hex || "gray"; + + // Find the first valid color that meets contrast and vibrancy thresholds + for (const color of sortedColors) { + const contrast = getContrast(color.hex, "#FFFFFF"); + if ( + contrast >= MIN_CONTRAST_RATIO && + getVibrancy(parseToHsl(color.hex)) >= MIN_VIBRANCY_THRESHOLD + ) { + selectedColor = color.hex; + break; + } + } + + selectedColor = alterUnappealingColor(selectedColor); + + // Improve contrast if necessary + if (getContrast(selectedColor, "#FFFFFF") < MIN_CONTRAST_RATIO) { + selectedColor = improveContrast(selectedColor); + } + + // Update the cache and song object + song.color = selectedColor; + setSongColor(selectedColor); + colorCache.set(songId, { color: selectedColor, signal: songColor, setter: setSongColor }); + + document.documentElement.style.setProperty('--extracted-color-rgb', hexToRgb(selectedColor)); + + // Set gradient colors if needed + setGradientColors({ + top: selectedColor, + bottom: lighten(0.2, selectedColor), + }); + }) + .catch((err) => { + console.error("Error extracting color:", err); + setSongColor("gray"); + }); + }; + + return songColor; + }; + + return { extractColorFromImage }; +} + +// Helper function to improve contrast by darkening the color +function improveContrast(color: string): string { + let darkenedColor = color; + let contrast = getContrast(darkenedColor, "#FFFFFF"); + for (let i = 0; i < 7 && contrast < MIN_CONTRAST_RATIO; i++) { + darkenedColor = darken(0.1, darkenedColor); // Darken the color by 10% + contrast = getContrast(darkenedColor, "#FFFFFF"); + } + return darkenedColor; +} + +// Helper function to convert hex to RGB +function hexToRgb(hex: string): string { + const bigint = parseInt(hex.slice(1), 16); + const r = (bigint >> 16) & 255; + const g = (bigint >> 8) & 255; + const b = bigint & 255; + return `${r}, ${g}, ${b}`; +} + +// Helper function to calculate vibrancy based on saturation and lightness +function getVibrancy(hsl: { hue: number; saturation: number; lightness: number }): number { + return hsl.saturation * (1 - Math.abs(hsl.lightness - 0.5) * 2); +} + +// Helper function to alter unappealing colors +function alterUnappealingColor(color: string): string { + const { hue, saturation, lightness } = parseToHsl(color); + // Alter brownish hues to red + if (hue >= 20 && hue <= 45 && saturation < 0.6 && lightness > 0.3 && lightness < 0.6) { + return hslToColorString({ hue: randomizeHue(0, 10), saturation: saturation + 0.3, lightness }); + } + // Alter pickle green/yellowish greens to more vibrant yellow or green + if (hue >= 70 && hue <= 100 && saturation > 0.2 && saturation < 0.4 && lightness > 0.3) { + const newHue = Math.random() > 0.5 ? randomizeHue(50, 65) : randomizeHue(100, 120); + return hslToColorString({ hue: newHue, saturation: saturation + 0.3, lightness }); + } + // Alter tan to a more appealing golden color + if (hue >= 35 && hue <= 50 && lightness > 0.7 && saturation < 0.5) { + return hslToColorString({ hue: randomizeHue(100, 190), saturation: saturation + 0.5, lightness: lightness - 0.2 }); + } + // Alter grayish brown + if (hue >= 20 && hue <= 30 && saturation >= 0.3 && saturation <= 0.5 && lightness >= 0.2 && lightness <= 0.5) { + return hslToColorString({ hue: 75, saturation: saturation + 0.3, lightness: lightness + 0.3 }); + } + // Alter muted purple + if (hue >= 240 && hue <= 280 && saturation <= 0.5 && lightness >= 0.5 && lightness <= 0.7) { + return hslToColorString({ hue: 260, saturation: saturation + 0.4, lightness: lightness + 0.1 }); + } + return color; +} + +// Helper function to add slight randomization to hue values +function randomizeHue(min: number, max: number): number { + return Math.floor(Math.random() * (max - min + 1)) + min; +} diff --git a/src/renderer/src/components/song/song-detail/SongControls.tsx b/src/renderer/src/components/song/song-detail/SongControls.tsx index febc3b9..aacb1c0 100644 --- a/src/renderer/src/components/song/song-detail/SongControls.tsx +++ b/src/renderer/src/components/song/song-detail/SongControls.tsx @@ -49,7 +49,7 @@ const SongControls: Component = (props) => { return (
- +
); @@ -55,10 +66,15 @@ const ProgressBar = (props: { averageColor: string }) => { onValueStart={handleSeekStart} onValueCommit={handleSeekEnd} animate - style={{ "--bar-fill-color": props.averageColor }} // Use dynamic color for progress bar > - - + + diff --git a/src/renderer/src/components/song/song-item/SongItem.tsx b/src/renderer/src/components/song/song-item/SongItem.tsx index c80fbee..0f1988b 100644 --- a/src/renderer/src/components/song/song-item/SongItem.tsx +++ b/src/renderer/src/components/song/song-item/SongItem.tsx @@ -5,7 +5,7 @@ import SongImage from "../SongImage"; import { ignoreClickInContextMenu } from "../context-menu/SongContextMenu"; import { song as selectedSong } from "../song.utils"; import { Component, createSignal, onMount } from "solid-js"; -import { useSongColor } from "../SongColor"; // Import the color hook +import { useColorExtractor } from "../colorExtractor"; // Import the color hook type SongItemProps = { song: Song; @@ -21,34 +21,29 @@ const SongItem: Component = ({ group, onSelect, song, - children, draggable: isDraggable, onDrop, selectable, }) => { - const showSignal = createSignal(false); - const [, setCoords] = createSignal<[number, number]>([0, 0], { equals: false }); let item: HTMLDivElement | undefined; + const [, setCoords] = createSignal<[number, number]>([0, 0], { equals: false }); - const { averageColor, handleImageLoad } = useSongColor(); // Use the color extraction + const { extractColorFromImage } = useColorExtractor(); + const songColor = extractColorFromImage(song); - const showMenu = (evt: MouseEvent) => { - if (children === undefined) { - showSignal[1](false); - return; - } - - setCoords([evt.clientX, evt.clientY]); - showSignal[1](true); - }; + // Build gradient style using songColor + const gradientStyle = () => ({ + background: `linear-gradient(to right, ${songColor()}, transparent)`, + }); onMount(() => { - if (!item) { - return; - } + if (!item) return; + // Initialize draggable functionality draggable(item, { - onClick: ignoreClickInContextMenu(() => onSelect(song.path)), + onClick: ignoreClickInContextMenu(() => { + onSelect(song.path); + }), onDrop: onDrop ?? (() => {}), createHint: SongHint, useOnlyAsOnClickBinder: !isDraggable || selectedSong().path === song.path, @@ -59,6 +54,7 @@ const SongItem: Component = ({ } }); + return (
= ({ data-active={selectedSong().path === song.path} ref={item} data-url={song.bg} - onContextMenu={showMenu} - style={{ backgroundColor: averageColor() }} // Correctly using camelCase for backgroundColor + onContextMenu={(evt) => setCoords([evt.clientX, evt.clientY])} + style={gradientStyle()} // Apply the gradient background using inline styles > = ({ }} src={song.bg} group={group} - onImageLoad={handleImageLoad} // Pass the image load handler for color extraction /> -

{song.title} diff --git a/src/renderer/src/scenes/main-scene/MainScene.tsx b/src/renderer/src/scenes/main-scene/MainScene.tsx index 910c09b..af98b78 100644 --- a/src/renderer/src/scenes/main-scene/MainScene.tsx +++ b/src/renderer/src/scenes/main-scene/MainScene.tsx @@ -32,7 +32,7 @@ const MainScene: Component = () => {