mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
fix custom gradients in audiomotion visualizer
This commit is contained in:
+72
-134
@@ -1003,25 +1003,29 @@ const GeneralSettings = () => {
|
||||
};
|
||||
|
||||
type CustomGradient = {
|
||||
colorStops: (string | { color: string; level?: number; pos?: number })[];
|
||||
colorStops: StoredColorStop[];
|
||||
dir?: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type StoredColorStop = {
|
||||
color: string;
|
||||
level?: number;
|
||||
levelEnabled?: boolean;
|
||||
pos?: number;
|
||||
positionEnabled?: boolean;
|
||||
};
|
||||
|
||||
const CustomGradientsManager = () => {
|
||||
const { t } = useTranslation();
|
||||
const { updateProperty, visualizer } = useUpdateAudioMotionAnalyzer();
|
||||
const [isAdding, setIsAdding] = useState(false);
|
||||
const [editingIndex, setEditingIndex] = useState<null | number>(null);
|
||||
const [newGradient, setNewGradient] = useState<CustomGradient>({
|
||||
colorStops: ['#ff0000'],
|
||||
colorStops: [{ color: '#ff0000', levelEnabled: false, positionEnabled: false }],
|
||||
dir: 'v',
|
||||
name: '',
|
||||
});
|
||||
// Track which checkboxes are enabled for each color stop
|
||||
const [colorStopOptions, setColorStopOptions] = useState<
|
||||
Array<{ enableLevel: boolean; enablePos: boolean }>
|
||||
>([{ enableLevel: false, enablePos: false }]);
|
||||
|
||||
const customGradients = visualizer.audiomotionanalyzer.customGradients || [];
|
||||
|
||||
@@ -1030,8 +1034,19 @@ const CustomGradientsManager = () => {
|
||||
|
||||
const updatedGradients = [...customGradients, newGradient];
|
||||
updateProperty('customGradients', updatedGradients);
|
||||
setNewGradient({ colorStops: ['#ff0000'], dir: 'v', name: '' });
|
||||
setColorStopOptions([{ enableLevel: false, enablePos: false }]);
|
||||
setNewGradient({
|
||||
colorStops: [
|
||||
{
|
||||
color: '#ff0000',
|
||||
level: 0,
|
||||
levelEnabled: false,
|
||||
pos: 0,
|
||||
positionEnabled: false,
|
||||
},
|
||||
],
|
||||
dir: 'v',
|
||||
name: '',
|
||||
});
|
||||
setIsAdding(false);
|
||||
};
|
||||
|
||||
@@ -1043,12 +1058,6 @@ const CustomGradientsManager = () => {
|
||||
const handleEditGradient = (index: number) => {
|
||||
const gradient = customGradients[index];
|
||||
setNewGradient(gradient);
|
||||
// Initialize checkbox states based on existing color stops
|
||||
const options = gradient.colorStops.map((stop) => ({
|
||||
enableLevel: typeof stop !== 'string' && stop.level !== undefined,
|
||||
enablePos: typeof stop !== 'string' && stop.pos !== undefined,
|
||||
}));
|
||||
setColorStopOptions(options);
|
||||
setEditingIndex(index);
|
||||
setIsAdding(true);
|
||||
};
|
||||
@@ -1059,15 +1068,21 @@ const CustomGradientsManager = () => {
|
||||
const updatedGradients = [...customGradients];
|
||||
updatedGradients[editingIndex] = newGradient;
|
||||
updateProperty('customGradients', updatedGradients);
|
||||
setNewGradient({ colorStops: ['#ff0000'], dir: 'v', name: '' });
|
||||
setColorStopOptions([{ enableLevel: false, enablePos: false }]);
|
||||
setNewGradient({
|
||||
colorStops: [{ color: '#ff0000', levelEnabled: false, positionEnabled: false }],
|
||||
dir: 'v',
|
||||
name: '',
|
||||
});
|
||||
setEditingIndex(null);
|
||||
setIsAdding(false);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setNewGradient({ colorStops: ['#ff0000'], dir: 'v', name: '' });
|
||||
setColorStopOptions([{ enableLevel: false, enablePos: false }]);
|
||||
setNewGradient({
|
||||
colorStops: [{ color: '#ff0000', levelEnabled: false, positionEnabled: false }],
|
||||
dir: 'v',
|
||||
name: '',
|
||||
});
|
||||
setEditingIndex(null);
|
||||
setIsAdding(false);
|
||||
};
|
||||
@@ -1075,9 +1090,11 @@ const CustomGradientsManager = () => {
|
||||
const handleAddColorStop = () => {
|
||||
setNewGradient({
|
||||
...newGradient,
|
||||
colorStops: [...newGradient.colorStops, '#00ff00'],
|
||||
colorStops: [
|
||||
...newGradient.colorStops,
|
||||
{ color: '#00ff00', levelEnabled: false, positionEnabled: false },
|
||||
],
|
||||
});
|
||||
setColorStopOptions([...colorStopOptions, { enableLevel: false, enablePos: false }]);
|
||||
};
|
||||
|
||||
const handleRemoveColorStop = (index: number) => {
|
||||
@@ -1086,33 +1103,16 @@ const CustomGradientsManager = () => {
|
||||
...newGradient,
|
||||
colorStops: newGradient.colorStops.filter((_, i) => i !== index),
|
||||
});
|
||||
setColorStopOptions(colorStopOptions.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleColorStopChange = (index: number, color: string) => {
|
||||
const updatedColorStops = [...newGradient.colorStops];
|
||||
const currentStop = updatedColorStops[index];
|
||||
const options = colorStopOptions[index];
|
||||
|
||||
// If neither checkbox is enabled, store as string
|
||||
if (!options.enablePos && !options.enableLevel) {
|
||||
updatedColorStops[index] = color;
|
||||
} else {
|
||||
// Otherwise, store as object with enabled properties
|
||||
updatedColorStops[index] = {
|
||||
color,
|
||||
...(options.enablePos &&
|
||||
typeof currentStop !== 'string' &&
|
||||
currentStop.pos !== undefined
|
||||
? { pos: currentStop.pos }
|
||||
: {}),
|
||||
...(options.enableLevel &&
|
||||
typeof currentStop !== 'string' &&
|
||||
currentStop.level !== undefined
|
||||
? { level: currentStop.level }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
updatedColorStops[index] = {
|
||||
...currentStop,
|
||||
color,
|
||||
};
|
||||
|
||||
setNewGradient({ ...newGradient, colorStops: updatedColorStops });
|
||||
};
|
||||
@@ -1121,18 +1121,10 @@ const CustomGradientsManager = () => {
|
||||
const updatedColorStops = [...newGradient.colorStops];
|
||||
const currentStop = updatedColorStops[index];
|
||||
const posValue = typeof pos === 'number' ? pos : parseFloat(pos) || undefined;
|
||||
const options = colorStopOptions[index];
|
||||
|
||||
const color = typeof currentStop === 'string' ? currentStop : currentStop.color;
|
||||
|
||||
updatedColorStops[index] = {
|
||||
color,
|
||||
...(options.enablePos && posValue !== undefined ? { pos: posValue } : {}),
|
||||
...(options.enableLevel &&
|
||||
typeof currentStop !== 'string' &&
|
||||
currentStop.level !== undefined
|
||||
? { level: currentStop.level }
|
||||
: {}),
|
||||
...currentStop,
|
||||
...(currentStop.positionEnabled && posValue !== undefined ? { pos: posValue } : {}),
|
||||
};
|
||||
|
||||
setNewGradient({ ...newGradient, colorStops: updatedColorStops });
|
||||
@@ -1142,87 +1134,43 @@ const CustomGradientsManager = () => {
|
||||
const updatedColorStops = [...newGradient.colorStops];
|
||||
const currentStop = updatedColorStops[index];
|
||||
const levelValue = typeof level === 'number' ? level : parseFloat(level) || undefined;
|
||||
const options = colorStopOptions[index];
|
||||
|
||||
const color = typeof currentStop === 'string' ? currentStop : currentStop.color;
|
||||
|
||||
updatedColorStops[index] = {
|
||||
color,
|
||||
...(options.enablePos &&
|
||||
typeof currentStop !== 'string' &&
|
||||
currentStop.pos !== undefined
|
||||
? { pos: currentStop.pos }
|
||||
: {}),
|
||||
...(options.enableLevel && levelValue !== undefined ? { level: levelValue } : {}),
|
||||
...currentStop,
|
||||
...(currentStop.levelEnabled && levelValue !== undefined ? { level: levelValue } : {}),
|
||||
};
|
||||
|
||||
setNewGradient({ ...newGradient, colorStops: updatedColorStops });
|
||||
};
|
||||
|
||||
const handleTogglePos = (index: number, enabled: boolean) => {
|
||||
const updatedOptions = [...colorStopOptions];
|
||||
updatedOptions[index] = { ...updatedOptions[index], enablePos: enabled };
|
||||
setColorStopOptions(updatedOptions);
|
||||
const updatedColorStops = [...newGradient.colorStops];
|
||||
const currentStop = updatedColorStops[index];
|
||||
|
||||
// If both are now disabled, convert to string
|
||||
if (!enabled && !updatedOptions[index].enableLevel) {
|
||||
const updatedColorStops = [...newGradient.colorStops];
|
||||
const currentStop = updatedColorStops[index];
|
||||
const color = typeof currentStop === 'string' ? currentStop : currentStop.color;
|
||||
updatedColorStops[index] = color;
|
||||
setNewGradient({ ...newGradient, colorStops: updatedColorStops });
|
||||
} else {
|
||||
// Otherwise, ensure it's an object
|
||||
const updatedColorStops = [...newGradient.colorStops];
|
||||
const currentStop = updatedColorStops[index];
|
||||
const color = typeof currentStop === 'string' ? currentStop : currentStop.color;
|
||||
updatedColorStops[index] = {
|
||||
...currentStop,
|
||||
positionEnabled: enabled,
|
||||
// Remove pos if disabling
|
||||
...(enabled && currentStop.pos !== undefined ? { pos: currentStop.pos } : {}),
|
||||
...(!enabled ? { pos: undefined } : {}),
|
||||
};
|
||||
|
||||
updatedColorStops[index] = {
|
||||
color,
|
||||
...(enabled && typeof currentStop !== 'string' && currentStop.pos !== undefined
|
||||
? { pos: currentStop.pos }
|
||||
: {}),
|
||||
...(updatedOptions[index].enableLevel &&
|
||||
typeof currentStop !== 'string' &&
|
||||
currentStop.level !== undefined
|
||||
? { level: currentStop.level }
|
||||
: {}),
|
||||
};
|
||||
setNewGradient({ ...newGradient, colorStops: updatedColorStops });
|
||||
}
|
||||
setNewGradient({ ...newGradient, colorStops: updatedColorStops });
|
||||
};
|
||||
|
||||
const handleToggleLevel = (index: number, enabled: boolean) => {
|
||||
const updatedOptions = [...colorStopOptions];
|
||||
updatedOptions[index] = { ...updatedOptions[index], enableLevel: enabled };
|
||||
setColorStopOptions(updatedOptions);
|
||||
const updatedColorStops = [...newGradient.colorStops];
|
||||
const currentStop = updatedColorStops[index];
|
||||
|
||||
// If both are now disabled, convert to string
|
||||
if (!enabled && !updatedOptions[index].enablePos) {
|
||||
const updatedColorStops = [...newGradient.colorStops];
|
||||
const currentStop = updatedColorStops[index];
|
||||
const color = typeof currentStop === 'string' ? currentStop : currentStop.color;
|
||||
updatedColorStops[index] = color;
|
||||
setNewGradient({ ...newGradient, colorStops: updatedColorStops });
|
||||
} else {
|
||||
// Otherwise, ensure it's an object
|
||||
const updatedColorStops = [...newGradient.colorStops];
|
||||
const currentStop = updatedColorStops[index];
|
||||
const color = typeof currentStop === 'string' ? currentStop : currentStop.color;
|
||||
updatedColorStops[index] = {
|
||||
...currentStop,
|
||||
levelEnabled: enabled,
|
||||
// Remove level if disabling
|
||||
...(enabled && currentStop.level !== undefined ? { level: currentStop.level } : {}),
|
||||
...(!enabled ? { level: undefined } : {}),
|
||||
};
|
||||
|
||||
updatedColorStops[index] = {
|
||||
color,
|
||||
...(updatedOptions[index].enablePos &&
|
||||
typeof currentStop !== 'string' &&
|
||||
currentStop.pos !== undefined
|
||||
? { pos: currentStop.pos }
|
||||
: {}),
|
||||
...(enabled && typeof currentStop !== 'string' && currentStop.level !== undefined
|
||||
? { level: currentStop.level }
|
||||
: {}),
|
||||
};
|
||||
setNewGradient({ ...newGradient, colorStops: updatedColorStops });
|
||||
}
|
||||
setNewGradient({ ...newGradient, colorStops: updatedColorStops });
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -1311,10 +1259,6 @@ const CustomGradientsManager = () => {
|
||||
</Button>
|
||||
</Group>
|
||||
{newGradient.colorStops.map((stop, index) => {
|
||||
const options = colorStopOptions[index] || {
|
||||
enableLevel: false,
|
||||
enablePos: false,
|
||||
};
|
||||
return (
|
||||
<Group grow key={index}>
|
||||
<ColorInput
|
||||
@@ -1323,20 +1267,18 @@ const CustomGradientsManager = () => {
|
||||
handleColorStopChange(index, color)
|
||||
}
|
||||
size="sm"
|
||||
value={typeof stop === 'string' ? stop : stop.color}
|
||||
value={stop.color}
|
||||
/>
|
||||
<VisualizerSlider
|
||||
defaultValue={
|
||||
typeof stop === 'string' ? undefined : stop.pos
|
||||
}
|
||||
disabled={!options.enablePos}
|
||||
defaultValue={stop.pos}
|
||||
disabled={!stop.positionEnabled}
|
||||
label={
|
||||
<Group
|
||||
gap="xs"
|
||||
style={{ alignItems: 'center' }}
|
||||
>
|
||||
<Checkbox
|
||||
checked={options.enablePos}
|
||||
checked={stop.positionEnabled || false}
|
||||
onChange={(e) =>
|
||||
handleTogglePos(
|
||||
index,
|
||||
@@ -1358,19 +1300,15 @@ const CustomGradientsManager = () => {
|
||||
step={0.1}
|
||||
/>
|
||||
<VisualizerSlider
|
||||
defaultValue={
|
||||
typeof stop === 'string'
|
||||
? undefined
|
||||
: stop.level
|
||||
}
|
||||
disabled={!options.enableLevel}
|
||||
defaultValue={stop.level}
|
||||
disabled={!stop.levelEnabled}
|
||||
label={
|
||||
<Group
|
||||
gap="xs"
|
||||
style={{ alignItems: 'center' }}
|
||||
>
|
||||
<Checkbox
|
||||
checked={options.enableLevel}
|
||||
checked={stop.levelEnabled || false}
|
||||
onChange={(e) =>
|
||||
handleToggleLevel(
|
||||
index,
|
||||
|
||||
@@ -117,6 +117,50 @@ const VisualizerInner = () => {
|
||||
};
|
||||
}, [visualizer, gradientsRegistered, isCustomGradient]);
|
||||
|
||||
const transformGradientForVisualizer = useCallback(
|
||||
(gradient: {
|
||||
colorStops: Array<{
|
||||
color: string;
|
||||
level?: number;
|
||||
levelEnabled?: boolean;
|
||||
pos?: number;
|
||||
positionEnabled?: boolean;
|
||||
}>;
|
||||
dir?: string;
|
||||
}): {
|
||||
colorStops: (string | { color: string; level?: number; pos?: number })[];
|
||||
dir?: string;
|
||||
} => {
|
||||
const transformedColorStops = gradient.colorStops.map((stop) => {
|
||||
// If neither position nor level is enabled, return just the color string
|
||||
if (!stop.positionEnabled && !stop.levelEnabled) {
|
||||
return stop.color;
|
||||
}
|
||||
|
||||
// Otherwise, return an object with only enabled properties
|
||||
const transformedStop: { color: string; level?: number; pos?: number } = {
|
||||
color: stop.color,
|
||||
};
|
||||
|
||||
if (stop.positionEnabled && stop.pos !== undefined) {
|
||||
transformedStop.pos = stop.pos;
|
||||
}
|
||||
|
||||
if (stop.levelEnabled && stop.level !== undefined) {
|
||||
transformedStop.level = stop.level;
|
||||
}
|
||||
|
||||
return transformedStop;
|
||||
});
|
||||
|
||||
return {
|
||||
colorStops: transformedColorStops,
|
||||
...(gradient.dir ? { dir: gradient.dir } : {}),
|
||||
};
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const registerCustomGradients = useCallback(
|
||||
(audioMotionInstance: AudioMotionAnalyzer) => {
|
||||
if (visualizer.type !== 'audiomotionanalyzer') {
|
||||
@@ -127,18 +171,8 @@ const VisualizerInner = () => {
|
||||
|
||||
customGradients.forEach((gradient) => {
|
||||
try {
|
||||
const gradientConfig: {
|
||||
colorStops: (string | { color: string; level?: number; pos?: number })[];
|
||||
dir?: string;
|
||||
} = {
|
||||
colorStops: gradient.colorStops,
|
||||
};
|
||||
const gradientConfig = transformGradientForVisualizer(gradient);
|
||||
|
||||
if (gradient.dir) {
|
||||
gradientConfig.dir = gradient.dir;
|
||||
}
|
||||
|
||||
// Type assertion needed as TypeScript definitions may be incomplete
|
||||
audioMotionInstance.registerGradient(gradient.name, gradientConfig as any);
|
||||
} catch (error) {
|
||||
console.error(`Failed to register gradient "${gradient.name}":`, error);
|
||||
@@ -148,7 +182,7 @@ const VisualizerInner = () => {
|
||||
// Mark gradients as registered
|
||||
setGradientsRegistered(true);
|
||||
},
|
||||
[visualizer],
|
||||
[visualizer, transformGradientForVisualizer],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -243,13 +243,13 @@ const AudioMotionAnalyzerSettingsSchema = z.object({
|
||||
customGradients: z.array(
|
||||
z.object({
|
||||
colorStops: z.array(
|
||||
z.string().or(
|
||||
z.object({
|
||||
color: z.string(),
|
||||
level: z.number().min(0).max(1).optional(),
|
||||
pos: z.number().min(0).max(1).optional(),
|
||||
}),
|
||||
),
|
||||
z.object({
|
||||
color: z.string(),
|
||||
level: z.number().min(0).max(1).optional(),
|
||||
levelEnabled: z.boolean().optional(),
|
||||
pos: z.number().min(0).max(1).optional(),
|
||||
positionEnabled: z.boolean().optional(),
|
||||
}),
|
||||
),
|
||||
dir: z.string().optional(),
|
||||
name: z.string(),
|
||||
|
||||
Reference in New Issue
Block a user