feat(webdav): add WebDAV synchronization configuration and functionality

This commit is contained in:
HouYunFei
2026-06-08 14:59:30 +08:00
parent dd9347e85d
commit 39a1e859b1
10 changed files with 845 additions and 4 deletions
+7
View File
@@ -28,10 +28,12 @@ type AssetBase<T extends AssetKind> = {
};
type AssetStore = {
hydrated: boolean;
assets: Asset[];
addAsset: (asset: Omit<Asset, "id" | "createdAt" | "updatedAt">) => string;
updateAsset: (id: string, patch: Partial<Omit<Asset, "id" | "createdAt">>) => void;
removeAsset: (id: string) => void;
replaceAssets: (assets: Asset[]) => void;
cleanupImages: (extra?: unknown) => void;
};
@@ -66,6 +68,7 @@ const assetStorage: PersistStorage<AssetStore> = {
export const useAssetStore = create<AssetStore>()(
persist(
(set, get) => ({
hydrated: false,
assets: [],
addAsset: (asset) => {
const now = new Date().toISOString();
@@ -83,6 +86,7 @@ export const useAssetStore = create<AssetStore>()(
get().cleanupImages({ assets });
return { assets };
}),
replaceAssets: (assets) => set({ assets }),
cleanupImages: (extra) => {
window.setTimeout(async () => {
const { useCanvasStore } = await import("@/app/(user)/canvas/stores/use-canvas-store");
@@ -95,6 +99,9 @@ export const useAssetStore = create<AssetStore>()(
name: ASSET_STORE_KEY,
storage: assetStorage,
partialize: (state) => ({ assets: state.assets }) as StorageValue<AssetStore>["state"],
onRehydrateStorage: () => () => {
useAssetStore.setState({ hydrated: true });
},
},
),
);
+33 -2
View File
@@ -36,6 +36,15 @@ export type AiConfig = {
canvasImageCount: string;
};
export type WebdavSyncConfig = {
proxyMode: "direct" | "nextjs";
url: string;
username: string;
password: string;
directory: string;
lastSyncedAt: string;
};
export const CONFIG_STORE_KEY = "infinite-canvas:ai_config_store";
export type ModelCapability = "image" | "video" | "text" | "audio";
@@ -68,13 +77,24 @@ export const defaultConfig: AiConfig = {
canvasImageCount: "3",
};
export const defaultWebdavSyncConfig: WebdavSyncConfig = {
proxyMode: "direct",
url: "",
username: "",
password: "",
directory: "infinite-canvas",
lastSyncedAt: "",
};
type ConfigStore = {
config: AiConfig;
webdav: WebdavSyncConfig;
publicSettings: AdminPublicSettings | null;
isPublicSettingsLoading: boolean;
isConfigOpen: boolean;
shouldPromptContinue: boolean;
updateConfig: <K extends keyof AiConfig>(key: K, value: AiConfig[K]) => void;
updateWebdavConfig: <K extends keyof WebdavSyncConfig>(key: K, value: WebdavSyncConfig[K]) => void;
loadPublicSettings: () => Promise<void>;
isAiConfigReady: (config: AiConfig, model: string) => boolean;
openConfigDialog: (shouldPromptContinue?: boolean) => void;
@@ -168,6 +188,7 @@ export const useConfigStore = create<ConfigStore>()(
persist(
(set, get) => ({
config: defaultConfig,
webdav: defaultWebdavSyncConfig,
publicSettings: null,
isPublicSettingsLoading: false,
isConfigOpen: false,
@@ -179,6 +200,13 @@ export const useConfigStore = create<ConfigStore>()(
[key]: value,
},
})),
updateWebdavConfig: (key, value) =>
set((state) => ({
webdav: {
...state.webdav,
[key]: value,
},
})),
loadPublicSettings: async () => {
if (get().isPublicSettingsLoading) return;
set({ isPublicSettingsLoading: true });
@@ -195,12 +223,15 @@ export const useConfigStore = create<ConfigStore>()(
}),
{
name: CONFIG_STORE_KEY,
partialize: (state) => ({ config: state.config }),
partialize: (state) => ({ config: state.config, webdav: state.webdav }),
merge: (persisted, current) => {
const persistedConfig = ((persisted as Partial<ConfigStore>).config || {}) as Partial<AiConfig>;
const persistedState = (persisted || {}) as Partial<ConfigStore>;
const persistedConfig = (persistedState.config || {}) as Partial<AiConfig>;
const persistedWebdav = (persistedState.webdav || {}) as Partial<WebdavSyncConfig>;
const config = { ...defaultConfig, ...persistedConfig };
return {
...current,
webdav: { ...defaultWebdavSyncConfig, ...persistedWebdav },
config: {
...config,
channelMode: config.channelMode || "remote",