mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 19:54:44 -07:00
unify notepad config with the app config
This commit is contained in:
parent
7ca81f05ec
commit
18c94fd7dd
10 changed files with 197 additions and 179 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "gerbil",
|
||||
"productName": "Gerbil",
|
||||
"version": "1.5.0",
|
||||
"version": "1.5.1",
|
||||
"description": "Run Large Language Models locally",
|
||||
"main": "out/main/index.js",
|
||||
"homepage": "./",
|
||||
|
|
|
|||
|
|
@ -26,45 +26,40 @@ export const NotepadContainer = () => {
|
|||
const [resizeDirection, setResizeDirection] = useState<string | null>(null);
|
||||
const [confirmCloseModal, setConfirmCloseModal] = useState<{
|
||||
isOpen: boolean;
|
||||
tabId: string | null;
|
||||
tabTitle: string;
|
||||
title: string;
|
||||
}>({
|
||||
isOpen: false,
|
||||
tabId: null,
|
||||
tabTitle: '',
|
||||
title: '',
|
||||
});
|
||||
|
||||
const activeTab = tabs.find((tab) => tab.id === activeTabId);
|
||||
const activeTab = tabs.find((tab) => tab.title === activeTabId);
|
||||
|
||||
const handleCreateNewTab = async () => {
|
||||
const newTab = await window.electronAPI.notepad.createNewTab();
|
||||
addTab(newTab);
|
||||
};
|
||||
|
||||
const handleTabCloseRequest = (tabId: string) => {
|
||||
const tab = tabs.find((t) => t.id === tabId);
|
||||
const handleTabCloseRequest = (title: string) => {
|
||||
const tab = tabs.find((t) => t.title === title);
|
||||
if (!tab) return;
|
||||
|
||||
if (tab.content.trim().length > 0) {
|
||||
setConfirmCloseModal({
|
||||
isOpen: true,
|
||||
tabId,
|
||||
tabTitle: tab.title,
|
||||
title,
|
||||
});
|
||||
} else {
|
||||
removeTab(tabId);
|
||||
removeTab(title);
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirmClose = () => {
|
||||
if (confirmCloseModal.tabId) {
|
||||
removeTab(confirmCloseModal.tabId);
|
||||
}
|
||||
setConfirmCloseModal({ isOpen: false, tabId: null, tabTitle: '' });
|
||||
removeTab(confirmCloseModal.title);
|
||||
setConfirmCloseModal({ isOpen: false, title: '' });
|
||||
};
|
||||
|
||||
const handleCancelClose = () => {
|
||||
setConfirmCloseModal({ isOpen: false, tabId: null, tabTitle: '' });
|
||||
setConfirmCloseModal({ isOpen: false, title: '' });
|
||||
};
|
||||
|
||||
const handleResizeStart =
|
||||
|
|
@ -207,7 +202,7 @@ export const NotepadContainer = () => {
|
|||
onClick={() => setVisible(false)}
|
||||
color="red"
|
||||
>
|
||||
<X size={12} />
|
||||
<X size={16} />
|
||||
</ActionIcon>
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
@ -219,7 +214,7 @@ export const NotepadContainer = () => {
|
|||
|
||||
<CloseConfirmModal
|
||||
isOpen={confirmCloseModal.isOpen}
|
||||
tabTitle={confirmCloseModal.tabTitle}
|
||||
tabTitle={confirmCloseModal.title}
|
||||
onConfirm={handleConfirmClose}
|
||||
onCancel={handleCancelClose}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -38,12 +38,12 @@ export const NotepadEditor = ({ tab }: NotepadEditorProps) => {
|
|||
}
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
saveTabContent(tab.id, newContent);
|
||||
saveTabContent(tab.title, newContent);
|
||||
}, 500);
|
||||
|
||||
setSaveTimeout(timeout);
|
||||
},
|
||||
[tab.id, saveTabContent, saveTimeout]
|
||||
[tab.title, saveTabContent, saveTimeout]
|
||||
);
|
||||
|
||||
const handleEditorContextMenu = (e: MouseEvent) => {
|
||||
|
|
@ -62,7 +62,7 @@ export const NotepadEditor = ({ tab }: NotepadEditorProps) => {
|
|||
|
||||
useEffect(() => {
|
||||
setContent(tab.content);
|
||||
}, [tab.content, tab.id]);
|
||||
}, [tab.content, tab.title]);
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
|
|
|
|||
|
|
@ -13,14 +13,13 @@ import { usePreferencesStore } from '@/stores/preferences';
|
|||
|
||||
interface NotepadTabsProps {
|
||||
onCreateNewTab: () => Promise<void>;
|
||||
onCloseTab: (tabId: string) => void;
|
||||
onCloseTab: (title: string) => void;
|
||||
}
|
||||
|
||||
interface TabProps {
|
||||
id: string;
|
||||
title: string;
|
||||
index: number;
|
||||
isActive: boolean;
|
||||
title: string;
|
||||
onSelect: () => void;
|
||||
onClose: (e: MouseEvent) => void;
|
||||
onDragStart: (e: DragEvent, index: number) => void;
|
||||
|
|
@ -166,6 +165,7 @@ const Tab = ({
|
|||
) : (
|
||||
<Text
|
||||
size="xs"
|
||||
title={title}
|
||||
onClick={handleTitleClick}
|
||||
onDoubleClick={handleTitleDoubleClick}
|
||||
onContextMenu={(e) => {
|
||||
|
|
@ -208,17 +208,18 @@ export const NotepadTabs = ({
|
|||
const [draggedTabIndex, setDraggedTabIndex] = useState<number | null>(null);
|
||||
const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
|
||||
|
||||
const handleTabSelect = (tabId: string) => {
|
||||
setActiveTab(tabId);
|
||||
const handleTabSelect = (title: string) => {
|
||||
setActiveTab(title);
|
||||
};
|
||||
|
||||
const handleTabClose = (e: MouseEvent, tabId: string) => {
|
||||
const handleTabClose = (e: MouseEvent, title: string) => {
|
||||
e.stopPropagation();
|
||||
onCloseTab(tabId);
|
||||
onCloseTab(title);
|
||||
};
|
||||
|
||||
const handleTabRename = (tabId: string, newTitle: string) => {
|
||||
updateTab(tabId, { title: newTitle });
|
||||
const handleTabRename = (title: string, newTitle: string) => {
|
||||
updateTab(title, { title: newTitle });
|
||||
window.electronAPI.notepad.renameTab(title, newTitle);
|
||||
};
|
||||
|
||||
const handleTabBarContextMenu = (e: MouseEvent) => {
|
||||
|
|
@ -258,8 +259,6 @@ export const NotepadTabs = ({
|
|||
setDragOverIndex(null);
|
||||
};
|
||||
|
||||
if (tabs.length === 0) return null;
|
||||
|
||||
return (
|
||||
<Box
|
||||
onContextMenu={handleTabBarContextMenu}
|
||||
|
|
@ -276,18 +275,17 @@ export const NotepadTabs = ({
|
|||
>
|
||||
{tabs.map((tab, index) => (
|
||||
<Box
|
||||
key={tab.id}
|
||||
key={tab.title}
|
||||
onDragEnter={() => handleDragEnter(index)}
|
||||
onDragLeave={handleDragLeave}
|
||||
>
|
||||
<Tab
|
||||
id={tab.id}
|
||||
index={index}
|
||||
isActive={tab.id === activeTabId}
|
||||
title={tab.title}
|
||||
onSelect={() => handleTabSelect(tab.id)}
|
||||
onClose={(e) => handleTabClose(e, tab.id)}
|
||||
onRename={(newTitle) => handleTabRename(tab.id, newTitle)}
|
||||
index={index}
|
||||
isActive={tab.title === activeTabId}
|
||||
onSelect={() => handleTabSelect(tab.title)}
|
||||
onClose={(e) => handleTabClose(e, tab.title)}
|
||||
onRename={(newTitle) => handleTabRename(tab.title, newTitle)}
|
||||
onDragStart={handleDragStart}
|
||||
onDragOver={handleDragOver}
|
||||
onDrop={handleDrop}
|
||||
|
|
@ -303,7 +301,7 @@ export const NotepadTabs = ({
|
|||
size="xs"
|
||||
onClick={onCreateNewTab}
|
||||
style={{
|
||||
margin: '4px',
|
||||
margin: '0.25rem',
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ import {
|
|||
loadNotepadState,
|
||||
deleteTabFile,
|
||||
createNewTab,
|
||||
renameTab,
|
||||
} from '@/main/modules/notepad';
|
||||
import {
|
||||
detectGPU,
|
||||
|
|
@ -279,11 +280,15 @@ export function setupIPCHandlers() {
|
|||
|
||||
ipcMain.handle(
|
||||
'notepad:saveTabContent',
|
||||
(_, tabId: string, content: string) => saveTabContent(tabId, content)
|
||||
(_, title: string, content: string) => saveTabContent(title, content)
|
||||
);
|
||||
|
||||
ipcMain.handle('notepad:loadTabContent', (_, tabId: string) =>
|
||||
loadTabContent(tabId)
|
||||
ipcMain.handle('notepad:loadTabContent', (_, title: string) =>
|
||||
loadTabContent(title)
|
||||
);
|
||||
|
||||
ipcMain.handle('notepad:renameTab', (_, oldTitle: string, newTitle: string) =>
|
||||
renameTab(oldTitle, newTitle)
|
||||
);
|
||||
|
||||
ipcMain.handle('notepad:saveState', (_, state: NotepadState) =>
|
||||
|
|
@ -292,8 +297,8 @@ export function setupIPCHandlers() {
|
|||
|
||||
ipcMain.handle('notepad:loadState', () => loadNotepadState());
|
||||
|
||||
ipcMain.handle('notepad:deleteTab', (_, tabId: string) =>
|
||||
deleteTabFile(tabId)
|
||||
ipcMain.handle('notepad:deleteTab', (_, title: string) =>
|
||||
deleteTabFile(title)
|
||||
);
|
||||
|
||||
ipcMain.handle('notepad:createNewTab', (_, title?: string) =>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { nativeTheme } from 'electron';
|
|||
import { PRODUCT_NAME } from '@/constants';
|
||||
import type { FrontendPreference } from '@/types';
|
||||
import type { MantineColorScheme } from '@mantine/core';
|
||||
import type { SavedNotepadState } from '@/types/electron';
|
||||
|
||||
interface WindowBounds {
|
||||
x: number;
|
||||
|
|
@ -28,6 +29,7 @@ interface AppConfig {
|
|||
skipEjectConfirmation?: boolean;
|
||||
dismissedUpdates?: string[];
|
||||
zoomLevel?: number;
|
||||
notepad?: SavedNotepadState;
|
||||
}
|
||||
|
||||
let config: AppConfig = {};
|
||||
|
|
|
|||
|
|
@ -1,116 +1,119 @@
|
|||
import { promises as fs } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { safeExecute, tryExecute } from '@/utils/node/logging';
|
||||
import { getInstallDir } from './config';
|
||||
import type { SavedNotepadState, SavedNotepadTab } from '@/types/electron';
|
||||
import { get, set, getInstallDir } from './config';
|
||||
import type { SavedNotepadState } from '@/types/electron';
|
||||
import {
|
||||
DEFAULT_NOTEPAD_POSITION,
|
||||
DEFAULT_TAB_CONTENT,
|
||||
} from '@/constants/notepad';
|
||||
|
||||
const NOTEPAD_DIR = join(getInstallDir(), 'notepad');
|
||||
const NOTEPAD_STATE_FILE = join(NOTEPAD_DIR, 'state.json');
|
||||
import { pathExists } from '@/utils/node/fs';
|
||||
import { join } from 'path';
|
||||
import { readFile, readdir, writeFile, unlink, rename } from 'fs/promises';
|
||||
|
||||
const DEFAULT_NOTEPAD_STATE: SavedNotepadState = {
|
||||
tabs: [],
|
||||
activeTabId: null,
|
||||
position: DEFAULT_NOTEPAD_POSITION,
|
||||
isVisible: false,
|
||||
};
|
||||
|
||||
async function ensureNotepadDir() {
|
||||
return tryExecute(async () => {
|
||||
await fs.mkdir(NOTEPAD_DIR, { recursive: true });
|
||||
}, 'Failed to create notepad directory');
|
||||
}
|
||||
const getNotepadDir = () => join(getInstallDir(), 'notepad');
|
||||
|
||||
export async function saveTabContent(tabId: string, content: string) {
|
||||
return tryExecute(async () => {
|
||||
await ensureNotepadDir();
|
||||
const filePath = join(NOTEPAD_DIR, `${tabId}.txt`);
|
||||
await fs.writeFile(filePath, content, 'utf8');
|
||||
}, 'Failed to save tab content');
|
||||
}
|
||||
const getTabsFromStorage = async () => {
|
||||
const tabs = [];
|
||||
|
||||
export async function loadTabContent(tabId: string) {
|
||||
const filePath = join(NOTEPAD_DIR, `${tabId}.txt`);
|
||||
try {
|
||||
return fs.readFile(filePath, 'utf8');
|
||||
} catch (error) {
|
||||
if ((error as { code?: string }).code === 'ENOENT') {
|
||||
return '';
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const notepadDir = getNotepadDir();
|
||||
if (await pathExists(notepadDir)) {
|
||||
const files = await readdir(notepadDir);
|
||||
const tabFiles = files.filter((f) => f.endsWith('.txt'));
|
||||
|
||||
export async function saveNotepadState(state: SavedNotepadState) {
|
||||
return tryExecute(async () => {
|
||||
await ensureNotepadDir();
|
||||
await fs.writeFile(
|
||||
NOTEPAD_STATE_FILE,
|
||||
JSON.stringify(state, null, 2),
|
||||
'utf8'
|
||||
);
|
||||
}, 'Failed to save notepad state');
|
||||
}
|
||||
|
||||
export async function loadNotepadState() {
|
||||
const result = await safeExecute(async () => {
|
||||
try {
|
||||
const data = await fs.readFile(NOTEPAD_STATE_FILE, 'utf8');
|
||||
return JSON.parse(data) as SavedNotepadState;
|
||||
} catch {
|
||||
return DEFAULT_NOTEPAD_STATE;
|
||||
}
|
||||
}, 'Failed to load notepad state');
|
||||
|
||||
return result || DEFAULT_NOTEPAD_STATE;
|
||||
}
|
||||
|
||||
export async function deleteTabFile(tabId: string) {
|
||||
return tryExecute(async () => {
|
||||
const filePath = join(NOTEPAD_DIR, `${tabId}.txt`);
|
||||
try {
|
||||
await fs.unlink(filePath);
|
||||
} catch (error) {
|
||||
if ((error as { code?: string }).code !== 'ENOENT') {
|
||||
throw error;
|
||||
for (const file of tabFiles) {
|
||||
const title = file.replace('.txt', '');
|
||||
tabs.push({ title });
|
||||
}
|
||||
}
|
||||
}, 'Failed to delete tab file');
|
||||
}
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
|
||||
let tabCounter = 1;
|
||||
return tabs;
|
||||
};
|
||||
|
||||
export async function createNewTab(title?: string) {
|
||||
export const renameTab = async (oldTitle: string, newTitle: string) => {
|
||||
try {
|
||||
const notepadDir = getNotepadDir();
|
||||
|
||||
await rename(
|
||||
join(notepadDir, `${oldTitle}.txt`),
|
||||
join(notepadDir, `${newTitle}.txt`)
|
||||
);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const saveTabContent = async (title: string, content: string) => {
|
||||
try {
|
||||
const notepadDir = getNotepadDir();
|
||||
const filePath = join(notepadDir, `${title}.txt`);
|
||||
|
||||
await writeFile(filePath, content, 'utf-8');
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const loadTabContent = async (title: string) => {
|
||||
try {
|
||||
const notepadDir = getNotepadDir();
|
||||
return readFile(join(notepadDir, `${title}.txt`), 'utf-8');
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export const saveNotepadState = async (state: SavedNotepadState) => {
|
||||
await set('notepad', state);
|
||||
return true;
|
||||
};
|
||||
|
||||
export const loadNotepadState = async () => {
|
||||
const stored = get('notepad') || DEFAULT_NOTEPAD_STATE;
|
||||
const tabs = await getTabsFromStorage();
|
||||
const activeTabId =
|
||||
stored.activeTabId && tabs.some((tab) => tab.title === stored.activeTabId)
|
||||
? stored.activeTabId
|
||||
: tabs[0]?.title || null;
|
||||
|
||||
return { ...stored, tabs, activeTabId };
|
||||
};
|
||||
|
||||
export const deleteTabFile = async (title: string) => {
|
||||
try {
|
||||
await unlink(join(getNotepadDir(), `${title}.txt`));
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const createNewTab = async (title?: string) => {
|
||||
if (!title) {
|
||||
const state = await loadNotepadState();
|
||||
const noteNumbers = state.tabs
|
||||
.map((tab: SavedNotepadTab) => {
|
||||
const match = tab.title.match(/^Note (\d+)$/);
|
||||
return match ? parseInt(match[1], 10) : 0;
|
||||
})
|
||||
.filter((num: number) => num > 0)
|
||||
.sort((a: number, b: number) => a - b);
|
||||
.map((tab) => tab.title.match(/^Note (\d+)$/)?.[1])
|
||||
.filter(Boolean)
|
||||
.map(Number)
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
tabCounter = 1;
|
||||
let counter = 1;
|
||||
for (const num of noteNumbers) {
|
||||
if (num === tabCounter) {
|
||||
tabCounter++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
if (num === counter) counter++;
|
||||
else break;
|
||||
}
|
||||
title = `Note ${counter}`;
|
||||
}
|
||||
|
||||
const newTab = {
|
||||
id: `tab-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
|
||||
title: title || `Note ${tabCounter++}`,
|
||||
content: DEFAULT_TAB_CONTENT,
|
||||
};
|
||||
|
||||
await saveTabContent(newTab.id, newTab.content);
|
||||
|
||||
return newTab;
|
||||
}
|
||||
await saveTabContent(title, DEFAULT_TAB_CONTENT);
|
||||
return { title, content: DEFAULT_TAB_CONTENT };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -191,13 +191,15 @@ const updaterAPI: UpdaterAPI = {
|
|||
};
|
||||
|
||||
const notepadAPI: NotepadAPI = {
|
||||
saveTabContent: (tabId, content) =>
|
||||
ipcRenderer.invoke('notepad:saveTabContent', tabId, content),
|
||||
loadTabContent: (tabId) =>
|
||||
ipcRenderer.invoke('notepad:loadTabContent', tabId),
|
||||
saveTabContent: (title, content) =>
|
||||
ipcRenderer.invoke('notepad:saveTabContent', title, content),
|
||||
loadTabContent: (title) =>
|
||||
ipcRenderer.invoke('notepad:loadTabContent', title),
|
||||
renameTab: (oldTitle, newTitle) =>
|
||||
ipcRenderer.invoke('notepad:renameTab', oldTitle, newTitle),
|
||||
saveState: (state) => ipcRenderer.invoke('notepad:saveState', state),
|
||||
loadState: () => ipcRenderer.invoke('notepad:loadState'),
|
||||
deleteTab: (tabId) => ipcRenderer.invoke('notepad:deleteTab', tabId),
|
||||
deleteTab: (title) => ipcRenderer.invoke('notepad:deleteTab', title),
|
||||
createNewTab: (title) => ipcRenderer.invoke('notepad:createNewTab', title),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -9,17 +9,17 @@ import {
|
|||
interface NotepadStore extends NotepadState {
|
||||
isLoaded: boolean;
|
||||
setTabs: (tabs: NotepadTab[]) => void;
|
||||
setActiveTab: (tabId: string | null) => void;
|
||||
setActiveTab: (title: string | null) => void;
|
||||
addTab: (tab: NotepadTab) => void;
|
||||
updateTab: (tabId: string, updates: Partial<NotepadTab>) => void;
|
||||
removeTab: (tabId: string) => void;
|
||||
updateTab: (title: string, updates: Partial<NotepadTab>) => void;
|
||||
removeTab: (title: string) => void;
|
||||
reorderTabs: (fromIndex: number, toIndex: number) => void;
|
||||
setPosition: (position: NotepadState['position']) => void;
|
||||
setVisible: (visible: boolean) => void;
|
||||
setShowLineNumbers: (showLineNumbers: boolean) => void;
|
||||
loadState: () => Promise<void>;
|
||||
saveState: () => Promise<void>;
|
||||
saveTabContent: (tabId: string, content: string) => Promise<void>;
|
||||
saveTabContent: (title: string, content: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useNotepadStore = create<NotepadStore>()(
|
||||
|
|
@ -33,45 +33,55 @@ export const useNotepadStore = create<NotepadStore>()(
|
|||
|
||||
setTabs: (tabs) => set({ tabs }),
|
||||
|
||||
setActiveTab: (tabId) => set({ activeTabId: tabId }),
|
||||
setActiveTab: (title) => set({ activeTabId: title }),
|
||||
|
||||
addTab: (tab) => {
|
||||
set((state) => ({
|
||||
tabs: [...state.tabs, tab],
|
||||
activeTabId: tab.id,
|
||||
activeTabId: tab.title,
|
||||
}));
|
||||
},
|
||||
|
||||
updateTab: (tabId, updates) => {
|
||||
set((state) => ({
|
||||
tabs: state.tabs.map((tab) =>
|
||||
tab.id === tabId ? { ...tab, ...updates } : tab
|
||||
),
|
||||
}));
|
||||
updateTab: (title, updates) => {
|
||||
set((state) => {
|
||||
const updatedTabs = state.tabs.map((tab) =>
|
||||
tab.title === title ? { ...tab, ...updates } : tab
|
||||
);
|
||||
|
||||
let newActiveTabId = state.activeTabId;
|
||||
if (updates.title && state.activeTabId === title) {
|
||||
newActiveTabId = updates.title;
|
||||
}
|
||||
|
||||
return {
|
||||
tabs: updatedTabs,
|
||||
activeTabId: newActiveTabId,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
removeTab: (tabId) => {
|
||||
removeTab: (title) => {
|
||||
const state = get();
|
||||
|
||||
if (state.tabs.length <= 1) {
|
||||
const tab = state.tabs.find((t) => t.id === tabId);
|
||||
const tab = state.tabs.find((t) => t.title === title);
|
||||
if (tab) {
|
||||
set((state) => ({
|
||||
tabs: state.tabs.map((t) =>
|
||||
t.id === tabId ? { ...t, content: DEFAULT_TAB_CONTENT } : t
|
||||
t.title === title ? { ...t, content: DEFAULT_TAB_CONTENT } : t
|
||||
),
|
||||
}));
|
||||
window.electronAPI.notepad.saveTabContent(tabId, DEFAULT_TAB_CONTENT);
|
||||
window.electronAPI.notepad.saveTabContent(title, DEFAULT_TAB_CONTENT);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const newTabs = state.tabs.filter((tab) => tab.id !== tabId);
|
||||
const newTabs = state.tabs.filter((tab) => tab.title !== title);
|
||||
const newActiveTabId =
|
||||
state.activeTabId === tabId
|
||||
state.activeTabId === title
|
||||
? newTabs.length > 0
|
||||
? newTabs[Math.max(0, newTabs.length - 1)].id
|
||||
? newTabs[Math.max(0, newTabs.length - 1)].title
|
||||
: null
|
||||
: state.activeTabId;
|
||||
|
||||
|
|
@ -80,7 +90,7 @@ export const useNotepadStore = create<NotepadStore>()(
|
|||
activeTabId: newActiveTabId,
|
||||
});
|
||||
|
||||
window.electronAPI.notepad.deleteTab(tabId);
|
||||
window.electronAPI.notepad.deleteTab(title);
|
||||
},
|
||||
|
||||
reorderTabs: (fromIndex, toIndex) => {
|
||||
|
|
@ -109,10 +119,14 @@ export const useNotepadStore = create<NotepadStore>()(
|
|||
const savedState = await window.electronAPI.notepad.loadState();
|
||||
|
||||
const tabsWithContent = await Promise.all(
|
||||
savedState.tabs.map(async (tab) => ({
|
||||
...tab,
|
||||
content: await window.electronAPI.notepad.loadTabContent(tab.id),
|
||||
}))
|
||||
savedState.tabs.map((tab) =>
|
||||
window.electronAPI.notepad
|
||||
.loadTabContent(tab.title)
|
||||
.then((content) => ({
|
||||
...tab,
|
||||
content,
|
||||
}))
|
||||
)
|
||||
);
|
||||
|
||||
if (tabsWithContent.length === 0) {
|
||||
|
|
@ -122,7 +136,8 @@ export const useNotepadStore = create<NotepadStore>()(
|
|||
|
||||
set({
|
||||
tabs: tabsWithContent,
|
||||
activeTabId: savedState.activeTabId || tabsWithContent[0]?.id || null,
|
||||
activeTabId:
|
||||
savedState.activeTabId || tabsWithContent[0]?.title || null,
|
||||
position: savedState.position,
|
||||
isVisible: savedState.isVisible,
|
||||
showLineNumbers: savedState.showLineNumbers ?? true,
|
||||
|
|
@ -132,7 +147,7 @@ export const useNotepadStore = create<NotepadStore>()(
|
|||
const defaultTab = await window.electronAPI.notepad.createNewTab();
|
||||
set({
|
||||
tabs: [defaultTab],
|
||||
activeTabId: defaultTab.id,
|
||||
activeTabId: defaultTab.title,
|
||||
position: DEFAULT_NOTEPAD_POSITION,
|
||||
isVisible: false,
|
||||
isLoaded: true,
|
||||
|
|
@ -145,10 +160,6 @@ export const useNotepadStore = create<NotepadStore>()(
|
|||
if (!state.isLoaded) return;
|
||||
|
||||
await window.electronAPI.notepad.saveState({
|
||||
tabs: state.tabs.map((tab) => ({
|
||||
id: tab.id,
|
||||
title: tab.title,
|
||||
})),
|
||||
activeTabId: state.activeTabId,
|
||||
position: state.position,
|
||||
isVisible: state.isVisible,
|
||||
|
|
@ -156,9 +167,9 @@ export const useNotepadStore = create<NotepadStore>()(
|
|||
});
|
||||
},
|
||||
|
||||
saveTabContent: async (tabId, content) => {
|
||||
await window.electronAPI.notepad.saveTabContent(tabId, content);
|
||||
get().updateTab(tabId, { content });
|
||||
saveTabContent: async (title, content) => {
|
||||
await window.electronAPI.notepad.saveTabContent(title, content);
|
||||
get().updateTab(title, { content });
|
||||
},
|
||||
}))
|
||||
);
|
||||
|
|
|
|||
16
src/types/electron.d.ts
vendored
16
src/types/electron.d.ts
vendored
|
|
@ -210,13 +210,11 @@ export interface UpdaterAPI {
|
|||
}
|
||||
|
||||
export interface NotepadTab {
|
||||
id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface SavedNotepadTab {
|
||||
id: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
|
|
@ -232,7 +230,6 @@ export interface NotepadState {
|
|||
}
|
||||
|
||||
export interface SavedNotepadState {
|
||||
tabs: SavedNotepadTab[];
|
||||
activeTabId: string | null;
|
||||
position: {
|
||||
width: number;
|
||||
|
|
@ -242,12 +239,17 @@ export interface SavedNotepadState {
|
|||
showLineNumbers?: boolean;
|
||||
}
|
||||
|
||||
export interface NotepadStateWithTabs extends SavedNotepadState {
|
||||
tabs: SavedNotepadTab[];
|
||||
}
|
||||
|
||||
export interface NotepadAPI {
|
||||
saveTabContent: (tabId: string, content: string) => Promise<boolean>;
|
||||
loadTabContent: (tabId: string) => Promise<string>;
|
||||
saveTabContent: (title: string, content: string) => Promise<boolean>;
|
||||
loadTabContent: (title: string) => Promise<string>;
|
||||
renameTab: (oldTitle: string, newTitle: string) => Promise<boolean>;
|
||||
saveState: (state: SavedNotepadState) => Promise<boolean>;
|
||||
loadState: () => Promise<SavedNotepadState>;
|
||||
deleteTab: (tabId: string) => Promise<boolean>;
|
||||
loadState: () => Promise<NotepadStateWithTabs>;
|
||||
deleteTab: (title: string) => Promise<boolean>;
|
||||
createNewTab: (title?: string) => Promise<NotepadTab>;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue