TidGi-Desktop/src/windows/EditWorkspace/useForm.ts
linonetwo cdb496961c Improve shutdown DB cleanup and edit workspace UI
Add end-to-end test for editing workspace and improve shutdown/database cleanup and edit-workspace behavior.

Key changes:
- features/editWorkspace.feature: new E2E scenario to verify save button behavior when enabling HTTP API and restarting a wiki.
- src/main.ts: wrap before-quit cleanup in try/catch/finally, call databaseService.closeAllDatabases() early, and add logging to make shutdown order explicit.
- src/services/database/*: add prepareDatabase pragmas (busy_timeout, synchronous) to SQLite config, make closeAppDatabase more robust with safer dataSource.destroy() handling, and add closeAllDatabases() to close all connections and backup stream to avoid better-sqlite3 crashes.
- src/services/database/interface.ts: expose closeAllDatabases() in the service interface and IPC descriptor.
- src/services/workspaces/interface.ts: mark runtime-only fields as non-config (add lastUrl, homeUrl, hibernated, active), move port to localOnlyFields and remove it from syncableConfigFields to avoid spurious save prompts.
- src/services/workspacesView/index.ts: emit a test log marker ([test-id-WIKI_WORKER_RESTARTING]) when a workspace restart is initiated to help tests detect restart events.
- src/windows/EditWorkspace/server.tsx: add data-testid attributes to server options accordion and HTTP API switch to support the new test selectors.
- src/windows/EditWorkspace/useForm.ts: tighten effect dependencies and adjust originalWorkspace change handling to avoid unnecessary form resets during user edits.

Why: fixes intermittent crashes on app quit related to better-sqlite3 by closing DBs first and finalizing resources, and stabilizes edit-workspace UI and tests by preventing runtime-only field changes from triggering save UI and by adding testable hooks.
2026-02-09 00:04:26 +08:00

52 lines
2.5 KiB
TypeScript

import usePreviousValue from 'beautiful-react-hooks/usePreviousValue';
import { isEqual, omit } from 'lodash';
import { useCallback, useEffect, useState } from 'react';
import type { IWorkspace } from '@services/workspaces/interface';
export function useForm(
originalWorkspace?: IWorkspace,
requestRestartCountDown: () => void = () => {},
): [IWorkspace | undefined, (newValue: IWorkspace, requestSaveAndRestart?: boolean) => void, () => Promise<void>] {
const [workspace, workspaceSetter] = useState(originalWorkspace);
const [requestRestartAfterSave, requestRestartAfterSaveSetter] = useState(false);
const previous = usePreviousValue(originalWorkspace);
// initial observable value maybe undefined, we pass an non-null initial value to the form
useEffect(() => {
if (previous === undefined && originalWorkspace !== undefined) {
workspaceSetter(originalWorkspace);
}
}, [previous, originalWorkspace]);
// Sync workspace state with originalWorkspace after save to ensure save button disappears
useEffect(() => {
if (originalWorkspace !== undefined && workspace !== undefined && previous !== undefined) {
// If originalWorkspace changed after a save operation, update workspace state to match it
// Only check if originalWorkspace changed (not workspace), to avoid triggering on every user edit
if (!isEqual(originalWorkspace, previous)) {
// Check if the current form state matches the new originalWorkspace (excluding non-config fields)
if (isEqual(omit(workspace, ['metadata', 'lastNodeJSArgv']), omit(originalWorkspace, ['metadata', 'lastNodeJSArgv']))) {
workspaceSetter(originalWorkspace);
}
}
}
}, [originalWorkspace, previous]);
const onSave = useCallback(async () => {
if (workspace === undefined) {
return;
}
await window.service.workspace.update(workspace.id, workspace);
await window.service.native.log('info', '[test-id-WORKSPACE_SAVED]', { workspaceId: workspace.id, workspaceName: workspace.name });
if (requestRestartAfterSave) {
requestRestartCountDown();
}
}, [workspace, requestRestartAfterSave, requestRestartCountDown]);
const setterWithRestartOption = (newValue: IWorkspace, requestSaveAndRestart?: boolean) => {
workspaceSetter(newValue);
if (requestSaveAndRestart === true && !isEqual(newValue, originalWorkspace)) {
requestRestartAfterSaveSetter(true);
}
};
return [workspace, setterWithRestartOption, onSave];
}