From 7e563166a3633befba3fef6de149d7c8c18b16d7 Mon Sep 17 00:00:00 2001 From: BaptisteAuscher Date: Mon, 6 Apr 2026 20:37:05 +0200 Subject: [PATCH 1/8] add color wheel to background and annotations --- package-lock.json | 137 ++++++++++++++ package.json | 1 + .../video-editor/AnnotationSettingsPanel.tsx | 168 +++++++++++++++--- src/components/video-editor/SettingsPanel.tsx | 105 +++++++++-- src/i18n/locales/en/settings.json | 2 + src/i18n/locales/es/settings.json | 2 + src/i18n/locales/zh-CN/settings.json | 2 + 7 files changed, 376 insertions(+), 41 deletions(-) diff --git a/package-lock.json b/package-lock.json index fdbd6b9..2ff6cd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "@types/gif.js": "^0.2.5", "@uiw/color-convert": "^2.9.2", "@uiw/react-color-block": "^2.9.2", + "@uiw/react-color-colorful": "^2.9.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "dnd-timeline": "^2.2.0", @@ -4875,6 +4876,36 @@ "@babel/runtime": ">=7.19.0" } }, + "node_modules/@uiw/react-color-alpha": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@uiw/react-color-alpha/-/react-color-alpha-2.9.6.tgz", + "integrity": "sha512-DNzEVHZ0Izp4NAwzKqTcl4rLdPjSFjyZCP6Q2vKJEglugZ/bdPsmZaos9IYOrgnd1kPDmTSKZ/p8nI7vBIATGw==", + "license": "MIT", + "dependencies": { + "@uiw/color-convert": "2.9.6", + "@uiw/react-drag-event-interactive": "2.9.6" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.19.0", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@uiw/react-color-alpha/node_modules/@uiw/color-convert": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@uiw/color-convert/-/color-convert-2.9.6.tgz", + "integrity": "sha512-w8TpU3MRcquurQJxWR1daKcRygu/a0hLP/VGsLMA3ebb41sAZGxMQLHtS+zC/e3ciFNB7BbPrSPlzOcz6w6cRg==", + "license": "MIT", + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.19.0" + } + }, "node_modules/@uiw/react-color-block": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/@uiw/react-color-block/-/react-color-block-2.9.2.tgz", @@ -4894,6 +4925,38 @@ "react-dom": ">=16.9.0" } }, + "node_modules/@uiw/react-color-colorful": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@uiw/react-color-colorful/-/react-color-colorful-2.9.6.tgz", + "integrity": "sha512-h74zo+ve9Rpv7xwb1dRfoa23yN39b6eYScDIm7V2d5FzkXN6hR7jnnJ7ZUD9Joz/rdaCz1eFQD9ig+wp8+wSnQ==", + "license": "MIT", + "dependencies": { + "@uiw/color-convert": "2.9.6", + "@uiw/react-color-alpha": "2.9.6", + "@uiw/react-color-hue": "2.9.6", + "@uiw/react-color-saturation": "2.9.6" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.19.0", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@uiw/react-color-colorful/node_modules/@uiw/color-convert": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@uiw/color-convert/-/color-convert-2.9.6.tgz", + "integrity": "sha512-w8TpU3MRcquurQJxWR1daKcRygu/a0hLP/VGsLMA3ebb41sAZGxMQLHtS+zC/e3ciFNB7BbPrSPlzOcz6w6cRg==", + "license": "MIT", + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.19.0" + } + }, "node_modules/@uiw/react-color-editable-input": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/@uiw/react-color-editable-input/-/react-color-editable-input-2.9.2.tgz", @@ -4908,6 +4971,66 @@ "react-dom": ">=16.9.0" } }, + "node_modules/@uiw/react-color-hue": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@uiw/react-color-hue/-/react-color-hue-2.9.6.tgz", + "integrity": "sha512-B99dW2/AHMD3py83BrXl94bhXeGCZR1FMpU/FNbIIbUrV9QTiIXDs2/SB/tMD9ltcSP59RD5Sc5m2vCb/8anjw==", + "license": "MIT", + "dependencies": { + "@uiw/color-convert": "2.9.6", + "@uiw/react-color-alpha": "2.9.6" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.19.0", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@uiw/react-color-hue/node_modules/@uiw/color-convert": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@uiw/color-convert/-/color-convert-2.9.6.tgz", + "integrity": "sha512-w8TpU3MRcquurQJxWR1daKcRygu/a0hLP/VGsLMA3ebb41sAZGxMQLHtS+zC/e3ciFNB7BbPrSPlzOcz6w6cRg==", + "license": "MIT", + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.19.0" + } + }, + "node_modules/@uiw/react-color-saturation": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@uiw/react-color-saturation/-/react-color-saturation-2.9.6.tgz", + "integrity": "sha512-R1tiKbTG2WiJXerkmuaKnBFfzgyZUn08q9OjQSvNH1f3ov2/YeUVlOwQY9MbQE7ytZv+9x+1h0Lpk4QG7AdulQ==", + "license": "MIT", + "dependencies": { + "@uiw/color-convert": "2.9.6", + "@uiw/react-drag-event-interactive": "2.9.6" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.19.0", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@uiw/react-color-saturation/node_modules/@uiw/color-convert": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@uiw/color-convert/-/color-convert-2.9.6.tgz", + "integrity": "sha512-w8TpU3MRcquurQJxWR1daKcRygu/a0hLP/VGsLMA3ebb41sAZGxMQLHtS+zC/e3ciFNB7BbPrSPlzOcz6w6cRg==", + "license": "MIT", + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.19.0" + } + }, "node_modules/@uiw/react-color-swatch": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/@uiw/react-color-swatch/-/react-color-swatch-2.9.2.tgz", @@ -4925,6 +5048,20 @@ "react-dom": ">=16.9.0" } }, + "node_modules/@uiw/react-drag-event-interactive": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@uiw/react-drag-event-interactive/-/react-drag-event-interactive-2.9.6.tgz", + "integrity": "sha512-jXzt3Xis/BIYap2Hj2++gB3aEUD0mZoVNGfckurrwjAwxasxNiwkmTGxV5er3due0ZgaVKdOAfTRoYKlgZukSg==", + "license": "MIT", + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "@babel/runtime": ">=7.19.0", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", diff --git a/package.json b/package.json index 8817372..2496471 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@types/gif.js": "^0.2.5", "@uiw/color-convert": "^2.9.2", "@uiw/react-color-block": "^2.9.2", + "@uiw/react-color-colorful": "^2.9.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "dnd-timeline": "^2.2.0", diff --git a/src/components/video-editor/AnnotationSettingsPanel.tsx b/src/components/video-editor/AnnotationSettingsPanel.tsx index b289392..c897c03 100644 --- a/src/components/video-editor/AnnotationSettingsPanel.tsx +++ b/src/components/video-editor/AnnotationSettingsPanel.tsx @@ -1,4 +1,5 @@ import Block from "@uiw/react-color-block"; +import Colorful from "@uiw/react-color-colorful"; import { AlignCenter, AlignLeft, @@ -67,7 +68,7 @@ export function AnnotationSettingsPanel({ const t = useScopedT("settings"); const fileInputRef = useRef(null); const [customFonts, setCustomFonts] = useState([]); - + const [colorMode, setColorMode] = useState<"wheel" | "palette">("wheel"); const fontStyleLabels: Record = { classic: t("fontStyles.classic"), editor: t("fontStyles.editor"), @@ -139,6 +140,15 @@ export function AnnotationSettingsPanel({ event.target.value = ""; }; + const getTextColor = (color: string) => { + if (color === "transparent") return "#ffffff"; + const r = parseInt(color.slice(1, 3), 16); + const g = parseInt(color.slice(3, 5), 16); + const b = parseInt(color.slice(5, 7), 16); + const luminance = 0.299 * r + 0.587 * g + 0.114 * b; + if (luminance > 186) return "#000000"; + return "#ffffff"; + }; return (
@@ -380,17 +390,68 @@ export function AnnotationSettingsPanel({ - - { - onStyleChange({ color: color.hex }); - }} - style={{ - borderRadius: "8px", - }} - /> + +
+ {colorMode === "palette" && ( + { + onStyleChange({ color: color.hex }); + }} + style={{ + borderRadius: "8px", + }} + /> + )} + {colorMode === "wheel" && ( + <> +
+ + {annotation.style.color} + +
+ { + onStyleChange({ color: color.hex }); + }} + style={{ + borderRadius: "8px", + }} + disableAlpha={true} + /> + + )} +
+ + +
+
@@ -419,21 +480,74 @@ export function AnnotationSettingsPanel({ - - { - onStyleChange({ backgroundColor: color.hex }); - }} - style={{ - borderRadius: "8px", - }} - /> + +
+ {colorMode === "palette" && ( + { + onStyleChange({ backgroundColor: color.hex }); + }} + style={{ + borderRadius: "8px", + }} + /> + )} + {colorMode === "wheel" && ( + <> +
+ + {annotation.style.backgroundColor} + +
+ { + onStyleChange({ backgroundColor: color.hex }); + }} + style={{ + borderRadius: "8px", + }} + disableAlpha={true} + /> + + )} +
+ + +
+
+ +
+ {backgroundColorMode === "wheel" && ( + <> +
+ + {selectedColor} + +
+ { + setSelectedColor(color.hex); + onWallpaperChange(color.hex); + }} + style={{ + borderRadius: "8px", + }} + disableAlpha={true} + /> + { + setSelectedColor(e.target.value); + onWallpaperChange(e.target.value); + }} + /> + + )} + {backgroundColorMode === "palette" && ( + { + setSelectedColor(color.hex); + onWallpaperChange(color.hex); + }} + style={{ + width: "100%", + borderRadius: "8px", + }} + /> + )} diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json index 632a569..da98aea 100644 --- a/src/i18n/locales/en/settings.json +++ b/src/i18n/locales/en/settings.json @@ -108,6 +108,8 @@ "background": "Background", "none": "None", "color": "Color", + "colorWheel": "Color Wheel", + "colorPalette": "Color Palette", "clearBackground": "Clear Background", "uploadImage": "Upload Image", "supportedFormats": "Supported formats: JPG, PNG, GIF, WebP", diff --git a/src/i18n/locales/es/settings.json b/src/i18n/locales/es/settings.json index 586e840..9af4632 100644 --- a/src/i18n/locales/es/settings.json +++ b/src/i18n/locales/es/settings.json @@ -108,6 +108,8 @@ "background": "Fondo", "none": "Ninguno", "color": "Color", + "colorWheel": "Rueda de colores", + "colorPalette": "Paleta de colores", "clearBackground": "Quitar fondo", "uploadImage": "Subir imagen", "supportedFormats": "Formatos compatibles: JPG, PNG, GIF, WebP", diff --git a/src/i18n/locales/zh-CN/settings.json b/src/i18n/locales/zh-CN/settings.json index ab0d41b..a9aa32d 100644 --- a/src/i18n/locales/zh-CN/settings.json +++ b/src/i18n/locales/zh-CN/settings.json @@ -108,6 +108,8 @@ "background": "背景", "none": "无", "color": "颜色", + "colorWheel": "颜色轮", + "colorPalette": "颜色调色板", "clearBackground": "清除背景", "uploadImage": "上传图片", "supportedFormats": "支持的格式:JPG、PNG、GIF、WebP", From 2c10073d308550fbfd09d7767134c97c73f90513 Mon Sep 17 00:00:00 2001 From: BaptisteAuscher Date: Mon, 6 Apr 2026 21:02:50 +0200 Subject: [PATCH 2/8] ai review changes --- src/components/video-editor/SettingsPanel.tsx | 4 ++-- src/i18n/locales/en/settings.json | 4 +++- src/i18n/locales/es/settings.json | 4 +++- src/i18n/locales/zh-CN/settings.json | 4 +++- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 7e4ff35..6df3574 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -1014,7 +1014,7 @@ export function SettingsPanel({ }} > - {t("annotation.colorWheel")} + {t("background.colorWheel")} diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json index da98aea..0d18efd 100644 --- a/src/i18n/locales/en/settings.json +++ b/src/i18n/locales/en/settings.json @@ -41,7 +41,9 @@ "color": "Color", "gradient": "Gradient", "uploadCustom": "Upload Custom", - "gradientLabel": "Gradient {{index}}" + "gradientLabel": "Gradient {{index}}", + "colorWheel": "Color Wheel", + "colorPalette": "Color Palette" }, "crop": { "title": "Crop", diff --git a/src/i18n/locales/es/settings.json b/src/i18n/locales/es/settings.json index 9af4632..1eb6d46 100644 --- a/src/i18n/locales/es/settings.json +++ b/src/i18n/locales/es/settings.json @@ -41,7 +41,9 @@ "color": "Color", "gradient": "Degradado", "uploadCustom": "Subir personalizado", - "gradientLabel": "Degradado {{index}}" + "gradientLabel": "Degradado {{index}}", + "colorWheel": "Rueda de colores", + "colorPalette": "Paleta de colores" }, "crop": { "title": "Recortar", diff --git a/src/i18n/locales/zh-CN/settings.json b/src/i18n/locales/zh-CN/settings.json index a9aa32d..8b554a4 100644 --- a/src/i18n/locales/zh-CN/settings.json +++ b/src/i18n/locales/zh-CN/settings.json @@ -41,7 +41,9 @@ "color": "颜色", "gradient": "渐变", "uploadCustom": "上传自定义", - "gradientLabel": "渐变 {{index}}" + "gradientLabel": "渐变 {{index}}", + "colorWheel": "颜色轮", + "colorPalette": "颜色调色板" }, "crop": { "title": "裁剪", From 10a8feb71d337b84bc75169a7d158c580cbaf1e5 Mon Sep 17 00:00:00 2001 From: BaptisteAuscher Date: Tue, 7 Apr 2026 22:33:39 +0200 Subject: [PATCH 3/8] changes after review, factor the color picker component and add validation for the input --- src/components/ui/color-picker.tsx | 141 +++++++++++++++ .../video-editor/AnnotationSettingsPanel.tsx | 166 +++--------------- src/components/video-editor/SettingsPanel.tsx | 105 ++--------- 3 files changed, 178 insertions(+), 234 deletions(-) create mode 100644 src/components/ui/color-picker.tsx diff --git a/src/components/ui/color-picker.tsx b/src/components/ui/color-picker.tsx new file mode 100644 index 0000000..ea5eb30 --- /dev/null +++ b/src/components/ui/color-picker.tsx @@ -0,0 +1,141 @@ +import Block from "@uiw/react-color-block"; +import Colorful from "@uiw/react-color-colorful"; +import { useEffect, useState } from "react"; +import { Button } from "./button"; +import { Input } from "./input"; + +export default function ColorPicker({ + selectedColor, + colorPalette, + translations, + clearBackgroundOption = false, + onUpdateColor, +}: { + selectedColor: string; + colorPalette: string[]; + translations: Record<"colorWheel" | "colorPalette", string> & + Partial>; + clearBackgroundOption?: boolean; + onUpdateColor: (color: string) => void; +}) { + const [colorMode, setColorMode] = useState<"wheel" | "palette">("wheel"); + const [hexInput, setHexInput] = useState(selectedColor); + + useEffect(() => { + setHexInput(selectedColor); + }, [selectedColor]); + + const getTextColor = (color: string) => { + if (color === "transparent") return "#ffffff"; + const r = parseInt(color.slice(1, 3), 16); + const g = parseInt(color.slice(3, 5), 16); + const b = parseInt(color.slice(5, 7), 16); + const luminance = 0.299 * r + 0.587 * g + 0.114 * b; + if (luminance > 186) return "#000000"; + return "#ffffff"; + }; + + // Normalize the hex input. + // Adds a # at the beginning of the input if it's not there. + const normalizeHexDraft = (raw: string) => { + const trimmed = raw.trim(); + if (trimmed === "") return ""; + if (/^[0-9A-Fa-f]/.test(trimmed[0])) return `#${trimmed}`; + return trimmed; + }; + + const handleColorInputChange = (e: React.ChangeEvent) => { + const normalized = normalizeHexDraft(e.target.value); + setHexInput(normalized); + // Check if the normalized hex is a valid hex color. + // It should follow the format #RRGGBB or #RGB. + const isValidHexColor = + /^#[0-9A-Fa-f]{3}$/.test(normalized) || /^#[0-9A-Fa-f]{6}$/.test(normalized); + if (isValidHexColor) { + onUpdateColor(normalized); + } + }; + return ( +
+
+ + +
+ {colorMode === "wheel" && ( + <> +
+ {selectedColor} +
+ { + onUpdateColor(color.hex); + }} + style={{ + borderRadius: "8px", + }} + disableAlpha={true} + /> + + + )} + {colorMode === "palette" && ( + { + onUpdateColor(color.hex); + }} + style={{ + width: "100%", + borderRadius: "8px", + }} + /> + )} + {clearBackgroundOption && ( + + )} +
+ ); +} diff --git a/src/components/video-editor/AnnotationSettingsPanel.tsx b/src/components/video-editor/AnnotationSettingsPanel.tsx index c897c03..eb6a9be 100644 --- a/src/components/video-editor/AnnotationSettingsPanel.tsx +++ b/src/components/video-editor/AnnotationSettingsPanel.tsx @@ -1,5 +1,4 @@ import Block from "@uiw/react-color-block"; -import Colorful from "@uiw/react-color-colorful"; import { AlignCenter, AlignLeft, @@ -31,6 +30,7 @@ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { useScopedT } from "@/contexts/I18nContext"; import { type CustomFont, getCustomFonts } from "@/lib/customFonts"; import { cn } from "@/lib/utils"; +import ColorPicker from "../ui/color-picker"; import { AddCustomFontDialog } from "./AddCustomFontDialog"; import { getArrowComponent } from "./ArrowSvgs"; import type { AnnotationRegion, AnnotationType, ArrowDirection, FigureData } from "./types"; @@ -68,7 +68,6 @@ export function AnnotationSettingsPanel({ const t = useScopedT("settings"); const fileInputRef = useRef(null); const [customFonts, setCustomFonts] = useState([]); - const [colorMode, setColorMode] = useState<"wheel" | "palette">("wheel"); const fontStyleLabels: Record = { classic: t("fontStyles.classic"), editor: t("fontStyles.editor"), @@ -140,15 +139,6 @@ export function AnnotationSettingsPanel({ event.target.value = ""; }; - const getTextColor = (color: string) => { - if (color === "transparent") return "#ffffff"; - const r = parseInt(color.slice(1, 3), 16); - const g = parseInt(color.slice(3, 5), 16); - const b = parseInt(color.slice(5, 7), 16); - const luminance = 0.299 * r + 0.587 * g + 0.114 * b; - if (luminance > 186) return "#000000"; - return "#ffffff"; - }; return (
@@ -394,64 +384,17 @@ export function AnnotationSettingsPanel({ side="top" className="w-[260px] p-3 bg-[#1a1a1c] border border-white/10 rounded-xl shadow-xl" > -
- {colorMode === "palette" && ( - { - onStyleChange({ color: color.hex }); - }} - style={{ - borderRadius: "8px", - }} - /> - )} - {colorMode === "wheel" && ( - <> -
- - {annotation.style.color} - -
- { - onStyleChange({ color: color.hex }); - }} - style={{ - borderRadius: "8px", - }} - disableAlpha={true} - /> - - )} -
- - -
-
+ { + onStyleChange({ color: color }); + }} + />
@@ -484,80 +427,19 @@ export function AnnotationSettingsPanel({ side="top" className="w-[260px] p-3 bg-[#1a1a1c] border border-white/10 rounded-xl shadow-xl" > -
- {colorMode === "palette" && ( - { - onStyleChange({ backgroundColor: color.hex }); - }} - style={{ - borderRadius: "8px", - }} - /> - )} - {colorMode === "wheel" && ( - <> -
- - {annotation.style.backgroundColor} - -
- { - onStyleChange({ backgroundColor: color.hex }); - }} - style={{ - borderRadius: "8px", - }} - disableAlpha={true} - /> - - )} -
- - -
-
- + clearBackgroundOption={true} + onUpdateColor={(color) => { + onStyleChange({ backgroundColor: color }); + }} + />
diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 6df3574..05d4940 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -1,5 +1,3 @@ -import Block from "@uiw/react-color-block"; -import Colorful from "@uiw/react-color-colorful"; import { Bug, Crop, @@ -42,7 +40,7 @@ import { GIF_FRAME_RATES, GIF_SIZE_PRESETS } from "@/lib/exporter"; import { cn } from "@/lib/utils"; import { type AspectRatio, isPortraitAspectRatio } from "@/utils/aspectRatioUtils"; import { getTestId } from "@/utils/getTestId"; -import { Input } from "../ui/input"; +import ColorPicker from "../ui/color-picker"; import { AnnotationSettingsPanel } from "./AnnotationSettingsPanel"; import { CropControl } from "./CropControl"; import { KeyboardShortcutsHelp } from "./KeyboardShortcutsHelp"; @@ -229,7 +227,6 @@ export function SettingsPanel({ const t = useScopedT("settings"); const [wallpaperPaths, setWallpaperPaths] = useState([]); const [customImages, setCustomImages] = useState([]); - const [backgroundColorMode, setBackgroundColorMode] = useState<"wheel" | "palette">("wheel"); const fileInputRef = useRef(null); useEffect(() => { @@ -322,16 +319,6 @@ export function SettingsPanel({ [cropRegion, onCropChange, videoWidth, videoHeight, cropAspectLocked], ); - const getTextColor = (color: string) => { - if (color === "transparent") return "#ffffff"; - const r = parseInt(color.slice(1, 3), 16); - const g = parseInt(color.slice(3, 5), 16); - const b = parseInt(color.slice(5, 7), 16); - const luminance = 0.299 * r + 0.587 * g + 0.114 * b; - if (luminance > 186) return "#000000"; - return "#ffffff"; - }; - const applyCropAspectPreset = useCallback( (preset: string) => { if (!cropRegion || !onCropChange) return; @@ -1001,84 +988,18 @@ export function SettingsPanel({ -
-
- - -
- {backgroundColorMode === "wheel" && ( - <> -
- - {selectedColor} - -
- { - setSelectedColor(color.hex); - onWallpaperChange(color.hex); - }} - style={{ - borderRadius: "8px", - }} - disableAlpha={true} - /> - { - setSelectedColor(e.target.value); - onWallpaperChange(e.target.value); - }} - /> - - )} - {backgroundColorMode === "palette" && ( - { - setSelectedColor(color.hex); - onWallpaperChange(color.hex); - }} - style={{ - width: "100%", - borderRadius: "8px", - }} - /> - )} -
+ { + setSelectedColor(color); + onWallpaperChange(color); + }} + />
From 545c02b5bbfaf7fd9d56c60ed50620b7847423fb Mon Sep 17 00:00:00 2001 From: BaptisteAuscher Date: Wed, 8 Apr 2026 22:04:19 +0200 Subject: [PATCH 4/8] handle transparent values for the color wheel --- src/components/ui/color-picker.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/components/ui/color-picker.tsx b/src/components/ui/color-picker.tsx index ea5eb30..eed2f02 100644 --- a/src/components/ui/color-picker.tsx +++ b/src/components/ui/color-picker.tsx @@ -1,3 +1,4 @@ +import { HsvaColor, hexToHsva } from "@uiw/color-convert"; import Block from "@uiw/react-color-block"; import Colorful from "@uiw/react-color-colorful"; import { useEffect, useState } from "react"; @@ -20,6 +21,12 @@ export default function ColorPicker({ }) { const [colorMode, setColorMode] = useState<"wheel" | "palette">("wheel"); const [hexInput, setHexInput] = useState(selectedColor); + const [transparentColorHSVA, setTransparentColorHSVA] = useState({ + h: 0, + s: 0, + v: 0, + a: 0, + }); useEffect(() => { setHexInput(selectedColor); @@ -55,6 +62,12 @@ export default function ColorPicker({ onUpdateColor(normalized); } }; + + const toTransparent = (color: string) => { + const hsva = hexToHsva(color); + hsva.a = 0; + return hsva; + }; return (
@@ -94,7 +107,7 @@ export default function ColorPicker({ {selectedColor}
{ onUpdateColor(color.hex); }} @@ -130,6 +143,8 @@ export default function ColorPicker({ size="sm" className="w-full mt-2 text-xs h-7 hover:bg-white/5 text-slate-400" onClick={() => { + const hsva = toTransparent(selectedColor); + setTransparentColorHSVA(hsva); onUpdateColor("transparent"); }} > From 765434b93545be95e65a786e41e735237e233579 Mon Sep 17 00:00:00 2001 From: BaptisteAuscher Date: Wed, 8 Apr 2026 22:23:52 +0200 Subject: [PATCH 5/8] code rabbit --- src/components/ui/color-picker.tsx | 33 +++++++++++++++++------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/components/ui/color-picker.tsx b/src/components/ui/color-picker.tsx index eed2f02..a1b78d5 100644 --- a/src/components/ui/color-picker.tsx +++ b/src/components/ui/color-picker.tsx @@ -5,20 +5,24 @@ import { useEffect, useState } from "react"; import { Button } from "./button"; import { Input } from "./input"; -export default function ColorPicker({ - selectedColor, - colorPalette, - translations, - clearBackgroundOption = false, - onUpdateColor, -}: { +type BaseProps = { selectedColor: string; colorPalette: string[]; - translations: Record<"colorWheel" | "colorPalette", string> & - Partial>; - clearBackgroundOption?: boolean; onUpdateColor: (color: string) => void; -}) { +}; + +type ColorPickerProps = + | (BaseProps & { + clearBackgroundOption?: false; + translations: Record<"colorWheel" | "colorPalette", string>; + }) + | (BaseProps & { + clearBackgroundOption: true; + translations: Record<"colorWheel" | "colorPalette" | "clearBackground", string>; + }); + +export default function ColorPicker(props: ColorPickerProps) { + const { selectedColor, colorPalette, translations, onUpdateColor } = props; const [colorMode, setColorMode] = useState<"wheel" | "palette">("wheel"); const [hexInput, setHexInput] = useState(selectedColor); const [transparentColorHSVA, setTransparentColorHSVA] = useState({ @@ -64,6 +68,7 @@ export default function ColorPicker({ }; const toTransparent = (color: string) => { + if (color === "transparent") return; const hsva = hexToHsva(color); hsva.a = 0; return hsva; @@ -137,18 +142,18 @@ export default function ColorPicker({ }} /> )} - {clearBackgroundOption && ( + {props.clearBackgroundOption === true && ( )}
From c3faca19fd7f6dfcbe06b61c5c1965b1431ca21b Mon Sep 17 00:00:00 2001 From: BaptisteAuscher Date: Wed, 8 Apr 2026 22:45:27 +0200 Subject: [PATCH 6/8] small fix: color block handles transparent values --- src/components/ui/color-picker.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ui/color-picker.tsx b/src/components/ui/color-picker.tsx index a1b78d5..d8ec2b3 100644 --- a/src/components/ui/color-picker.tsx +++ b/src/components/ui/color-picker.tsx @@ -131,7 +131,7 @@ export default function ColorPicker(props: ColorPickerProps) { )} {colorMode === "palette" && ( { onUpdateColor(color.hex); From 283fa406b259097f1b95c908b727dfe80ad57fa6 Mon Sep 17 00:00:00 2001 From: BaptisteAuscher Date: Wed, 8 Apr 2026 23:00:33 +0200 Subject: [PATCH 7/8] langages : tr and fr --- src/i18n/locales/fr/settings.json | 6 +++++- src/i18n/locales/tr/settings.json | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/i18n/locales/fr/settings.json b/src/i18n/locales/fr/settings.json index dd7610f..48ce7e3 100644 --- a/src/i18n/locales/fr/settings.json +++ b/src/i18n/locales/fr/settings.json @@ -41,7 +41,9 @@ "color": "Couleur", "gradient": "Dégradé", "uploadCustom": "Téléverser une image", - "gradientLabel": "Dégradé {{index}}" + "gradientLabel": "Dégradé {{index}}", + "colorWheel": "Roue chromatique", + "colorPalette": "Palette de couleurs" }, "crop": { "title": "Recadrage", @@ -108,6 +110,8 @@ "background": "Arrière-plan", "none": "Aucun", "color": "Couleur", + "colorWheel": "Roue chromatique", + "colorPalette": "Palette de couleurs", "clearBackground": "Supprimer l'arrière-plan", "uploadImage": "Téléverser une image", "supportedFormats": "Formats supportés : JPG, PNG, GIF, WebP", diff --git a/src/i18n/locales/tr/settings.json b/src/i18n/locales/tr/settings.json index 1fa4668..3cf33b1 100644 --- a/src/i18n/locales/tr/settings.json +++ b/src/i18n/locales/tr/settings.json @@ -41,7 +41,9 @@ "color": "Renk", "gradient": "Gradyan", "uploadCustom": "Özel Yükle", - "gradientLabel": "Gradyan {{index}}" + "gradientLabel": "Gradyan {{index}}", + "colorWheel": "Renk çarkı", + "colorPalette": "Renk paleti" }, "crop": { "title": "Kırpma", @@ -108,6 +110,8 @@ "background": "Arka Plan", "none": "Yok", "color": "Renk", + "colorWheel": "Renk çarkı", + "colorPalette": "Renk paleti", "clearBackground": "Arka Planı Temizle", "uploadImage": "Görüntü Yükle", "supportedFormats": "Desteklenen biçimler: JPG, PNG, GIF, WebP", From 8e8b194454e48f7216e3fed21cf4882bd5d373df Mon Sep 17 00:00:00 2001 From: BaptisteAuscher Date: Thu, 30 Apr 2026 22:22:46 +0200 Subject: [PATCH 8/8] adds support for japanese and chineese (taiwan) --- src/i18n/locales/ja-JP/settings.json | 6 +++++- src/i18n/locales/ko-KR/settings.json | 6 +++++- src/i18n/locales/zh-TW/settings.json | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/i18n/locales/ja-JP/settings.json b/src/i18n/locales/ja-JP/settings.json index 9cad3ef..129217c 100644 --- a/src/i18n/locales/ja-JP/settings.json +++ b/src/i18n/locales/ja-JP/settings.json @@ -52,7 +52,9 @@ "color": "色", "gradient": "グラデーション", "uploadCustom": "カスタムをアップロード", - "gradientLabel": "グラデーション {{index}}" + "gradientLabel": "グラデーション {{index}}", + "colorWheel": "カラーホイール", + "colorPalette": "カラーパレット" }, "crop": { "title": "クロップ", @@ -120,6 +122,8 @@ "background": "背景", "none": "なし", "color": "色", + "colorWheel": "カラーホイール", + "colorPalette": "カラーパレット", "clearBackground": "背景をクリア", "uploadImage": "画像をアップロード", "supportedFormats": "サポートされている形式: JPG, PNG, GIF, WebP", diff --git a/src/i18n/locales/ko-KR/settings.json b/src/i18n/locales/ko-KR/settings.json index cd9f734..5defbb6 100644 --- a/src/i18n/locales/ko-KR/settings.json +++ b/src/i18n/locales/ko-KR/settings.json @@ -44,7 +44,9 @@ "color": "색상", "gradient": "그라디언트", "uploadCustom": "직접 업로드", - "gradientLabel": "그라디언트 {{index}}" + "gradientLabel": "그라디언트 {{index}}", + "colorWheel": "색상 휠", + "colorPalette": "색상 팔레트" }, "crop": { "title": "자르기", @@ -111,6 +113,8 @@ "background": "배경", "none": "없음", "color": "색상", + "colorWheel": "색상 휠", + "colorPalette": "색상 팔레트", "clearBackground": "배경 지우기", "uploadImage": "이미지 업로드", "supportedFormats": "지원 형식: JPG, PNG, GIF, WebP", diff --git a/src/i18n/locales/zh-TW/settings.json b/src/i18n/locales/zh-TW/settings.json index 6344a99..652ab5a 100644 --- a/src/i18n/locales/zh-TW/settings.json +++ b/src/i18n/locales/zh-TW/settings.json @@ -52,7 +52,9 @@ "color": "顏色", "gradient": "漸層", "uploadCustom": "上傳自訂", - "gradientLabel": "漸層 {{index}}" + "gradientLabel": "漸層 {{index}}", + "colorWheel": "色輪", + "colorPalette": "調色盤" }, "crop": { "title": "裁剪", @@ -120,6 +122,8 @@ "background": "背景", "none": "無", "color": "顏色", + "colorWheel": "色輪", + "colorPalette": "調色盤", "clearBackground": "清除背景", "uploadImage": "上傳圖片", "supportedFormats": "支援的格式:JPG、PNG、GIF、WebP",