mirror of
https://github.com/Boof2015/prism.git
synced 2026-08-16 08:10:40 +02:00
update theming system, minor qol improvements
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user