mirror of
https://github.com/Boof2015/prism.git
synced 2026-08-12 05:10:51 +02:00
add icon
This commit is contained in:
+40
-1
@@ -1,4 +1,4 @@
|
||||
import { app, BrowserWindow, dialog, ipcMain, Menu, nativeTheme, safeStorage, screen, session, shell } from 'electron'
|
||||
import { app, BrowserWindow, dialog, ipcMain, Menu, nativeImage, nativeTheme, safeStorage, screen, session, shell } from 'electron'
|
||||
import type { BrowserWindowConstructorOptions, MenuItemConstructorOptions, OpenDialogOptions, WebContents } from 'electron'
|
||||
import { execFileSync } from 'child_process'
|
||||
import { existsSync, readFileSync } from 'fs'
|
||||
@@ -95,6 +95,7 @@ const NOW_PLAYING_CONFIG_DEFAULTS = {
|
||||
minHeight: 520,
|
||||
}
|
||||
|
||||
const STATIC_APP_ICON_FILENAME = 'icon.png'
|
||||
const MAIN_WINDOW_SYNC_SUPPRESSION_MS = 180
|
||||
const MAIN_WINDOW_VISIBLE_GRAB_MARGIN = 64
|
||||
const runtimeWindowCapabilities = resolveWindowCapabilities({
|
||||
@@ -286,6 +287,39 @@ function getWindowStateStore(): FileBackedWindowStateStore {
|
||||
return windowStateStore
|
||||
}
|
||||
|
||||
function getStaticAppIconPath(): string | undefined {
|
||||
const candidates = app.isPackaged
|
||||
? [join(process.resourcesPath, STATIC_APP_ICON_FILENAME)]
|
||||
: [
|
||||
join(process.cwd(), 'resources', STATIC_APP_ICON_FILENAME),
|
||||
join(__dirname, '../../resources', STATIC_APP_ICON_FILENAME),
|
||||
]
|
||||
|
||||
return candidates.find((candidate) => existsSync(candidate))
|
||||
}
|
||||
|
||||
function getStaticWindowIconOptions(): Pick<BrowserWindowConstructorOptions, 'icon'> {
|
||||
if (process.platform === 'darwin') {
|
||||
return {}
|
||||
}
|
||||
|
||||
const icon = getStaticAppIconPath()
|
||||
return icon ? { icon } : {}
|
||||
}
|
||||
|
||||
function applyStaticDockIcon(): void {
|
||||
if (process.platform !== 'darwin') {
|
||||
return
|
||||
}
|
||||
|
||||
const iconPath = getStaticAppIconPath()
|
||||
if (!iconPath) return
|
||||
|
||||
const icon = nativeImage.createFromPath(iconPath)
|
||||
if (icon.isEmpty()) return
|
||||
app.dock?.setIcon(icon)
|
||||
}
|
||||
|
||||
function broadcastNowPlayingState(state: NowPlayingState): void {
|
||||
for (const window of BrowserWindow.getAllWindows()) {
|
||||
if (window.isDestroyed() || window.webContents.isDestroyed()) continue
|
||||
@@ -895,6 +929,7 @@ async function showCustomDialog(options: DialogOptions): Promise<DialogResult> {
|
||||
hasShadow: false,
|
||||
skipTaskbar: true,
|
||||
show: false,
|
||||
...getStaticWindowIconOptions(),
|
||||
webPreferences: {
|
||||
preload: join(__dirname, '../preload/index.js'),
|
||||
sandbox: false,
|
||||
@@ -936,6 +971,7 @@ function createMainWindow(): void {
|
||||
maximizable: true,
|
||||
fullscreenable: true,
|
||||
title: 'Prism',
|
||||
...getStaticWindowIconOptions(),
|
||||
webPreferences: {
|
||||
preload: join(__dirname, '../preload/index.js'),
|
||||
sandbox: false,
|
||||
@@ -1105,6 +1141,7 @@ function createScopePopoutWindow(kind: ScopeKind, rawBounds?: WindowBounds): Bro
|
||||
title: `Prism ${SCOPE_LABELS[kind]}`,
|
||||
alwaysOnTop: getWindowStateStore().getPopoutAlwaysOnTop(kind),
|
||||
show: false,
|
||||
...getStaticWindowIconOptions(),
|
||||
webPreferences: {
|
||||
preload: join(__dirname, '../preload/index.js'),
|
||||
sandbox: false,
|
||||
@@ -1271,6 +1308,7 @@ function createNowPlayingConfigWindow(): BrowserWindow {
|
||||
autoHideMenuBar: true,
|
||||
title: 'Prism Now Playing',
|
||||
show: false,
|
||||
...getStaticWindowIconOptions(),
|
||||
webPreferences: {
|
||||
preload: join(__dirname, '../preload/index.js'),
|
||||
sandbox: false,
|
||||
@@ -1827,6 +1865,7 @@ if (!hasSingleInstanceLock) {
|
||||
setupIPC()
|
||||
await getWindowStateStore().initialize()
|
||||
await syncNativeThemeAppearance()
|
||||
applyStaticDockIcon()
|
||||
createMainWindow()
|
||||
queueProfileOpenPaths(extractProfilePathsFromArgv(process.argv))
|
||||
void processPendingProfileOpenPaths()
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { JSX } from 'react'
|
||||
|
||||
interface PrismLogoProps {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function PrismLogo({ className }: PrismLogoProps): JSX.Element {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
viewBox="0 0 675 540"
|
||||
fill="none"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
>
|
||||
<path
|
||||
className="prism-logo__path prism-logo__path--lower"
|
||||
d="M429.005,240.019c1.742,-1.006 3.718,-1.535 5.729,-1.535c5.527,0 17.412,0 26.401,0c6.328,0 11.459,5.13 11.459,11.459l0,47.969c0,4.094 -2.184,7.877 -5.729,9.923c-29.927,17.278 -167.716,96.831 -188.613,108.895c-1.742,1.006 -3.718,1.535 -5.729,1.535c-9.919,0 -41.456,0 -58.657,0c-6.328,-0 -11.459,-5.13 -11.459,-11.459c0,-9.188 0,-21.759 0,-29.346c-0,-4.094 2.184,-7.877 5.729,-9.923c32.974,-19.038 197.892,-114.253 220.869,-127.519Z"
|
||||
/>
|
||||
<path
|
||||
className="prism-logo__path prism-logo__path--upper"
|
||||
d="M266.17,121.735c2.011,-0 3.987,0.529 5.729,1.535c17.748,10.247 119.001,68.705 140.796,81.288c2.558,1.477 4.133,4.206 4.133,7.159c0,0.256 0,0.511 0,0.762c0,2.094 -1.121,4.029 -2.938,5.07c-10.641,6.1 -47.292,27.109 -61.53,35.27c-3.541,2.03 -7.893,2.023 -11.428,-0.018c-23.117,-13.347 -109.643,-63.302 -132.798,-76.671c-3.545,-2.047 -5.729,-5.83 -5.729,-9.924c-0,-8.35 -0,-22.847 -0,-33.013c0,-6.328 5.13,-11.459 11.459,-11.459c15.744,0 43.157,0 52.305,0Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { useSettingsStore } from '../stores/settingsStore'
|
||||
import { useUpdateStore } from '../stores/updateStore'
|
||||
import { useUiStore } from '../stores/uiStore'
|
||||
import { getRendererWindowCapabilities } from '../windowCapabilities'
|
||||
import PrismLogo from './PrismLogo'
|
||||
|
||||
function SettingsIcon(): JSX.Element {
|
||||
return (
|
||||
@@ -81,6 +82,7 @@ interface ToolbarProps {
|
||||
}
|
||||
|
||||
const DEFAULT_PROFILE_ID = 'profile_default'
|
||||
const PRISM_SUPPORT_URL = 'https://ko-fi.com/boof2015'
|
||||
const WAYLAND_REPOSITION_UNAVAILABLE_MESSAGE = 'Top/bottom repositioning is unavailable on native Wayland.'
|
||||
|
||||
function isToolbarInteractiveTarget(target: EventTarget | null): boolean {
|
||||
@@ -409,6 +411,16 @@ export default function Toolbar({ onOpenSettings, settingsOpen }: ToolbarProps):
|
||||
void openReleasesPage()
|
||||
}, [openReleasesPage])
|
||||
|
||||
const handleOpenSupport = useCallback(() => {
|
||||
void window.electronAPI.openExternalUrl(PRISM_SUPPORT_URL).catch((error) => {
|
||||
showBanner({
|
||||
tone: 'error',
|
||||
message: getErrorMessage(error, 'Could not open Ko-fi.'),
|
||||
actions: [],
|
||||
})
|
||||
})
|
||||
}, [showBanner])
|
||||
|
||||
const handleReposition = useCallback((position: 'top' | 'bottom') => {
|
||||
if (!supportsProgrammaticReposition) {
|
||||
return
|
||||
@@ -495,12 +507,18 @@ export default function Toolbar({ onOpenSettings, settingsOpen }: ToolbarProps):
|
||||
<GripIcon />
|
||||
</button>
|
||||
|
||||
<div
|
||||
<button
|
||||
type="button"
|
||||
className="toolbar__brand"
|
||||
onClick={handleOpenSupport}
|
||||
title="Support Prism on Ko-fi"
|
||||
aria-label="Support Prism on Ko-fi"
|
||||
>
|
||||
<span className="toolbar__brand-mark" />
|
||||
<span className="toolbar__brand-text">Prism</span>
|
||||
</div>
|
||||
<span className="toolbar__brand-logo">
|
||||
<PrismLogo />
|
||||
</span>
|
||||
<span className="toolbar__brand-heart" aria-hidden="true" />
|
||||
</button>
|
||||
|
||||
<AppVersionBlock
|
||||
buildInfo={appBuildInfo}
|
||||
|
||||
+120
-20
@@ -332,27 +332,119 @@ select {
|
||||
}
|
||||
|
||||
.toolbar__brand {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 86px;
|
||||
min-height: 28px;
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--control-border-active);
|
||||
background: linear-gradient(180deg, var(--control-bg-active), var(--panel-surface));
|
||||
box-shadow: inset 0 1px 0 var(--glass-highlight);
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 28px;
|
||||
padding: 2px;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
line-height: 0;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.toolbar__brand-mark {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 999px;
|
||||
background: rgba(var(--accent-rgb), 0.92);
|
||||
box-shadow: 0 0 10px rgba(var(--accent-rgb), 0.32);
|
||||
.toolbar__brand:hover {
|
||||
background: var(--glass-bg);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.toolbar__brand-logo {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 26px;
|
||||
height: 21px;
|
||||
flex-shrink: 0;
|
||||
transition: opacity 0.15s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.toolbar__brand-logo > svg {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.prism-logo__path {
|
||||
transition: fill 0.15s ease, filter 0.15s ease;
|
||||
}
|
||||
|
||||
.prism-logo__path--lower {
|
||||
fill: var(--accent);
|
||||
filter: drop-shadow(0 0 5px var(--accent-glow));
|
||||
}
|
||||
|
||||
.prism-logo__path--upper {
|
||||
fill: var(--accent);
|
||||
filter: drop-shadow(0 0 4px var(--accent-glow));
|
||||
}
|
||||
|
||||
.toolbar__brand-heart {
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
margin-top: -4px;
|
||||
margin-left: -5px;
|
||||
background: #fb7185;
|
||||
box-shadow:
|
||||
2px 0 0 #fb7185,
|
||||
6px 0 0 #fb7185,
|
||||
8px 0 0 #fb7185,
|
||||
0 2px 0 #fb7185,
|
||||
2px 2px 0 #fb7185,
|
||||
4px 2px 0 #fb7185,
|
||||
6px 2px 0 #fb7185,
|
||||
8px 2px 0 #fb7185,
|
||||
2px 4px 0 #fb7185,
|
||||
4px 4px 0 #fb7185,
|
||||
6px 4px 0 #fb7185,
|
||||
4px 6px 0 #fb7185;
|
||||
opacity: 0;
|
||||
transform: translate3d(0, 2px, 0);
|
||||
transform-origin: center;
|
||||
transition: opacity 0.15s ease, transform 0.15s ease;
|
||||
filter: drop-shadow(0 0 4px rgba(248, 113, 113, 0.45));
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
.toolbar__brand:hover .toolbar__brand-heart {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
animation: titlebarLogoHeartPop 0.22s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.toolbar__brand:hover .toolbar__brand-logo {
|
||||
opacity: 0;
|
||||
transform: scale(0.82);
|
||||
}
|
||||
|
||||
@keyframes titlebarLogoHeartPop {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, 3px, 0);
|
||||
}
|
||||
62% {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, -1px, 0);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar__brand-text,
|
||||
.toolbar__chip,
|
||||
.toolbar__icon-button,
|
||||
.settings-section-title,
|
||||
@@ -367,11 +459,6 @@ select {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.toolbar__brand-text {
|
||||
color: var(--text-secondary);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.toolbar__version {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -2018,6 +2105,7 @@ button.toolbar__version:hover {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.toolbar__brand:focus-visible,
|
||||
.toolbar__profile-button:focus-visible,
|
||||
.toolbar__chip:focus-visible,
|
||||
.toolbar__icon-button:focus-visible,
|
||||
@@ -2035,6 +2123,18 @@ button.toolbar__version:hover {
|
||||
0 0 0 4px rgba(var(--accent-rgb), 0.12);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.toolbar__brand,
|
||||
.toolbar__brand-logo,
|
||||
.toolbar__brand-heart {
|
||||
transition-duration: 0.08s !important;
|
||||
}
|
||||
|
||||
.toolbar__brand:hover .toolbar__brand-heart {
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.scope-popout__settings-panel {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
Reference in New Issue
Block a user