d3ro-voice/apps/desktop/src/renderer/App.tsx
yunchan8804 a041f1b6a9 feat(V2-1c): packages/ui 추출 — DS 컴포넌트 + theme + theme-vars 분리
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 런타임 모두 통과.
2026-04-08 14:50:33 +09:00

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>
)
}