Skip to content

Commit 29c70c5

Browse files
committed
Support save and restore app state. (sidebar state, window state)
1 parent fe0d262 commit 29c70c5

File tree

11 files changed

+288
-121
lines changed

11 files changed

+288
-121
lines changed

src/main/db_singleton.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { app } from 'electron'
2+
import path from 'path'
3+
import {
4+
AppState,
5+
defaultAppState,
6+
defaultNoteEditorSettings,
7+
Note,
8+
NoteEditorSettings
9+
} from '../types'
10+
import { JSONFileSyncPreset } from 'lowdb/node'
11+
import { LowSync } from 'lowdb'
12+
13+
type Dbs = {
14+
notesDb: LowSync<Note[]>
15+
settingsDb: LowSync<NoteEditorSettings>
16+
appStateDb: LowSync<AppState>
17+
}
18+
19+
class DbSingleton {
20+
private static instance: DbSingleton
21+
public dbs: Dbs
22+
23+
private constructor() {
24+
const userDir = app.getPath('userData')
25+
const defaultData: Note[] = []
26+
const notesDb = JSONFileSyncPreset<Note[]>(path.join(userDir, 'notes.json'), defaultData)
27+
const settingsDb = JSONFileSyncPreset<NoteEditorSettings>(
28+
path.join(userDir, 'settings.json'),
29+
defaultNoteEditorSettings
30+
)
31+
const appStateDb = JSONFileSyncPreset<AppState>(
32+
path.join(userDir, 'app-state.json'),
33+
defaultAppState
34+
)
35+
36+
this.dbs = {
37+
notesDb: notesDb,
38+
settingsDb: settingsDb,
39+
appStateDb: appStateDb
40+
}
41+
}
42+
43+
static getInstance() {
44+
if (!DbSingleton.instance) {
45+
DbSingleton.instance = new DbSingleton()
46+
// ... any one time initialization goes here ...
47+
}
48+
return DbSingleton.instance
49+
}
50+
51+
public async initDbs() {
52+
await this.dbs.notesDb.read()
53+
await this.dbs.settingsDb.read()
54+
await this.dbs.appStateDb.read()
55+
}
56+
}
57+
58+
const instance = DbSingleton.getInstance()
59+
await instance.initDbs()
60+
export const dbInstance = instance

src/main/index.ts

Lines changed: 22 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
import { app, shell, BrowserWindow, ipcMain } from 'electron'
2-
import path, { join } from 'path'
2+
import { join } from 'path'
33
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
44
import icon from '../../resources/icon.png?asset'
55

6-
import { getTime } from 'date-fns'
7-
import { defaultNoteEditorSettings, Note, NoteEditorSettings } from '../types'
8-
import { JSONFileSyncPreset } from 'lowdb/node'
9-
import { uuidv7 } from 'uuidv7'
6+
import { registerIpcHandles } from './ipcHandles'
7+
import { dbInstance } from './db_singleton'
108

119
function createWindow(): void {
10+
const appStateData = dbInstance.dbs.appStateDb.data
11+
1212
// Create the browser window.
1313
const mainWindow = new BrowserWindow({
14-
// width: 1024,
15-
// height: 768,
16-
width: 800,
17-
height: 600,
14+
width: appStateData?.windowWidth != null ? appStateData?.windowWidth : 800,
15+
height: appStateData?.windowHeight != null ? appStateData?.windowHeight : 600,
1816
useContentSize: true,
1917
show: false,
2018
autoHideMenuBar: true,
@@ -25,10 +23,24 @@ function createWindow(): void {
2523
}
2624
})
2725

26+
if (appStateData.windowX != null && appStateData.windowY != null) {
27+
mainWindow.setPosition(appStateData.windowX, appStateData.windowY, false)
28+
} else {
29+
mainWindow.center()
30+
}
31+
2832
mainWindow.on('ready-to-show', () => {
2933
mainWindow.show()
3034
})
3135

36+
mainWindow.on('move', async () => {
37+
const bounds = mainWindow.getBounds()
38+
const appStateDb = dbInstance.dbs.appStateDb
39+
appStateDb.data.windowX = bounds.x
40+
appStateDb.data.windowY = bounds.y
41+
await appStateDb.write()
42+
})
43+
3244
mainWindow.webContents.setWindowOpenHandler((details) => {
3345
shell.openExternal(details.url)
3446
return { action: 'deny' }
@@ -50,16 +62,6 @@ app.whenReady().then(async () => {
5062
// Set app user model id for windows
5163
electronApp.setAppUserModelId('me.saino.leavepad')
5264

53-
const userDir = app.getPath('userData')
54-
const defaultData: Note[] = []
55-
const notesDb = JSONFileSyncPreset<Note[]>(path.join(userDir, 'notes.json'), defaultData)
56-
await notesDb.read()
57-
const settingsDb = JSONFileSyncPreset<NoteEditorSettings>(
58-
path.join(userDir, 'settings.json'),
59-
defaultNoteEditorSettings
60-
)
61-
await settingsDb.read()
62-
6365
// Default open or close DevTools by F12 in development
6466
// and ignore CommandOrControl + R in production.
6567
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
@@ -70,91 +72,7 @@ app.whenReady().then(async () => {
7072
// IPC test
7173
ipcMain.on('ping', () => console.log('pong'))
7274

73-
ipcMain.handle('get-notes', (): Note[] => {
74-
return notesDb.data
75-
})
76-
77-
ipcMain.handle(
78-
'get-note',
79-
(_event: Electron.IpcMainInvokeEvent, noteId: string): Note | undefined => {
80-
const note = notesDb.data.find((note) => {
81-
note.id == noteId
82-
})
83-
84-
return note
85-
}
86-
)
87-
88-
ipcMain.handle('create-note', async () => {
89-
const note: Note = {
90-
id: uuidv7(),
91-
name: `No Name ${notesDb.data.length + 1}`,
92-
body: '',
93-
createdAt: getTime(new Date()),
94-
updatedAt: getTime(new Date())
95-
}
96-
notesDb.data.push(note)
97-
await notesDb.write()
98-
99-
return note
100-
})
101-
102-
ipcMain.handle(
103-
'update-note',
104-
async (_event: Electron.IpcMainInvokeEvent, willUpdateNote: Note) => {
105-
const note = notesDb.data.find((note) => {
106-
note.id === willUpdateNote.id
107-
})
108-
109-
const newNotes = notesDb.data.map((note) => {
110-
if (note.id === willUpdateNote.id) {
111-
return { ...note, ...willUpdateNote }
112-
} else {
113-
return note
114-
}
115-
})
116-
117-
notesDb.data = newNotes
118-
119-
await notesDb.write()
120-
121-
return note
122-
}
123-
)
124-
125-
ipcMain.handle('delete-note', async (_event: Electron.IpcMainInvokeEvent, noteId: string) => {
126-
const note = notesDb.data.find((note) => {
127-
note.id === noteId
128-
})
129-
130-
const newNotes = notesDb.data
131-
.map((note) => {
132-
if (note.id === noteId) {
133-
return undefined
134-
} else {
135-
return note
136-
}
137-
})
138-
.filter((note) => note != null)
139-
140-
notesDb.data = newNotes
141-
142-
await notesDb.write()
143-
144-
return note
145-
})
146-
147-
ipcMain.handle(
148-
'update-settings',
149-
async (_event: Electron.IpcMainInvokeEvent, settings: NoteEditorSettings) => {
150-
settingsDb.data = settings
151-
await settingsDb.write()
152-
}
153-
)
154-
155-
ipcMain.handle('get-settings', async (_event: Electron.IpcMainInvokeEvent) => {
156-
return settingsDb.data
157-
})
75+
registerIpcHandles(ipcMain)
15876

15977
createWindow()
16078

src/main/ipcHandles.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { getTime } from 'date-fns'
2+
import { uuidv7 } from 'uuidv7'
3+
4+
import { AppState, Note, NoteEditorSettings } from '../types'
5+
import { dbInstance } from './db_singleton'
6+
7+
export const registerIpcHandles = (ipcMain) => {
8+
const { notesDb, settingsDb, appStateDb } = dbInstance.dbs
9+
10+
ipcMain.handle('get-notes', (): Note[] => {
11+
return notesDb.data
12+
})
13+
14+
ipcMain.handle(
15+
'get-note',
16+
(_event: Electron.IpcMainInvokeEvent, noteId: string): Note | undefined => {
17+
const note = notesDb.data.find((note) => {
18+
note.id == noteId
19+
})
20+
21+
return note
22+
}
23+
)
24+
25+
ipcMain.handle('create-note', async () => {
26+
const note: Note = {
27+
id: uuidv7(),
28+
name: `No Name ${notesDb.data.length + 1}`,
29+
body: '',
30+
createdAt: getTime(new Date()),
31+
updatedAt: getTime(new Date())
32+
}
33+
notesDb.data.push(note)
34+
await notesDb.write()
35+
36+
return note
37+
})
38+
39+
ipcMain.handle(
40+
'update-note',
41+
async (_event: Electron.IpcMainInvokeEvent, willUpdateNote: Note) => {
42+
const note = notesDb.data.find((note) => {
43+
note.id === willUpdateNote.id
44+
})
45+
46+
const newNotes = notesDb.data.map((note) => {
47+
if (note.id === willUpdateNote.id) {
48+
return { ...note, ...willUpdateNote }
49+
} else {
50+
return note
51+
}
52+
})
53+
54+
notesDb.data = newNotes
55+
56+
await notesDb.write()
57+
58+
return note
59+
}
60+
)
61+
62+
ipcMain.handle('delete-note', async (_event: Electron.IpcMainInvokeEvent, noteId: string) => {
63+
const note = notesDb.data.find((note) => {
64+
note.id === noteId
65+
})
66+
67+
const newNotes = notesDb.data
68+
.map((note) => {
69+
if (note.id === noteId) {
70+
return undefined
71+
} else {
72+
return note
73+
}
74+
})
75+
.filter((note) => note != null)
76+
77+
notesDb.data = newNotes
78+
79+
await notesDb.write()
80+
81+
return note
82+
})
83+
84+
ipcMain.handle(
85+
'update-settings',
86+
async (_event: Electron.IpcMainInvokeEvent, settings: NoteEditorSettings) => {
87+
settingsDb.data = settings
88+
await settingsDb.write()
89+
}
90+
)
91+
92+
ipcMain.handle('get-settings', async (_event: Electron.IpcMainInvokeEvent) => {
93+
return settingsDb.data
94+
})
95+
96+
ipcMain.handle('get-app-state', async (_event: Electron.IpcMainInvokeEvent) => {
97+
return appStateDb.data
98+
})
99+
100+
ipcMain.handle(
101+
'update-app-state',
102+
async (_event: Electron.IpcMainInvokeEvent, appState: AppState) => {
103+
appStateDb.data = appState
104+
await appStateDb.write()
105+
}
106+
)
107+
}

src/preload/index.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ElectronAPI } from '@electron-toolkit/preload'
2-
import { Note, NoteEditorSettings } from 'src/types'
2+
import { AppState, Note, NoteEditorSettings } from 'src/types'
33

44
declare global {
55
interface Window {
@@ -12,6 +12,8 @@ declare global {
1212
deleteNote: (noteId: string) => Promise<Note | undefined>
1313
updateSettings: (settings: NoteEditorSettings) => void
1414
getSettings: () => Promise<NoteEditorSettings>
15+
getAppState: () => Promise<AppState>
16+
updateAppState: (appState: AppState) => void
1517
}
1618
}
1719
}

src/preload/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { contextBridge, ipcRenderer } from 'electron'
22
import { electronAPI } from '@electron-toolkit/preload'
3-
import { Note, NoteEditorSettings } from '../types'
3+
import { AppState, Note, NoteEditorSettings } from '../types'
44

55
// Custom APIs for renderer
66
const api = {
@@ -25,6 +25,12 @@ const api = {
2525
},
2626
getSettings: () => {
2727
return ipcRenderer.invoke('get-settings')
28+
},
29+
getAppState: () => {
30+
return ipcRenderer.invoke('get-app-state')
31+
},
32+
updateAppState: (appState: AppState) => {
33+
return ipcRenderer.invoke('update-app-state', appState)
2834
}
2935
}
3036

0 commit comments

Comments
 (0)