diff --git a/app/components/home.tsx b/app/components/home.tsx
index 465ad0f1e..f069b1fc4 100644
--- a/app/components/home.tsx
+++ b/app/components/home.tsx
@@ -29,6 +29,9 @@ import { AuthPage } from "./auth";
import { getClientConfig } from "../config/client";
import { type ClientApi, getClientApi } from "../client/api";
import { useAccessStore } from "../store";
+import { useSyncStore } from "../store/sync";
+import { showToast } from "./ui-lib";
+import Locale from "@/app/locales";
export function Loading(props: { noLogo?: boolean }) {
return (
@@ -149,6 +152,40 @@ export function WindowContent(props: { children: React.ReactNode }) {
);
}
+function useSyncOnStart() {
+ const syncStore = useSyncStore();
+ const storeHasHydrated = useSyncStore((s) => s._hasHydrated);
+ useEffect(() => {
+ let running = true;
+ setTimeout(async () => {
+ if (
+ !(
+ storeHasHydrated &&
+ running &&
+ syncStore.cloudSync() &&
+ syncStore.autoSync.onStart
+ )
+ ) {
+ return;
+ }
+ const dismissSyncingToast = showToast(Locale.Settings.Sync.IsSyncing);
+ try {
+ await syncStore.sync();
+ dismissSyncingToast();
+ showToast(Locale.Settings.Sync.Success);
+ } catch (e: unknown) {
+ dismissSyncingToast();
+ showToast(Locale.Settings.Sync.Fail);
+ console.error("[Sync]", e);
+ }
+ });
+ return () => {
+ running = false;
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [storeHasHydrated]);
+}
+
function Screen() {
const config = useAppConfig();
const location = useLocation();
@@ -165,6 +202,7 @@ function Screen() {
useEffect(() => {
loadAsyncGoogleFont();
}, []);
+ useSyncOnStart();
if (isArtifact) {
return (
diff --git a/app/components/settings.tsx b/app/components/settings.tsx
index e2666b551..c44b4a7e2 100644
--- a/app/components/settings.tsx
+++ b/app/components/settings.tsx
@@ -477,6 +477,21 @@ function SyncConfigModal(props: { onClose?: () => void }) {
)}
+
+
+
+ {
+ syncStore.update(
+ (config) =>
+ (config.autoSync.onStart = e.currentTarget.checked),
+ );
+ }}
+ />
+
+
);
diff --git a/app/components/ui-lib.tsx b/app/components/ui-lib.tsx
index 4af37dbba..5d52bef59 100644
--- a/app/components/ui-lib.tsx
+++ b/app/components/ui-lib.tsx
@@ -229,13 +229,19 @@ export function showToast(
content: string,
action?: ToastProps["action"],
delay = 3000,
-) {
+): () => void {
const div = document.createElement("div");
div.className = styles.show;
document.body.appendChild(div);
const root = createRoot(div);
+ let closeCalled = false;
const close = () => {
+ if (closeCalled) {
+ return;
+ } else {
+ closeCalled = true;
+ }
div.classList.add(styles.hide);
setTimeout(() => {
@@ -249,6 +255,8 @@ export function showToast(
}, delay);
root.render();
+
+ return close;
}
export type InputProps = React.HTMLProps & {
diff --git a/app/locales/cn.ts b/app/locales/cn.ts
index 9712593c6..7e8892e71 100644
--- a/app/locales/cn.ts
+++ b/app/locales/cn.ts
@@ -223,6 +223,7 @@ const cn = {
CloudState: "云端数据",
NotSyncYet: "还没有进行过同步",
Success: "同步成功",
+ IsSyncing: "正在同步...",
Fail: "同步失败",
Config: {
@@ -254,6 +255,10 @@ const cn = {
UserName: "备份名称",
Password: "UpStash Redis REST Token",
},
+
+ AutoSync: {
+ OnStartup: "启动时自动同步",
+ },
},
LocalState: "本地数据",
diff --git a/app/locales/en.ts b/app/locales/en.ts
index ac8d3aed2..d4fe441ea 100644
--- a/app/locales/en.ts
+++ b/app/locales/en.ts
@@ -225,6 +225,7 @@ const en: LocaleType = {
CloudState: "Last Update",
NotSyncYet: "Not sync yet",
Success: "Sync Success",
+ IsSyncing: "Sync in progress...",
Fail: "Sync Fail",
Config: {
@@ -257,6 +258,10 @@ const en: LocaleType = {
UserName: "Backup Name",
Password: "UpStash Redis REST Token",
},
+
+ AutoSync: {
+ OnStartup: "Sync on startup",
+ },
},
LocalState: "Local Data",
diff --git a/app/store/sync.ts b/app/store/sync.ts
index 8477c1e4b..c297699df 100644
--- a/app/store/sync.ts
+++ b/app/store/sync.ts
@@ -27,18 +27,22 @@ const DEFAULT_SYNC_STATE = {
useProxy: true,
proxyUrl: ApiPath.Cors as string,
- webdav: {
+ [ProviderType.WebDAV]: {
endpoint: "",
username: "",
password: "",
},
- upstash: {
+ [ProviderType.UpStash]: {
endpoint: "",
username: STORAGE_KEY,
apiKey: "",
},
+ autoSync: {
+ onStart: false,
+ },
+
lastSyncTime: 0,
lastProvider: "",
};
@@ -46,9 +50,14 @@ const DEFAULT_SYNC_STATE = {
export const useSyncStore = createPersistStore(
DEFAULT_SYNC_STATE,
(set, get) => ({
- cloudSync() {
+ cloudSync(): boolean {
const config = get()[get().provider];
- return Object.values(config).every((c) => c.toString().length > 0);
+ if (!config) {
+ return false;
+ }
+ return Object.values(config).every(
+ (c) => c != null && c.toString().length > 0,
+ );
},
markSyncTime() {
@@ -126,7 +135,7 @@ export const useSyncStore = createPersistStore(
}),
{
name: StoreKey.Sync,
- version: 1.2,
+ version: 1.3,
migrate(persistedState, version) {
const newState = persistedState as typeof DEFAULT_SYNC_STATE;
@@ -144,6 +153,10 @@ export const useSyncStore = createPersistStore(
}
}
+ if (version < 1.3) {
+ newState.autoSync = { ...DEFAULT_SYNC_STATE.autoSync };
+ }
+
return newState as any;
},
},