update theming system, minor qol improvements

This commit is contained in:
Boof2015
2026-03-31 14:15:56 -04:00
parent 6f63c1997f
commit 3a8a6dae75
25 changed files with 1838 additions and 740 deletions
-72
View File
@@ -47,16 +47,11 @@ export default function BottomBar({ onClose, onHeightChange }: BottomBarProps):
const frameTarget = usePerformanceStore((s) => s.frameTarget)
const dockedRenderFps = usePerformanceStore((s) => s.dockedRenderFps)
const setFrameTarget = usePerformanceStore((s) => s.setFrameTarget)
const themeId = useSettingsStore((s) => s.themeId)
const setThemeId = useSettingsStore((s) => s.setThemeId)
const {
themes,
activeThemeId,
loadTheme,
renameTheme,
deleteTheme,
reloadThemes,
importThemeFromDialog,
showThemesFolder,
} = useThemeStore()
const astraState = useAstraStore((s) => s.integrationState)
@@ -161,26 +156,6 @@ export default function BottomBar({ onClose, onHeightChange }: BottomBarProps):
setThemeId(value)
}
const handleRenameTheme = async (): Promise<void> => {
if (!activeThemeId || activeThemeId === 'theme_default') return
const activeTheme = themes[activeThemeId]
if (!activeTheme) return
const nextName = window.prompt('Rename theme', activeTheme.name)?.trim()
if (!nextName) return
await renameTheme(activeThemeId, nextName)
}
const handleDeleteTheme = async (): Promise<void> => {
if (!activeThemeId || activeThemeId === 'theme_default') return
const activeTheme = themes[activeThemeId]
if (!activeTheme) return
if (!window.confirm(`Delete "${activeTheme.name}"?`)) return
await deleteTheme(activeThemeId)
setThemeId(useThemeStore.getState().activeThemeId)
}
const handleSaveAstraConfig = async (): Promise<void> => {
const nextConfig: AstraIntegrationConfig = {
baseUrl: astraBaseUrlInput,
@@ -264,27 +239,6 @@ export default function BottomBar({ onClose, onHeightChange }: BottomBarProps):
</option>
))}
</ThemedSelect>
<button
type="button"
className="settings-chip"
onClick={() => {
void (async () => {
await importThemeFromDialog()
setThemeId(useThemeStore.getState().activeThemeId)
})()
}}
>
Import
</button>
<button
type="button"
className="settings-chip"
onClick={() => {
void reloadThemes()
}}
>
Reload
</button>
<button
type="button"
className="settings-chip"
@@ -294,32 +248,6 @@ export default function BottomBar({ onClose, onHeightChange }: BottomBarProps):
>
Folder
</button>
{activeThemeId && activeThemeId !== 'theme_default' ? (
<button
type="button"
className="settings-chip"
onClick={() => {
void handleRenameTheme()
}}
>
Rename
</button>
) : null}
{activeThemeId && activeThemeId !== 'theme_default' ? (
<button
type="button"
className="settings-chip"
onClick={() => {
void handleDeleteTheme()
}}
>
Delete
</button>
) : null}
<div className="settings-status-pill">
<span className="settings-status-pill__dot" />
<span>{themeId ? 'Saved With Profile' : 'Not Linked'}</span>
</div>
</div>
</div>
</section>
+31 -21
View File
@@ -71,12 +71,13 @@ export function scopeSettingsToOptions(
const s = settings as ScopeSettings['spectrum']
const t = theme as ResolvedSpectrumTheme
return {
lineColor: t.primary,
secondaryLineColor: t.secondary,
lineColor: t.line,
secondaryLineColor: t.sideLine,
gradientColors: t.fillGradient,
heatColors: t.heatColors,
backgroundColor: t.background,
gridColor: t.guides,
labelColor: t.labels,
fftSize: s.fftSize,
tiltDbPerOctave: s.tiltDbPerOctave,
heatmapFill: s.heatmap,
@@ -91,9 +92,10 @@ export function scopeSettingsToOptions(
const s = settings as ScopeSettings['oscilloscope']
const t = theme as ResolvedOscilloscopeTheme
return {
lineColor: t.primary,
lineColor: t.line,
backgroundColor: t.background,
gridColor: t.guides,
gridMajorColor: t.guides,
gridMinorColor: t.guidesSecondary,
underfillColor: t.fill,
pitchLock: s.pitchLock,
underfillEnabled: s.underfillEnabled,
@@ -105,13 +107,15 @@ export function scopeSettingsToOptions(
const s = settings as ScopeSettings['vectorscope']
const t = theme as ResolvedVectorscopeTheme
return {
lineColor: t.primary,
lineColor: t.trace,
backgroundColor: t.background,
gridColor: t.guides,
gridMajorColor: t.guides,
gridMinorColor: t.guidesSecondary,
labelColor: t.labels,
bandColors: {
low: t.lowBand,
mid: t.midBand,
high: t.highBand,
low: t.bandLow,
mid: t.bandMid,
high: t.bandHigh,
},
mode: s.mode,
multiband: s.multiband,
@@ -124,8 +128,9 @@ export function scopeSettingsToOptions(
const s = settings as ScopeSettings['spectrogram']
const t = theme as ResolvedSpectrogramTheme
return {
lineColor: t.primary,
lineColor: t.mono,
heatColors: t.heatColors,
backgroundColor: t.background,
fftSize: s.fftSize,
scrollSpeed: s.scrollSpeed,
clarityMode: s.clarityMode,
@@ -137,11 +142,13 @@ export function scopeSettingsToOptions(
const s = settings as ScopeSettings['vumeter']
const t = theme as ResolvedVUMeterTheme
return {
lineColor: t.primary,
backgroundColor: t.background,
lineColor: t.level,
trackColor: t.track,
peakColor: t.peak,
clipColor: t.clip,
scaleColor: t.guides,
labelColor: t.text,
scaleColor: t.scale,
labelColor: t.labels,
mode: s.mode,
orientation: s.orientation,
}
@@ -150,10 +157,12 @@ export function scopeSettingsToOptions(
const s = settings as ScopeSettings['lufsmeter']
const t = theme as ResolvedLUFSMeterTheme
return {
lineColor: t.primary,
backgroundColor: t.background,
lineColor: t.level,
trackColor: t.track,
targetColor: t.target,
scaleColor: t.guides,
labelColor: t.text,
scaleColor: t.scale,
labelColor: t.labels,
mode: s.mode,
}
}
@@ -161,13 +170,14 @@ export function scopeSettingsToOptions(
const s = settings as ScopeSettings['waveform']
const t = theme as ResolvedWaveformTheme
return {
lineColor: t.primary,
backgroundColor: t.background,
lineColor: t.line,
gridMajorColor: t.guides,
gridMinorColor: t.guides,
gridMinorColor: t.guidesSecondary,
bandColors: {
low: t.lowBand,
mid: t.midBand,
high: t.highBand,
low: t.bandLow,
mid: t.bandMid,
high: t.bandHigh,
},
mode: s.mode,
scrollSpeed: s.scrollSpeed,
+40 -11
View File
@@ -1,11 +1,19 @@
import { useState, useEffect, useCallback, useRef, type CSSProperties, type JSX, type PointerEvent as ReactPointerEvent } from 'react'
import { useState, useEffect, useCallback, useRef, type JSX, type PointerEvent as ReactPointerEvent } from 'react'
import { useSettingsStore } from '../stores/settingsStore'
function SettingsIcon(): JSX.Element {
return (
<svg viewBox="0 0 16 16" aria-hidden="true">
<circle cx="8" cy="8" r="2.2" fill="none" stroke="currentColor" strokeWidth="1.2" />
<path d="M8 1.7v2M8 12.3v2M14.3 8h-2M3.7 8h-2M12.4 3.6l-1.4 1.4M5 11l-1.4 1.4M12.4 12.4 11 11M5 5 3.6 3.6" fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" />
<g fill="currentColor">
<rect x="7.15" y="1.15" width="1.7" height="2.7" rx="0.45" />
<rect x="7.15" y="1.15" width="1.7" height="2.7" rx="0.45" transform="rotate(60 8 8)" />
<rect x="7.15" y="1.15" width="1.7" height="2.7" rx="0.45" transform="rotate(120 8 8)" />
<rect x="7.15" y="1.15" width="1.7" height="2.7" rx="0.45" transform="rotate(180 8 8)" />
<rect x="7.15" y="1.15" width="1.7" height="2.7" rx="0.45" transform="rotate(240 8 8)" />
<rect x="7.15" y="1.15" width="1.7" height="2.7" rx="0.45" transform="rotate(300 8 8)" />
</g>
<circle cx="8" cy="8" r="3.45" fill="none" stroke="currentColor" strokeWidth="1.85" />
<circle cx="8" cy="8" r="1.5" fill="none" stroke="currentColor" strokeWidth="1.05" />
</svg>
)
}
@@ -70,6 +78,11 @@ interface ToolbarProps {
const DEFAULT_PROFILE_ID = 'profile_default'
function isToolbarInteractiveTarget(target: EventTarget | null): boolean {
return target instanceof Element
&& target.closest('button, .toolbar__reposition-menu') !== null
}
function getErrorMessage(error: unknown, fallback: string): string {
return error instanceof Error && error.message
? error.message
@@ -88,13 +101,13 @@ export default function Toolbar({ onOpenSettings, settingsOpen }: ToolbarProps):
const updateActiveProfile = useSettingsStore((s) => s.updateActiveProfile)
const importProfileFromDialog = useSettingsStore((s) => s.importProfileFromDialog)
const showProfilesFolder = useSettingsStore((s) => s.showProfilesFolder)
const [isAlwaysOnTop, setIsAlwaysOnTop] = useState(true)
const [isAlwaysOnTop, setIsAlwaysOnTop] = useState(false)
const [showReposition, setShowReposition] = useState(false)
const [isProfileMenuOpen, setIsProfileMenuOpen] = useState(false)
const profileButtonRef = useRef<HTMLButtonElement>(null)
useEffect(() => {
window.electronAPI.isAlwaysOnTop().then(setIsAlwaysOnTop)
void window.electronAPI.isAlwaysOnTop().then(setIsAlwaysOnTop)
const unsubscribe = window.electronAPI.onAlwaysOnTopChanged(setIsAlwaysOnTop)
return unsubscribe
}, [])
@@ -284,10 +297,30 @@ export default function Toolbar({ onOpenSettings, settingsOpen }: ToolbarProps):
window.electronAPI.stopWindowMove()
}, [])
const handleToolbarDragStart = useCallback((event: ReactPointerEvent<HTMLDivElement>): void => {
if (isToolbarInteractiveTarget(event.target) || event.button !== 0) return
event.preventDefault()
event.currentTarget.setPointerCapture(event.pointerId)
window.electronAPI.startWindowMove()
}, [])
const handleToolbarDragEnd = useCallback((event: ReactPointerEvent<HTMLDivElement>): void => {
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
event.currentTarget.releasePointerCapture(event.pointerId)
}
window.electronAPI.stopWindowMove()
}, [])
const activeProfile = activeProfileId ? profiles[activeProfileId] : null
return (
<div className="toolbar">
<div
className="toolbar"
onPointerDown={handleToolbarDragStart}
onPointerUp={handleToolbarDragEnd}
onPointerCancel={handleToolbarDragEnd}
onLostPointerCapture={handleToolbarDragEnd}
>
<button
type="button"
className="toolbar__grab"
@@ -303,7 +336,6 @@ export default function Toolbar({ onOpenSettings, settingsOpen }: ToolbarProps):
<div
className="toolbar__brand"
style={{ WebkitAppRegion: 'drag' } as CSSProperties}
>
<span className="toolbar__brand-mark" />
<span className="toolbar__brand-text">Prism</span>
@@ -325,10 +357,7 @@ export default function Toolbar({ onOpenSettings, settingsOpen }: ToolbarProps):
</button>
</div>
<div
className="toolbar__spacer"
style={{ WebkitAppRegion: 'drag' } as CSSProperties}
/>
<div className="toolbar__spacer" />
<div className="toolbar__actions">
<div className="toolbar__reposition-wrap">