packages/ui (@d3ro/ui) 신규: - src/theme.ts (d3roPalette/d3roTypo/d3roShadow/d3roRadius SSOT) - src/theme-vars.ts (팝업/main 프로세스용 CSS 변수 맵) - src/components/ds/ (CrtDisplay, InstrumentPanel, Led, MetalCard, MetalDial, PhosphorText, PhysicalButton, ScreenPanel, ButtonGroup) - src/index.ts barrel - subpath exports: ./theme, ./theme-vars, ./components/ds - React/MUI/Emotion은 peerDependencies로 선언 - @d3ro/core만 직접 의존성 apps/desktop/src/shared/ 디렉토리 완전 제거: - theme-vars가 마지막 남은 파일이었음 - tsconfig include에서 src/shared/**/* 제거 일괄 치환 (renderer 전역): - ../theme, ../../theme, ./theme → @d3ro/ui/theme - ../components/ds, ../../components/ds, ./ds, ../ds → @d3ro/ui/components/ds - ../ds/<Component>, ../../ds/<Component> → @d3ro/ui/components/ds (세부 파일 import는 barrel로 통합) - @shared/theme-vars → @d3ro/ui/theme-vars (WindowManager) apps/desktop 설정: - package.json: @d3ro/ui: '*' dep 추가 - tsconfig.node/web.json: @shared/* paths 완전 제거, @d3ro/ui, @d3ro/ui/* paths 추가 - electron.vite.config.ts: @shared alias 제거, @d3ro/ui alias 추가, externalize exclude에 @d3ro/ui 추가 - vitest.config.ts: alias 교체 DS 컴포넌트 내부의 '../../theme' 상대 경로는 packages/ui 구조에서 동일하게 해결되어 그대로 유효. 검증: typecheck + build + dev 런타임 모두 통과.
71 lines
2.3 KiB
TypeScript
71 lines
2.3 KiB
TypeScript
// src/renderer/App.tsx — 루트 컴포넌트
|
|
// 테마 시스템: auto(시스템) / dark / light. 기본은 auto → 다크.
|
|
// i18n: I18nProvider가 전체 트리를 감쌈. ConfigService에서 언어 로드.
|
|
|
|
import { useState, useEffect, useMemo, useRef } from 'react'
|
|
import { ThemeProvider, CssBaseline, useMediaQuery } from '@mui/material'
|
|
import { getTheme } from '@d3ro/ui/theme'
|
|
import { I18nProvider } from './i18n'
|
|
import { AppLayout } from './components/AppLayout'
|
|
import { UpgradePromptModal } from './components/UpgradePromptModal'
|
|
import { startSystemAudioCapture, stopSystemAudioCapture } from './utils/systemAudioCapture'
|
|
import type { ThemeMode, ConfigChangedEvent } from '@d3ro/core/types'
|
|
|
|
export function App(): React.ReactElement {
|
|
const [themeMode, setThemeMode] = useState<ThemeMode>('auto')
|
|
const prefersDark = useMediaQuery('(prefers-color-scheme: dark)')
|
|
const systemAudioCleanupRef = useRef<(() => void) | null>(null)
|
|
|
|
// 설정에서 테마 로드 + 변경 감지
|
|
useEffect(() => {
|
|
window.electronAPI.config.getTheme().then((result) => {
|
|
if (result.success) {
|
|
setThemeMode(result.data)
|
|
}
|
|
})
|
|
|
|
const unsub = window.electronAPI.config.onChanged((e: ConfigChangedEvent) => {
|
|
if (e.key === 'theme') {
|
|
setThemeMode(e.value as ThemeMode)
|
|
}
|
|
})
|
|
return unsub
|
|
}, [])
|
|
|
|
// 시스템 오디오 캡처: 메인 프로세스의 시작/중지 요청에 응답
|
|
useEffect(() => {
|
|
const unsubStart = window.electronAPI.caption.onStartSystemAudio(() => {
|
|
startSystemAudioCapture((pcm16Buffer) => {
|
|
window.electronAPI.caption.sendSystemAudioData(pcm16Buffer)
|
|
}).catch(() => {
|
|
// 시스템 오디오 캡처 실패 시 무시 — 마이크 자막만 사용
|
|
})
|
|
})
|
|
|
|
const unsubStop = window.electronAPI.caption.onStopSystemAudio(() => {
|
|
stopSystemAudioCapture()
|
|
})
|
|
|
|
systemAudioCleanupRef.current = () => {
|
|
stopSystemAudioCapture()
|
|
}
|
|
|
|
return () => {
|
|
unsubStart()
|
|
unsubStop()
|
|
systemAudioCleanupRef.current?.()
|
|
}
|
|
}, [])
|
|
|
|
const theme = useMemo(() => getTheme(themeMode, prefersDark), [themeMode, prefersDark])
|
|
|
|
return (
|
|
<I18nProvider>
|
|
<ThemeProvider theme={theme}>
|
|
<CssBaseline />
|
|
<AppLayout />
|
|
<UpgradePromptModal />
|
|
</ThemeProvider>
|
|
</I18nProvider>
|
|
)
|
|
}
|