Merge pull request #370 from BaptisteAuscher/feature/color-wheel
feature/color-wheel
This commit is contained in:
Generated
+137
@@ -25,6 +25,7 @@
|
||||
"@types/gif.js": "^0.2.5",
|
||||
"@uiw/color-convert": "^2.10.1",
|
||||
"@uiw/react-color-block": "^2.10.1",
|
||||
"@uiw/react-color-colorful": "^2.9.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"dnd-timeline": "^2.4.0",
|
||||
@@ -4073,6 +4074,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.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color-block/-/react-color-block-2.10.1.tgz",
|
||||
@@ -4092,6 +4123,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.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color-editable-input/-/react-color-editable-input-2.10.1.tgz",
|
||||
@@ -4106,6 +4169,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.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color-swatch/-/react-color-swatch-2.10.1.tgz",
|
||||
@@ -4123,6 +4246,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": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz",
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"@types/gif.js": "^0.2.5",
|
||||
"@uiw/color-convert": "^2.10.1",
|
||||
"@uiw/react-color-block": "^2.10.1",
|
||||
"@uiw/react-color-colorful": "^2.9.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"dnd-timeline": "^2.4.0",
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
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";
|
||||
import { Button } from "./button";
|
||||
import { Input } from "./input";
|
||||
|
||||
type BaseProps = {
|
||||
selectedColor: string;
|
||||
colorPalette: string[];
|
||||
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<HsvaColor>({
|
||||
h: 0,
|
||||
s: 0,
|
||||
v: 0,
|
||||
a: 0,
|
||||
});
|
||||
|
||||
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<HTMLInputElement>) => {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
const toTransparent = (color: string) => {
|
||||
if (color === "transparent") return;
|
||||
const hsva = hexToHsva(color);
|
||||
hsva.a = 0;
|
||||
return hsva;
|
||||
};
|
||||
return (
|
||||
<div className="p-1 flex flex-col gap-4 items-center">
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full h-9 justify-start gap-2 bg-white/5 border-white/10 hover:bg-white/10 px-2"
|
||||
onClick={() => setColorMode("wheel")}
|
||||
style={{
|
||||
backgroundColor: colorMode === "wheel" ? "#34B27B" : "transparent",
|
||||
}}
|
||||
>
|
||||
<span className="text-xs text-slate-300 truncate flex-1 text-left">
|
||||
{translations.colorWheel}
|
||||
</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full h-9 justify-start gap-2 bg-white/5 border-white/10 hover:bg-white/10 px-2"
|
||||
onClick={() => setColorMode("palette")}
|
||||
style={{
|
||||
backgroundColor: colorMode === "palette" ? "#34B27B" : "transparent",
|
||||
}}
|
||||
>
|
||||
<span className="text-xs text-slate-300 truncate flex-1 text-left">
|
||||
{translations.colorPalette}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
{colorMode === "wheel" && (
|
||||
<>
|
||||
<div
|
||||
className={`w-full h-20 flex items-center justify-center border border-white/10 rounded-lg`}
|
||||
style={{ backgroundColor: selectedColor }}
|
||||
>
|
||||
<span style={{ color: getTextColor(selectedColor) }}>{selectedColor}</span>
|
||||
</div>
|
||||
<Colorful
|
||||
color={selectedColor !== "transparent" ? selectedColor : transparentColorHSVA}
|
||||
onChange={(color) => {
|
||||
onUpdateColor(color.hex);
|
||||
}}
|
||||
style={{
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
disableAlpha={true}
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
value={hexInput}
|
||||
className="w-full h-9 rounded-md border border-white/10 bg-white/5 px-2 text-xs text-slate-200 outline-none focus:border-[#34B27B]/50 focus:ring-1 focus:ring-[#34B27B]/30 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
|
||||
onChange={handleColorInputChange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{colorMode === "palette" && (
|
||||
<Block
|
||||
color={selectedColor !== "transparent" ? selectedColor : transparentColorHSVA}
|
||||
colors={colorPalette}
|
||||
onChange={(color) => {
|
||||
onUpdateColor(color.hex);
|
||||
}}
|
||||
style={{
|
||||
width: "100%",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{props.clearBackgroundOption === true && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="w-full mt-2 text-xs h-7 hover:bg-white/5 text-slate-400"
|
||||
onClick={() => {
|
||||
const hsva = toTransparent(selectedColor);
|
||||
if (hsva) setTransparentColorHSVA(hsva);
|
||||
onUpdateColor("transparent");
|
||||
}}
|
||||
>
|
||||
{props.translations.clearBackground}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -31,6 +31,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 {
|
||||
@@ -75,7 +76,6 @@ export function AnnotationSettingsPanel({
|
||||
const t = useScopedT("settings");
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [customFonts, setCustomFonts] = useState<CustomFont[]>([]);
|
||||
|
||||
const fontStyleLabels: Record<string, string> = {
|
||||
classic: t("fontStyles.classic"),
|
||||
editor: t("fontStyles.editor"),
|
||||
@@ -388,15 +388,19 @@ export function AnnotationSettingsPanel({
|
||||
<ChevronDown className="h-3 w-3 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[260px] p-3 bg-[#1a1a1c] border border-white/10 rounded-xl shadow-xl">
|
||||
<Block
|
||||
color={annotation.style.color}
|
||||
colors={colorPalette}
|
||||
onChange={(color) => {
|
||||
onStyleChange({ color: color.hex });
|
||||
<PopoverContent
|
||||
side="top"
|
||||
className="w-[260px] p-3 bg-[#1a1a1c] border border-white/10 rounded-xl shadow-xl"
|
||||
>
|
||||
<ColorPicker
|
||||
selectedColor={annotation.style.color}
|
||||
colorPalette={colorPalette}
|
||||
translations={{
|
||||
colorWheel: t("annotation.colorWheel"),
|
||||
colorPalette: t("annotation.colorPalette"),
|
||||
}}
|
||||
style={{
|
||||
borderRadius: "8px",
|
||||
onUpdateColor={(color) => {
|
||||
onStyleChange({ color: color });
|
||||
}}
|
||||
/>
|
||||
</PopoverContent>
|
||||
@@ -427,31 +431,23 @@ export function AnnotationSettingsPanel({
|
||||
<ChevronDown className="h-3 w-3 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[260px] p-3 bg-[#1a1a1c] border border-white/10 rounded-xl shadow-xl">
|
||||
<Block
|
||||
color={
|
||||
annotation.style.backgroundColor === "transparent"
|
||||
? "#000000"
|
||||
: annotation.style.backgroundColor
|
||||
}
|
||||
colors={colorPalette}
|
||||
onChange={(color) => {
|
||||
onStyleChange({ backgroundColor: color.hex });
|
||||
<PopoverContent
|
||||
side="top"
|
||||
className="w-[260px] p-3 bg-[#1a1a1c] border border-white/10 rounded-xl shadow-xl"
|
||||
>
|
||||
<ColorPicker
|
||||
selectedColor={annotation.style.backgroundColor}
|
||||
colorPalette={colorPalette}
|
||||
translations={{
|
||||
colorWheel: t("annotation.colorWheel"),
|
||||
colorPalette: t("annotation.colorPalette"),
|
||||
clearBackground: t("annotation.clearBackground"),
|
||||
}}
|
||||
style={{
|
||||
borderRadius: "8px",
|
||||
clearBackgroundOption={true}
|
||||
onUpdateColor={(color) => {
|
||||
onStyleChange({ backgroundColor: color });
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="w-full mt-2 text-xs h-7 hover:bg-white/5 text-slate-400"
|
||||
onClick={() => {
|
||||
onStyleChange({ backgroundColor: "transparent" });
|
||||
}}
|
||||
>
|
||||
{t("annotation.clearBackground")}
|
||||
</Button>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import Block from "@uiw/react-color-block";
|
||||
import {
|
||||
Bug,
|
||||
Crop,
|
||||
@@ -41,6 +40,7 @@ import { cn } from "@/lib/utils";
|
||||
import { resolveImageWallpaperUrl, WALLPAPER_PATHS } from "@/lib/wallpaper";
|
||||
import { type AspectRatio, isPortraitAspectRatio } from "@/utils/aspectRatioUtils";
|
||||
import { getTestId } from "@/utils/getTestId";
|
||||
import ColorPicker from "../ui/color-picker";
|
||||
import { AnnotationSettingsPanel } from "./AnnotationSettingsPanel";
|
||||
import { BlurSettingsPanel } from "./BlurSettingsPanel";
|
||||
import { CropControl } from "./CropControl";
|
||||
@@ -1035,7 +1035,7 @@ export function SettingsPanel({
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<div className="max-h-[min(200px,25vh)] overflow-y-auto custom-scrollbar">
|
||||
<div className="overflow-y-auto custom-scrollbar">
|
||||
<TabsContent value="image" className="mt-0 space-y-2">
|
||||
<input
|
||||
type="file"
|
||||
@@ -1109,20 +1109,18 @@ export function SettingsPanel({
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="color" className="mt-0">
|
||||
<div className="p-1">
|
||||
<Block
|
||||
color={selectedColor}
|
||||
colors={colorPalette}
|
||||
onChange={(color) => {
|
||||
setSelectedColor(color.hex);
|
||||
onWallpaperChange(color.hex);
|
||||
}}
|
||||
style={{
|
||||
width: "100%",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<ColorPicker
|
||||
selectedColor={selectedColor}
|
||||
colorPalette={colorPalette}
|
||||
translations={{
|
||||
colorWheel: t("background.colorWheel"),
|
||||
colorPalette: t("background.colorPalette"),
|
||||
}}
|
||||
onUpdateColor={(color) => {
|
||||
setSelectedColor(color);
|
||||
onWallpaperChange(color);
|
||||
}}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="gradient" className="mt-0">
|
||||
|
||||
@@ -45,7 +45,9 @@
|
||||
"color": "Color",
|
||||
"gradient": "Gradient",
|
||||
"uploadCustom": "Upload Custom",
|
||||
"gradientLabel": "Gradient {{index}}"
|
||||
"gradientLabel": "Gradient {{index}}",
|
||||
"colorWheel": "Color Wheel",
|
||||
"colorPalette": "Color Palette"
|
||||
},
|
||||
"crop": {
|
||||
"title": "Crop",
|
||||
@@ -113,6 +115,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",
|
||||
|
||||
@@ -45,7 +45,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",
|
||||
@@ -113,6 +115,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",
|
||||
|
||||
@@ -52,7 +52,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",
|
||||
@@ -120,6 +122,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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
@@ -109,6 +111,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",
|
||||
|
||||
@@ -45,7 +45,9 @@
|
||||
"color": "颜色",
|
||||
"gradient": "渐变",
|
||||
"uploadCustom": "上传自定义",
|
||||
"gradientLabel": "渐变 {{index}}"
|
||||
"gradientLabel": "渐变 {{index}}",
|
||||
"colorWheel": "颜色轮",
|
||||
"colorPalette": "颜色调色板"
|
||||
},
|
||||
"crop": {
|
||||
"title": "裁剪",
|
||||
@@ -113,6 +115,8 @@
|
||||
"background": "背景",
|
||||
"none": "无",
|
||||
"color": "颜色",
|
||||
"colorWheel": "颜色轮",
|
||||
"colorPalette": "颜色调色板",
|
||||
"clearBackground": "清除背景",
|
||||
"uploadImage": "上传图片",
|
||||
"supportedFormats": "支持的格式:JPG、PNG、GIF、WebP",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user