From 8d61a7e9440df16fa0f50612847263bfe009a08c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=5FMr=E4=B8=B6s=20=E3=82=9B=F0=9F=98=81=C2=AE?=
<31917567+ma123456mai@users.noreply.github.com>
Date: Sat, 10 Oct 2026 15:15:48 +0800
Subject: [PATCH] feat(sync): extend auto-upload interval range and improve
hints
---
.../editor/EditorSettingsDialog.vue | 27 +++++++++++-
.../src/composables/useWebDavAutoUpload.ts | 35 ++++++++++++---
apps/desktop/src/i18n/locales/az.ts | 5 +++
apps/desktop/src/i18n/locales/en.ts | 5 +++
apps/desktop/src/i18n/locales/es.ts | 5 +++
apps/desktop/src/i18n/locales/id.ts | 5 +++
apps/desktop/src/i18n/locales/it.ts | 5 +++
apps/desktop/src/i18n/locales/ja.ts | 5 +++
apps/desktop/src/i18n/locales/ko.ts | 5 +++
apps/desktop/src/i18n/locales/pt-BR.ts | 5 +++
apps/desktop/src/i18n/locales/ru.ts | 5 +++
apps/desktop/src/i18n/locales/tr.ts | 5 +++
apps/desktop/src/i18n/locales/zh-CN.ts | 5 +++
apps/desktop/src/i18n/locales/zh-TW.ts | 5 +++
.../lib/webdav/webdavAutoUploadConfig.spec.ts | 25 +++++++++++
.../src/lib/webdav/webdavAutoUploadConfig.ts | 44 ++++++++++++++++++-
apps/desktop/vite.config.ts | 1 +
packages/app-tests/webdavAutoUpload.test.ts | 2 +
18 files changed, 185 insertions(+), 9 deletions(-)
create mode 100644 apps/desktop/src/lib/webdav/webdavAutoUploadConfig.spec.ts
diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue
index 078b253f8c..dee49d54fa 100644
--- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue
+++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue
@@ -339,7 +339,18 @@ import {
} from "@/lib/settings/settingsSearch";
import { findSettingsSearchHighlightTarget } from "@/lib/settings/settingsSearchHighlight";
import { LOCALE_OPTIONS } from "@/lib/app/localeOptions";
-import { DEFAULT_WEB_DAV_AUTO_UPLOAD_INTERVAL_MINUTES, DEFAULT_WEB_DAV_REMOTE_PATH, normalizedWebDavAutoUploadInterval, readSyncMethod, writeWebDavAutoUploadFields, writeWebDavBackupSelection, writeSyncMethod, type SyncMethod } from "@/lib/webdav/webdavAutoUploadConfig";
+import {
+ DEFAULT_WEB_DAV_AUTO_UPLOAD_INTERVAL_MINUTES,
+ DEFAULT_WEB_DAV_REMOTE_PATH,
+ MAX_WEB_DAV_AUTO_UPLOAD_INTERVAL_MINUTES,
+ formatWebDavAutoUploadInterval,
+ normalizedWebDavAutoUploadInterval,
+ readSyncMethod,
+ writeWebDavAutoUploadFields,
+ writeWebDavBackupSelection,
+ writeSyncMethod,
+ type SyncMethod,
+} from "@/lib/webdav/webdavAutoUploadConfig";
import { apiUrl, webPath } from "@/lib/common/webPath";
import { DEFAULT_DATA_GRID_FONT_FAMILY, DEFAULT_UI_FONT_FAMILY, normalizeCustomFontFamilyInput, readableFontFamily, SYSTEM_UI_FONT_FAMILY } from "@/lib/app/appFonts";
import { buildFontFamilyOptions, displayFontFamily, isPresetFontFamily, loadSystemFontNames } from "@/lib/app/fontFamilyOptions";
@@ -4460,6 +4471,15 @@ const webdavSecretsPassphrase = ref("");
const webdavHasSavedSecretsPassphrase = ref(false);
const webdavAutoUploadEnabled = ref(localStorage.getItem("dbx-webdav-auto-upload-enabled") === "true");
const webdavAutoUploadIntervalMinutes = ref(Number(localStorage.getItem("dbx-webdav-auto-upload-interval-minutes") || String(DEFAULT_WEB_DAV_AUTO_UPLOAD_INTERVAL_MINUTES)));
+const webdavAutoUploadIntervalHint = computed(() => {
+ const formatted = formatWebDavAutoUploadInterval(webdavAutoUploadIntervalMinutes.value, locale.value, {
+ minute: t("settings.syncAutoUploadUnitMinute"),
+ hour: t("settings.syncAutoUploadUnitHour"),
+ day: t("settings.syncAutoUploadUnitDay"),
+ });
+ const key = formatted.approximate ? "settings.syncAutoUploadScheduleHintApproximate" : "settings.syncAutoUploadScheduleHint";
+ return t(key, { interval: formatted.interval });
+});
const webdavBusy = ref<"" | "test" | "upload" | "download">("");
const webdavMessage = ref("");
const webdavError = ref(false);
@@ -9858,9 +9878,12 @@ LIMIT 100;
-
+
{{ t("settings.syncAutoUploadMinutes") }}
+
+ {{ webdavAutoUploadIntervalHint }}
+
{{ t("settings.syncAutoUploadDescription") }}
diff --git a/apps/desktop/src/composables/useWebDavAutoUpload.ts b/apps/desktop/src/composables/useWebDavAutoUpload.ts
index c7ccc3799e..2194c72e25 100644
--- a/apps/desktop/src/composables/useWebDavAutoUpload.ts
+++ b/apps/desktop/src/composables/useWebDavAutoUpload.ts
@@ -6,23 +6,46 @@ import { readWebDavAutoUploadConfig, readWebDavBackupSelection, WEB_DAV_AUTO_UPL
export function useWebDavAutoUpload() {
const settingsStore = useSettingsStore();
- let timer: ReturnType | undefined;
+ const maxTimeoutDelayMs = 2_147_000_000;
+ let timer: ReturnType | undefined;
+ let nextUploadAt = 0;
let uploading = false;
function clearTimer() {
- if (!timer) return;
- window.clearInterval(timer);
+ if (timer === undefined) return;
+ window.clearTimeout(timer);
timer = undefined;
}
+ function armTimer() {
+ const remainingMs = Math.max(0, nextUploadAt - Date.now());
+ timer = window.setTimeout(
+ () => {
+ if (Date.now() < nextUploadAt) {
+ armTimer();
+ return;
+ }
+
+ void runAutoUpload();
+ const config = readWebDavAutoUploadConfig();
+ if (!config.enabled || !config.webDavConfig) {
+ clearTimer();
+ return;
+ }
+ nextUploadAt = Date.now() + config.intervalMinutes * 60_000;
+ armTimer();
+ },
+ Math.min(remainingMs, maxTimeoutDelayMs),
+ );
+ }
+
function schedule() {
clearTimer();
const config = readWebDavAutoUploadConfig();
if (!config.enabled || !config.webDavConfig) return;
- timer = window.setInterval(() => {
- void runAutoUpload();
- }, config.intervalMinutes * 60_000);
+ nextUploadAt = Date.now() + config.intervalMinutes * 60_000;
+ armTimer();
}
async function runAutoUpload() {
diff --git a/apps/desktop/src/i18n/locales/az.ts b/apps/desktop/src/i18n/locales/az.ts
index aeacc0e49b..be8356566b 100644
--- a/apps/desktop/src/i18n/locales/az.ts
+++ b/apps/desktop/src/i18n/locales/az.ts
@@ -7690,6 +7690,11 @@ export default withEnglishFallback({
syncAutoUpload: "Anlıq nüsxələri avtomatik yüklə",
syncAutoUploadInterval: "Hər",
syncAutoUploadMinutes: "dəqiqə",
+ syncAutoUploadScheduleHint: "Snapshot hər {interval} avtomatik yüklənəcək.",
+ syncAutoUploadScheduleHintApproximate: "Snapshot təxminən hər {interval} avtomatik yüklənəcək.",
+ syncAutoUploadUnitMinute: "dəq.",
+ syncAutoUploadUnitHour: "saat",
+ syncAutoUploadUnitDay: "gün",
syncAutoUploadDescription: "DBX açıq olduğu müddətdə işləyir. Yerli yadda saxlanılmış SQL-in gözlənilmədən əvəz olunmaması üçün endirmələr yenə əl ilə təsdiq tələb edir.",
syncAutoUploadSuccess: "{path} yoluna {bytes} bayt avtomatik yükləndi.",
syncDownloadSuccess: "{path} yolundan {bytes} bayt endirildi və tətbiq edildi.",
diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts
index c5c514b391..70382f0777 100644
--- a/apps/desktop/src/i18n/locales/en.ts
+++ b/apps/desktop/src/i18n/locales/en.ts
@@ -8752,6 +8752,11 @@ export default {
syncAutoUpload: "Automatically upload snapshots",
syncAutoUploadInterval: "Every",
syncAutoUploadMinutes: "minutes",
+ syncAutoUploadScheduleHint: "A snapshot will be uploaded every {interval}.",
+ syncAutoUploadScheduleHintApproximate: "A snapshot will be uploaded about every {interval}.",
+ syncAutoUploadUnitMinute: "min",
+ syncAutoUploadUnitHour: "h",
+ syncAutoUploadUnitDay: "d",
syncAutoUploadDescription: "Runs while DBX is open. Downloads still require manual confirmation to avoid replacing local saved SQL unexpectedly.",
syncAutoUploadSuccess: "Auto-uploaded {bytes} bytes to {path}.",
syncDownloadSuccess: "Downloaded and applied {bytes} bytes from {path}.",
diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts
index a7f85bfe80..6e32bb4f7e 100644
--- a/apps/desktop/src/i18n/locales/es.ts
+++ b/apps/desktop/src/i18n/locales/es.ts
@@ -8018,6 +8018,11 @@ export default withEnglishFallback({
syncAutoUpload: "Subir instantáneas automáticamente",
syncAutoUploadInterval: "Cada",
syncAutoUploadMinutes: "minutos",
+ syncAutoUploadScheduleHint: "Se subirá una instantánea automáticamente cada {interval}.",
+ syncAutoUploadScheduleHintApproximate: "Se subirá una instantánea automáticamente aproximadamente cada {interval}.",
+ syncAutoUploadUnitMinute: "min",
+ syncAutoUploadUnitHour: "h",
+ syncAutoUploadUnitDay: "d",
syncAutoUploadDescription: "Se ejecuta mientras DBX está abierto. Las descargas aún requieren confirmación manual para evitar reemplazar SQL guardado localmente.",
syncAutoUploadSuccess: "Se subieron automáticamente {bytes} bytes a {path}.",
syncDownloadSuccess: "Se descargaron y aplicaron {bytes} bytes desde {path}.",
diff --git a/apps/desktop/src/i18n/locales/id.ts b/apps/desktop/src/i18n/locales/id.ts
index 21dc322124..a83b951b8b 100644
--- a/apps/desktop/src/i18n/locales/id.ts
+++ b/apps/desktop/src/i18n/locales/id.ts
@@ -8474,6 +8474,11 @@ export default withEnglishFallback({
syncAutoUpload: "Unggah snapshot secara otomatis",
syncAutoUploadInterval: "Setiap",
syncAutoUploadMinutes: "menit",
+ syncAutoUploadScheduleHint: "Snapshot akan diunggah otomatis setiap {interval}.",
+ syncAutoUploadScheduleHintApproximate: "Snapshot akan diunggah otomatis sekitar setiap {interval}.",
+ syncAutoUploadUnitMinute: "mnt",
+ syncAutoUploadUnitHour: "jam",
+ syncAutoUploadUnitDay: "hari",
syncAutoUploadDescription: "Berjalan selama DBX terbuka. Unduhan tetap memerlukan konfirmasi manual untuk menghindari penggantian SQL tersimpan lokal secara tidak terduga.",
syncAutoUploadSuccess: "Auto-unggah {bytes} byte ke {path}.",
syncDownloadSuccess: "Berhasil mengunduh dan menerapkan {bytes} byte dari {path}.",
diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts
index 8255b977c1..83f8f613c9 100644
--- a/apps/desktop/src/i18n/locales/it.ts
+++ b/apps/desktop/src/i18n/locales/it.ts
@@ -7795,6 +7795,11 @@ export default withEnglishFallback({
syncAutoUpload: "Carica automaticamente gli snapshot",
syncAutoUploadInterval: "Ogni",
syncAutoUploadMinutes: "minuti",
+ syncAutoUploadScheduleHint: "Lo snapshot verrà caricato automaticamente ogni {interval}.",
+ syncAutoUploadScheduleHintApproximate: "Lo snapshot verrà caricato automaticamente circa ogni {interval}.",
+ syncAutoUploadUnitMinute: "min",
+ syncAutoUploadUnitHour: "h",
+ syncAutoUploadUnitDay: "g",
syncAutoUploadDescription: "Viene eseguito mentre DBX è aperto. I download richiedono ancora conferma manuale per evitare di sostituire SQL salvati localmente.",
syncAutoUploadSuccess: "Caricati automaticamente {bytes} byte su {path}.",
syncDownloadSuccess: "Scaricati e applicati {bytes} byte da {path}.",
diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts
index 4418cd1c44..001b19b212 100644
--- a/apps/desktop/src/i18n/locales/ja.ts
+++ b/apps/desktop/src/i18n/locales/ja.ts
@@ -7808,6 +7808,11 @@ export default withEnglishFallback({
syncAutoUpload: "スナップショットを自動アップロード",
syncAutoUploadInterval: "間隔",
syncAutoUploadMinutes: "分",
+ syncAutoUploadScheduleHint: "スナップショットを {interval} ごとに自動アップロードします。",
+ syncAutoUploadScheduleHintApproximate: "スナップショットを約 {interval} ごとに自動アップロードします。",
+ syncAutoUploadUnitMinute: "分",
+ syncAutoUploadUnitHour: "時間",
+ syncAutoUploadUnitDay: "日",
syncAutoUploadDescription: "DBXを開いている間だけ実行されます。ローカルの保存済みSQLを誤って置き換えないよう、ダウンロードは手動確認が必要です。",
syncAutoUploadSuccess: "{bytes}バイトを{path}に自動アップロードしました。",
syncDownloadSuccess: "{path}から{bytes}バイトをダウンロードして適用しました。",
diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts
index 4cbf13eb39..75681b69d7 100644
--- a/apps/desktop/src/i18n/locales/ko.ts
+++ b/apps/desktop/src/i18n/locales/ko.ts
@@ -7635,6 +7635,11 @@ export default withEnglishFallback({
syncAutoUpload: "자동으로 스냅샷 업로드",
syncAutoUploadInterval: "매",
syncAutoUploadMinutes: "분",
+ syncAutoUploadScheduleHint: "스냅샷을 {interval}마다 자동 업로드합니다.",
+ syncAutoUploadScheduleHintApproximate: "스냅샷을 약 {interval}마다 자동 업로드합니다.",
+ syncAutoUploadUnitMinute: "분",
+ syncAutoUploadUnitHour: "시간",
+ syncAutoUploadUnitDay: "일",
syncAutoUploadDescription: "DBX가 열려 있는 동안 실행됩니다. 로컬에 저장된 SQL이 예기치 않게 교체되는 것을 방지하기 위해 다운로드는 여전히 수동 확인이 필요합니다.",
syncAutoUploadSuccess: "{path}에 {bytes}바이트를 자동 업로드했습니다.",
syncDownloadSuccess: "{path}에서 {bytes}바이트를 다운로드하여 적용했습니다.",
diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts
index c0ff6b9bb1..119e626996 100644
--- a/apps/desktop/src/i18n/locales/pt-BR.ts
+++ b/apps/desktop/src/i18n/locales/pt-BR.ts
@@ -7795,6 +7795,11 @@ export default withEnglishFallback({
syncAutoUpload: "Enviar snapshots automaticamente",
syncAutoUploadInterval: "A cada",
syncAutoUploadMinutes: "minutos",
+ syncAutoUploadScheduleHint: "Um snapshot será enviado automaticamente a cada {interval}.",
+ syncAutoUploadScheduleHintApproximate: "Um snapshot será enviado automaticamente aproximadamente a cada {interval}.",
+ syncAutoUploadUnitMinute: "min",
+ syncAutoUploadUnitHour: "h",
+ syncAutoUploadUnitDay: "d",
syncAutoUploadDescription: "Executa enquanto o DBX está aberto. Downloads ainda exigem confirmação manual para evitar substituir SQL salvos localmente.",
syncAutoUploadSuccess: "Enviados automaticamente {bytes} bytes para {path}.",
syncDownloadSuccess: "Baixados e aplicados {bytes} bytes de {path}.",
diff --git a/apps/desktop/src/i18n/locales/ru.ts b/apps/desktop/src/i18n/locales/ru.ts
index aae953e523..d5ac8d9fd1 100644
--- a/apps/desktop/src/i18n/locales/ru.ts
+++ b/apps/desktop/src/i18n/locales/ru.ts
@@ -9048,6 +9048,11 @@ export default withEnglishFallback({
syncAutoUpload: "Автоматически загружать снимки",
syncAutoUploadInterval: "Каждые",
syncAutoUploadMinutes: "мин.",
+ syncAutoUploadScheduleHint: "Снимок будет автоматически загружаться каждые {interval}.",
+ syncAutoUploadScheduleHintApproximate: "Снимок будет автоматически загружаться примерно каждые {interval}.",
+ syncAutoUploadUnitMinute: "мин.",
+ syncAutoUploadUnitHour: "ч",
+ syncAutoUploadUnitDay: "дн.",
syncAutoUploadDescription: "Выполняется, пока DBX открыт. Скачивание по-прежнему требует ручного подтверждения, чтобы не заменить локальный сохранённый SQL неожиданно.",
syncAutoUploadSuccess: "Автоматически загружено {bytes} байт в {path}.",
syncDownloadSuccess: "Скачано и применено {bytes} байт из {path}.",
diff --git a/apps/desktop/src/i18n/locales/tr.ts b/apps/desktop/src/i18n/locales/tr.ts
index 95356f7dba..1ac49e13a1 100644
--- a/apps/desktop/src/i18n/locales/tr.ts
+++ b/apps/desktop/src/i18n/locales/tr.ts
@@ -7588,6 +7588,11 @@ export default withEnglishFallback({
syncAutoUpload: "Anlık görüntüleri otomatik yükle",
syncAutoUploadInterval: "Her",
syncAutoUploadMinutes: "dakika",
+ syncAutoUploadScheduleHint: "Anlık görüntü her {interval} otomatik yüklenecek.",
+ syncAutoUploadScheduleHintApproximate: "Anlık görüntü yaklaşık her {interval} otomatik yüklenecek.",
+ syncAutoUploadUnitMinute: "dk",
+ syncAutoUploadUnitHour: "sa",
+ syncAutoUploadUnitDay: "gün",
syncAutoUploadDescription: "DBX açıkken çalışır. Yerel kayıtlı SQL'in beklenmedik biçimde değiştirilmemesi için indirmeler yine elle onay gerektirir.",
syncAutoUploadSuccess: "{path} konumuna otomatik olarak {bytes} bayt yüklendi.",
syncDownloadSuccess: "{path} konumundan {bytes} bayt indirildi ve uygulandı.",
diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts
index 510e1f8ba6..3834ee5422 100644
--- a/apps/desktop/src/i18n/locales/zh-CN.ts
+++ b/apps/desktop/src/i18n/locales/zh-CN.ts
@@ -8736,6 +8736,11 @@ export default withEnglishFallback({
syncAutoUpload: "自动上传快照",
syncAutoUploadInterval: "每",
syncAutoUploadMinutes: "分钟",
+ syncAutoUploadScheduleHint: "每 {interval} 自动上传一次快照。",
+ syncAutoUploadScheduleHintApproximate: "约每 {interval} 自动上传一次快照。",
+ syncAutoUploadUnitMinute: "分钟",
+ syncAutoUploadUnitHour: "小时",
+ syncAutoUploadUnitDay: "天",
syncAutoUploadDescription: "仅在 DBX 打开时运行。下载仍需手动确认,避免意外覆盖本地保存的 SQL。",
syncAutoUploadSuccess: "已自动上传 {bytes} 字节到 {path}。",
syncDownloadSuccess: "已从 {path} 下载并应用 {bytes} 字节。",
diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts
index 7774a5cf8e..6dafba6b2c 100644
--- a/apps/desktop/src/i18n/locales/zh-TW.ts
+++ b/apps/desktop/src/i18n/locales/zh-TW.ts
@@ -7142,6 +7142,11 @@ export default withEnglishFallback({
syncAutoUpload: "自動上傳快照",
syncAutoUploadInterval: "每",
syncAutoUploadMinutes: "分鐘",
+ syncAutoUploadScheduleHint: "每 {interval} 自動上傳一次快照。",
+ syncAutoUploadScheduleHintApproximate: "約每 {interval} 自動上傳一次快照。",
+ syncAutoUploadUnitMinute: "分鐘",
+ syncAutoUploadUnitHour: "小時",
+ syncAutoUploadUnitDay: "天",
syncAutoUploadDescription: "僅在 DBX 開啟時執行。下載仍需手動確認,避免意外覆蓋本機已儲存的 SQL。",
syncAutoUploadSuccess: "已自動上傳 {bytes} 位元組到 {path}。",
syncDownloadSuccess: "已從 {path} 下載並套用 {bytes} 位元組。",
diff --git a/apps/desktop/src/lib/webdav/webdavAutoUploadConfig.spec.ts b/apps/desktop/src/lib/webdav/webdavAutoUploadConfig.spec.ts
new file mode 100644
index 0000000000..008c6eb972
--- /dev/null
+++ b/apps/desktop/src/lib/webdav/webdavAutoUploadConfig.spec.ts
@@ -0,0 +1,25 @@
+import { describe, expect, it } from "vitest";
+import { DEFAULT_WEB_DAV_AUTO_UPLOAD_INTERVAL_MINUTES, MAX_WEB_DAV_AUTO_UPLOAD_INTERVAL_MINUTES, formatWebDavAutoUploadInterval, normalizedWebDavAutoUploadInterval } from "./webdavAutoUploadConfig";
+
+const zhCnUnits = { minute: "分钟", hour: "小时", day: "天" };
+
+describe("WebDAV auto-upload interval", () => {
+ it("keeps configured intervals in minutes and accepts up to one year", () => {
+ expect(normalizedWebDavAutoUploadInterval(90)).toBe(90);
+ expect(normalizedWebDavAutoUploadInterval(MAX_WEB_DAV_AUTO_UPLOAD_INTERVAL_MINUTES + 1)).toBe(MAX_WEB_DAV_AUTO_UPLOAD_INTERVAL_MINUTES);
+ expect(normalizedWebDavAutoUploadInterval("invalid")).toBe(DEFAULT_WEB_DAV_AUTO_UPLOAD_INTERVAL_MINUTES);
+ });
+
+ it.each([
+ [30, "30 分钟", false],
+ [60, "1 小时", false],
+ [90, "1 小时 30 分钟", false],
+ [95, "1 小时 35 分钟", false],
+ [1439, "23 小时 59 分钟", false],
+ [1440, "1 天", false],
+ [1441, "1 天 1 分钟", false],
+ [1530, "1 天 1 小时 30 分钟", false],
+ ])("formats %i minutes as a readable interval", (minutes, interval, approximate) => {
+ expect(formatWebDavAutoUploadInterval(minutes, "zh-CN", zhCnUnits)).toEqual({ interval, approximate });
+ });
+});
diff --git a/apps/desktop/src/lib/webdav/webdavAutoUploadConfig.ts b/apps/desktop/src/lib/webdav/webdavAutoUploadConfig.ts
index 84606aa2b4..511fc1a45c 100644
--- a/apps/desktop/src/lib/webdav/webdavAutoUploadConfig.ts
+++ b/apps/desktop/src/lib/webdav/webdavAutoUploadConfig.ts
@@ -7,6 +7,7 @@ export const WEB_DAV_AUTO_UPLOAD_STORAGE_KEYS = ["dbx-webdav-endpoint", "dbx-web
export const DEFAULT_WEB_DAV_REMOTE_PATH = "DBX/sync/snapshot.json";
export const DEFAULT_WEB_DAV_AUTO_UPLOAD_INTERVAL_MINUTES = 30;
+export const MAX_WEB_DAV_AUTO_UPLOAD_INTERVAL_MINUTES = 365 * 24 * 60;
export type SyncMethod = "webdav" | "snippet" | "local";
@@ -28,7 +29,48 @@ export interface WebDavAutoUploadConfig {
export function normalizedWebDavAutoUploadInterval(value: unknown): number {
const numberValue = Number(value);
if (!Number.isFinite(numberValue)) return DEFAULT_WEB_DAV_AUTO_UPLOAD_INTERVAL_MINUTES;
- return Math.max(1, Math.min(1440, Math.round(numberValue)));
+ return Math.max(1, Math.min(MAX_WEB_DAV_AUTO_UPLOAD_INTERVAL_MINUTES, Math.round(numberValue)));
+}
+
+export interface WebDavAutoUploadIntervalUnits {
+ minute: string;
+ hour: string;
+ day: string;
+}
+
+export interface FormattedWebDavAutoUploadInterval {
+ interval: string;
+ approximate: boolean;
+}
+
+export function formatWebDavAutoUploadInterval(value: unknown, locale: string, units: WebDavAutoUploadIntervalUnits): FormattedWebDavAutoUploadInterval {
+ const minutes = normalizedWebDavAutoUploadInterval(value);
+ const formatNumber = (number: number) => new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }).format(number);
+ const formatHourMinutes = (hourMinutes: number) => {
+ const hours = Math.floor(hourMinutes / 60);
+ const remainderMinutes = hourMinutes % 60;
+ if (hours === 0) return { interval: `${formatNumber(remainderMinutes)} ${units.minute}`, approximate: false };
+ if (remainderMinutes === 0) return { interval: `${formatNumber(hours)} ${units.hour}`, approximate: false };
+ return { interval: `${formatNumber(hours)} ${units.hour} ${formatNumber(remainderMinutes)} ${units.minute}`, approximate: false };
+ };
+
+ if (minutes < 60) {
+ return { interval: `${formatNumber(minutes)} ${units.minute}`, approximate: false };
+ }
+ if (minutes < 24 * 60) {
+ return formatHourMinutes(minutes);
+ }
+
+ const days = Math.floor(minutes / (24 * 60));
+ const remainingMinutes = minutes % (24 * 60);
+ if (remainingMinutes === 0) {
+ return { interval: `${formatNumber(days)} ${units.day}`, approximate: false };
+ }
+ const remaining = formatHourMinutes(remainingMinutes);
+ return {
+ interval: `${formatNumber(days)} ${units.day} ${remaining.interval}`,
+ approximate: false,
+ };
}
export function readWebDavAutoUploadConfig(): WebDavAutoUploadConfig {
diff --git a/apps/desktop/vite.config.ts b/apps/desktop/vite.config.ts
index 9e5118e6ca..f0c56d7511 100644
--- a/apps/desktop/vite.config.ts
+++ b/apps/desktop/vite.config.ts
@@ -64,6 +64,7 @@ const backendUrl = process.env.DBX_BACKEND_URL || "http://localhost:4224";
export default defineConfig(async () => ({
root: import.meta.dirname,
+ cacheDir: process.env.DBX_DEV_CACHE_DIR,
base: viteBase,
plugins: [connectionTypesPlugin(), publicBasePathRedirectPlugin(publicBasePath), vue(), tailwindcss()],
resolve: {
diff --git a/packages/app-tests/webdavAutoUpload.test.ts b/packages/app-tests/webdavAutoUpload.test.ts
index 81eb22ba7c..2624520309 100644
--- a/packages/app-tests/webdavAutoUpload.test.ts
+++ b/packages/app-tests/webdavAutoUpload.test.ts
@@ -56,6 +56,8 @@ function installWindow() {
Object.defineProperty(globalThis, "window", {
configurable: true,
value: {
+ setTimeout: globalThis.setTimeout.bind(globalThis),
+ clearTimeout: globalThis.clearTimeout.bind(globalThis),
setInterval: globalThis.setInterval.bind(globalThis),
clearInterval: globalThis.clearInterval.bind(globalThis),
addEventListener: (type: string, listener: (event: Event) => void) => {