新增自定义调色板导入导出功能,优化调色板编辑器交互体验。添加文件输入以支持导入配置,提供导出选中颜色的功能,并在界面中增加相应按钮。更新相关逻辑以确保用户操作的流畅性和有效性。

This commit is contained in:
zihanjian
2025-05-04 19:54:22 +08:00
parent ae2bb7ccc0
commit 18db27829e
2 changed files with 128 additions and 9 deletions

View File

@@ -157,7 +157,9 @@ export default function Home() {
const originalCanvasRef = useRef<HTMLCanvasElement>(null);
const pixelatedCanvasRef = useRef<HTMLCanvasElement>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
// const longPressTimerRef = useRef<NodeJS.Timeout | null>(null);
// ++ 添加: Ref for import file input ++
const importPaletteInputRef = useRef<HTMLInputElement>(null);
//const longPressTimerRef = useRef<NodeJS.Timeout | null>(null);
// ++ Re-add touch refs needed for tooltip logic ++
//const touchStartPosRef = useRef<{ x: number; y: number; pageX: number; pageY: number } | null>(null);
//const touchMovedRef = useRef<boolean>(false);
@@ -1057,9 +1059,10 @@ export default function Home() {
paletteOptions[typedPresetKey].keys || []
);
setCustomPaletteSelections(newSelections);
setSelectedPaletteKeySet(typedPresetKey);
setIsCustomPalette(false);
setIsCustomPaletteEditorOpen(false);
setSelectedPaletteKeySet(typedPresetKey); // 同步更新预设选择状态
setIsCustomPalette(false); // 应用预设后,标记为非自定义(除非用户再次修改)
// 不要在这里关闭编辑器,让用户可以继续编辑
// setIsCustomPaletteEditorOpen(false);
};
// 保存自定义色板并应用
@@ -1074,6 +1077,89 @@ export default function Home() {
setSelectedColor(null);
};
// ++ 新增:导出自定义色板配置 ++
const handleExportCustomPalette = () => {
const selectedKeys = Object.entries(customPaletteSelections)
.filter(([, isSelected]) => isSelected)
.map(([key]) => key);
if (selectedKeys.length === 0) {
alert("当前没有选中的颜色,无法导出。");
return;
}
const blob = new Blob([JSON.stringify({ selectedKeys }, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'custom-perler-palette.json';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
// ++ 新增:处理导入的色板文件 ++
const handleImportPaletteFile = (event: ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
try {
const content = e.target?.result as string;
const data = JSON.parse(content);
if (!data || !Array.isArray(data.selectedKeys)) {
throw new Error("无效的文件格式:缺少 'selectedKeys' 数组。");
}
const importedKeys = data.selectedKeys as string[];
const validKeys = new Set(allPaletteKeys);
const validImportedKeys = importedKeys.filter(key => validKeys.has(key));
const invalidKeys = importedKeys.filter(key => !validKeys.has(key));
if (invalidKeys.length > 0) {
console.warn("导入时发现无效的颜色key:", invalidKeys);
alert(`导入完成,但以下色号无效已被忽略:\n${invalidKeys.join(', ')}`);
}
if (validImportedKeys.length === 0) {
alert("导入的文件中不包含任何有效的色号。");
return;
}
// 基于有效导入的key创建新的selections对象
const newSelections = presetToSelections(allPaletteKeys, validImportedKeys);
setCustomPaletteSelections(newSelections);
setIsCustomPalette(true); // 标记为自定义
alert(`成功导入 ${validImportedKeys.length} 个色号!`);
} catch (error) {
console.error("导入色板配置失败:", error);
alert(`导入失败: ${error instanceof Error ? error.message : '未知错误'}`);
} finally {
// 重置文件输入,以便可以再次导入相同的文件
if (event.target) {
event.target.value = '';
}
}
};
reader.onerror = () => {
alert("读取文件失败。");
// 重置文件输入
if (event.target) {
event.target.value = '';
}
};
reader.readAsText(file);
};
// ++ 新增:触发导入文件选择 ++
const triggerImportPalette = () => {
importPaletteInputRef.current?.click();
};
return (
<>
{/* 添加自定义动画样式 */}
@@ -1321,8 +1407,16 @@ export default function Home() {
{/* 自定义色板编辑器弹窗 - 这是新增的部分 */}
{isCustomPaletteEditorOpen && (
<div className="fixed inset-0 bg-black bg-opacity-50 backdrop-blur-sm z-50 flex justify-center items-center p-4">
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-2xl w-full max-w-4xl max-h-[90vh] overflow-hidden">
<div className="p-4 sm:p-6">
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-2xl w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
{/* 添加隐藏的文件输入框 */}
<input
type="file"
accept=".json"
ref={importPaletteInputRef}
onChange={handleImportPaletteFile}
className="hidden"
/>
<div className="p-4 sm:p-6 flex-1 overflow-y-auto"> {/* 让内容区域可滚动 */}
<CustomPaletteEditor
allColors={fullBeadPalette}
currentSelections={customPaletteSelections}
@@ -1331,6 +1425,9 @@ export default function Home() {
onSaveCustomPalette={handleSaveCustomPalette}
onClose={() => setIsCustomPaletteEditorOpen(false)}
paletteOptions={paletteOptions}
// ++ 传递新的处理函数 ++
onExportCustomPalette={handleExportCustomPalette}
onImportCustomPalette={triggerImportPalette}
/>
</div>
</div>

View File

@@ -36,6 +36,8 @@ interface CustomPaletteEditorProps {
onSaveCustomPalette: () => void;
onClose: () => void;
paletteOptions: Record<string, { name: string; keys: string[] }>;
onExportCustomPalette: () => void;
onImportCustomPalette: () => void;
}
const CustomPaletteEditor: React.FC<CustomPaletteEditorProps> = ({
@@ -45,7 +47,9 @@ const CustomPaletteEditor: React.FC<CustomPaletteEditorProps> = ({
onApplyPreset,
onSaveCustomPalette,
onClose,
paletteOptions
paletteOptions,
onExportCustomPalette,
onImportCustomPalette,
}) => {
// 用于跟踪当前展开的颜色组
const [expandedGroups, setExpandedGroups] = useState<Record<string, boolean>>({});
@@ -91,7 +95,7 @@ const CustomPaletteEditor: React.FC<CustomPaletteEditorProps> = ({
};
return (
<div className="flex flex-col h-full max-h-[80vh]">
<div className="flex flex-col h-full max-h-[calc(90vh-80px)]">
{/* 头部 */}
<div className="flex justify-between items-center border-b dark:border-gray-700 pb-3 mb-3">
<h2 className="text-lg font-semibold text-gray-800 dark:text-gray-100 flex items-center">
@@ -158,7 +162,7 @@ const CustomPaletteEditor: React.FC<CustomPaletteEditorProps> = ({
</div>
{/* 快捷操作按钮 */}
<div className="flex flex-wrap gap-2 mb-4">
<div className="flex flex-wrap gap-2 mb-4 items-center">
<button
onClick={() => toggleAllColors(true)}
className="px-3 py-1.5 text-xs bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300 rounded-md hover:bg-green-200 dark:hover:bg-green-900/50"
@@ -171,6 +175,24 @@ const CustomPaletteEditor: React.FC<CustomPaletteEditorProps> = ({
>
</button>
<button
onClick={onImportCustomPalette}
className="px-3 py-1.5 text-xs bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300 rounded-md hover:bg-blue-200 dark:hover:bg-blue-900/50 flex items-center gap-1"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
</svg>
</button>
<button
onClick={onExportCustomPalette}
className="px-3 py-1.5 text-xs bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300 rounded-md hover:bg-purple-200 dark:hover:bg-purple-900/50 flex items-center gap-1"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
</button>
</div>
{/* 颜色列表 */}