Skip to content

Commit ecbc009

Browse files
committed
feat(stage-tamagotchi): perserve window pos & size
1 parent d7d8f2d commit ecbc009

File tree

5 files changed

+119
-2
lines changed

5 files changed

+119
-2
lines changed

apps/stage-tamagotchi/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,12 @@
6363
"colorjs.io": "^0.5.2",
6464
"culori": "^4.0.2",
6565
"date-fns": "^4.1.0",
66+
"defu": "^6.1.4",
67+
"destr": "^2.0.5",
6668
"dompurify": "^3.2.7",
6769
"drizzle-kit": "^0.31.5",
6870
"drizzle-orm": "^0.44.6",
71+
"es-toolkit": "^1.39.10",
6972
"jszip": "^3.10.1",
7073
"listhen": "^1.9.0",
7174
"localforage": "^1.10.0",

apps/stage-tamagotchi/src/main/windows/main/index.ts

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,42 @@
1+
import type { BrowserWindowConstructorOptions, Rectangle } from 'electron'
2+
13
import { dirname, join } from 'node:path'
24
import { env } from 'node:process'
35
import { fileURLToPath } from 'node:url'
46

57
import { is } from '@electron-toolkit/utils'
8+
import { defu } from 'defu'
69
import { BrowserWindow, shell } from 'electron'
710
import { isMacOS } from 'std-env'
811

912
import icon from '../../../../resources/icon.png?asset'
1013

1114
import { transparentWindowConfig } from '../shared'
15+
import { createConfig } from '../shared/persistence'
1216
import { setupAppInvokeHandlers } from './eventa/index.electron'
1317
import { setupWebInvokes } from './eventa/index.web'
1418

19+
interface AppConfig {
20+
windows?: Array<Pick<BrowserWindowConstructorOptions, 'title' | 'x' | 'y' | 'width' | 'height'> & { tag: string }>
21+
}
22+
1523
export function setup() {
24+
const {
25+
setup: setupConfig,
26+
get: getConfig,
27+
update: updateConfig,
28+
} = createConfig<AppConfig>('app', 'config.json', { default: { windows: [] } })
29+
30+
setupConfig()
31+
32+
const mainWindowConfig = getConfig()?.windows?.find(w => w.title === 'AIRI' && w.tag === 'main')
33+
1634
const window = new BrowserWindow({
1735
title: 'AIRI',
18-
width: 450.0,
19-
height: 600.0,
36+
width: mainWindowConfig?.width ?? 450.0,
37+
height: mainWindowConfig?.height ?? 600.0,
38+
x: mainWindowConfig?.x,
39+
y: mainWindowConfig?.y,
2040
show: false,
2141
icon,
2242
webPreferences: {
@@ -26,6 +46,41 @@ export function setup() {
2646
...transparentWindowConfig(),
2747
})
2848

49+
function handleNewBounds(newBounds: Rectangle) {
50+
const config = getConfig()!
51+
if (!config.windows || !Array.isArray(config.windows)) {
52+
config.windows = []
53+
}
54+
55+
const existingConfigIndex = config.windows.findIndex(w => w.title === 'AIRI' && w.tag === 'main')
56+
57+
if (existingConfigIndex === -1) {
58+
config.windows.push({
59+
title: 'AIRI',
60+
tag: 'main',
61+
x: newBounds.x,
62+
y: newBounds.y,
63+
width: newBounds.width,
64+
height: newBounds.height,
65+
})
66+
}
67+
else {
68+
const mainWindowConfig = defu(config.windows[existingConfigIndex], { title: 'AIRI', tag: 'main' })
69+
70+
mainWindowConfig.x = newBounds.x
71+
mainWindowConfig.y = newBounds.y
72+
mainWindowConfig.width = newBounds.width
73+
mainWindowConfig.height = newBounds.height
74+
75+
config.windows[existingConfigIndex] = mainWindowConfig
76+
}
77+
78+
updateConfig(config)
79+
}
80+
81+
window.on('resize', () => handleNewBounds(window.getBounds()))
82+
window.on('move', () => handleNewBounds(window.getBounds()))
83+
2984
window.setAlwaysOnTop(true)
3085
if (isMacOS) {
3186
window.setWindowButtonVisibility(false)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { readFileSync } from 'node:fs'
2+
import { writeFile } from 'node:fs/promises'
3+
import { join } from 'node:path'
4+
5+
import { safeDestr } from 'destr'
6+
import { app } from 'electron'
7+
import { throttle } from 'es-toolkit'
8+
9+
function parseOrFallback<T>(config: string, fallback: T | undefined): T | undefined {
10+
return safeDestr<T>(config) || fallback
11+
}
12+
13+
const persistenceMap = new Map<string, any>()
14+
15+
export function createConfig<T>(namespace: string, filename: string, options?: { default?: T }) {
16+
function configPath() {
17+
const path = join(app.getPath('userData'), `${namespace}-${filename}`)
18+
return path
19+
}
20+
21+
function setup() {
22+
const data = parseOrFallback<T>(readFileSync(configPath(), { encoding: 'utf-8' }), options?.default)
23+
persistenceMap.set(`${namespace}-${filename}`, data)
24+
}
25+
26+
const save = throttle(async () => {
27+
try {
28+
await writeFile(configPath(), JSON.stringify(persistenceMap.get(`${namespace}-${filename}`)))
29+
}
30+
catch (e) {
31+
console.error('Failed to save config', e)
32+
}
33+
}, 250)
34+
35+
function update(newData: T) {
36+
persistenceMap.set(`${namespace}-${filename}`, newData)
37+
save()
38+
}
39+
40+
function get(): T | undefined {
41+
return persistenceMap.get(`${namespace}-${filename}`) as T | undefined
42+
}
43+
44+
return {
45+
setup,
46+
get,
47+
update,
48+
}
49+
}

cspell.config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ words:
6868
- defu
6969
- demi
7070
- demodel
71+
- destr
7172
- devlog
7273
- devlogs
7374
- directml

pnpm-lock.yaml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)