feat(update): auto-replace stale downloads and relocate update toggle
Closes #9140
This commit is contained in:
16 files changed
+123
-61
No files matched your search
@@ -1735,7 +1735,6 @@ function resetDefaultsForTab(tab: SettingsCategory) {
|
||||
editGenerateSqlQuoteIdentifiers.value = DEFAULT_EDITOR_SETTINGS.generateSqlQuoteIdentifiers;
|
||||
editFormatSqlOnSqlFileSave.value = DEFAULT_EDITOR_SETTINGS.formatSqlOnSqlFileSave;
|
||||
editClickTableNavigationTarget.value = DEFAULT_EDITOR_SETTINGS.clickTableNavigationTarget;
|
||||
editUpdateNotificationsEnabled.value = DEFAULT_EDITOR_SETTINGS.updateNotificationsEnabled;
|
||||
editSidebarObjectInfoMode.value = DEFAULT_EDITOR_SETTINGS.sidebarObjectInfoMode;
|
||||
editSidebarAllowHorizontalScroll.value = DEFAULT_EDITOR_SETTINGS.sidebarAllowHorizontalScroll;
|
||||
editSidebarShowTooltips.value = DEFAULT_EDITOR_SETTINGS.sidebarShowTooltips;
|
||||
@@ -1790,6 +1789,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
|
||||
editSnippets.value = DEFAULT_SQL_SNIPPETS.map((s) => ({ ...s }));
|
||||
} else if (tab === "about") {
|
||||
editUpdateDownloadSource.value = DEFAULT_EDITOR_SETTINGS.updateDownloadSource;
|
||||
editUpdateNotificationsEnabled.value = DEFAULT_EDITOR_SETTINGS.updateNotificationsEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6217,16 +6217,6 @@ onUnmounted(() => {
|
||||
<Switch id="quit-on-close" v-model="editQuitOnClose" />
|
||||
</div>
|
||||
|
||||
<div class="settings-item flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
|
||||
<div class="space-y-1">
|
||||
<Label for="update-notifications-enabled">{{ t("settings.updateNotificationsEnabled") }}</Label>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ t("settings.updateNotificationsEnabledDescription") }}
|
||||
</p>
|
||||
</div>
|
||||
<Switch id="update-notifications-enabled" v-model="editUpdateNotificationsEnabled" />
|
||||
</div>
|
||||
|
||||
<div class="settings-appearance-group" data-icon-theme-settings>
|
||||
<Label>{{ t("settings.iconTheme") }}</Label>
|
||||
<div class="settings-appearance-choice-grid settings-icon-theme-grid">
|
||||
@@ -9398,8 +9388,17 @@ LIMIT 100;</pre
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-item rounded-lg border p-4">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="settings-item flex flex-col gap-4 rounded-lg border p-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="min-w-0 space-y-1">
|
||||
<Label for="update-notifications-enabled">{{ t("settings.updateNotificationsEnabled") }}</Label>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{{ t("settings.updateNotificationsEnabledDescription") }}
|
||||
</p>
|
||||
</div>
|
||||
<Switch id="update-notifications-enabled" v-model="editUpdateNotificationsEnabled" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-3 border-t border-border/60 pt-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="min-w-0 space-y-1">
|
||||
<Label>{{ t("settings.updateDownloadSource") }}</Label>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
@@ -9420,12 +9419,9 @@ LIMIT 100;</pre
|
||||
|
||||
<ChangelogPanel :checking-updates="props.checkingUpdates" @check-updates="emit('check-updates')" />
|
||||
|
||||
<div class="grid gap-3 sm:grid-cols-2">
|
||||
<div class="grid gap-3 sm:grid-cols-3">
|
||||
<button type="button" class="rounded-lg border p-4 text-left transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" @click="openExternalUrl('https://qm.qq.com/cgi-bin/qm/qr?k=&group_code=1087880322')">
|
||||
<div class="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
||||
{{ t("settings.community") }}
|
||||
</div>
|
||||
<div class="mt-3 flex items-center gap-2 text-sm font-medium">
|
||||
<div class="flex items-center gap-2 text-sm font-medium">
|
||||
<img
|
||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iODYiIHdpZHRoPSI4NiIgdmlld0JveD0iMCAwIDEyMCAxNDUiPjxwYXRoIGZpbGw9IiNmYWFiMDciIGQ9Ik02MC41MDMgMTQyLjIzN2MtMTIuNTMzIDAtMjQuMDM4LTQuMTk1LTMxLjQ0NS0xMC40Ni0zLjc2MiAxLjEyNC04LjU3NCAyLjkzMi0xMS42MSA1LjE3NS0yLjYgMS45MTgtMi4yNzUgMy44NzQtMS44MDcgNC42NjMgMi4wNTYgMy40NyAzNS4yNzMgMi4yMTYgNDQuODYyIDEuMTM2em0wIDBjMTIuNTM1IDAgMjQuMDM5LTQuMTk1IDMxLjQ0Ny0xMC40NiAzLjc2IDEuMTI0IDguNTczIDIuOTMyIDExLjYxIDUuMTc1IDIuNTk4IDEuOTE4IDIuMjc0IDMuODc0IDEuODA1IDQuNjYzLTIuMDU2IDMuNDctMzUuMjcyIDIuMjE2LTQ0Ljg2MiAxLjEzNnptMCAwIi8+PHBhdGggZD0iTTYwLjU3NiA2Ny4xMTljMjAuNjk4LS4xNCAzNy4yODYtNC4xNDcgNDIuOTA3LTUuNjgzIDEuMzQtLjM2NyAyLjA1Ni0xLjAyNCAyLjA1Ni0xLjAyNC4wMDUtLjE4OS4wODUtMy4zNy4wODUtNS4wMUMxMDUuNjI0IDI3Ljc2OCA5Mi41OC4wMDEgNjAuNSAwIDI4LjQyLjAwMSAxNS4zNzUgMjcuNzY5IDE1LjM3NSA1NS40MDFjMCAxLjY0Mi4wOCA0LjgyMi4wODYgNS4wMSAwIDAgLjU4My42MTUgMS42NS45MTMgNS4xOSAxLjQ0NCAyMi4wOSA1LjY1IDQzLjMxMiA1Ljc5NXptNTYuMjQ1IDIzLjAyYy0xLjI4My00LjEyOS0zLjAzNC04Ljk0NC00LjgwOC0xMy41NjggMCAwLTEuMDItLjEyNi0xLjUzNy4wMjMtMTUuOTEzIDQuNjIzLTM1LjIwMiA3LjU3LTQ5LjkgNy4zOTJoLS4xNTNjLTE0LjYxNi4xNzUtMzMuNzc0LTIuNzM3LTQ5LjYzNC03LjMxNS0uNjA2LS4xNzUtMS44MDItLjEtMS44MDItLjEtMS43NzQgNC42MjQtMy41MjUgOS40NC00LjgwOCAxMy41NjgtNi4xMTkgMTkuNjktNC4xMzYgMjcuODM4LTIuNjI3IDI4LjAyIDMuMjM5LjM5MiAxMi42MDYtMTQuODIxIDEyLjYwNi0xNC44MjEgMCAxNS40NTkgMTMuOTU3IDM5LjE5NSA0NS45MTggMzkuNDEzaC44NDhjMzEuOTYtLjIxOCA0NS45MTctMjMuOTU0IDQ1LjkxNy0zOS40MTMgMCAwIDkuMzY4IDE1LjIxMyAxMi42MDcgMTQuODIyIDEuNTA4LS4xODMgMy40OTEtOC4zMzItMi42MjctMjguMDIxIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTQ5LjA4NSA0MC44MjRjLTQuMzUyLjE5Ny04LjA3LTQuNzYtOC4zMDQtMTEuMDYzLS4yMzYtNi4zMDUgMy4wOTgtMTEuNTc2IDcuNDUtMTEuNzczIDQuMzQ3LS4xOTUgOC4wNjQgNC43NiA4LjMgMTEuMDY1LjIzOCA2LjMwNi0zLjA5NyAxMS41NzctNy40NDYgMTEuNzcxbTMxLjEzMy0xMS4wNjNjLS4yMzMgNi4zMDItMy45NTEgMTEuMjYtOC4zMDMgMTEuMDYzLTQuMzUtLjE5NS03LjY4NC01LjQ2NS03LjQ0Ni0xMS43Ny4yMzYtNi4zMDUgMy45NTItMTEuMjYgOC4zLTExLjA2NiA0LjM1Mi4xOTcgNy42ODYgNS40NjggNy40NDkgMTEuNzczIi8+PHBhdGggZmlsbD0iI2ZhYWIwNyIgZD0iTTg3Ljk1MiA0OS43MjVDODYuNzkgNDcuMTUgNzUuMDc3IDQ0LjI4IDYwLjU3OCA0NC4yOGgtLjE1NmMtMTQuNSAwLTI2LjIxMiAyLjg3LTI3LjM3NSA1LjQ0NmEuODYzLjg2MyAwIDAwLS4wODUuMzY3Ljg4Ljg4IDAgMDAuMTYuNDk2Yy45OCAxLjQyNyAxMy45ODUgOC40ODcgMjcuMyA4LjQ4N2guMTU2YzEzLjMxNCAwIDI2LjMxOS03LjA1OCAyNy4yOTktOC40ODdhLjg3My44NzMgMCAwMC4xNi0uNDk4Ljg1Ni44NTYgMCAwMC0uMDg1LS4zNjUiLz48cGF0aCBkPSJNNTQuNDM0IDI5Ljg1NGMuMTk5IDIuNDktMS4xNjcgNC43MDItMy4wNDYgNC45NDMtMS44ODMuMjQyLTMuNTY4LTEuNTgtMy43NjgtNC4wNy0uMTk3LTIuNDkyIDEuMTY3LTQuNzA0IDMuMDQzLTQuOTQ0IDEuODg2LS4yNDQgMy41NzQgMS41OCAzLjc3MSA0LjA3bTExLjk1Ni44MzNjLjM4NS0uNjg5IDMuMDA0LTQuMzEyIDguNDI3LTIuOTkzIDEuNDI1LjM0NyAyLjA4NC44NTcgMi4yMjMgMS4wNTcuMjA1LjI5Ni4yNjIuNzE4LjA1MyAxLjI4Ni0uNDEyIDEuMTI2LTEuMjYzIDEuMDk1LTEuNzM0Ljg3NS0uMzA1LS4xNDItNC4wODItMi42Ni03LjU2MiAxLjA5Ny0uMjQuMjU3LS42NjguMzQ2LTEuMDczLjA0LS40MDctLjMwOC0uNTc0LS45My0uMzM0LTEuMzYyIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTYwLjU3NiA4My4wOGgtLjE1M2MtOS45OTYuMTItMjIuMTE2LTEuMjA0LTMzLjg1NC0zLjUxOC0xLjAwNCA1LjgxOC0xLjYxIDEzLjEzMi0xLjA5IDIxLjg1MyAxLjMxNiAyMi4wNDMgMTQuNDA3IDM1LjkgMzQuNjE0IDM2LjFoLjgyYzIwLjIwOC0uMiAzMy4yOTgtMTQuMDU3IDM0LjYxNi0zNi4xLjUyLTguNzIzLS4wODctMTYuMDM1LTEuMDkyLTIxLjg1NC0xMS43MzkgMi4zMTUtMjMuODYyIDMuNjQtMzMuODYgMy41MTgiLz48cGF0aCBmaWxsPSIjZWIxOTIzIiBkPSJNMzIuMTAyIDgxLjIzNXYyMS42OTNzOS45MzcgMi4wMDQgMTkuODkzLjYxNlY4My41MzVjLTYuMzA3LS4zNTctMTMuMTA5LTEuMTUyLTE5Ljg5My0yLjMiLz48cGF0aCBmaWxsPSIjZWIxOTIzIiBkPSJNMTA1LjUzOSA2MC40MTJzLTE5LjMzIDYuMTAyLTQ0Ljk2MyA2LjI3NWgtLjE1M2MtMjUuNTkxLS4xNzItNDQuODk2LTYuMjU1LTQ0Ljk2Mi02LjI3NUw4Ljk4NyA3Ni41N2MxNi4xOTMgNC44ODIgMzYuMjYxIDguMDI4IDUxLjQzNiA3Ljg0NWguMTUzYzE1LjE3NS4xODMgMzUuMjQyLTIuOTYzIDUxLjQzNy03Ljg0NXptMCAwIi8+PC9zdmc+"
|
||||
alt="QQ"
|
||||
@@ -9437,10 +9433,7 @@ LIMIT 100;</pre
|
||||
<div class="mt-1 font-mono text-base">1087880322</div>
|
||||
</button>
|
||||
<button type="button" class="rounded-lg border p-4 text-left transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" @click="openExternalUrl('https://discord.gg/W7NyVDRt6a')">
|
||||
<div class="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
||||
{{ t("settings.community") }}
|
||||
</div>
|
||||
<div class="mt-3 flex items-center gap-2 text-sm font-medium">
|
||||
<div class="flex items-center gap-2 text-sm font-medium">
|
||||
<img src="https://cdn.simpleicons.org/discord/5865F2" alt="Discord" class="h-7 w-7 rounded-md bg-white p-1" />
|
||||
Discord
|
||||
<ExternalLink class="ml-auto h-3.5 w-3.5 text-muted-foreground" />
|
||||
@@ -9448,10 +9441,7 @@ LIMIT 100;</pre
|
||||
<div class="mt-1 text-sm text-primary">discord.gg/W7NyVDRt6a</div>
|
||||
</button>
|
||||
<button type="button" class="rounded-lg border p-4 text-left transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" @click="openExternalUrl('https://docs.qq.com/doc/DVVhMY0h1ekJqc0tz')">
|
||||
<div class="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
||||
{{ t("settings.community") }}
|
||||
</div>
|
||||
<div class="mt-3 flex items-center gap-2 text-sm font-medium">
|
||||
<div class="flex items-center gap-2 text-sm font-medium">
|
||||
<span class="flex h-7 w-7 items-center justify-center rounded-md bg-[#07C160] text-white">
|
||||
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path
|
||||
@@ -9467,10 +9457,7 @@ LIMIT 100;</pre
|
||||
</div>
|
||||
</button>
|
||||
<button type="button" class="rounded-lg border p-4 text-left transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" @click="openExternalUrl('https://github.com/t8y2/dbx')">
|
||||
<div class="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
||||
{{ t("settings.project") }}
|
||||
</div>
|
||||
<div class="mt-3 flex items-center gap-2 text-sm font-medium">
|
||||
<div class="flex items-center gap-2 text-sm font-medium">
|
||||
<img src="https://cdn.simpleicons.org/github/181717" alt="GitHub" class="h-7 w-7 rounded-md bg-white p-1" />
|
||||
{{ t("settings.openSource") }}
|
||||
<ExternalLink class="ml-auto h-3.5 w-3.5 text-muted-foreground" />
|
||||
@@ -9478,10 +9465,7 @@ LIMIT 100;</pre
|
||||
<div class="mt-1 text-sm text-primary">github.com/t8y2/dbx</div>
|
||||
</button>
|
||||
<button type="button" class="rounded-lg border p-4 text-left transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" @click="openExternalUrl('https://dbxio.com')">
|
||||
<div class="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
||||
{{ t("settings.project") }}
|
||||
</div>
|
||||
<div class="mt-3 flex items-center gap-2 text-sm font-medium">
|
||||
<div class="flex items-center gap-2 text-sm font-medium">
|
||||
<AppLogo class="h-7 w-7" />
|
||||
{{ t("settings.officialDocs") }}
|
||||
<ExternalLink class="ml-auto h-3.5 w-3.5 text-muted-foreground" />
|
||||
|
||||
@@ -101,7 +101,8 @@ describe("silent update lifecycle", () => {
|
||||
const updater = mount({ prepareForUpdate: prepare });
|
||||
await updater.initialize();
|
||||
await updater.installDownloadedUpdate();
|
||||
expect(mocks.checkForUpdates).not.toHaveBeenCalled();
|
||||
// Only the startup recheck runs (same latest version keeps the cache); installing never needs fresh metadata or a re-download.
|
||||
expect(mocks.checkForUpdates).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.downloadUpdate).not.toHaveBeenCalled();
|
||||
expect(mocks.installDownloadedUpdate).toHaveBeenCalledWith("cached", "1.1.0");
|
||||
expect(prepare.mock.invocationCallOrder[0]).toBeLessThan(mocks.installDownloadedUpdate.mock.invocationCallOrder[0]);
|
||||
@@ -291,4 +292,71 @@ describe("silent update lifecycle", () => {
|
||||
await vi.waitFor(() => expect(mocks.downloadUpdate).toHaveBeenCalledTimes(2));
|
||||
expect(updater.updateDownloaded.value).toBe(true);
|
||||
});
|
||||
|
||||
it("replaces a downloaded update when a newer version is released", async () => {
|
||||
const newer = { ...info, latest_version: "1.2.0", release_name: "v1.2.0" };
|
||||
const replacement = { ...cache, version: "1.2.0" };
|
||||
mocks.getDownloadedUpdate.mockResolvedValue(cache);
|
||||
mocks.checkForUpdates.mockResolvedValue(newer);
|
||||
mocks.downloadUpdate.mockResolvedValue(replacement);
|
||||
const updater = mount();
|
||||
await updater.initialize();
|
||||
await vi.waitFor(() => expect(mocks.downloadUpdate).toHaveBeenCalled());
|
||||
expect(mocks.discardDownloadedUpdate).toHaveBeenCalledWith("cached");
|
||||
expect(mocks.downloadUpdate).toHaveBeenCalledWith("official", "1.2.0", expect.any(String), "Changes");
|
||||
expect(updater.updateDownloaded.value).toBe(true);
|
||||
expect(updater.updateInfo.value?.latest_version).toBe("1.2.0");
|
||||
expect(updater.phase.value).toBe("ready");
|
||||
});
|
||||
|
||||
it("keeps a downloaded update when the released version is not newer", async () => {
|
||||
mocks.getDownloadedUpdate.mockResolvedValue(cache);
|
||||
mocks.checkForUpdates.mockResolvedValue({ ...info, latest_version: "1.1.0" });
|
||||
const updater = mount();
|
||||
await updater.initialize();
|
||||
await flush();
|
||||
expect(mocks.checkForUpdates).toHaveBeenCalled();
|
||||
expect(mocks.discardDownloadedUpdate).not.toHaveBeenCalled();
|
||||
expect(mocks.downloadUpdate).not.toHaveBeenCalled();
|
||||
expect(updater.phase.value).toBe("ready");
|
||||
expect(updater.updateDownloaded.value).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps a downloaded update when the remote reports an older version", async () => {
|
||||
const updater = mount();
|
||||
await updater.checkUpdates({ silent: true });
|
||||
expect(updater.updateDownloaded.value).toBe(true);
|
||||
mocks.checkForUpdates.mockResolvedValue({ ...info, latest_version: "1.0.5" });
|
||||
await updater.checkUpdates({ silent: true });
|
||||
expect(mocks.discardDownloadedUpdate).not.toHaveBeenCalled();
|
||||
expect(mocks.downloadUpdate).toHaveBeenCalledOnce();
|
||||
expect(updater.phase.value).toBe("ready");
|
||||
});
|
||||
|
||||
it("does not disturb an installation that starts while a recheck is in flight", async () => {
|
||||
mocks.getDownloadedUpdate.mockResolvedValue(cache);
|
||||
const gate = deferred<typeof info>();
|
||||
mocks.checkForUpdates.mockReturnValue(gate.promise);
|
||||
const release = vi.fn();
|
||||
const updater = mount({ prepareForUpdate: async () => release });
|
||||
await updater.initialize();
|
||||
await updater.installDownloadedUpdate();
|
||||
gate.resolve(info);
|
||||
await flush();
|
||||
expect(updater.phase.value).toBe("restart");
|
||||
expect(mocks.discardDownloadedUpdate).not.toHaveBeenCalled();
|
||||
expect(mocks.downloadUpdate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps the ready package when discarding a superseded update fails", async () => {
|
||||
mocks.getDownloadedUpdate.mockResolvedValue(cache);
|
||||
mocks.checkForUpdates.mockResolvedValue({ ...info, latest_version: "1.2.0" });
|
||||
mocks.discardDownloadedUpdate.mockRejectedValue(new Error("file busy"));
|
||||
const updater = mount();
|
||||
await updater.initialize();
|
||||
await flush();
|
||||
expect(mocks.downloadUpdate).not.toHaveBeenCalled();
|
||||
expect(updater.updateDownloaded.value).toBe(true);
|
||||
expect(updater.updateCheckMessage.value).toContain("file busy");
|
||||
});
|
||||
});
|
||||
@@ -77,6 +77,13 @@ export function isUpdateIgnored(info: api.UpdateInfo | null, ignoredVersion: str
|
||||
return compareParsedUpdateVersions(parsedLatest, parsedIgnored) <= 0;
|
||||
}
|
||||
|
||||
export function isNewerRemoteVersion(latest: string, cached: string): boolean {
|
||||
const parsedLatest = parseUpdateVersion(latest);
|
||||
const parsedCached = parseUpdateVersion(cached);
|
||||
if (!parsedLatest || !parsedCached) return normalizeUpdateVersion(latest) !== normalizeUpdateVersion(cached);
|
||||
return compareParsedUpdateVersions(parsedLatest, parsedCached) > 0;
|
||||
}
|
||||
|
||||
export function normalizeUpdateDownloadSource(value: unknown): SettingsUpdateDownloadSource {
|
||||
// Old persisted AtomGit preferences should retain their mainland mirror behavior.
|
||||
if (value === "atomgit") return "cnb";
|
||||
@@ -199,7 +206,9 @@ export function useAppUpdater(options: UseAppUpdaterOptions = {}) {
|
||||
async function checkUpdates(checkOptions: { silent?: boolean } = {}) {
|
||||
if (disposed || isIgnoringUpdate.value) return;
|
||||
if (!checkOptions.silent) showUpdateDialog.value = true;
|
||||
if (phase.value !== "idle" || downloaded.value || (checkOptions.silent && !notificationsEnabled.value)) return;
|
||||
// A downloaded-but-uninstalled update keeps the app in the ready phase; checks
|
||||
// continue so a newer release can replace the cached package.
|
||||
if ((phase.value !== "idle" && phase.value !== "ready") || (checkOptions.silent && !notificationsEnabled.value)) return;
|
||||
clearRetry();
|
||||
const token = ++generation;
|
||||
phase.value = "checking";
|
||||
@@ -208,18 +217,38 @@ export function useAppUpdater(options: UseAppUpdaterOptions = {}) {
|
||||
const info = await api.checkForUpdates(currentLocale(), normalizeUpdateDownloadSource(settingsStore.editorSettings.updateDownloadSource));
|
||||
if (token !== generation || disposed) return;
|
||||
updateInfo.value = info;
|
||||
phase.value = "idle";
|
||||
// Installation may have started while the check was in flight; only restore
|
||||
// the phase this check itself owns.
|
||||
if (phase.value === "checking") phase.value = downloaded.value ? "ready" : "idle";
|
||||
if (!info.update_available) updateCheckMessage.value = t("updates.upToDate", { version: info.current_version });
|
||||
if (canDownloadAndInstallUpdate(info, isTauriRuntime()) && !isUpdateIgnored(info, settingsStore.editorSettings.ignoredUpdateVersion)) {
|
||||
const cached = downloaded.value;
|
||||
if (cached && !isNewerRemoteVersion(info.latest_version, cached.version)) return;
|
||||
if (cached && !(await discardSupersededUpdate(cached))) return;
|
||||
await downloadUpdateInBackground();
|
||||
}
|
||||
} catch (error) {
|
||||
if (token !== generation || disposed) return;
|
||||
phase.value = "idle";
|
||||
if (phase.value === "checking") phase.value = downloaded.value ? "ready" : "idle";
|
||||
fail(error);
|
||||
scheduleRetry();
|
||||
}
|
||||
}
|
||||
async function discardSupersededUpdate(cache: api.DownloadedUpdate): Promise<boolean> {
|
||||
if (isInstallingUpdate.value || isIgnoringUpdate.value) return false;
|
||||
try {
|
||||
await api.discardDownloadedUpdate(cache.cache_id);
|
||||
if (downloaded.value?.cache_id === cache.cache_id) {
|
||||
downloaded.value = null;
|
||||
downloadProgress.value = null;
|
||||
if (phase.value === "ready" || phase.value === "checking") phase.value = "idle";
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
fail(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
async function downloadUpdateInBackground() {
|
||||
if (disposed || isIgnoringUpdate.value || phase.value !== "idle" || downloaded.value || !canDownloadAndInstallUpdate(updateInfo.value, isTauriRuntime())) return;
|
||||
const version = updateInfo.value!.latest_version;
|
||||
|
||||
@@ -7779,11 +7779,9 @@ export default withEnglishFallback({
|
||||
settingsTransferImportConfirmAction: "Parametrləri yüklə",
|
||||
settingsTransferImportLoaded: "Parametrlər yükləndi. Onları yadda saxlamaq üçün Tətbiq et düyməsinə klikləyin.",
|
||||
settingsTransferCategoryOther: "Digər",
|
||||
community: "İcma",
|
||||
qqGroup: "QQ qrupu",
|
||||
wechatGroup: "WeChat qrupu",
|
||||
wechatGroupInvite: "Tencent Docs dəvəti",
|
||||
project: "Layihə",
|
||||
openSource: "Açıq mənbəli kod deposu",
|
||||
officialDocs: "Rəsmi sənədlər",
|
||||
changelogTitle: "Dəyişikliklər jurnalı",
|
||||
|
||||
@@ -7968,11 +7968,9 @@ export default {
|
||||
settingsTransferImportConfirmAction: "Load settings",
|
||||
settingsTransferImportLoaded: "Settings loaded. Click Apply to save them.",
|
||||
settingsTransferCategoryOther: "Other",
|
||||
community: "Community",
|
||||
qqGroup: "QQ Group",
|
||||
wechatGroup: "WeChat Group",
|
||||
wechatGroupInvite: "Tencent Docs invite",
|
||||
project: "Project",
|
||||
openSource: "Open-source repository",
|
||||
officialDocs: "Official docs",
|
||||
changelogTitle: "Changelog",
|
||||
|
||||
@@ -7469,11 +7469,9 @@ export default withEnglishFallback({
|
||||
settingsTransferImportConfirmAction: "Cargar configuración",
|
||||
settingsTransferImportLoaded: "Configuración cargada. Haz clic en «Aplicar» para guardarla.",
|
||||
settingsTransferCategoryOther: "Otro",
|
||||
community: "Comunidad",
|
||||
qqGroup: "Grupo QQ",
|
||||
wechatGroup: "Grupo WeChat",
|
||||
wechatGroupInvite: "Invitación de Tencent Docs",
|
||||
project: "Proyecto",
|
||||
openSource: "Repositorio de código abierto",
|
||||
officialDocs: "Documentación oficial",
|
||||
changelogTitle: "Registro de cambios",
|
||||
|
||||
@@ -7469,11 +7469,9 @@ export default withEnglishFallback({
|
||||
settingsTransferImportConfirmAction: "Carica impostazioni",
|
||||
settingsTransferImportLoaded: "Impostazioni caricate. Fai clic su «Applica» per salvarle.",
|
||||
settingsTransferCategoryOther: "Altro",
|
||||
community: "Community",
|
||||
qqGroup: "Gruppo QQ",
|
||||
wechatGroup: "Gruppo WeChat",
|
||||
wechatGroupInvite: "Invito Tencent Docs",
|
||||
project: "Progetto",
|
||||
openSource: "Repository open-source",
|
||||
officialDocs: "Documenti ufficiali",
|
||||
changelogTitle: "Registro modifiche",
|
||||
|
||||
@@ -7495,11 +7495,9 @@ export default withEnglishFallback({
|
||||
settingsTransferImportConfirmAction: "設定を読み込む",
|
||||
settingsTransferImportLoaded: "設定を読み込みました。「適用」をクリックして保存してください。",
|
||||
settingsTransferCategoryOther: "その他",
|
||||
community: "コミュニティ",
|
||||
qqGroup: "QQグループ",
|
||||
wechatGroup: "WeChatグループ",
|
||||
wechatGroupInvite: "Tencent Docs招待",
|
||||
project: "プロジェクト",
|
||||
openSource: "オープンソースリポジトリ",
|
||||
officialDocs: "公式ドキュメント",
|
||||
changelogTitle: "更新履歴",
|
||||
|
||||
@@ -7239,11 +7239,9 @@ export default withEnglishFallback({
|
||||
settingsTransferImportConfirmAction: "설정 불러오기",
|
||||
settingsTransferImportLoaded: "설정을 불러왔습니다. 저장하려면 '적용'을 클릭하세요.",
|
||||
settingsTransferCategoryOther: "기타",
|
||||
community: "커뮤니티",
|
||||
qqGroup: "QQ 그룹",
|
||||
wechatGroup: "위챗 그룹",
|
||||
wechatGroupInvite: "텐센트 문서 초대",
|
||||
project: "프로젝트",
|
||||
openSource: "오픈 소스 저장소",
|
||||
officialDocs: "공식 문서",
|
||||
changelogTitle: "변경 이력",
|
||||
|
||||
@@ -7471,11 +7471,9 @@ export default withEnglishFallback({
|
||||
settingsTransferImportConfirmAction: "Carregar configurações",
|
||||
settingsTransferImportLoaded: "Configurações carregadas. Clique em «Aplicar» para salvá-las.",
|
||||
settingsTransferCategoryOther: "Outro",
|
||||
community: "Comunidade",
|
||||
qqGroup: "Grupo do QQ",
|
||||
wechatGroup: "Grupo do WeChat",
|
||||
wechatGroupInvite: "Convite do Tencent Docs",
|
||||
project: "Projeto",
|
||||
openSource: "Repositório de código aberto",
|
||||
officialDocs: "Documentação oficial",
|
||||
changelogTitle: "Registro de alterações",
|
||||
|
||||
@@ -7633,11 +7633,9 @@ export default withEnglishFallback({
|
||||
supportInfoOperatingSystem: "İşletim Sistemi",
|
||||
supportInfoArchitecture: "Mimari",
|
||||
supportInfoUnknown: "Bilinmiyor",
|
||||
community: "Community",
|
||||
qqGroup: "QQ Grubu",
|
||||
wechatGroup: "WeChat Grubu",
|
||||
wechatGroupInvite: "Tencent Docs daveti",
|
||||
project: "Proje",
|
||||
openSource: "Açık kaynak deposu",
|
||||
officialDocs: "Resmî belgeler",
|
||||
changelogTitle: "Değişiklik günlüğü",
|
||||
|
||||
@@ -7944,11 +7944,9 @@ export default withEnglishFallback({
|
||||
settingsTransferImportConfirmAction: "载入设置",
|
||||
settingsTransferImportLoaded: "已载入设置,请点击“应用”保存",
|
||||
settingsTransferCategoryOther: "其他",
|
||||
community: "社区",
|
||||
qqGroup: "QQ 群",
|
||||
wechatGroup: "微信交流群",
|
||||
wechatGroupInvite: "腾讯文档邀请",
|
||||
project: "项目",
|
||||
openSource: "开源仓库",
|
||||
officialDocs: "官方文档",
|
||||
changelogTitle: "更新日志",
|
||||
|
||||
@@ -6491,11 +6491,9 @@ export default withEnglishFallback({
|
||||
settingsTransferImportConfirmAction: "載入設定",
|
||||
settingsTransferImportLoaded: "已載入設定,請點擊「套用」儲存",
|
||||
settingsTransferCategoryOther: "其他",
|
||||
community: "社群",
|
||||
qqGroup: "QQ 群",
|
||||
wechatGroup: "微信交流群",
|
||||
wechatGroupInvite: "騰訊文件邀請",
|
||||
project: "專案",
|
||||
openSource: "開源倉庫",
|
||||
officialDocs: "官方文件",
|
||||
changelogTitle: "更新日誌",
|
||||
|
||||
@@ -173,7 +173,6 @@ export const SETTINGS_SEARCH_DEFINITIONS: readonly SettingsSearchDefinition[] =
|
||||
{ id: "appearance-icons", category: "appearance", titleKey: "settings.iconTheme", targetId: "appearance", visible: desktopOnly },
|
||||
{ id: "appearance-tray", category: "appearance", titleKey: "settings.showTrayIcon", descriptionKey: "settings.showTrayIconDescription", targetId: "appearance", visible: desktopOnly },
|
||||
{ id: "appearance-quit", category: "appearance", titleKey: "settings.quitOnClose", descriptionKey: "settings.quitOnCloseDescription", targetId: "appearance", visible: desktopOnly },
|
||||
{ id: "appearance-updates", category: "appearance", titleKey: "settings.updateNotificationsEnabled", descriptionKey: "settings.updateNotificationsEnabledDescription", targetId: "appearance" },
|
||||
{ id: "appearance-debug-logs", category: "appearance", titleKey: "settings.debugLoggingEnabled", descriptionKey: "settings.debugLoggingEnabledDescription", targetId: "appearance", visible: desktopOnly },
|
||||
{ id: "navigation", category: "navigation", titleKey: "settings.navigationTab", targetId: "navigation" },
|
||||
{ id: "navigation-sidebar", category: "navigation", titleKey: "settings.sidebarActivation", targetId: "navigation" },
|
||||
@@ -263,6 +262,7 @@ export const SETTINGS_SEARCH_DEFINITIONS: readonly SettingsSearchDefinition[] =
|
||||
{ id: "security-password", category: "security", titleKey: "auth.changePassword", targetId: "security", visible: webOnly },
|
||||
{ id: "about-support", category: "about", titleKey: "settings.supportInfoTitle", descriptionKey: "settings.supportInfoDescription", targetId: "about" },
|
||||
{ id: "about-transfer", category: "about", titleKey: "settings.settingsTransferTitle", descriptionKey: "settings.settingsTransferDescription", targetId: "about" },
|
||||
{ id: "about-update-notifications", category: "about", titleKey: "settings.updateNotificationsEnabled", descriptionKey: "settings.updateNotificationsEnabledDescription", targetId: "about" },
|
||||
{ id: "about-update", category: "about", titleKey: "settings.updateDownloadSource", descriptionKey: "settings.updateDownloadSourceDescription", targetId: "about" },
|
||||
];
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ export function serializeSettingsTransfer(settings: EditorSettings, meta: Settin
|
||||
const SETTINGS_TRANSFER_CATEGORY_ORDER: readonly SettingsTransferCategoryId[] = ["appearance", "editor", "formatter", "navigation", "data", "shortcuts", "snippets", "other"];
|
||||
|
||||
const SETTINGS_TRANSFER_CATEGORY_KEYS: Record<SettingsTransferCategoryId, readonly EditorSettingsDraftKey[]> = {
|
||||
appearance: ["fontFamily", "fontSize", "tableFontFamily", "uiFontFamily", "uiScale", "theme", "customThemes", "activeCustomThemeId", "backgroundImage", "toolbarItems", "updateNotificationsEnabled"],
|
||||
appearance: ["fontFamily", "fontSize", "tableFontFamily", "uiFontFamily", "uiScale", "theme", "customThemes", "activeCustomThemeId", "backgroundImage", "toolbarItems"],
|
||||
editor: [
|
||||
"executeMode",
|
||||
"defaultTransactionMode",
|
||||
@@ -171,7 +171,7 @@ const SETTINGS_TRANSFER_CATEGORY_KEYS: Record<SettingsTransferCategoryId, readon
|
||||
],
|
||||
shortcuts: ["shortcuts", "sqlShortcuts"],
|
||||
snippets: ["snippets"],
|
||||
other: ["updateDownloadSource"],
|
||||
other: ["updateDownloadSource", "updateNotificationsEnabled"],
|
||||
};
|
||||
|
||||
const KEY_TO_CATEGORY = new Map<string, SettingsTransferCategoryId>();
|
||||
|
||||
Reference in new issue
Block a user