refactor: storage

This commit is contained in:
codytseng
2024-11-24 17:43:07 +08:00
parent 6d4bb00f8b
commit 3aa383ad32
9 changed files with 83 additions and 116 deletions

4
src/common/constants.ts Normal file
View File

@@ -0,0 +1,4 @@
export const StorageKey = {
THEME_SETTING: 'themeSetting',
RELAY_GROUPS: 'relayGroups'
}

View File

@@ -24,14 +24,13 @@ export type TElectronWindow = {
isEncryptionAvailable: () => Promise<boolean>
}
theme: {
onChange: (cb: (theme: TTheme) => void) => void
addChangeListener: (listener: (theme: TTheme) => void) => void
removeChangeListener: () => void
current: () => Promise<TTheme>
themeSetting: () => Promise<TThemeSetting>
set: (themeSetting: TThemeSetting) => Promise<void>
}
storage: {
getRelayGroups: () => Promise<TRelayGroup[]>
setRelayGroups: (relayGroups: TRelayGroup[]) => Promise<void>
getItem: (key: string) => Promise<string>
setItem: (key: string, value: string) => Promise<void>
}
nostr: {
login: (nsec: string) => Promise<{

View File

@@ -71,7 +71,7 @@ app.whenReady().then(async () => {
const storageService = new StorageService()
storageService.init()
const themeService = new ThemeService(storageService, sendToRenderer)
const themeService = new ThemeService(sendToRenderer)
themeService.init()
const nostrService = new NostrService()

View File

@@ -1,42 +1,10 @@
import { TConfig, TRelayGroup, TThemeSetting } from '@common/types'
import { app, ipcMain } from 'electron'
import { existsSync, readFileSync, writeFileSync } from 'fs'
import path from 'path'
export class StorageService {
private storage: Storage
constructor() {
this.storage = new Storage()
}
init() {
ipcMain.handle('storage:getRelayGroups', () => this.getRelayGroups())
ipcMain.handle('storage:setRelayGroups', (_, relayGroups: TRelayGroup[]) =>
this.setRelayGroups(relayGroups)
)
}
getRelayGroups(): TRelayGroup[] | null {
return this.storage.get('relayGroups') ?? null
}
setRelayGroups(relayGroups: TRelayGroup[]) {
this.storage.set('relayGroups', relayGroups)
}
getTheme() {
return this.storage.get('theme') ?? 'system'
}
setTheme(theme: TThemeSetting) {
this.storage.set('theme', theme)
}
}
class Storage {
private path: string
private config: TConfig
private config: Record<string, string> = {}
private writeTimer: NodeJS.Timeout | null = null
constructor() {
@@ -46,11 +14,20 @@ class Storage {
this.config = JSON.parse(json)
}
get<K extends keyof TConfig, V extends TConfig[K]>(key: K): V | undefined {
return this.config[key] as V
init() {
ipcMain.handle('storage:getItem', (_, key: string) => this.getItem(key))
ipcMain.handle('storage:setItem', (_, key: string, value: string) => this.setItem(key, value))
}
set<K extends keyof TConfig>(key: K, value: TConfig[K]) {
getItem(key: string): string | undefined {
const value = this.config[key]
// backward compatibility
if (value && typeof value !== 'string') return JSON.stringify(value)
return value
}
setItem(key: string, value: string) {
this.config[key] = value
if (this.writeTimer) return

View File

@@ -1,40 +1,18 @@
import { TThemeSetting } from '@common/types'
import { ipcMain, nativeTheme } from 'electron'
import { TSendToRenderer } from '../types'
import { StorageService } from './storage.service'
export class ThemeService {
private themeSetting: TThemeSetting = 'system'
constructor(
private storageService: StorageService,
private sendToRenderer: TSendToRenderer
) {}
constructor(private sendToRenderer: TSendToRenderer) {}
init() {
this.themeSetting = this.storageService.getTheme()
ipcMain.handle('theme:current', () => this.getCurrentTheme())
ipcMain.handle('theme:themeSetting', () => this.themeSetting)
ipcMain.handle('theme:set', (_, theme: TThemeSetting) => this.setTheme(theme))
nativeTheme.on('updated', () => {
if (this.themeSetting === 'system') {
this.sendCurrentThemeToRenderer()
}
this.sendCurrentThemeToRenderer()
})
}
getCurrentTheme() {
if (this.themeSetting === 'system') {
return nativeTheme.shouldUseDarkColors ? 'dark' : 'light'
}
return this.themeSetting
}
private setTheme(theme: TThemeSetting) {
this.themeSetting = theme
this.storageService.setTheme(theme)
this.sendCurrentThemeToRenderer()
return nativeTheme.shouldUseDarkColors ? 'dark' : 'light'
}
private sendCurrentThemeToRenderer() {

View File

@@ -1,4 +1,4 @@
import { TDraftEvent, TRelayGroup, TThemeSetting } from '@common/types'
import { TDraftEvent, TTheme } from '@common/types'
import { electronAPI } from '@electron-toolkit/preload'
import { contextBridge, ipcRenderer } from 'electron'
@@ -8,19 +8,19 @@ const api = {
isEncryptionAvailable: () => ipcRenderer.invoke('system:isEncryptionAvailable')
},
theme: {
onChange: (cb: (theme: 'dark' | 'light') => void) => {
addChangeListener: (listener: (theme: TTheme) => void) => {
ipcRenderer.on('theme:change', (_, theme) => {
cb(theme)
listener(theme)
})
},
current: () => ipcRenderer.invoke('theme:current'),
themeSetting: () => ipcRenderer.invoke('theme:themeSetting'),
set: (themeSetting: TThemeSetting) => ipcRenderer.invoke('theme:set', themeSetting)
removeChangeListener: () => {
ipcRenderer.removeAllListeners('theme:change')
},
current: () => ipcRenderer.invoke('theme:current')
},
storage: {
getRelayGroups: () => ipcRenderer.invoke('storage:getRelayGroups'),
setRelayGroups: (relayGroups: TRelayGroup[]) =>
ipcRenderer.invoke('storage:setRelayGroups', relayGroups)
getItem: (key: string) => ipcRenderer.invoke('storage:getItem', key),
setItem: (key: string, value: string) => ipcRenderer.invoke('storage:setItem', key, value)
},
nostr: {
login: (nsec: string) => ipcRenderer.invoke('nostr:login', nsec),

View File

@@ -1,5 +1,6 @@
import { TTheme, TThemeSetting } from '@common/types'
import { isElectron } from '@renderer/lib/env'
import storage from '@renderer/services/storage.service'
import { createContext, useContext, useEffect, useState } from 'react'
type ThemeProviderProps = {
@@ -12,8 +13,10 @@ type ThemeProviderState = {
setThemeSetting: (themeSetting: TThemeSetting) => Promise<void>
}
// web only
function getSystemTheme() {
async function getSystemTheme() {
if (isElectron(window)) {
return await window.api.theme.current()
}
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
@@ -27,33 +30,28 @@ export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
useEffect(() => {
const init = async () => {
// electron
if (isElectron(window)) {
const [themeSetting, theme] = await Promise.all([
window.api.theme.themeSetting(),
window.api.theme.current()
])
setTheme(theme)
setThemeSetting(themeSetting)
window.api.theme.onChange((theme) => {
setTheme(theme)
})
} else {
// web
if (themeSetting === 'system') {
setTheme(getSystemTheme())
return
}
setTheme(themeSetting)
const themeSetting = await storage.getThemeSetting()
if (themeSetting === 'system') {
setTheme(await getSystemTheme())
return
}
setTheme(themeSetting)
}
init()
}, [])
useEffect(() => {
if (themeSetting !== 'system' || isElectron(window)) return
if (themeSetting !== 'system') return
if (isElectron(window)) {
window.api.theme.addChangeListener((theme) => {
setTheme(theme)
})
return () => {
isElectron(window) && window.api.theme.removeChangeListener()
}
}
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
const handleChange = (e: MediaQueryListEvent) => {
@@ -80,14 +78,10 @@ export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
const value = {
themeSetting: themeSetting,
setThemeSetting: async (themeSetting: TThemeSetting) => {
if (isElectron(window)) {
await window.api.theme.set(themeSetting)
} else {
localStorage.setItem('themeSetting', themeSetting)
}
await storage.setThemeSetting(themeSetting)
setThemeSetting(themeSetting)
if (themeSetting === 'system') {
setTheme(getSystemTheme())
setTheme(await getSystemTheme())
return
}
setTheme(themeSetting)

View File

@@ -1,4 +1,5 @@
import { TRelayGroup } from '@common/types'
import { StorageKey } from '@common/constants'
import { TRelayGroup, TThemeSetting } from '@common/types'
import { isElectron } from '@renderer/lib/env'
const DEFAULT_RELAY_GROUPS: TRelayGroup[] = [
@@ -15,21 +16,19 @@ const DEFAULT_RELAY_GROUPS: TRelayGroup[] = [
]
class Storage {
async getRelayGroups() {
async getItem(key: string) {
if (isElectron(window)) {
const relayGroups = await window.api.storage.getRelayGroups()
return relayGroups ?? DEFAULT_RELAY_GROUPS
return window.api.storage.getItem(key)
} else {
const relayGroupsStr = localStorage.getItem('relayGroups')
return relayGroupsStr ? (JSON.parse(relayGroupsStr) as TRelayGroup[]) : DEFAULT_RELAY_GROUPS
return localStorage.getItem(key)
}
}
async setRelayGroups(relayGroups: TRelayGroup[]) {
async setItem(key: string, value: string) {
if (isElectron(window)) {
return window.api.storage.setRelayGroups(relayGroups)
return window.api.storage.setItem(key, value)
} else {
localStorage.setItem('relayGroups', JSON.stringify(relayGroups))
return localStorage.setItem(key, value)
}
}
}
@@ -39,6 +38,7 @@ class StorageService {
private initPromise!: Promise<void>
private relayGroups: TRelayGroup[] = []
private themeSetting: TThemeSetting = 'system'
private storage: Storage = new Storage()
constructor() {
@@ -50,7 +50,10 @@ class StorageService {
}
async init() {
this.relayGroups = await this.storage.getRelayGroups()
const relayGroupsStr = await this.storage.getItem(StorageKey.RELAY_GROUPS)
this.relayGroups = relayGroupsStr ? JSON.parse(relayGroupsStr) : DEFAULT_RELAY_GROUPS
this.themeSetting =
((await this.storage.getItem(StorageKey.THEME_SETTING)) as TThemeSetting) ?? 'system'
}
async getRelayGroups() {
@@ -60,9 +63,20 @@ class StorageService {
async setRelayGroups(relayGroups: TRelayGroup[]) {
await this.initPromise
await this.storage.setRelayGroups(relayGroups)
await this.storage.setItem(StorageKey.RELAY_GROUPS, JSON.stringify(relayGroups))
this.relayGroups = relayGroups
}
async getThemeSetting() {
await this.initPromise
return this.themeSetting
}
async setThemeSetting(themeSetting: TThemeSetting) {
await this.initPromise
await this.storage.setItem(StorageKey.THEME_SETTING, themeSetting)
this.themeSetting = themeSetting
}
}
const instance = new StorageService()