feat(settings): allow disabling update reminders

This commit is contained in:
t8y2 2026-06-07 01:07:31 +08:00
parent 8c0fe3a6e7
commit 3eefe87380
12 changed files with 116 additions and 18 deletions

View File

@ -140,13 +140,18 @@ const activeConnection = computed(() => {
});
function updateAgentDriverUpdateCount(count: number) {
if (!settingsStore.editorSettings.updateNotificationsEnabled) {
agentDriverUpdateCount.value = 0;
return;
}
agentDriverUpdateCount.value = count;
}
async function refreshAgentDriverUpdateCount() {
if (!isDesktop) return;
if (!isDesktop || !settingsStore.editorSettings.updateNotificationsEnabled) return;
try {
const drivers = await invoke<AgentDriverUpdateBadgeState[]>("list_installed_agents");
if (!settingsStore.editorSettings.updateNotificationsEnabled) return;
updateAgentDriverUpdateCount(countAvailableAgentDriverUpdates(drivers));
} catch {
// Driver update availability is only a badge hint; keep the existing count if the registry cannot be reached.
@ -222,6 +227,11 @@ useVisibilityChange();
const appVersion = ref("");
const isClassicLayout = computed(() => settingsStore.editorSettings.appLayout === "classic");
const updateNotificationsEnabled = computed(() => settingsStore.editorSettings.updateNotificationsEnabled);
const toolbarAgentDriverUpdateCount = computed(() =>
updateNotificationsEnabled.value ? agentDriverUpdateCount.value : 0,
);
const toolbarHasUpdateAvailable = computed(() => updateNotificationsEnabled.value && hasUpdateAvailable.value);
const hasSqlFileConnections = computed(() =>
connectionStore.connections.some((c) => supportsSqlFileExecution(c.db_type)),
);
@ -878,6 +888,27 @@ function openDriverStoreFromEvent() {
showDriverStore.value = true;
}
function runUpdateNotificationChecks() {
if (!updateNotificationsEnabled.value) return;
checkUpdates({ silent: true });
void refreshAgentDriverUpdateCount();
}
watch(updateNotificationsEnabled, (enabled) => {
if (!enabled) {
agentDriverUpdateCount.value = 0;
if (updateCheckTimer) {
clearInterval(updateCheckTimer);
updateCheckTimer = undefined;
}
return;
}
runUpdateNotificationChecks();
if (!updateCheckTimer) {
updateCheckTimer = setInterval(runUpdateNotificationChecks, UPDATE_CHECK_INTERVAL_MS);
}
});
onMounted(async () => {
console.log("[STARTUP] onMounted begin");
const mountStart = performance.now();
@ -915,14 +946,11 @@ onMounted(async () => {
}
initApp();
setupFileDrop().catch(() => {});
void refreshAgentDriverUpdateCount();
setTimeout(() => {
checkUpdates({ silent: true });
void refreshAgentDriverUpdateCount();
updateCheckTimer = setInterval(() => {
checkUpdates({ silent: true });
void refreshAgentDriverUpdateCount();
}, UPDATE_CHECK_INTERVAL_MS);
runUpdateNotificationChecks();
if (updateNotificationsEnabled.value && !updateCheckTimer) {
updateCheckTimer = setInterval(runUpdateNotificationChecks, UPDATE_CHECK_INTERVAL_MS);
}
}, 10_000);
api
.getAppVersion()
@ -966,8 +994,8 @@ onUnmounted(() => {
:show-history="showHistory"
:show-driver-store="showDriverStore"
:checking-updates="checkingUpdates"
:has-update-available="hasUpdateAvailable"
:agent-driver-update-count="agentDriverUpdateCount"
:has-update-available="toolbarHasUpdateAvailable"
:agent-driver-update-count="toolbarAgentDriverUpdateCount"
:has-connections="connectionStore.connections.length > 0"
:has-sql-file-connections="hasSqlFileConnections"
@new-connection="showConnectionDialog = true"
@ -1029,13 +1057,14 @@ onUnmounted(() => {
<div class="h-full flex flex-col min-w-0">
<AppTabBar
:show-driver-store="showDriverStore"
:agent-driver-update-count="agentDriverUpdateCount"
:agent-driver-update-count="toolbarAgentDriverUpdateCount"
@toggle-driver-store="showDriverStore = true"
@close-driver-store="showDriverStore = false"
/>
<DriverStorePage
v-if="showDriverStore"
class="flex-1 min-h-0"
:update-notifications-enabled="updateNotificationsEnabled"
@update-count-change="updateAgentDriverUpdateCount"
/>
<div v-else-if="activeTab" class="flex flex-col flex-1 min-h-0">

View File

@ -56,6 +56,15 @@ const { t } = useI18n();
const { toast } = useToast();
const isWeb = !isTauriRuntime();
const props = withDefaults(
defineProps<{
updateNotificationsEnabled?: boolean;
}>(),
{
updateNotificationsEnabled: true,
},
);
const emit = defineEmits<{
"update-count-change": [count: number];
}>();
@ -117,7 +126,9 @@ const progressText = computed(() => {
const progressNumber = computed(() => driverInstallProgressPercent(progress.value));
const updatableCount = computed(() => drivers.value.filter((d) => d.update_available).length);
const updatableCount = computed(() =>
props.updateNotificationsEnabled ? drivers.value.filter((d) => d.update_available).length : 0,
);
const usageSummary = computed(() => {
const usage = driverStoreUsage.value;
if (!usage) return [];
@ -142,10 +153,18 @@ function updateAgentDrivers(nextDrivers: AgentDriverInfo[]) {
emitDriverUpdateCount();
}
const agentTabUpdateCount = computed(() => drivers.value.filter((d) => d.update_available).length);
const jdbcTabUpdateCount = computed(() => (jdbcPluginStatus.value?.update_available ? 1 : 0));
const agentTabUpdateCount = computed(() =>
props.updateNotificationsEnabled ? drivers.value.filter((d) => d.update_available).length : 0,
);
const jdbcTabUpdateCount = computed(() =>
props.updateNotificationsEnabled && jdbcPluginStatus.value?.update_available ? 1 : 0,
);
function emitDriverUpdateCount() {
if (!props.updateNotificationsEnabled) {
emit("update-count-change", 0);
return;
}
emit("update-count-change", countAvailableDriverUpdates(drivers.value, jdbcPluginStatus.value));
}
@ -722,9 +741,11 @@ onMounted(async () => {
void loadJavaRuntimeConfig();
void loadDriverStoreUsage();
api.listInstalledAgents().then((result) => {
updateAgentDrivers(result);
});
if (props.updateNotificationsEnabled) {
api.listInstalledAgents().then((result) => {
updateAgentDrivers(result);
});
}
unlisten = await api.listenAgentInstallProgress((payload) => {
if (payload.step === "done" || payload.step === "all-done") {
@ -739,7 +760,7 @@ onMounted(async () => {
}
});
void loadJdbcDrivers();
void loadJdbcPluginStatus();
if (props.updateNotificationsEnabled) void loadJdbcPluginStatus();
});
onUnmounted(() => {

View File

@ -18,6 +18,7 @@ import type {
TransportLayerConfig,
} from "@/types/database";
import { useConnectionStore } from "@/stores/connectionStore";
import { useSettingsStore } from "@/stores/settingsStore";
import { useToast } from "@/composables/useToast";
import DatabaseIcon from "@/components/icons/DatabaseIcon.vue";
import * as api from "@/lib/api";
@ -77,6 +78,7 @@ type ConnectionForm = Omit<ConnectionConfig, "id">;
const { t } = useI18n();
const { toast } = useToast();
const settingsStore = useSettingsStore();
const open = defineModel<boolean>("open", { default: false });
const isDesktop = isTauriRuntime();
@ -1688,6 +1690,7 @@ async function loadJdbcDrivers() {
async function loadAgentDrivers() {
try {
agentDrivers.value = await api.listInstalledAgentsLocal();
if (!settingsStore.editorSettings.updateNotificationsEnabled) return;
api
.listInstalledAgents()
.then((drivers) => {

View File

@ -127,6 +127,7 @@ const editDisconnectTabHandlingMode = ref<DisconnectTabHandlingMode>(
settingsStore.editorSettings.disconnectTabHandlingMode,
);
const editReuseDataTab = ref(settingsStore.editorSettings.reuseDataTab);
const editUpdateNotificationsEnabled = ref(settingsStore.editorSettings.updateNotificationsEnabled);
const editSidebarHiddenTablePrefixes = ref(settingsStore.editorSettings.sidebarHiddenTablePrefixes.join("\n"));
const editSidebarHideTableComments = ref(settingsStore.editorSettings.sidebarHideTableComments);
const editSidebarAllowHorizontalScroll = ref(settingsStore.editorSettings.sidebarAllowHorizontalScroll);
@ -279,6 +280,7 @@ watch(
editAutoSelectActiveSidebarNode.value = settingsStore.editorSettings.autoSelectActiveSidebarNode;
editDisconnectTabHandlingMode.value = settingsStore.editorSettings.disconnectTabHandlingMode;
editReuseDataTab.value = settingsStore.editorSettings.reuseDataTab;
editUpdateNotificationsEnabled.value = settingsStore.editorSettings.updateNotificationsEnabled;
editSidebarHiddenTablePrefixes.value = settingsStore.editorSettings.sidebarHiddenTablePrefixes.join("\n");
editSidebarHideTableComments.value = settingsStore.editorSettings.sidebarHideTableComments;
editSidebarAllowHorizontalScroll.value = settingsStore.editorSettings.sidebarAllowHorizontalScroll;
@ -325,6 +327,7 @@ function hasChanges(): boolean {
editAutoSelectActiveSidebarNode.value !== settingsStore.editorSettings.autoSelectActiveSidebarNode ||
editDisconnectTabHandlingMode.value !== settingsStore.editorSettings.disconnectTabHandlingMode ||
editReuseDataTab.value !== settingsStore.editorSettings.reuseDataTab ||
editUpdateNotificationsEnabled.value !== settingsStore.editorSettings.updateNotificationsEnabled ||
editSidebarHideTableComments.value !== settingsStore.editorSettings.sidebarHideTableComments ||
editSidebarAllowHorizontalScroll.value !== settingsStore.editorSettings.sidebarAllowHorizontalScroll ||
editExportBatchSize.value !== settingsStore.editorSettings.exportBatchSize ||
@ -358,6 +361,7 @@ async function persistSettings() {
autoSelectActiveSidebarNode: editAutoSelectActiveSidebarNode.value,
disconnectTabHandlingMode: editDisconnectTabHandlingMode.value,
reuseDataTab: editReuseDataTab.value,
updateNotificationsEnabled: editUpdateNotificationsEnabled.value,
sidebarHideTableComments: editSidebarHideTableComments.value,
sidebarAllowHorizontalScroll: editSidebarAllowHorizontalScroll.value,
sidebarHiddenTablePrefixes: normalizeSidebarHiddenTablePrefixes(editSidebarHiddenTablePrefixes.value),
@ -404,6 +408,7 @@ function resetDefaults() {
editAutoSelectActiveSidebarNode.value = DEFAULT_EDITOR_SETTINGS.autoSelectActiveSidebarNode;
editDisconnectTabHandlingMode.value = DEFAULT_EDITOR_SETTINGS.disconnectTabHandlingMode;
editReuseDataTab.value = DEFAULT_EDITOR_SETTINGS.reuseDataTab;
editUpdateNotificationsEnabled.value = DEFAULT_EDITOR_SETTINGS.updateNotificationsEnabled;
editSidebarHideTableComments.value = DEFAULT_EDITOR_SETTINGS.sidebarHideTableComments;
editSidebarAllowHorizontalScroll.value = DEFAULT_EDITOR_SETTINGS.sidebarAllowHorizontalScroll;
editSidebarHiddenTablePrefixes.value = DEFAULT_EDITOR_SETTINGS.sidebarHiddenTablePrefixes.join("\n");
@ -1491,6 +1496,16 @@ watch(
<Switch id="show-tray-icon" v-model="editShowTrayIcon" />
</div>
<div class="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>
<Separator />
<div class="space-y-3">

View File

@ -1629,6 +1629,9 @@ export default {
showTrayIcon: "Show tray/menu bar icon",
showTrayIconDescription:
"When disabled, no icon is shown, but closing the window still hides DBX in the background as before.",
updateNotificationsEnabled: "Enable update reminders",
updateNotificationsEnabledDescription:
"When disabled, DBX will not automatically check app or driver updates or show update badges. Manual checks are still available.",
dataGridDisplay: "Data grid display",
showColumnCommentsInHeader: "Show column comments under names",
showColumnCommentsInHeaderDescription: "Display table column comments directly below grid column names.",

View File

@ -1518,6 +1518,9 @@ export default {
showTrayIcon: "Mostrar icono en bandeja/barra de menú",
showTrayIconDescription:
"Si está desactivado, no se muestra el icono, pero cerrar la ventana sigue ocultando DBX en segundo plano como antes.",
updateNotificationsEnabled: "Activar recordatorios de actualización",
updateNotificationsEnabledDescription:
"Al desactivarlo, DBX no comprobará automáticamente actualizaciones de la app ni de los controladores, ni mostrará indicadores. Las comprobaciones manuales siguen disponibles.",
dataGridDisplay: "Visualización de la tabla",
showColumnCommentsInHeader: "Mostrar comentarios bajo los nombres",
showColumnCommentsInHeaderDescription:

View File

@ -1660,6 +1660,9 @@ export default {
showTrayIcon: "Mostra icona nella barra delle applicazioni/menu",
showTrayIconDescription:
"Se disattivato, non viene mostrata alcuna icona, ma la chiusura della finestra nasconderà comunque DBX in background.",
updateNotificationsEnabled: "Abilita promemoria aggiornamenti",
updateNotificationsEnabledDescription:
"Se disattivato, DBX non controllerà automaticamente gli aggiornamenti dell'app o dei driver e non mostrerà badge. I controlli manuali restano disponibili.",
dataGridDisplay: "Visualizzazione griglia dati",
showColumnCommentsInHeader: "Mostra i commenti delle colonne sotto i nomi",
showColumnCommentsInHeaderDescription:

View File

@ -1651,6 +1651,9 @@ export default {
showTrayIcon: "Mostrar ícone na bandeja/barra de menus",
showTrayIconDescription:
"Quando desativado, nenhum ícone é exibido, mas fechar a janela ainda oculta o DBX em segundo plano como antes.",
updateNotificationsEnabled: "Ativar lembretes de atualização",
updateNotificationsEnabledDescription:
"Quando desativado, o DBX não verificará automaticamente atualizações do app ou dos drivers nem mostrará indicadores. Verificações manuais continuam disponíveis.",
dataGridDisplay: "Exibição da grade de dados",
showColumnCommentsInHeader: "Mostrar comentários de coluna sob os nomes",
showColumnCommentsInHeaderDescription:

View File

@ -1599,6 +1599,9 @@ export default {
iconThemeBlackDescription: "窗口、托盘和 Logo 都使用黑色 DBX 标识。",
showTrayIcon: "显示系统托盘/菜单栏图标",
showTrayIconDescription: "关闭后不显示图标,但关闭窗口仍会像之前一样隐藏到后台。",
updateNotificationsEnabled: "启用更新提醒",
updateNotificationsEnabledDescription:
"关闭后DBX 不会自动检查应用和驱动更新,也不会显示更新红点;仍可手动检查更新。",
dataGridDisplay: "数据表格显示",
showColumnCommentsInHeader: "在字段名下方显示注释",
showColumnCommentsInHeaderDescription: "把表字段注释直接显示在结果表头字段名下方。",

View File

@ -1575,6 +1575,9 @@ export default {
iconThemeBlackDescription: "視窗、系統匣和 Logo 都使用黑色 DBX 標誌。",
showTrayIcon: "顯示系統匣/選單欄圖示",
showTrayIconDescription: "關閉後不顯示圖示,但關閉視窗仍會像之前一樣隱藏到後台。",
updateNotificationsEnabled: "啟用更新提醒",
updateNotificationsEnabledDescription:
"關閉後DBX 不會自動檢查應用程式和驅動程式更新,也不會顯示更新紅點;仍可手動檢查更新。",
dataGridDisplay: "資料表格顯示",
showColumnCommentsInHeader: "在欄位名稱下方顯示註解",
showColumnCommentsInHeaderDescription: "直接在資料表格欄位名稱下方顯示資料表欄位註解。",

View File

@ -258,6 +258,7 @@ export interface EditorSettings {
autoSelectActiveSidebarNode: boolean;
disconnectTabHandlingMode: DisconnectTabHandlingMode;
reuseDataTab: boolean;
updateNotificationsEnabled: boolean;
sidebarHiddenTablePrefixes: string[];
sidebarHideTableComments: boolean;
sidebarAllowHorizontalScroll: boolean;
@ -322,6 +323,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
autoSelectActiveSidebarNode: false,
disconnectTabHandlingMode: "close-tabs",
reuseDataTab: false,
updateNotificationsEnabled: true,
sidebarHiddenTablePrefixes: [],
sidebarHideTableComments: false,
sidebarAllowHorizontalScroll: false,
@ -495,6 +497,8 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
(settings as Partial<EditorSettings> & { closeQueryTabsOnDisconnect?: boolean }).closeQueryTabsOnDisconnect,
),
reuseDataTab: settings.reuseDataTab ?? DEFAULT_EDITOR_SETTINGS.reuseDataTab,
updateNotificationsEnabled:
settings.updateNotificationsEnabled ?? DEFAULT_EDITOR_SETTINGS.updateNotificationsEnabled,
sidebarHiddenTablePrefixes: normalizeSidebarHiddenTablePrefixes(settings.sidebarHiddenTablePrefixes),
sidebarHideTableComments: settings.sidebarHideTableComments ?? DEFAULT_EDITOR_SETTINGS.sidebarHideTableComments,
sidebarAllowHorizontalScroll:
@ -667,6 +671,8 @@ export const useSettingsStore = defineStore("settings", () => {
partial.disconnectTabHandlingMode,
);
if (partial.reuseDataTab !== undefined) editorSettings.value.reuseDataTab = partial.reuseDataTab;
if (partial.updateNotificationsEnabled !== undefined)
editorSettings.value.updateNotificationsEnabled = partial.updateNotificationsEnabled;
if (partial.sidebarHiddenTablePrefixes !== undefined)
editorSettings.value.sidebarHiddenTablePrefixes = normalizeSidebarHiddenTablePrefixes(
partial.sidebarHiddenTablePrefixes,

View File

@ -43,6 +43,12 @@ test("defaults dangerous SQL confirmation to enabled", () => {
assert.equal(normalizeEditorSettings({ confirmDangerousSqlExecution: false }).confirmDangerousSqlExecution, false);
});
test("defaults update notifications to enabled", () => {
assert.equal(DEFAULT_EDITOR_SETTINGS.updateNotificationsEnabled, true);
assert.equal(normalizeEditorSettings({}).updateNotificationsEnabled, true);
assert.equal(normalizeEditorSettings({ updateNotificationsEnabled: false } as any).updateNotificationsEnabled, false);
});
test("defaults shortcut settings", () => {
const settings = normalizeEditorSettings({});