feat(UI): Support custcom css in mini window (#4255)
* feat(UI): enable custom CSS functionality with miniWindow * feat(UI): implement custom CSS handling in IPC and update related components
This commit is contained in:
parent
c5580f5b71
commit
4fa04a801a
@ -12,6 +12,7 @@ export enum IpcChannel {
|
||||
App_SetTrayOnClose = 'app:set-tray-on-close',
|
||||
App_RestartTray = 'app:restart-tray',
|
||||
App_SetTheme = 'app:set-theme',
|
||||
App_SetCustomCss = 'app:set-custom-css',
|
||||
|
||||
App_IsBinaryExist = 'app:is-binary-exist',
|
||||
App_GetBinaryPath = 'app:get-binary-path',
|
||||
|
||||
@ -130,6 +130,22 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
mainWindow.setTitleBarOverlay(theme === 'dark' ? titleBarOverlayDark : titleBarOverlayLight)
|
||||
})
|
||||
|
||||
// custom css
|
||||
ipcMain.handle(IpcChannel.App_SetCustomCss, (event, css: string) => {
|
||||
if (css === configManager.getCustomCss()) return
|
||||
configManager.setCustomCss(css)
|
||||
|
||||
// Broadcast to all windows including the mini window
|
||||
const senderWindowId = event.sender.id
|
||||
const windows = BrowserWindow.getAllWindows()
|
||||
// 向其他窗口广播主题变化
|
||||
windows.forEach((win) => {
|
||||
if (win.webContents.id !== senderWindowId) {
|
||||
win.webContents.send('custom-css:update', css)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// clear cache
|
||||
ipcMain.handle(IpcChannel.App_ClearCache, async () => {
|
||||
const sessions = [session.defaultSession, session.fromPartition('persist:webview')]
|
||||
|
||||
@ -42,6 +42,14 @@ export class ConfigManager {
|
||||
this.set(ConfigKeys.Theme, theme)
|
||||
}
|
||||
|
||||
getCustomCss(): string {
|
||||
return this.store.get('customCss', '') as string
|
||||
}
|
||||
|
||||
setCustomCss(css: string) {
|
||||
this.store.set('customCss', css)
|
||||
}
|
||||
|
||||
getLaunchToTray(): boolean {
|
||||
return !!this.get(ConfigKeys.LaunchToTray, false)
|
||||
}
|
||||
|
||||
1
src/preload/index.d.ts
vendored
1
src/preload/index.d.ts
vendored
@ -29,6 +29,7 @@ declare global {
|
||||
setTrayOnClose: (isActive: boolean) => void
|
||||
restartTray: () => void
|
||||
setTheme: (theme: 'light' | 'dark') => void
|
||||
setCustomCss: (css: string) => void
|
||||
reload: () => void
|
||||
clearCache: () => Promise<{ success: boolean; error?: string }>
|
||||
system: {
|
||||
|
||||
@ -19,6 +19,7 @@ const api = {
|
||||
setTrayOnClose: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTrayOnClose, isActive),
|
||||
restartTray: () => ipcRenderer.invoke(IpcChannel.App_RestartTray),
|
||||
setTheme: (theme: 'light' | 'dark') => ipcRenderer.invoke(IpcChannel.App_SetTheme, theme),
|
||||
setCustomCss: (css: string) => ipcRenderer.invoke(IpcChannel.App_SetCustomCss, css),
|
||||
openWebsite: (url: string) => ipcRenderer.invoke(IpcChannel.Open_Website, url),
|
||||
clearCache: () => ipcRenderer.invoke(IpcChannel.App_ClearCache),
|
||||
system: {
|
||||
|
||||
@ -331,7 +331,7 @@ const MinappPopupContainer: React.FC = () => {
|
||||
height={'100%'}
|
||||
maskClosable={false}
|
||||
closeIcon={null}
|
||||
style={{ marginLeft: 'var(--sidebar-width)' }}>
|
||||
style={{ marginLeft: 'var(--sidebar-width)', backgroundColor: 'var(--color-background)' }}>
|
||||
{!isReady && (
|
||||
<EmptyView>
|
||||
<Avatar
|
||||
|
||||
@ -75,7 +75,7 @@ const WebviewContainer = memo(
|
||||
const WebviewStyle: React.CSSProperties = {
|
||||
width: 'calc(100vw - var(--sidebar-width))',
|
||||
height: 'calc(100vh - var(--navbar-height))',
|
||||
backgroundColor: 'white',
|
||||
backgroundColor: 'var(--color-background)',
|
||||
display: 'inline-flex'
|
||||
}
|
||||
|
||||
|
||||
@ -188,7 +188,10 @@ const DisplaySettings: FC = () => {
|
||||
<SettingDivider />
|
||||
<Input.TextArea
|
||||
value={customCss}
|
||||
onChange={(e) => dispatch(setCustomCss(e.target.value))}
|
||||
onChange={(e) => {
|
||||
dispatch(setCustomCss(e.target.value))
|
||||
window.api.setCustomCss(e.target.value)
|
||||
}}
|
||||
placeholder={t('settings.display.custom.css.placeholder')}
|
||||
style={{
|
||||
minHeight: 200,
|
||||
|
||||
@ -286,6 +286,9 @@ const settingsSlice = createSlice({
|
||||
setTheme: (state, action: PayloadAction<ThemeMode>) => {
|
||||
state.theme = action.payload
|
||||
},
|
||||
setCustomCss: (state, action: PayloadAction<string>) => {
|
||||
state.customCss = action.payload
|
||||
},
|
||||
setFontSize: (state, action: PayloadAction<number>) => {
|
||||
state.fontSize = action.payload
|
||||
},
|
||||
@ -382,9 +385,6 @@ const settingsSlice = createSlice({
|
||||
setPasteLongTextThreshold: (state, action: PayloadAction<number>) => {
|
||||
state.pasteLongTextThreshold = action.payload
|
||||
},
|
||||
setCustomCss: (state, action: PayloadAction<string>) => {
|
||||
state.customCss = action.payload
|
||||
},
|
||||
setTopicNamingPrompt: (state, action: PayloadAction<string>) => {
|
||||
state.topicNamingPrompt = action.payload
|
||||
},
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import '@renderer/databases'
|
||||
|
||||
import store, { persistor } from '@renderer/store'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import store, { persistor, useAppDispatch } from '@renderer/store'
|
||||
import { message } from 'antd'
|
||||
import { setCustomCss } from '@renderer/store/settings'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Provider } from 'react-redux'
|
||||
import { PersistGate } from 'redux-persist/integration/react'
|
||||
|
||||
@ -10,6 +13,50 @@ import { SyntaxHighlighterProvider } from '../../context/SyntaxHighlighterProvid
|
||||
import { ThemeProvider } from '../../context/ThemeProvider'
|
||||
import HomeWindow from './home/HomeWindow'
|
||||
|
||||
function useMiniWindowCustomCss() {
|
||||
const { customCss } = useSettings()
|
||||
const dispatch = useAppDispatch()
|
||||
const [isInitialized, setIsInitialized] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
// 初始化时从主进程获取最新的CSS配置
|
||||
window.api.config.get('customCss').then((css) => {
|
||||
if (css !== undefined) {
|
||||
dispatch(setCustomCss(css))
|
||||
}
|
||||
setIsInitialized(true)
|
||||
})
|
||||
|
||||
// Listen for custom CSS updates from main window
|
||||
const removeListener = window.electron.ipcRenderer.on('custom-css:update', (_event, css) => {
|
||||
dispatch(setCustomCss(css))
|
||||
})
|
||||
|
||||
return () => {
|
||||
removeListener()
|
||||
}
|
||||
}, [dispatch])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isInitialized) return
|
||||
|
||||
// Apply custom CSS
|
||||
const oldCustomCss = document.getElementById('user-defined-custom-css')
|
||||
if (oldCustomCss) {
|
||||
oldCustomCss.remove()
|
||||
}
|
||||
|
||||
if (customCss) {
|
||||
const style = document.createElement('style')
|
||||
style.id = 'user-defined-custom-css'
|
||||
style.textContent = customCss
|
||||
document.head.appendChild(style)
|
||||
}
|
||||
}, [customCss, isInitialized])
|
||||
|
||||
return isInitialized
|
||||
}
|
||||
|
||||
function MiniWindow(): React.ReactElement {
|
||||
//miniWindow should register its own message component
|
||||
const [messageApi, messageContextHolder] = message.useMessage()
|
||||
@ -22,7 +69,7 @@ function MiniWindow(): React.ReactElement {
|
||||
<SyntaxHighlighterProvider>
|
||||
<PersistGate loading={null} persistor={persistor}>
|
||||
{messageContextHolder}
|
||||
<HomeWindow />
|
||||
<MiniWindowContent />
|
||||
</PersistGate>
|
||||
</SyntaxHighlighterProvider>
|
||||
</AntdProvider>
|
||||
@ -31,4 +78,16 @@ function MiniWindow(): React.ReactElement {
|
||||
)
|
||||
}
|
||||
|
||||
// Inner component that uses the hook after Redux is initialized
|
||||
function MiniWindowContent(): React.ReactElement {
|
||||
const cssInitialized = useMiniWindowCustomCss()
|
||||
|
||||
// Show empty fragment until CSS is initialized
|
||||
if (!cssInitialized) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return <HomeWindow />
|
||||
}
|
||||
|
||||
export default MiniWindow
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user