chore: more refactorings to make stuff more similar
This commit is contained in:
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import MonacoEditor from '@monaco-editor/react';
|
import MonacoEditor from '@monaco-editor/react';
|
||||||
import type { ScriptData } from '../../../main/shared/electronApi';
|
import type { ScriptData } from '../../../main/shared/electronApi';
|
||||||
import { useAppStore } from '../../store';
|
import { useAppStore } from '../../store';
|
||||||
|
import { BDS_EVENT_SCRIPTS_CHANGED, dispatchWindowEvent } from '../../utils';
|
||||||
import { getPythonRuntimeManager } from '../../python/runtimeManagerInstance';
|
import { getPythonRuntimeManager } from '../../python/runtimeManagerInstance';
|
||||||
import { useI18n } from '../../i18n';
|
import { useI18n } from '../../i18n';
|
||||||
import { showToast } from '../Toast';
|
import { showToast } from '../Toast';
|
||||||
@@ -197,9 +198,7 @@ export const ScriptsView: React.FC<ScriptsViewProps> = ({ scriptId }) => {
|
|||||||
setScriptContent(updated.content || '');
|
setScriptContent(updated.content || '');
|
||||||
const normalizedExisting = toFunctionSlug(updated.slug || updated.title || '');
|
const normalizedExisting = toFunctionSlug(updated.slug || updated.title || '');
|
||||||
setIsSlugManuallyEdited(normalizedExisting !== toFunctionSlug(updated.title || ''));
|
setIsSlugManuallyEdited(normalizedExisting !== toFunctionSlug(updated.title || ''));
|
||||||
if (typeof window.dispatchEvent === 'function') {
|
dispatchWindowEvent(BDS_EVENT_SCRIPTS_CHANGED);
|
||||||
window.dispatchEvent(new CustomEvent('bds:scripts-changed'));
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsSaving(false);
|
setIsSaving(false);
|
||||||
}
|
}
|
||||||
@@ -217,9 +216,7 @@ export const ScriptsView: React.FC<ScriptsViewProps> = ({ scriptId }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
closeTab(script.id);
|
closeTab(script.id);
|
||||||
if (typeof window.dispatchEvent === 'function') {
|
dispatchWindowEvent(BDS_EVENT_SCRIPTS_CHANGED);
|
||||||
window.dispatchEvent(new CustomEvent('bds:scripts-changed'));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to delete script:', error);
|
console.error('Failed to delete script:', error);
|
||||||
showToast.error(t('sidebar.scripts.deleteFailed'));
|
showToast.error(t('sidebar.scripts.deleteFailed'));
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
||||||
import { useAppStore, PostData, MediaData } from '../../store';
|
import { useAppStore, PostData, MediaData } from '../../store';
|
||||||
import { showToast } from '../Toast';
|
import { showToast } from '../Toast';
|
||||||
import { getContrastColor, groupPostsByStatus, loadTagColorMap } from '../../utils';
|
import { BDS_EVENT_SCRIPTS_CHANGED, dispatchWindowEvent, getContrastColor, groupPostsByStatus, loadTagColorMap } from '../../utils';
|
||||||
import type { ChatConversation, ImportDefinitionData } from '../../types/electron';
|
import type { ChatConversation, ImportDefinitionData } from '../../types/electron';
|
||||||
import { GitSidebar } from '../GitSidebar/GitSidebar';
|
import { GitSidebar } from '../GitSidebar/GitSidebar';
|
||||||
import { scrollToSettingsSection, SettingsCategory } from '../SettingsView/SettingsView';
|
import { scrollToSettingsSection, SettingsCategory } from '../SettingsView/SettingsView';
|
||||||
@@ -1598,7 +1598,7 @@ const ScriptsList: React.FC = () => {
|
|||||||
} = useProjectScopedSidebarData<Array<{ id: string; title: string; updatedAt: string }>[number]>({
|
} = useProjectScopedSidebarData<Array<{ id: string; title: string; updatedAt: string }>[number]>({
|
||||||
load: loadScripts,
|
load: loadScripts,
|
||||||
activeProjectId,
|
activeProjectId,
|
||||||
refreshEventName: 'bds:scripts-changed',
|
refreshEventName: BDS_EVENT_SCRIPTS_CHANGED,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleCreateScript = async () => {
|
const handleCreateScript = async () => {
|
||||||
@@ -1619,9 +1619,7 @@ const ScriptsList: React.FC = () => {
|
|||||||
{ id: created.id, title: created.title, updatedAt: created.updatedAt },
|
{ id: created.id, title: created.title, updatedAt: created.updatedAt },
|
||||||
...prev.filter((script) => script.id !== created.id),
|
...prev.filter((script) => script.id !== created.id),
|
||||||
]);
|
]);
|
||||||
if (typeof window.dispatchEvent === 'function') {
|
dispatchWindowEvent(BDS_EVENT_SCRIPTS_CHANGED);
|
||||||
window.dispatchEvent(new CustomEvent('bds:scripts-changed'));
|
|
||||||
}
|
|
||||||
openScriptTab(openTab, created.id, 'pin');
|
openScriptTab(openTab, created.id, 'pin');
|
||||||
void reloadScripts();
|
void reloadScripts();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1640,9 +1638,7 @@ const ScriptsList: React.FC = () => {
|
|||||||
}
|
}
|
||||||
setScripts((prev) => prev.filter((script) => script.id !== scriptId));
|
setScripts((prev) => prev.filter((script) => script.id !== scriptId));
|
||||||
closeTab(scriptId);
|
closeTab(scriptId);
|
||||||
if (typeof window.dispatchEvent === 'function') {
|
dispatchWindowEvent(BDS_EVENT_SCRIPTS_CHANGED);
|
||||||
window.dispatchEvent(new CustomEvent('bds:scripts-changed'));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to delete script:', error);
|
console.error('Failed to delete script:', error);
|
||||||
showToast.error(t('sidebar.scripts.deleteFailed'));
|
showToast.error(t('sidebar.scripts.deleteFailed'));
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { useCallback, useEffect, useState, type Dispatch, type SetStateAction } from 'react';
|
import { useCallback, useEffect, useState, type Dispatch, type SetStateAction } from 'react';
|
||||||
|
import { addWindowEventListener, type BdsWindowEventName } from '../../utils';
|
||||||
|
|
||||||
interface ProjectScopedSidebarDataOptions<TItem> {
|
interface ProjectScopedSidebarDataOptions<TItem> {
|
||||||
load: () => Promise<TItem[] | null | undefined>;
|
load: () => Promise<TItem[] | null | undefined>;
|
||||||
activeProjectId?: string;
|
activeProjectId?: string;
|
||||||
refreshEventName?: string;
|
refreshEventName?: BdsWindowEventName;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ProjectScopedSidebarDataResult<TItem> {
|
interface ProjectScopedSidebarDataResult<TItem> {
|
||||||
@@ -53,18 +54,11 @@ export function useProjectScopedSidebarData<TItem>(options: ProjectScopedSidebar
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof window.addEventListener !== 'function' || typeof window.removeEventListener !== 'function') {
|
const unsubscribe = addWindowEventListener(refreshEventName, () => {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleRefreshEvent = () => {
|
|
||||||
void reload();
|
void reload();
|
||||||
};
|
});
|
||||||
|
|
||||||
window.addEventListener(refreshEventName, handleRefreshEvent);
|
return unsubscribe;
|
||||||
return () => {
|
|
||||||
window.removeEventListener(refreshEventName, handleRefreshEvent);
|
|
||||||
};
|
|
||||||
}, [refreshEventName, reload]);
|
}, [refreshEventName, reload]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ export { unescapeMacroSyntax } from './markdownEscape';
|
|||||||
export { groupPostsByStatus, type GroupedPosts, type PostStatus } from './postGrouping';
|
export { groupPostsByStatus, type GroupedPosts, type PostStatus } from './postGrouping';
|
||||||
export { loadTabsForProject, saveTabsForProject } from './tabPersistence';
|
export { loadTabsForProject, saveTabsForProject } from './tabPersistence';
|
||||||
export { buildTagColorMap, loadTagColorMap } from './tagColors';
|
export { buildTagColorMap, loadTagColorMap } from './tagColors';
|
||||||
|
export { BDS_EVENT_SCRIPTS_CHANGED, addWindowEventListener, dispatchWindowEvent, type BdsWindowEventName } from './windowEvents';
|
||||||
|
|||||||
34
src/renderer/utils/windowEvents.ts
Normal file
34
src/renderer/utils/windowEvents.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
export const BDS_EVENT_SCRIPTS_CHANGED = 'bds:scripts-changed' as const;
|
||||||
|
|
||||||
|
export type BdsWindowEventName =
|
||||||
|
| typeof BDS_EVENT_SCRIPTS_CHANGED
|
||||||
|
| 'bds:site-validation-updated';
|
||||||
|
|
||||||
|
export function addWindowEventListener<TDetail = unknown>(
|
||||||
|
eventName: BdsWindowEventName,
|
||||||
|
handler: (event: CustomEvent<TDetail>) => void,
|
||||||
|
): () => void {
|
||||||
|
if (typeof window.addEventListener !== 'function' || typeof window.removeEventListener !== 'function') {
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const listener = (event: Event) => {
|
||||||
|
handler(event as CustomEvent<TDetail>);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener(eventName, listener as EventListener);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener(eventName, listener as EventListener);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dispatchWindowEvent<TDetail = unknown>(
|
||||||
|
eventName: BdsWindowEventName,
|
||||||
|
detail?: TDetail,
|
||||||
|
): boolean {
|
||||||
|
if (typeof window.dispatchEvent !== 'function') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return window.dispatchEvent(new CustomEvent<TDetail>(eventName, { detail }));
|
||||||
|
}
|
||||||
61
tests/renderer/utils/windowEvents.test.ts
Normal file
61
tests/renderer/utils/windowEvents.test.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
import { addWindowEventListener, dispatchWindowEvent } from '../../../src/renderer/utils/windowEvents';
|
||||||
|
|
||||||
|
describe('windowEvents utils', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
|
||||||
|
const listeners = new Map<string, Set<(event: Event) => void>>();
|
||||||
|
(window as any).addEventListener = vi.fn((type: string, listener: (event: Event) => void) => {
|
||||||
|
if (!listeners.has(type)) {
|
||||||
|
listeners.set(type, new Set());
|
||||||
|
}
|
||||||
|
listeners.get(type)?.add(listener);
|
||||||
|
});
|
||||||
|
(window as any).removeEventListener = vi.fn((type: string, listener: (event: Event) => void) => {
|
||||||
|
listeners.get(type)?.delete(listener);
|
||||||
|
});
|
||||||
|
(window as any).dispatchEvent = vi.fn((event: Event) => {
|
||||||
|
listeners.get(event.type)?.forEach((listener) => listener(event));
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatches custom events with detail payload', () => {
|
||||||
|
const listener = vi.fn();
|
||||||
|
addWindowEventListener('bds:scripts-changed', listener);
|
||||||
|
|
||||||
|
const dispatched = dispatchWindowEvent('bds:scripts-changed', { scriptId: 'script-1' });
|
||||||
|
|
||||||
|
expect(dispatched).toBe(true);
|
||||||
|
expect(listener).toHaveBeenCalledTimes(1);
|
||||||
|
const event = listener.mock.calls[0][0] as CustomEvent<{ scriptId: string }>;
|
||||||
|
expect(event.detail).toEqual({ scriptId: 'script-1' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns no-op unsubscribe when listener APIs are unavailable', () => {
|
||||||
|
const originalAdd = (window as any).addEventListener;
|
||||||
|
const originalRemove = (window as any).removeEventListener;
|
||||||
|
(window as any).addEventListener = undefined;
|
||||||
|
(window as any).removeEventListener = undefined;
|
||||||
|
|
||||||
|
const unsubscribe = addWindowEventListener('bds:scripts-changed', vi.fn());
|
||||||
|
expect(typeof unsubscribe).toBe('function');
|
||||||
|
expect(() => unsubscribe()).not.toThrow();
|
||||||
|
|
||||||
|
(window as any).addEventListener = originalAdd;
|
||||||
|
(window as any).removeEventListener = originalRemove;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('subscribes and unsubscribes listeners', () => {
|
||||||
|
const listener = vi.fn();
|
||||||
|
const unsubscribe = addWindowEventListener('bds:scripts-changed', listener);
|
||||||
|
|
||||||
|
dispatchWindowEvent('bds:scripts-changed');
|
||||||
|
expect(listener).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
unsubscribe();
|
||||||
|
dispatchWindowEvent('bds:scripts-changed');
|
||||||
|
expect(listener).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user