三栏布局 - ResizeObserver 等比例分配替代固定默认值恢复 - 修复拖拽+窗口调整竞态卡死(needsRecalcRef 延迟补算) - containerRef.current 动态读取 DOM 替代闭包 el Seedance2Content - 从占位文本重构为内容编排控件(文本+图片/视频/音频上传) - 根据 spec.image_files/video_files/audio_files 动态渲染上传区域 - 值 JSON 序列化 → 提交时转 ContentItem[](text/image_url/video_url/audio_url) 图片上传兼容视频文件 - buildAccept 根据 file_filter 区分 image/video 前缀 - createMediaBeforeUpload 动态放行 video/* MIME - 公共模块 media-upload-utils.ts 本地存储基建 - sql.js (WASM SQLite) + safeStorage 加密持久化 - 8个IPC文件操作通道 + resolveSafe 路径穿越防护 - <userData>/heixiu-data/ 沙箱 + JWT用户隔离 - 7表DDL + 游标分页 + LRU媒体淘汰 其他 - 修复 errorMessageRef 渲染期写入警告 - 媒体预览回退直接URL(<img>无CORS限制)+ output_type类型补充 - CSP wasm-unsafe-eval + https: CDN媒体源
144 lines
3.9 KiB
TypeScript
144 lines
3.9 KiB
TypeScript
// ============================================================
|
||
// SettingsProvider — 设置项集中状态管理 Context + Provider + Hook
|
||
//
|
||
// 职责:
|
||
// - 存储路径的集中管理(read/write/clear)
|
||
// - 持久化到 localStorage,遵循 ele-heixiu-* 键名规范
|
||
// - 全局组件通过 useSettings() 消费
|
||
// ============================================================
|
||
|
||
import { createContext, useContext, useState, useCallback, type ReactNode } from 'react';
|
||
import { setOutputPath as saveOutputPath, setTeamRepoPath as saveTeamRepoPath } from '@/infrastructure/storage';
|
||
|
||
// ---------- localStorage 键 ----------
|
||
|
||
const OUTPUT_PATH_KEY = 'ele-heixiu-output-path';
|
||
const TEAM_REPO_PATH_KEY = 'ele-heixiu-team-repo-path';
|
||
|
||
// ---------- Context 类型 ----------
|
||
|
||
export interface SettingsContextValue {
|
||
/** 大模型产物存储仓库路径 */
|
||
outputPath: string;
|
||
/** 团队创作存储仓库路径(企业版) */
|
||
teamRepoPath: string;
|
||
/** 设置产物输出路径 */
|
||
setOutputPath: (path: string) => void;
|
||
/** 设置团队创作仓库路径 */
|
||
setTeamRepoPath: (path: string) => void;
|
||
/** 清除产物输出路径 */
|
||
clearOutputPath: () => void;
|
||
/** 清除团队创作仓库路径 */
|
||
clearTeamRepoPath: () => void;
|
||
}
|
||
|
||
export const SettingsContext = createContext<SettingsContextValue | null>(null);
|
||
|
||
// ---------- 工具函数(localStorage 读写)----------
|
||
|
||
export function readStoredOutputPath(): string {
|
||
try {
|
||
return localStorage.getItem(OUTPUT_PATH_KEY) || '';
|
||
} catch {
|
||
return '';
|
||
}
|
||
}
|
||
|
||
export function writeStoredOutputPath(path: string): void {
|
||
try {
|
||
localStorage.setItem(OUTPUT_PATH_KEY, path);
|
||
} catch {
|
||
/* 静默忽略 */
|
||
}
|
||
}
|
||
|
||
export function clearStoredOutputPath(): void {
|
||
try {
|
||
localStorage.removeItem(OUTPUT_PATH_KEY);
|
||
} catch {
|
||
/* 静默忽略 */
|
||
}
|
||
}
|
||
|
||
export function readStoredTeamRepoPath(): string {
|
||
try {
|
||
return localStorage.getItem(TEAM_REPO_PATH_KEY) || '';
|
||
} catch {
|
||
return '';
|
||
}
|
||
}
|
||
|
||
export function writeStoredTeamRepoPath(path: string): void {
|
||
try {
|
||
localStorage.setItem(TEAM_REPO_PATH_KEY, path);
|
||
} catch {
|
||
/* 静默忽略 */
|
||
}
|
||
}
|
||
|
||
export function clearStoredTeamRepoPath(): void {
|
||
try {
|
||
localStorage.removeItem(TEAM_REPO_PATH_KEY);
|
||
} catch {
|
||
/* 静默忽略 */
|
||
}
|
||
}
|
||
|
||
// ---------- Consumer Hook ----------
|
||
|
||
export function useSettings(): SettingsContextValue {
|
||
const ctx = useContext(SettingsContext);
|
||
if (!ctx) {
|
||
throw new Error('useSettings() 必须在 <SettingsProvider> 内部调用');
|
||
}
|
||
return ctx;
|
||
}
|
||
|
||
// ---------- Provider 组件 ----------
|
||
|
||
interface SettingsProviderProps {
|
||
children: ReactNode;
|
||
}
|
||
|
||
export function SettingsProvider({ children }: SettingsProviderProps) {
|
||
const [outputPath, setOutputPathState] = useState<string>(readStoredOutputPath);
|
||
const [teamRepoPath, setTeamRepoPathState] = useState<string>(readStoredTeamRepoPath);
|
||
|
||
// ---------- outputPath ----------
|
||
|
||
const setOutputPath = useCallback((path: string) => {
|
||
setOutputPathState(path);
|
||
writeStoredOutputPath(path);
|
||
try { saveOutputPath(path); } catch { /* db 未初始化则跳过 */ }
|
||
}, []);
|
||
|
||
const clearOutputPath = useCallback(() => {
|
||
setOutputPathState('');
|
||
clearStoredOutputPath();
|
||
}, []);
|
||
|
||
// ---------- teamRepoPath ----------
|
||
|
||
const setTeamRepoPath = useCallback((path: string) => {
|
||
setTeamRepoPathState(path);
|
||
writeStoredTeamRepoPath(path);
|
||
try { saveTeamRepoPath(path); } catch { /* db 未初始化则跳过 */ }
|
||
}, []);
|
||
|
||
const clearTeamRepoPath = useCallback(() => {
|
||
setTeamRepoPathState('');
|
||
clearStoredTeamRepoPath();
|
||
}, []);
|
||
|
||
const value: SettingsContextValue = {
|
||
outputPath,
|
||
teamRepoPath,
|
||
setOutputPath,
|
||
setTeamRepoPath,
|
||
clearOutputPath,
|
||
clearTeamRepoPath,
|
||
};
|
||
|
||
return <SettingsContext.Provider value={value}>{children}</SettingsContext.Provider>;
|
||
}
|