mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-22 21:27:11 +08:00
- 关闭应用前拦截 Wails 退出事件,前端弹出确认退出、保存退出和取消三按钮\n- 保存退出支持外部 SQL 文件和已保存查询,未命名临时查询保留草稿恢复语义\n- 补充退出保护后端测试、前端保存目标测试和多语言文案
148 lines
4.6 KiB
TypeScript
148 lines
4.6 KiB
TypeScript
import type { SavedQuery, TabData } from '../types';
|
|
import { ReadSQLFile, WriteSQLFile } from '../../wailsjs/go/app/App';
|
|
import {
|
|
getSQLFileTabPath,
|
|
hasSQLFileTabUnsavedChanges,
|
|
isSQLFileMissingReadResult,
|
|
isSQLFileQueryTab,
|
|
normalizeSQLFileReadContent,
|
|
} from './sqlFileTabDirty';
|
|
import { getQueryTabDraft, getSQLFileTabDraft } from './sqlFileTabDrafts';
|
|
|
|
type QueryResultLike = {
|
|
success?: boolean;
|
|
message?: string;
|
|
data?: unknown;
|
|
};
|
|
|
|
export type ApplicationQuitUnsavedSQLTarget =
|
|
| {
|
|
kind: 'sql-file';
|
|
tabId: string;
|
|
title: string;
|
|
filePath: string;
|
|
draft: string;
|
|
}
|
|
| {
|
|
kind: 'saved-query';
|
|
tabId: string;
|
|
title: string;
|
|
savedQuery: SavedQuery;
|
|
draft: string;
|
|
connectionId: string;
|
|
dbName: string;
|
|
};
|
|
|
|
export type ReadSQLFileForQuit = (filePath: string) => Promise<QueryResultLike>;
|
|
export type WriteSQLFileForQuit = (filePath: string, content: string) => Promise<QueryResultLike>;
|
|
export type SaveQueryForQuit = (query: SavedQuery) => Promise<SavedQuery>;
|
|
|
|
const toTrimmedString = (value: unknown): string => String(value ?? '').trim();
|
|
|
|
const resolveTabTitle = (tab: TabData, fallback: string): string =>
|
|
toTrimmedString(tab.title) || fallback;
|
|
|
|
const resolveSavedQueryForTab = (
|
|
tab: TabData,
|
|
savedQueries: SavedQuery[],
|
|
): SavedQuery | null => {
|
|
if (tab.type !== 'query' || isSQLFileQueryTab(tab)) return null;
|
|
const savedId = toTrimmedString(tab.savedQueryId) || toTrimmedString(tab.id);
|
|
if (!savedId) return null;
|
|
return savedQueries.find((query) => query.id === savedId) || null;
|
|
};
|
|
|
|
const hasSavedQueryUnsavedChanges = (
|
|
tab: TabData,
|
|
savedQuery: SavedQuery,
|
|
draft: string,
|
|
): boolean => {
|
|
const connectionId = toTrimmedString(tab.connectionId || savedQuery.connectionId);
|
|
const dbName = toTrimmedString(tab.dbName || savedQuery.dbName);
|
|
return draft !== String(savedQuery.sql ?? '')
|
|
|| connectionId !== toTrimmedString(savedQuery.connectionId)
|
|
|| dbName !== toTrimmedString(savedQuery.dbName);
|
|
};
|
|
|
|
export const buildApplicationQuitUnsavedSQLLabel = (
|
|
targets: ApplicationQuitUnsavedSQLTarget[],
|
|
): string => {
|
|
if (targets.length === 0) return '';
|
|
if (targets.length === 1) return targets[0].title;
|
|
return String(targets.length);
|
|
};
|
|
|
|
export const collectApplicationQuitUnsavedSQLTargets = async (
|
|
tabs: TabData[],
|
|
savedQueries: SavedQuery[],
|
|
readSQLFile: ReadSQLFileForQuit = ReadSQLFile,
|
|
): Promise<ApplicationQuitUnsavedSQLTarget[]> => {
|
|
const targets: ApplicationQuitUnsavedSQLTarget[] = [];
|
|
|
|
for (const tab of tabs) {
|
|
if (tab.type !== 'query') continue;
|
|
|
|
if (isSQLFileQueryTab(tab)) {
|
|
const filePath = getSQLFileTabPath(tab);
|
|
const draft = getSQLFileTabDraft(tab.id, String(tab.query ?? ''));
|
|
const title = resolveTabTitle(tab, filePath);
|
|
try {
|
|
const res = await readSQLFile(filePath);
|
|
if (res?.success) {
|
|
if (hasSQLFileTabUnsavedChanges({ ...tab, query: draft }, normalizeSQLFileReadContent(res.data))) {
|
|
targets.push({ kind: 'sql-file', tabId: tab.id, title, filePath, draft });
|
|
}
|
|
continue;
|
|
}
|
|
if (isSQLFileMissingReadResult(res)) {
|
|
targets.push({ kind: 'sql-file', tabId: tab.id, title, filePath, draft });
|
|
continue;
|
|
}
|
|
throw new Error(res?.message || filePath);
|
|
} catch {
|
|
targets.push({ kind: 'sql-file', tabId: tab.id, title, filePath, draft });
|
|
}
|
|
continue;
|
|
}
|
|
|
|
const savedQuery = resolveSavedQueryForTab(tab, savedQueries);
|
|
if (!savedQuery) continue;
|
|
const draft = getQueryTabDraft(tab.id, String(tab.query ?? ''));
|
|
if (!hasSavedQueryUnsavedChanges(tab, savedQuery, draft)) continue;
|
|
targets.push({
|
|
kind: 'saved-query',
|
|
tabId: tab.id,
|
|
title: resolveTabTitle(tab, savedQuery.name),
|
|
savedQuery,
|
|
draft,
|
|
connectionId: toTrimmedString(tab.connectionId || savedQuery.connectionId),
|
|
dbName: toTrimmedString(tab.dbName || savedQuery.dbName),
|
|
});
|
|
}
|
|
|
|
return targets;
|
|
};
|
|
|
|
export const saveApplicationQuitUnsavedSQLTargets = async (
|
|
targets: ApplicationQuitUnsavedSQLTarget[],
|
|
saveQuery: SaveQueryForQuit,
|
|
writeSQLFile: WriteSQLFileForQuit = WriteSQLFile,
|
|
): Promise<void> => {
|
|
for (const target of targets) {
|
|
if (target.kind === 'sql-file') {
|
|
const res = await writeSQLFile(target.filePath, target.draft);
|
|
if (!res?.success) {
|
|
throw new Error(res?.message || target.filePath);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
await saveQuery({
|
|
...target.savedQuery,
|
|
sql: target.draft,
|
|
connectionId: target.connectionId,
|
|
dbName: target.dbName,
|
|
});
|
|
}
|
|
};
|