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) => {