新增颜色替换功能,支持选择源颜色和目标颜色的替换逻辑,更新调色板组件以集成颜色替换状态和交互,提升用户体验和代码可读性。
This commit is contained in:
158
src/app/page.tsx
158
src/app/page.tsx
@@ -147,6 +147,16 @@ export default function Home() {
|
||||
|
||||
// 新增:完整色板切换状态
|
||||
const [showFullPalette, setShowFullPalette] = useState<boolean>(false);
|
||||
|
||||
// 新增:颜色替换相关状态
|
||||
const [colorReplaceState, setColorReplaceState] = useState<{
|
||||
isActive: boolean;
|
||||
step: 'select-source' | 'select-target';
|
||||
sourceColor?: { key: string; color: string };
|
||||
}>({
|
||||
isActive: false,
|
||||
step: 'select-source'
|
||||
});
|
||||
|
||||
const originalCanvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const pixelatedCanvasRef = useRef<HTMLCanvasElement>(null);
|
||||
@@ -337,6 +347,15 @@ export default function Home() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果当前在颜色替换模式,先退出替换模式
|
||||
if (colorReplaceState.isActive) {
|
||||
setColorReplaceState({
|
||||
isActive: false,
|
||||
step: 'select-source'
|
||||
});
|
||||
setHighlightColorKey(null);
|
||||
}
|
||||
|
||||
setIsEraseMode(!isEraseMode);
|
||||
// 如果开启擦除模式,取消选中的颜色
|
||||
if (!isEraseMode) {
|
||||
@@ -913,6 +932,19 @@ export default function Home() {
|
||||
if (i >= 0 && i < N && j >= 0 && j < M) {
|
||||
const cellData = mappedPixelData[j][i];
|
||||
|
||||
// 颜色替换模式逻辑 - 选择源颜色
|
||||
if (isClick && colorReplaceState.isActive && colorReplaceState.step === 'select-source') {
|
||||
if (cellData && !cellData.isExternal && cellData.key && cellData.key !== TRANSPARENT_KEY) {
|
||||
// 执行选择源颜色
|
||||
handleCanvasColorSelect({
|
||||
key: cellData.key,
|
||||
color: cellData.color
|
||||
});
|
||||
setTooltipData(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 一键擦除模式逻辑
|
||||
if (isClick && isEraseMode) {
|
||||
if (cellData && !cellData.isExternal && cellData.key && cellData.key !== TRANSPARENT_KEY) {
|
||||
@@ -1197,6 +1229,127 @@ export default function Home() {
|
||||
setShowFullPalette(!showFullPalette);
|
||||
};
|
||||
|
||||
// 新增:处理颜色选择,同时管理模式切换
|
||||
const handleColorSelect = (colorData: { key: string; color: string; isExternal?: boolean }) => {
|
||||
// 如果选择的是橡皮擦(透明色)且当前在颜色替换模式,退出替换模式
|
||||
if (colorData.key === TRANSPARENT_KEY && colorReplaceState.isActive) {
|
||||
setColorReplaceState({
|
||||
isActive: false,
|
||||
step: 'select-source'
|
||||
});
|
||||
setHighlightColorKey(null);
|
||||
}
|
||||
|
||||
// 选择任何颜色(包括橡皮擦)时,都应该退出一键擦除模式
|
||||
if (isEraseMode) {
|
||||
setIsEraseMode(false);
|
||||
}
|
||||
|
||||
// 设置选中的颜色
|
||||
setSelectedColor(colorData);
|
||||
};
|
||||
|
||||
// 新增:颜色替换相关处理函数
|
||||
const handleColorReplaceToggle = () => {
|
||||
setColorReplaceState(prev => {
|
||||
if (prev.isActive) {
|
||||
// 退出替换模式
|
||||
return {
|
||||
isActive: false,
|
||||
step: 'select-source'
|
||||
};
|
||||
} else {
|
||||
// 进入替换模式
|
||||
// 只退出冲突的模式,但保持在手动上色模式下
|
||||
setIsEraseMode(false);
|
||||
setSelectedColor(null);
|
||||
return {
|
||||
isActive: true,
|
||||
step: 'select-source'
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 新增:处理从画布选择源颜色
|
||||
const handleCanvasColorSelect = (colorData: { key: string; color: string }) => {
|
||||
if (colorReplaceState.isActive && colorReplaceState.step === 'select-source') {
|
||||
// 高亮显示选中的颜色
|
||||
setHighlightColorKey(colorData.color);
|
||||
// 进入第二步:选择目标颜色
|
||||
setColorReplaceState({
|
||||
isActive: true,
|
||||
step: 'select-target',
|
||||
sourceColor: colorData
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 新增:执行颜色替换
|
||||
const handleColorReplace = (sourceColor: { key: string; color: string }, targetColor: { key: string; color: string }) => {
|
||||
if (!mappedPixelData || !gridDimensions) return;
|
||||
|
||||
const { N, M } = gridDimensions;
|
||||
const newPixelData = mappedPixelData.map(row => row.map(cell => ({ ...cell })));
|
||||
let replaceCount = 0;
|
||||
|
||||
// 遍历所有像素,替换匹配的颜色
|
||||
for (let j = 0; j < M; j++) {
|
||||
for (let i = 0; i < N; i++) {
|
||||
const currentCell = newPixelData[j][i];
|
||||
if (currentCell && !currentCell.isExternal &&
|
||||
currentCell.color.toUpperCase() === sourceColor.color.toUpperCase()) {
|
||||
// 替换颜色
|
||||
newPixelData[j][i] = {
|
||||
key: targetColor.key,
|
||||
color: targetColor.color,
|
||||
isExternal: false
|
||||
};
|
||||
replaceCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (replaceCount > 0) {
|
||||
// 更新像素数据
|
||||
setMappedPixelData(newPixelData);
|
||||
|
||||
// 重新计算颜色统计
|
||||
if (colorCounts) {
|
||||
const newColorCounts: { [hexKey: string]: { count: number; color: string } } = {};
|
||||
let newTotalCount = 0;
|
||||
|
||||
newPixelData.flat().forEach(cell => {
|
||||
if (cell && !cell.isExternal && cell.key !== TRANSPARENT_KEY) {
|
||||
const cellHex = cell.color.toUpperCase();
|
||||
if (!newColorCounts[cellHex]) {
|
||||
newColorCounts[cellHex] = {
|
||||
count: 0,
|
||||
color: cellHex
|
||||
};
|
||||
}
|
||||
newColorCounts[cellHex].count++;
|
||||
newTotalCount++;
|
||||
}
|
||||
});
|
||||
|
||||
setColorCounts(newColorCounts);
|
||||
setTotalBeadCount(newTotalCount);
|
||||
}
|
||||
|
||||
console.log(`颜色替换完成:将 ${replaceCount} 个 ${sourceColor.key} 替换为 ${targetColor.key}`);
|
||||
}
|
||||
|
||||
// 退出替换模式
|
||||
setColorReplaceState({
|
||||
isActive: false,
|
||||
step: 'select-source'
|
||||
});
|
||||
|
||||
// 清除高亮
|
||||
setHighlightColorKey(null);
|
||||
};
|
||||
|
||||
// 生成完整色板数据(用户自定义色板中选中的所有颜色)
|
||||
const fullPaletteColors = useMemo(() => {
|
||||
const selectedColors: { key: string; color: string }[] = [];
|
||||
@@ -1568,7 +1721,7 @@ export default function Home() {
|
||||
<ColorPalette
|
||||
colors={[transparentColorData, ...currentGridColors]}
|
||||
selectedColor={selectedColor}
|
||||
onColorSelect={setSelectedColor}
|
||||
onColorSelect={handleColorSelect}
|
||||
transparentKey={TRANSPARENT_KEY}
|
||||
selectedColorSystem={selectedColorSystem}
|
||||
isEraseMode={isEraseMode}
|
||||
@@ -1577,6 +1730,9 @@ export default function Home() {
|
||||
fullPaletteColors={fullPaletteColors}
|
||||
showFullPalette={showFullPalette}
|
||||
onToggleFullPalette={handleToggleFullPalette}
|
||||
colorReplaceState={colorReplaceState}
|
||||
onColorReplaceToggle={handleColorReplaceToggle}
|
||||
onColorReplace={handleColorReplace}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,13 @@ interface ColorData {
|
||||
isExternal?: boolean; // 添加 isExternal 属性以支持透明/橡皮擦功能
|
||||
}
|
||||
|
||||
// 新增:颜色替换相关接口
|
||||
interface ColorReplaceState {
|
||||
isActive: boolean;
|
||||
step: 'select-source' | 'select-target'; // 替换步骤:选择源颜色 | 选择目标颜色
|
||||
sourceColor?: ColorData; // 被替换的颜色
|
||||
}
|
||||
|
||||
interface ColorPaletteProps {
|
||||
colors: ColorData[];
|
||||
selectedColor: ColorData | null;
|
||||
@@ -25,6 +32,10 @@ interface ColorPaletteProps {
|
||||
fullPaletteColors?: ColorData[]; // 用户自定义色板中的所有颜色
|
||||
showFullPalette?: boolean; // 是否显示完整色板
|
||||
onToggleFullPalette?: () => void; // 切换完整色板显示
|
||||
// 新增:颜色替换相关props
|
||||
colorReplaceState?: ColorReplaceState; // 颜色替换状态
|
||||
onColorReplaceToggle?: () => void; // 切换颜色替换模式
|
||||
onColorReplace?: (sourceColor: ColorData, targetColor: ColorData) => void; // 执行颜色替换
|
||||
}
|
||||
|
||||
const ColorPalette: React.FC<ColorPaletteProps> = ({
|
||||
@@ -38,7 +49,10 @@ const ColorPalette: React.FC<ColorPaletteProps> = ({
|
||||
onHighlightColor,
|
||||
fullPaletteColors,
|
||||
showFullPalette,
|
||||
onToggleFullPalette
|
||||
onToggleFullPalette,
|
||||
colorReplaceState,
|
||||
onColorReplaceToggle,
|
||||
onColorReplace
|
||||
}) => {
|
||||
if (!colors || colors.length === 0) {
|
||||
// Apply dark mode text color
|
||||
@@ -84,6 +98,81 @@ const ColorPalette: React.FC<ColorPaletteProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 颜色替换状态提示 */}
|
||||
{colorReplaceState?.isActive && (
|
||||
<div className="p-3 border-b border-purple-100 dark:border-purple-800 bg-purple-50 dark:bg-purple-900/20">
|
||||
<div className="text-center">
|
||||
<div className="flex items-center justify-center gap-2 mb-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-purple-600 dark:text-purple-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
|
||||
</svg>
|
||||
<span className="text-sm font-medium text-purple-700 dark:text-purple-300">颜色替换模式</span>
|
||||
</div>
|
||||
|
||||
{colorReplaceState.step === 'select-source' ? (
|
||||
<div className="text-xs text-purple-600 dark:text-purple-400">
|
||||
<p className="mb-1">步骤 1/2:点击图中要被替换的颜色</p>
|
||||
<p className="text-gray-500 dark:text-gray-400">选择后将高亮显示该颜色的所有位置</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-xs text-purple-600 dark:text-purple-400">
|
||||
<p className="mb-1">步骤 2/2:从下方色板选择替换成的颜色</p>
|
||||
<div className="flex items-center justify-center gap-2 mt-2">
|
||||
<span className="text-gray-500 dark:text-gray-400">被替换的颜色:</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<span
|
||||
className="inline-block w-4 h-4 rounded border border-gray-400 dark:border-gray-500"
|
||||
style={{ backgroundColor: colorReplaceState.sourceColor?.color }}
|
||||
></span>
|
||||
<span className="font-mono text-xs">
|
||||
{selectedColorSystem ? getDisplayColorKey(colorReplaceState.sourceColor?.color || '', selectedColorSystem) : colorReplaceState.sourceColor?.key}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 一键擦除状态提示 */}
|
||||
{isEraseMode && (
|
||||
<div className="p-3 border-b border-orange-100 dark:border-orange-800 bg-orange-50 dark:bg-orange-900/20">
|
||||
<div className="text-center">
|
||||
<div className="flex items-center justify-center gap-2 mb-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-orange-600 dark:text-orange-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
<span className="text-sm font-medium text-orange-700 dark:text-orange-300">背景擦除模式</span>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-orange-600 dark:text-orange-400">
|
||||
<p className="mb-1">点击图中任意颜色,删除整个颜色块</p>
|
||||
<p className="text-gray-500 dark:text-gray-400">使用洪水填充算法,一次性擦除连通的相同颜色区域</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 橡皮擦选中状态提示 */}
|
||||
{selectedColor?.key === transparentKey && !isEraseMode && !colorReplaceState?.isActive && (
|
||||
<div className="p-3 border-b border-gray-100 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50">
|
||||
<div className="text-center">
|
||||
<div className="flex items-center justify-center gap-2 mb-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-gray-600 dark:text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">橡皮擦模式</span>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-gray-600 dark:text-gray-400">
|
||||
<p className="mb-1">点击图中任意位置清除单个格子</p>
|
||||
<p className="text-gray-500 dark:text-gray-400">逐个删除不需要的颜色,不会影响其他格子</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 颜色按钮区域 */}
|
||||
<div className="flex flex-wrap justify-center gap-2 p-2">
|
||||
{/* 一键擦除按钮 */}
|
||||
@@ -111,6 +200,31 @@ const ColorPalette: React.FC<ColorPaletteProps> = ({
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* 颜色替换按钮 */}
|
||||
{onColorReplaceToggle && (
|
||||
<button
|
||||
onClick={onColorReplaceToggle}
|
||||
className={`w-12 h-12 rounded border-2 flex-shrink-0 transition-transform transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-blue-400 dark:focus:ring-blue-500 flex items-center justify-center ${
|
||||
colorReplaceState?.isActive
|
||||
? 'border-purple-500 bg-purple-100 dark:bg-purple-900 ring-2 ring-offset-1 ring-purple-400 dark:ring-purple-500 scale-110 shadow-md'
|
||||
: 'border-purple-300 dark:border-purple-600 bg-purple-100 dark:bg-purple-800 hover:border-purple-500 dark:hover:border-purple-400'
|
||||
}`}
|
||||
title={colorReplaceState?.isActive ? '退出颜色替换模式' : '颜色替换 (将图中A颜色全部替换为B颜色)'}
|
||||
aria-label={colorReplaceState?.isActive ? '退出颜色替换模式' : '开启颜色替换模式'}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={`h-5 w-5 ${colorReplaceState?.isActive ? 'text-purple-600 dark:text-purple-400' : 'text-purple-600 dark:text-purple-400'}`}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{colorsToShow.map((colorData) => {
|
||||
// 检查当前颜色是否是透明/橡皮擦
|
||||
const isTransparent = transparentKey && colorData.key === transparentKey;
|
||||
@@ -137,7 +251,16 @@ const ColorPalette: React.FC<ColorPaletteProps> = ({
|
||||
<button
|
||||
key={colorData.key}
|
||||
onClick={() => {
|
||||
// 颜色替换模式下的特殊处理
|
||||
if (colorReplaceState?.isActive && colorReplaceState.step === 'select-target' && !isTransparent && onColorReplace && colorReplaceState.sourceColor) {
|
||||
// 步骤2:选择目标颜色并执行替换
|
||||
onColorReplace(colorReplaceState.sourceColor, colorData);
|
||||
return;
|
||||
}
|
||||
|
||||
// 正常的颜色选择逻辑
|
||||
onColorSelect(colorData);
|
||||
|
||||
// 如果不是透明颜色且有高亮回调,触发高亮效果
|
||||
if (!isTransparent && onHighlightColor) {
|
||||
onHighlightColor(colorData.color);
|
||||
|
||||
Reference in New Issue
Block a user