From c6e41c29154e554cddb63e5850c45f46b38f04cd Mon Sep 17 00:00:00 2001 From: Su-Yong Date: Wed, 29 Nov 2023 19:01:33 +0900 Subject: [PATCH 1/7] feat(renderer): improve styling (WIP) --- renderer/hooks/useClassStyle.ts | 23 +++ renderer/main/App.tsx | 47 ++----- renderer/main/components/LyricProgressBar.tsx | 133 +++++++++++++----- renderer/utils/userCSSSelectors.ts | 9 +- 4 files changed, 138 insertions(+), 74 deletions(-) create mode 100644 renderer/hooks/useClassStyle.ts diff --git a/renderer/hooks/useClassStyle.ts b/renderer/hooks/useClassStyle.ts new file mode 100644 index 00000000..0d82ff20 --- /dev/null +++ b/renderer/hooks/useClassStyle.ts @@ -0,0 +1,23 @@ +import { Accessor, createEffect, on, onCleanup, onMount } from 'solid-js'; + +export const useClassStyle = (className: string, style: Accessor) => { + const stylesheet = new CSSStyleSheet(); + + + onMount(() => { + const isExist = Array.from(document.adoptedStyleSheets).some((adoptedStyleSheet) => adoptedStyleSheet === stylesheet); + if (isExist) return; + + document.adoptedStyleSheets = [...document.adoptedStyleSheets, stylesheet]; + }) + + createEffect(on(style, () => { + stylesheet.insertRule(`*:is(.${className}) {\n${style()}\n}`, stylesheet.cssRules.length); + })); + + onCleanup(() => { + document.adoptedStyleSheets = Array.from(document.adoptedStyleSheets).filter((adoptedStyleSheet) => adoptedStyleSheet !== stylesheet); + }); + + return stylesheet; +}; diff --git a/renderer/main/App.tsx b/renderer/main/App.tsx index e786dbd0..41520a14 100644 --- a/renderer/main/App.tsx +++ b/renderer/main/App.tsx @@ -7,10 +7,10 @@ import Lyrics from './components/Lyrics'; import PlayingInfoProvider from '../components/PlayingInfoProvider'; import UserCSS from '../components/UserCSS'; import useConfig from '../hooks/useConfig'; -import { cx } from '../utils/classNames'; -import { userCSSSelectors } from '../utils/userCSSSelectors'; +import { userCSSSelectors, userCSSVariables } from '../utils/userCSSSelectors'; import usePluginsCSS from '../hooks/usePluginsCSS'; import useStyle from '../hooks/useStyle'; +import { useClassStyle } from '../hooks/useClassStyle'; const useProximityStyle = () => { @@ -91,52 +91,23 @@ const App = () => { const style = useStyle(); - const lyricStyle = () => { - let result = ''; - const styleData = style(); - - if (styleData?.nowPlaying.maxWidth) result += `max-width: ${styleData.nowPlaying.maxWidth}px;`; - if (styleData?.nowPlaying.color) result += `color: ${styleData.nowPlaying.color};`; - if (styleData?.nowPlaying.background) result += `background-color: ${styleData.nowPlaying.background};`; - if (styleData?.font) result += `font-family: ${styleData.font};`; - if (styleData?.fontWeight) result += `font-weight: ${styleData.fontWeight};`; - - return result; - }; - - const textStyle = () => { - let result = ''; - - const styleData = style(); - if (styleData?.nowPlaying.fontSize) result += `font-size: ${styleData.nowPlaying.fontSize}px;`; - - return result; - }; - - const progressStyle = () => { - let result = ''; - - const styleData = style(); - if (styleData?.nowPlaying.backgroundProgress) result += `background-color: ${styleData.nowPlaying.backgroundProgress};`; - - return result; - }; - const proximityHandles = useProximityStyle(); + useClassStyle(userCSSSelectors.wrapper, () => ` + display: flex; + flex-direction: column; + row-gap: ${style()?.rowGap ?? '2'}rem; + `); + return ( diff --git a/renderer/main/components/LyricProgressBar.tsx b/renderer/main/components/LyricProgressBar.tsx index 10be817a..407cea11 100644 --- a/renderer/main/components/LyricProgressBar.tsx +++ b/renderer/main/components/LyricProgressBar.tsx @@ -11,6 +11,8 @@ import { formatTime } from '../../utils/formatTime'; import { userCSSSelectors, userCSSVariables } from '../../utils/userCSSSelectors'; +import { useClassStyle } from '../../hooks/useClassStyle'; + import type { JSX } from 'solid-js/jsx-runtime'; import type { StyleConfig } from '../../../common/schema'; @@ -51,59 +53,122 @@ const LyricProgressBar = (props: LyricProgressBarProps) => { } }); + useClassStyle(userCSSSelectors.nowplaying, () => { + const style = themeStyle(); + + return ` + position: relative; + + max-width: ${style.nowPlaying.maxWidth}px; + padding: 0.75rem; + + color: ${style.nowPlaying.color}; + background-color: ${style.nowPlaying.background}; + font-family: ${style.font}; + font-weight: ${style.fontWeight}; + opacity: ${status() !== 'playing' ? style.nowPlaying.stoppedOpacity : 1}; + border-radius: 0.375rem; + + overflow: hidden; + + will-change: opacity, transform; + transition: all 0.225s ease-out; + `; + }); + + useClassStyle(userCSSSelectors['nowplaying-progress-bar'], () => ` + position: absolute; + inset: 0; + `); + + useClassStyle(userCSSSelectors['nowplaying-progress'], () => ` + position: absolute; + inset: 0; + + background-color: ${themeStyle().nowPlaying.backgroundProgress}; + + transform-origin: left; + transform: scaleX(var(${userCSSVariables['var-nowplaying-percent']})); + + ${progressTransition() ? 'transition: transform 0.225s cubic-bezier(0.34, 1.56, 0.64, 1);' : ''} + `); + + useClassStyle(userCSSSelectors['nowplaying-container'], () => ` + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + gap: 0.5rem; + `); + + useClassStyle(userCSSSelectors['nowplaying-cover'], () => ` + width: 1.5rem; + height: 1.5rem; + + object-fit: contain; + + transition: all 0.225s ease-out; + `); + + useClassStyle(`${userCSSSelectors['nowplaying--stopped']} .${userCSSSelectors['nowplaying-cover']}`, () => ` + filter: grayscale(100%); + scale: 95%; + `); + + const textStyle = () => ` + font-size: ${themeStyle().nowPlaying.fontSize}px; + `; + useClassStyle(userCSSSelectors['nowplaying-playing-text'], () => ` + ${textStyle()} + + width: fit-content; + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + gap: 0.5rem; + `); + + useClassStyle(userCSSSelectors['nowplaying-artist'], textStyle); + useClassStyle(userCSSSelectors['nowplaying-divider'], textStyle); + useClassStyle(userCSSSelectors['nowplaying-title'], textStyle); + return (
0 ? (progress() / duration() * 100) : 0}%; ${userCSSVariables['var-nowplaying-duration']}: '${formatTime(duration())}'; ${userCSSVariables['var-nowplaying-progress']}: '${formatTime(progress())}'; - opacity: ${status() !== 'playing' ? themeStyle().nowPlaying.stoppedOpacity : 1}; - will-change: opacity, transform; ${style.style ?? ''} `} - class={cx( - ` - relative p-3 transition-all duration-[225ms] ease-out z-0 - bg-gray-900/50 text-gray-50 rounded-md overflow-hidden - `, - userCSSSelectors.nowplaying, - style.class, - )} + classList={{ + [userCSSSelectors.nowplaying]: true, + [userCSSSelectors['nowplaying--stopped']]: status() === 'stopped', + [userCSSSelectors['nowplaying--idle']]: status() === 'idle', + [userCSSSelectors['nowplaying--playing']]: status() === 'playing', + [style.class ?? '']: !!style.class, + }} {...containerProps} > -
+
-
+
{'Thumbnail'} + style={`${userCSSVariables['var-cover-url']}: '${coverUrl() ?? icon}';`} + alt={'Thumbnail'} + />
Date: Wed, 29 Nov 2023 20:59:21 +0900 Subject: [PATCH 2/7] feat(renderer): improve styling and fix finish animation bug --- renderer/hooks/useClassStyle.ts | 2 +- renderer/main/App.tsx | 13 +-- renderer/main/components/AnchoredView.tsx | 57 +++++++--- renderer/main/components/LyricProgressBar.tsx | 5 +- renderer/main/components/Lyrics.tsx | 106 ++++++++++++------ renderer/main/components/LyricsItem.tsx | 18 ++- renderer/main/components/LyricsTransition.tsx | 27 +---- renderer/utils/userCSSSelectors.ts | 10 +- 8 files changed, 140 insertions(+), 98 deletions(-) diff --git a/renderer/hooks/useClassStyle.ts b/renderer/hooks/useClassStyle.ts index 0d82ff20..b0e8c1ae 100644 --- a/renderer/hooks/useClassStyle.ts +++ b/renderer/hooks/useClassStyle.ts @@ -12,7 +12,7 @@ export const useClassStyle = (className: string, style: Accessor) => { }) createEffect(on(style, () => { - stylesheet.insertRule(`*:is(.${className}) {\n${style()}\n}`, stylesheet.cssRules.length); + stylesheet.replaceSync(`*:is(.${className}) {\n${style()}\n}`); })); onCleanup(() => { diff --git a/renderer/main/App.tsx b/renderer/main/App.tsx index 41520a14..9f416f2d 100644 --- a/renderer/main/App.tsx +++ b/renderer/main/App.tsx @@ -7,10 +7,9 @@ import Lyrics from './components/Lyrics'; import PlayingInfoProvider from '../components/PlayingInfoProvider'; import UserCSS from '../components/UserCSS'; import useConfig from '../hooks/useConfig'; -import { userCSSSelectors, userCSSVariables } from '../utils/userCSSSelectors'; +import { userCSSSelectors } from '../utils/userCSSSelectors'; import usePluginsCSS from '../hooks/usePluginsCSS'; import useStyle from '../hooks/useStyle'; -import { useClassStyle } from '../hooks/useClassStyle'; const useProximityStyle = () => { @@ -90,15 +89,8 @@ const App = () => { usePluginsCSS(); const style = useStyle(); - const proximityHandles = useProximityStyle(); - useClassStyle(userCSSSelectors.wrapper, () => ` - display: flex; - flex-direction: column; - row-gap: ${style()?.rowGap ?? '2'}rem; - `); - return ( { > - + diff --git a/renderer/main/components/AnchoredView.tsx b/renderer/main/components/AnchoredView.tsx index 2560c910..b3eaae47 100644 --- a/renderer/main/components/AnchoredView.tsx +++ b/renderer/main/components/AnchoredView.tsx @@ -6,6 +6,9 @@ import useConfig from '../../hooks/useConfig'; import useStyle from '../../hooks/useStyle'; import type { JSX } from 'solid-js/jsx-runtime'; +import { userCSSSelectors } from '../../utils/userCSSSelectors'; +import { usePlayingInfo } from '../../components/PlayingInfoProvider'; +import { useClassStyle } from '../../hooks/useClassStyle'; interface AnchoredViewProps extends JSX.HTMLAttributes { class?: string; @@ -16,30 +19,54 @@ interface AnchoredViewProps extends JSX.HTMLAttributes { const AnchoredView = (props: AnchoredViewProps) => { const [config] = useConfig(); const style = useStyle(); + const { status } = usePlayingInfo(); + const [, containerProps] = splitProps( props, ['class', 'style', 'children'], ); + useClassStyle(userCSSSelectors.wrapper, () => { + const anchor = config()?.windowPosition.anchor ?? 'bottom-right'; + + return ` + position: fixed; + ${anchor.includes('top') ? 'top: 0;' : ''} + ${anchor.includes('bottom') ? 'bottom: 0;' : ''} + ${anchor.includes('left') ? 'left: 0;' : ''} + ${anchor.includes('right') ? 'right: 0;' : ''} + --translate-x: 0; + --translate-y: 0; + ${['top', 'bottom', 'center'].includes(anchor) ? 'left: 50%; right: 50%; --translate-x: -50%;' : ''} + ${['left', 'right', 'center'].includes(anchor) ? 'top: 50%; bottom: 50%; --translate-y: -50%;' : ''} + translate: var(--translate-x) var(--translate-y); + + width: 100%; + height: fit-content; + + display: flex; + flex-direction: ${style().lyric.direction}; + + ${anchor.includes('top') ? 'justify-content: flex-start;' : ''} + ${['left', 'right', 'center'].includes(anchor) ? 'justify-content: center;' : ''} + ${anchor.includes('bottom') ? 'justify-content: flex-end;' : ''} + + ${anchor.includes('left') ? 'align-items: flex-start;' : ''} + ${['top', 'bottom', 'center'].includes(anchor) ? 'align-items: center;' : ''} + ${anchor.includes('right') ? 'align-items: flex-end;' : ''} + + row-gap: ${style()?.rowGap ?? '2'}rem; + `; + }); + return (
{props.children} diff --git a/renderer/main/components/LyricProgressBar.tsx b/renderer/main/components/LyricProgressBar.tsx index 407cea11..9164f514 100644 --- a/renderer/main/components/LyricProgressBar.tsx +++ b/renderer/main/components/LyricProgressBar.tsx @@ -110,7 +110,7 @@ const LyricProgressBar = (props: LyricProgressBarProps) => { transition: all 0.225s ease-out; `); - useClassStyle(`${userCSSSelectors['nowplaying--stopped']} .${userCSSSelectors['nowplaying-cover']}`, () => ` + useClassStyle(`${userCSSSelectors['wrapper--stopped']} .${userCSSSelectors['nowplaying-cover']}`, () => ` filter: grayscale(100%); scale: 95%; `); @@ -143,9 +143,6 @@ const LyricProgressBar = (props: LyricProgressBarProps) => { `} classList={{ [userCSSSelectors.nowplaying]: true, - [userCSSSelectors['nowplaying--stopped']]: status() === 'stopped', - [userCSSSelectors['nowplaying--idle']]: status() === 'idle', - [userCSSSelectors['nowplaying--playing']]: status() === 'playing', [style.class ?? '']: !!style.class, }} {...containerProps} diff --git a/renderer/main/components/Lyrics.tsx b/renderer/main/components/Lyrics.tsx index 23133295..3faa62b3 100644 --- a/renderer/main/components/Lyrics.tsx +++ b/renderer/main/components/Lyrics.tsx @@ -8,12 +8,13 @@ import { usePlayingInfo } from '../../components/PlayingInfoProvider'; import useLyric from '../../hooks/useLyric'; import { cx } from '../../utils/classNames'; -import { userCSSSelectors, userCSSTransitions } from '../../utils/userCSSSelectors'; +import { userCSSSelectors, userCSSTransitions, userCSSVariables } from '../../utils/userCSSSelectors'; import useConfig from '../../hooks/useConfig'; import useStyle from '../../hooks/useStyle'; import type { JSX } from 'solid-js/jsx-runtime'; +import { useClassStyle } from '../../hooks/useClassStyle'; type LyricsProps = { style?: string; @@ -58,34 +59,74 @@ const Lyrics = (props: LyricsProps) => { return `lyric-${configuredName}`; }; - const previousStyle = createMemo(on(config, (configData) => configData ? ` + useClassStyle(userCSSSelectors['lyrics-container'], () => ` + width: 100%; + + display: flex; + flex-direction: column; + justify-content: center; + align-items: ${anchorTypeToItemsAlignType(config()?.windowPosition.anchor)}; + row-gap: ${style().lyric.multipleContainerRowGap}rem; + + `); + useClassStyle(`${userCSSSelectors['wrapper--stopped']} .${userCSSSelectors['lyrics-container']}`, () => ` + opacity: ${style().lyric.stoppedOpacity}; + `); + + useClassStyle(userCSSSelectors['lyrics-transition-wrapper'], () => ` + top: var(--top, 0); + width: fit-content; + `); + + useClassStyle(userCSSSelectors['lyrics-wrapper'], () => ` + transition: all 0.25s; + `); + useClassStyle(userCSSSelectors['lyrics-wrapper--previous'], () => ` scale: ${style().lyric.previousLyricScale}; opacity: ${style().lyric.previousLyricOpacity}; - transform-origin: ${anchorTypeToOriginType(configData.windowPosition.anchor, '100%')}; - ` : '')); - - const nextStyle = createMemo(on(config, (configData) => configData ? ` + transform-origin: ${anchorTypeToOriginType(config()?.windowPosition.anchor, '100%')}; + `); + useClassStyle(userCSSSelectors['lyrics-wrapper--next'], () => ` scale: ${style().lyric.nextLyricScale}; opacity: ${style().lyric.nextLyricOpacity}; - transform-origin: ${anchorTypeToOriginType(configData.windowPosition.anchor)}; - ` : '')); - - const getStyle = (index: number) => { - const previousLength = getPreviousLyricLength() ?? 0; - if (index < previousLength) return previousStyle(); - if (index > previousLength) return nextStyle(); - - return ''; - }; + transform-origin: ${anchorTypeToOriginType(config()?.windowPosition.anchor)}; + `); + + useClassStyle(userCSSSelectors['lyrics'], () => ` + row-gap: ${style().lyric.containerRowGap}rem; + flex-direction: ${style().lyric.direction ?? 'column'}; + align-items: ${anchorTypeToItemsAlignType(config()?.windowPosition.anchor)}; + transform-origin: ${anchorTypeToOriginType(config()?.windowPosition.anchor)}; + `); + + useClassStyle(userCSSSelectors['lyrics-item'], () => ` + top: var(--top); + + width: fit-content; + + padding: 0.25rem 0.5rem; /* y-1 x-2 */ + whitespace: pre-line; + text-align: center; + + transition: all 0.225s ease-out; + transition-delay: var(--transition-delay, 0s); + transform-origin: ${anchorTypeToOriginType(config()?.windowPosition.anchor)}; + will-change: transform; + + font-family: ${style().font}; + font-weight: ${style().fontWeight}; + font-size: ${style().lyric.fontSize}px; + color: ${style().lyric.color}; + background-color: ${style().lyric.background}; + `); + + useClassStyle(`${userCSSSelectors['wrapper--stopped']} .${userCSSSelectors['lyrics-item']}`, () => ` + scale: 0.95; + `); return (
{ > {(lyrics, index) => ( -
+
event.currentTarget.style.setProperty('--top', `${event.currentTarget?.offsetTop}px`)} + class={userCSSSelectors['lyrics-transition-wrapper']} + >
(getPreviousLyricLength() ?? 0), + }} >
diff --git a/renderer/main/components/LyricsItem.tsx b/renderer/main/components/LyricsItem.tsx index a29e463c..2398120f 100644 --- a/renderer/main/components/LyricsItem.tsx +++ b/renderer/main/components/LyricsItem.tsx @@ -1,7 +1,8 @@ import { createMemo, createSignal, onMount, splitProps } from 'solid-js'; import { Status } from '../../components/PlayingInfoProvider'; -import { cx } from '../../utils/classNames'; + +import { userCSSVariables } from '../../utils/userCSSSelectors'; import type { JSX } from 'solid-js/jsx-runtime'; @@ -12,19 +13,18 @@ export interface LyricsItemProps extends JSX.HTMLAttributes { } const LyricsItem = (props: LyricsItemProps) => { - const [local, leftProps] = splitProps(props, ['status']); + const [, leftProps] = splitProps(props, ['status']); let dom!: HTMLDivElement; const [init, setInit] = createSignal(false); const style = createMemo(() => { - if (!init()) return 'transition-delay: calc(255ms + var(--order) * 75ms);'; + if (!init()) return `transition-delay: calc(255ms + var(${userCSSVariables['var-lyric-order']}) * 75ms);`; return ` - top: ${dom.offsetTop}px; - transition-delay: calc(var(--order) * 75ms); - scale: ${local.status === 'stopped' ? '0.95' : '1'}; + --top: ${dom.offsetTop}px; + transition-delay: calc(var(${userCSSVariables['var-lyric-order']}) * 75ms); `; }); @@ -40,11 +40,7 @@ const LyricsItem = (props: LyricsItemProps) => { {...leftProps} ref={dom} style={`${style()}; ${props.style}`} - class={cx(` - py-1 px-2 whitespace-pre-line text-center - bg-gray-900/50 text-gray-100 - transition-all duration-[225ms] ease-out origin-right will-change-transform - `, leftProps.class)} + class={leftProps.class} > {leftProps.children}
diff --git a/renderer/main/components/LyricsTransition.tsx b/renderer/main/components/LyricsTransition.tsx index 5c051e8d..6c5f9f1b 100644 --- a/renderer/main/components/LyricsTransition.tsx +++ b/renderer/main/components/LyricsTransition.tsx @@ -1,4 +1,4 @@ -import { createMemo, For, JSX, Match, on, splitProps, Switch, untrack } from 'solid-js'; +import { For, JSX, Match, splitProps, Switch, untrack } from 'solid-js'; import { TransitionGroup } from 'solid-transition-group'; import LyricsItem from './LyricsItem'; @@ -13,20 +13,15 @@ import type { StyleConfig } from '../../../common/schema'; type LyricsProps = { lyrics: string[]; status: Status; - style: string; }; const Lyrics = (props: LyricsProps) => ( - {(item, index) => item && ( + {(item, index) => ( {item} @@ -37,7 +32,6 @@ const Lyrics = (props: LyricsProps) => ( type LyricsTransitionGroupProps = { animation: string; lyrics: string[]; - lyricsStyle: string; lyricsStatus: Status; container: ({ children }: { children: JSX.Element }) => JSX.Element; }; @@ -52,7 +46,7 @@ const LyricsTransitionGroupAllAtOnce = (props: LyricsTransitionGroupProps) => { {(lyrics) => ( - + )} @@ -67,7 +61,7 @@ const LyricsTransitionGroupSequential = (props: LyricsTransitionGroupProps) => { return ( - + ); @@ -95,14 +89,6 @@ const LyricsTransition = (props: LyricTransitionProps) => { return `lyric-${configuredName}`; }; - const lyricsStyle = createMemo(on(style, (styleData) => ` - font-family: ${styleData.font}; - font-weight: ${styleData.fontWeight}; - font-size: ${styleData.lyric.fontSize}px; - color: ${styleData.lyric.color}; - background-color: ${styleData.lyric.background}; - `)); - const Container = (containerProps: { children: JSX.Element }) => (
{containerProps.children} @@ -126,7 +112,6 @@ const LyricsTransition = (props: LyricTransitionProps) => { container={Container} lyrics={lyricsProps.lyrics} lyricsStatus={lyricsProps.status} - lyricsStyle={lyricsStyle()} /> ); }; diff --git a/renderer/utils/userCSSSelectors.ts b/renderer/utils/userCSSSelectors.ts index 036af00b..a2f40bb0 100644 --- a/renderer/utils/userCSSSelectors.ts +++ b/renderer/utils/userCSSSelectors.ts @@ -1,14 +1,17 @@ export const userCSSSelectors = { 'wrapper': 'alspotron-usercss-wrapper', + 'wrapper--stopped': 'alspotron-usercss-wrapper--stopped', + 'wrapper--idle': 'alspotron-usercss-wrapper--idle', + 'wrapper--playing': 'alspotron-usercss-wrapper--playing', 'lyrics-item': 'alspotron-usercss-lyrics-item', 'lyrics': 'alspotron-usercss-lyrics', 'lyrics-wrapper': 'alspotron-usercss-lyrics-wrapper', + 'lyrics-wrapper--previous': 'alspotron-usercss-lyrics-wrapper--previous', + 'lyrics-wrapper--current': 'alspotron-usercss-lyrics-wrapper--current', + 'lyrics-wrapper--next': 'alspotron-usercss-lyrics-wrapper--next', 'lyrics-transition-wrapper': 'alspotron-usercss-lyrics-transition-wrapper', 'lyrics-container': 'alspotron-usercss-lyrics-container', 'nowplaying': 'alspotron-usercss-nowplaying', - 'nowplaying--stopped': 'alspotron-usercss-nowplaying--stopped', - 'nowplaying--idle': 'alspotron-usercss-nowplaying--idle', - 'nowplaying--playing': 'alspotron-usercss-nowplaying--playing', 'nowplaying-progress': 'alspotron-usercss-nowplaying-progress', 'nowplaying-progress-bar': 'alspotron-usercss-nowplaying-progress-bar', 'nowplaying-container': 'alspotron-usercss-nowplaying-container', @@ -33,4 +36,5 @@ export const userCSSVariables = { 'var-nowplaying-duration': '--nowplaying-duration', 'var-nowplaying-progress': '--nowplaying-progress', 'var-lyric-order': '--lyric-order', + 'var-lyric-order-offset': '--lyric-order-offset', }; From 76dac5e6a4cbb594eb507719f977c6a89fcb9ea0 Mon Sep 17 00:00:00 2001 From: Su-Yong Date: Wed, 29 Nov 2023 21:10:03 +0900 Subject: [PATCH 3/7] fix(renderer): fix entering animation always animates `pretty` --- renderer/main/components/Lyrics.tsx | 3 ++- renderer/main/components/LyricsItem.tsx | 1 - renderer/main/components/LyricsTransition.tsx | 14 +++----------- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/renderer/main/components/Lyrics.tsx b/renderer/main/components/Lyrics.tsx index 3faa62b3..c13ee257 100644 --- a/renderer/main/components/Lyrics.tsx +++ b/renderer/main/components/Lyrics.tsx @@ -104,7 +104,7 @@ const Lyrics = (props: LyricsProps) => { width: fit-content; - padding: 0.25rem 0.5rem; /* y-1 x-2 */ + padding: 0.25rem 0.5rem; whitespace: pre-line; text-align: center; @@ -148,6 +148,7 @@ const Lyrics = (props: LyricsProps) => { }} > { if (!init()) return `transition-delay: calc(255ms + var(${userCSSVariables['var-lyric-order']}) * 75ms);`; return ` - --top: ${dom.offsetTop}px; transition-delay: calc(var(${userCSSVariables['var-lyric-order']}) * 75ms); `; }); diff --git a/renderer/main/components/LyricsTransition.tsx b/renderer/main/components/LyricsTransition.tsx index 6c5f9f1b..3ee5a319 100644 --- a/renderer/main/components/LyricsTransition.tsx +++ b/renderer/main/components/LyricsTransition.tsx @@ -68,6 +68,7 @@ const LyricsTransitionGroupSequential = (props: LyricsTransitionGroupProps) => { }; type LyricTransitionProps = JSX.HTMLAttributes & { + animation: string; lyrics: string[]; status: Status; style: string; @@ -75,20 +76,11 @@ type LyricTransitionProps = JSX.HTMLAttributes & { }; const LyricsTransition = (props: LyricTransitionProps) => { - const [, lyricsProps, passedProps] = splitProps(props, ['class'], ['lyrics', 'status']); + const [, lyricsProps, passedProps] = splitProps(props, ['class'], ['lyrics', 'status', 'animation']); const theme = useStyle(); const style = () => props.theme ?? theme(); - const animation = () => { - const configuredName = style()?.animation ?? 'pretty'; - if (configuredName === 'custom') { - return userCSSTransitions['transition-lyric']; - } - - return `lyric-${configuredName}`; - }; - const Container = (containerProps: { children: JSX.Element }) => (
{containerProps.children} @@ -108,7 +100,7 @@ const LyricsTransition = (props: LyricTransitionProps) => { return ( Date: Wed, 29 Nov 2023 21:32:12 +0900 Subject: [PATCH 4/7] fix(renderer): update style --- renderer/main/components/Lyrics.tsx | 6 ++++-- renderer/main/components/LyricsTransition.tsx | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/renderer/main/components/Lyrics.tsx b/renderer/main/components/Lyrics.tsx index c13ee257..29688229 100644 --- a/renderer/main/components/Lyrics.tsx +++ b/renderer/main/components/Lyrics.tsx @@ -79,7 +79,7 @@ const Lyrics = (props: LyricsProps) => { `); useClassStyle(userCSSSelectors['lyrics-wrapper'], () => ` - transition: all 0.25s; + transition: all 0.6s; `); useClassStyle(userCSSSelectors['lyrics-wrapper--previous'], () => ` scale: ${style().lyric.previousLyricScale}; @@ -93,9 +93,11 @@ const Lyrics = (props: LyricsProps) => { `); useClassStyle(userCSSSelectors['lyrics'], () => ` - row-gap: ${style().lyric.containerRowGap}rem; + display: flex; flex-direction: ${style().lyric.direction ?? 'column'}; align-items: ${anchorTypeToItemsAlignType(config()?.windowPosition.anchor)}; + row-gap: ${style().lyric.containerRowGap}rem; + transform-origin: ${anchorTypeToOriginType(config()?.windowPosition.anchor)}; `); diff --git a/renderer/main/components/LyricsTransition.tsx b/renderer/main/components/LyricsTransition.tsx index 3ee5a319..47e77045 100644 --- a/renderer/main/components/LyricsTransition.tsx +++ b/renderer/main/components/LyricsTransition.tsx @@ -82,7 +82,7 @@ const LyricsTransition = (props: LyricTransitionProps) => { const style = () => props.theme ?? theme(); const Container = (containerProps: { children: JSX.Element }) => ( -
+
{containerProps.children}
); From b9de2d46b668c42932759cd2aac5d213dd80b16a Mon Sep 17 00:00:00 2001 From: Su-Yong Date: Wed, 29 Nov 2023 21:52:00 +0900 Subject: [PATCH 5/7] fix(renderer): fix animation preview bug --- renderer/main/components/Lyrics.tsx | 77 +++++++++++-------- renderer/main/components/LyricsItem.tsx | 5 +- .../settings/containers/ThemeContainer.tsx | 20 ++++- 3 files changed, 64 insertions(+), 38 deletions(-) diff --git a/renderer/main/components/Lyrics.tsx b/renderer/main/components/Lyrics.tsx index 29688229..8336f038 100644 --- a/renderer/main/components/Lyrics.tsx +++ b/renderer/main/components/Lyrics.tsx @@ -1,5 +1,5 @@ -import { For, createMemo, on, splitProps } from 'solid-js'; +import { Accessor, For, splitProps } from 'solid-js'; import { TransitionGroup } from 'solid-transition-group'; import LyricsTransition from './LyricsTransition'; @@ -13,9 +13,11 @@ import { userCSSSelectors, userCSSTransitions, userCSSVariables } from '../../ut import useConfig from '../../hooks/useConfig'; import useStyle from '../../hooks/useStyle'; -import type { JSX } from 'solid-js/jsx-runtime'; import { useClassStyle } from '../../hooks/useClassStyle'; +import type { JSX } from 'solid-js/jsx-runtime'; +import type { Config, StyleConfig } from '../../../common/schema'; + type LyricsProps = { style?: string; } & JSX.HTMLAttributes; @@ -40,25 +42,10 @@ const anchorTypeToOriginType = (anchor?: string, y = '0') => { } }; -const Lyrics = (props: LyricsProps) => { - const [config] = useConfig(); - const style = useStyle(); - const [, containerProps] = splitProps(props, ['class', 'style']); - const { status } = usePlayingInfo(); - const [, , lyricsRange, getPreviousLyricLength] = useLyric(); - - const orderOffset = () => (getPreviousLyricLength() ?? 0) * 3; - const offset = () => style().animationAtOnce ? 1 : 3; - - const animation = () => { - const configuredName = style()?.animation ?? 'pretty'; - if (configuredName === 'custom') { - return userCSSTransitions['transition-lyric']; - } - - return `lyric-${configuredName}`; - }; - +export const useLyricsStyle = ( + style: Accessor, + config: Accessor, +) => { useClassStyle(userCSSSelectors['lyrics-container'], () => ` width: 100%; @@ -66,11 +53,11 @@ const Lyrics = (props: LyricsProps) => { flex-direction: column; justify-content: center; align-items: ${anchorTypeToItemsAlignType(config()?.windowPosition.anchor)}; - row-gap: ${style().lyric.multipleContainerRowGap}rem; + row-gap: ${style()?.lyric.multipleContainerRowGap}rem; `); useClassStyle(`${userCSSSelectors['wrapper--stopped']} .${userCSSSelectors['lyrics-container']}`, () => ` - opacity: ${style().lyric.stoppedOpacity}; + opacity: ${style()?.lyric.stoppedOpacity}; `); useClassStyle(userCSSSelectors['lyrics-transition-wrapper'], () => ` @@ -82,21 +69,21 @@ const Lyrics = (props: LyricsProps) => { transition: all 0.6s; `); useClassStyle(userCSSSelectors['lyrics-wrapper--previous'], () => ` - scale: ${style().lyric.previousLyricScale}; - opacity: ${style().lyric.previousLyricOpacity}; + scale: ${style()?.lyric.previousLyricScale}; + opacity: ${style()?.lyric.previousLyricOpacity}; transform-origin: ${anchorTypeToOriginType(config()?.windowPosition.anchor, '100%')}; `); useClassStyle(userCSSSelectors['lyrics-wrapper--next'], () => ` - scale: ${style().lyric.nextLyricScale}; - opacity: ${style().lyric.nextLyricOpacity}; + scale: ${style()?.lyric.nextLyricScale}; + opacity: ${style()?.lyric.nextLyricOpacity}; transform-origin: ${anchorTypeToOriginType(config()?.windowPosition.anchor)}; `); useClassStyle(userCSSSelectors['lyrics'], () => ` display: flex; - flex-direction: ${style().lyric.direction ?? 'column'}; + flex-direction: ${style()?.lyric.direction ?? 'column'}; align-items: ${anchorTypeToItemsAlignType(config()?.windowPosition.anchor)}; - row-gap: ${style().lyric.containerRowGap}rem; + row-gap: ${style()?.lyric.containerRowGap}rem; transform-origin: ${anchorTypeToOriginType(config()?.windowPosition.anchor)}; `); @@ -115,16 +102,38 @@ const Lyrics = (props: LyricsProps) => { transform-origin: ${anchorTypeToOriginType(config()?.windowPosition.anchor)}; will-change: transform; - font-family: ${style().font}; - font-weight: ${style().fontWeight}; - font-size: ${style().lyric.fontSize}px; - color: ${style().lyric.color}; - background-color: ${style().lyric.background}; + font-family: ${style()?.font}; + font-weight: ${style()?.fontWeight}; + font-size: ${style()?.lyric.fontSize}px; + color: ${style()?.lyric.color}; + background-color: ${style()?.lyric.background}; `); useClassStyle(`${userCSSSelectors['wrapper--stopped']} .${userCSSSelectors['lyrics-item']}`, () => ` scale: 0.95; `); +}; + +const Lyrics = (props: LyricsProps) => { + const [config] = useConfig(); + const style = useStyle(); + const [, containerProps] = splitProps(props, ['class', 'style']); + const { status } = usePlayingInfo(); + const [, , lyricsRange, getPreviousLyricLength] = useLyric(); + + const orderOffset = () => (getPreviousLyricLength() ?? 0) * 3; + const offset = () => style().animationAtOnce ? 1 : 3; + + const animation = () => { + const configuredName = style()?.animation ?? 'pretty'; + if (configuredName === 'custom') { + return userCSSTransitions['transition-lyric']; + } + + return `lyric-${configuredName}`; + }; + + useLyricsStyle(style, config); return (
{ const [init, setInit] = createSignal(false); const style = createMemo(() => { - if (!init()) return `transition-delay: calc(255ms + var(${userCSSVariables['var-lyric-order']}) * 75ms);`; + if (!init()) return `--transition-delay: calc(255ms + var(${userCSSVariables['var-lyric-order']}) * 75ms);`; return ` - transition-delay: calc(var(${userCSSVariables['var-lyric-order']}) * 75ms); + --transition-delay: calc(var(${userCSSVariables['var-lyric-order']}) * 75ms); + --top: ${dom.offsetTop}px; `; }); diff --git a/renderer/settings/containers/ThemeContainer.tsx b/renderer/settings/containers/ThemeContainer.tsx index 3afa3ec8..256fed51 100644 --- a/renderer/settings/containers/ThemeContainer.tsx +++ b/renderer/settings/containers/ThemeContainer.tsx @@ -14,8 +14,12 @@ import { cx } from '../../utils/classNames'; import Switch from '../../components/Switch'; import LyricPreview from '../components/LyricPreview'; -import type { PartialDeep } from 'type-fest'; +import { userCSSTransitions } from '../../utils/userCSSSelectors'; +import { useLyricsStyle } from '../../main/components/Lyrics'; +import useConfig from '../../hooks/useConfig'; + import type { StyleConfig } from '../../../common/schema'; +import type { PartialDeep } from 'type-fest'; const ANIMATION_LIST = [ 'none', @@ -33,6 +37,7 @@ const ThemeContainer = () => { const navigate = useNavigate(); const [themeList, setThemeList] = useThemeList(); const [t] = useTransContext(); + const [config] = useConfig(); const PREVIEW_TEXT_A = [ t('setting.theme.animation.preview-text-a.0'), @@ -53,6 +58,14 @@ const ThemeContainer = () => { const themeName = () => decodeURIComponent(params.name); const theme = () => themeList()[themeName()]; + const animation = () => { + const configuredName = theme()?.animation ?? 'pretty'; + if (configuredName === 'custom') { + return userCSSTransitions['transition-lyric']; + } + + return `lyric-${configuredName}`; + }; let previewRef: HTMLDivElement | undefined; let parentRef: HTMLDivElement | undefined; @@ -117,6 +130,8 @@ const ThemeContainer = () => { URL.revokeObjectURL(link.href); }; + useLyricsStyle(() => theme() ?? null, config); + return
@@ -237,8 +252,9 @@ const ThemeContainer = () => {
-
+
Date: Wed, 29 Nov 2023 22:10:01 +0900 Subject: [PATCH 6/7] fix(renderer): fix theme preview --- renderer/settings/components/LyricPreview.tsx | 56 ++++++------------- .../settings/containers/ThemeContainer.tsx | 2 +- 2 files changed, 19 insertions(+), 39 deletions(-) diff --git a/renderer/settings/components/LyricPreview.tsx b/renderer/settings/components/LyricPreview.tsx index b248eedd..203c4a56 100644 --- a/renderer/settings/components/LyricPreview.tsx +++ b/renderer/settings/components/LyricPreview.tsx @@ -7,6 +7,10 @@ import LyricProgressBar from '../../main/components/LyricProgressBar'; import LyricsTransition from '../../main/components/LyricsTransition'; import { cx } from '../../utils/classNames'; +import { userCSSTransitions } from '../../utils/userCSSSelectors'; +import { useLyricsStyle } from '../../main/components/Lyrics'; +import useConfig from '../../hooks/useConfig'; + import type { StyleConfig } from '../../../common/schema'; const isMac = /Mac/.test(navigator.userAgent); @@ -19,6 +23,7 @@ export interface LyricPreviewProps extends JSX.HTMLAttributes { const LyricPreview = (props: LyricPreviewProps) => { const [local, leftProps] = splitProps(props, ['theme']); const [t] = useTransContext(); + const [config] = useConfig(); const PREVIEW_TEXT_A = [ t('setting.theme.animation.preview-text-a.0'), @@ -34,41 +39,19 @@ const LyricPreview = (props: LyricPreviewProps) => { const [animationPreview, setAnimationPreview] = createSignal(PREVIEW_TEXT_A); - const lyricStyle = () => { - let result = ''; - const style = props.theme; - - if (style?.nowPlaying.maxWidth) result += `max-width: ${style.nowPlaying.maxWidth}px;`; - if (style?.nowPlaying.color) result += `color: ${style.nowPlaying.color};`; - if (style?.nowPlaying.background) result += `background-color: ${style.nowPlaying.background};`; - if (style?.font) result += `font-family: ${style.font};`; - if (style?.fontWeight) result += `font-weight: ${style.fontWeight};`; - - return result; - }; - - const textStyle = () => { - let result = ''; + const animation = () => { + const configuredName = props.theme?.animation ?? 'pretty'; + if (configuredName === 'custom') { + return userCSSTransitions['transition-lyric']; + } - const style = props.theme; - if (style?.nowPlaying.fontSize) result += `font-size: ${style.nowPlaying.fontSize}px;`; - - return result; + return `lyric-${configuredName}`; }; - const progressStyle = () => { - let result = ''; - - const style = props.theme; - if (style?.nowPlaying.backgroundProgress) result += `background-color: ${style.nowPlaying.backgroundProgress};`; - - return result; - }; - - let interval: NodeJS.Timer | null = null; + let interval: number | null = null; onMount(() => { let isTick = false; - interval = setInterval(() => { + interval = window.setInterval(() => { const nextPreview = untrack(() => isTick ? PREVIEW_TEXT_A : PREVIEW_TEXT_B); isTick = !isTick; @@ -80,6 +63,8 @@ const LyricPreview = (props: LyricPreviewProps) => { if (typeof interval === 'number') clearInterval(interval); }); + useLyricsStyle(() => props.theme, config); + return ( { )} subCards={[ <> - + , , ]} > diff --git a/renderer/settings/containers/ThemeContainer.tsx b/renderer/settings/containers/ThemeContainer.tsx index 256fed51..fcd7c753 100644 --- a/renderer/settings/containers/ThemeContainer.tsx +++ b/renderer/settings/containers/ThemeContainer.tsx @@ -258,7 +258,7 @@ const ThemeContainer = () => { class={'w-full items-end'} style={`row-gap: ${theme()?.lyric.containerRowGap}rem;`} lyrics={animationPreview()} - status="playing" + status={'playing'} />
, From e73fd9d2024ba2cedd54d8213e08a117852f7f36 Mon Sep 17 00:00:00 2001 From: Su-Yong Date: Wed, 29 Nov 2023 22:41:50 +0900 Subject: [PATCH 7/7] feat(theme): add theme preset --- common/constants.ts | 4 +- common/intl/translations/de.json | 6 +++ common/intl/translations/en.json | 6 +++ common/intl/translations/ja.json | 6 +++ common/intl/translations/ko.json | 6 +++ common/presets/album-cover.json | 36 +++++++++++++++ common/presets/index.ts | 11 +++++ common/presets/multiple-lyric.json | 36 +++++++++++++++ renderer/hooks/useStyle.ts | 16 ++++--- .../containers/ThemeListContainer.tsx | 46 +++++++++++++++++-- src/config/theme-list.ts | 4 +- 11 files changed, 164 insertions(+), 13 deletions(-) create mode 100644 common/presets/album-cover.json create mode 100644 common/presets/index.ts create mode 100644 common/presets/multiple-lyric.json diff --git a/common/constants.ts b/common/constants.ts index 31868ad4..7fdaf27f 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -29,7 +29,7 @@ export const DEFAULT_STYLE = { lyric: { color: '#FFFFFF', background: 'rgba(29, 29, 29, .70)', - fontSize: 15, + fontSize: 13, maxWidth: 700, stoppedOpacity: 0.5, containerRowGap: 1, @@ -74,3 +74,5 @@ export const DEFAULT_CONFIG = { streamingMode: false, provider: LyricProviderList[0].provider as 'alsong', }; + +export const PRESET_PREFIX = '__preset__'; diff --git a/common/intl/translations/de.json b/common/intl/translations/de.json index 5e7f0294..6737f3f4 100644 --- a/common/intl/translations/de.json +++ b/common/intl/translations/de.json @@ -174,6 +174,12 @@ "setting.theme.import-from-file": "Import from file", "setting.theme.export-as-file": "Export as a file", "setting.theme.import-theme-failed": "Failed to import a theme", + "setting.theme.built-in-themes": "Built-in themes", + "setting.theme.no-available-themes": "Available themes not found.", + "setting.theme.preset.default": "Default Theme", + "setting.theme.preset.album-cover": "Album Cover Theme", + "setting.theme.preset.multiple-lyric": "Lyric Theme", + "setting.user-css-warning.bold": "Achtung!", "setting.user-css-warning.0": "Bei Verwendung von benutzerdefinierten Styles könnten diese nach einem Update visuelle fehler verursachen", "setting.user-css-warning.1": "Das Alspotron-Team garantiert die Stabilität der Styles nicht, da benutzerdefinierte Styles eine experimentelle Funktion sind.", diff --git a/common/intl/translations/en.json b/common/intl/translations/en.json index ddc8157d..d21cc329 100644 --- a/common/intl/translations/en.json +++ b/common/intl/translations/en.json @@ -174,6 +174,12 @@ "setting.theme.import-from-file": "Import from file", "setting.theme.export-as-file": "Export as a file", "setting.theme.import-theme-failed": "Failed to import a theme", + "setting.theme.built-in-themes": "Built-in themes", + "setting.theme.no-available-themes": "Available themes not found.", + "setting.theme.preset.default": "Default Theme", + "setting.theme.preset.album-cover": "Album Cover Theme", + "setting.theme.preset.multiple-lyric": "Lyric Theme", + "setting.user-css-warning.bold": "WARNING!", "setting.user-css-warning.0": "If you're using custom selectors, CSS settings can break after updates.", "setting.user-css-warning.1": "Custom CSS is an experimental feature. Alspotron Team does not guarantee custom CSS to work after updates.", diff --git a/common/intl/translations/ja.json b/common/intl/translations/ja.json index 835ecbcd..2a3c7c7c 100644 --- a/common/intl/translations/ja.json +++ b/common/intl/translations/ja.json @@ -175,6 +175,12 @@ "setting.theme.import-from-file": "Import from file", "setting.theme.export-as-file": "Export as a file", "setting.theme.import-theme-failed": "Failed to import a theme", + "setting.theme.built-in-themes": "Built-in themes", + "setting.theme.no-available-themes": "Available themes not found.", + "setting.theme.preset.default": "Default Theme", + "setting.theme.preset.album-cover": "Album Cover Theme", + "setting.theme.preset.multiple-lyric": "Lyric Theme", + "setting.user-css-warning.bold": "注意!", "setting.user-css-warning.0": "カスタムCSSセレクタを使用している場合、更新後にCSS設定が壊れてしまうことがあります。", "setting.user-css-warning.1": "カスタムCSSは実験的な機能です。Alspotron Teamはアップデート後のカスタムCSSの動作を保証しません。", diff --git a/common/intl/translations/ko.json b/common/intl/translations/ko.json index f291a7af..086f8001 100644 --- a/common/intl/translations/ko.json +++ b/common/intl/translations/ko.json @@ -174,6 +174,12 @@ "setting.theme.import-from-file": "파일로부터 불러오기", "setting.theme.export-as-file": "파일로 저장하기", "setting.theme.import-theme-failed": "테마를 불러오지 못하였습니다.", + "setting.theme.built-in-themes": "내장된 테마", + "setting.theme.no-available-themes": "사용 가능한 테마 없음", + "setting.theme.preset.default": "기본 테마", + "setting.theme.preset.album-cover": "앨범 커버 테마", + "setting.theme.preset.multiple-lyric": "가사 테마", + "setting.user-css-warning.bold": "주의!", "setting.user-css-warning.0": "제공된 셀렉터, 변수 외의 다른 셀렉터 등을 사용하실 경우 업데이트 시 CSS가 깨질 확률이 높습니다.", "setting.user-css-warning.1": "사용자 CSS는 어디까지나 실험기능으로, Alspotron 팀에서는 업데이트 시 스타일의 안정성을 보장하지 않습니다.", diff --git a/common/presets/album-cover.json b/common/presets/album-cover.json new file mode 100644 index 00000000..7809e7a2 --- /dev/null +++ b/common/presets/album-cover.json @@ -0,0 +1,36 @@ +{ + "font": "Pretendard Variable", + "fontWeight": "400", + "animation": "pretty", + "animationAtOnce": false, + "maxHeight": 400, + "proximityOpacity": 0, + "proximitySensitivity": 2, + "rowGap": 2, + "nowPlaying": { + "color": "#FFFFFF", + "background": "rgba(29, 29, 29, .50)", + "backgroundProgress": "rgba(29, 29, 29, .80)", + "fontSize": 13, + "maxWidth": 300, + "visible": true, + "stoppedOpacity": 0.5 + }, + "lyric": { + "color": "#FFFFFF", + "background": "rgba(29, 29, 29, .70)", + "fontSize": 13, + "maxWidth": 700, + "stoppedOpacity": 0.5, + "containerRowGap": 0.25, + "multipleContainerRowGap": 0.75, + "direction": "column", + "nextLyric": 1, + "previousLyric": 1, + "nextLyricScale": 0.9, + "previousLyricScale": 0.9, + "nextLyricOpacity": 0.5, + "previousLyricOpacity": 0.5 + }, + "userCSS": "alspotron-wrapper {\n min-height: 400px;\n\n flex-flow: row !important;\n gap: 32px;\n \n background: linear-gradient(\n 315deg,\n rgba(0, 0, 0, 0.5) 0%,\n rgba(0, 0, 0, 0) 25%\n );\n\n padding: 32px;\n}\n\nalspotron-lyrics-item {\n border-radius: 8px;\n padding: 4px 8px;\n}\n\n\nalspotron-nowplaying {\n width: 150px;\n height: 150px;\n max-width: 150px;\n\n border-radius: 12px;\n flex-shrink: 0;\n\n position: relative;\n display: inline-flex;\n flex-flow: column;\n justify-content: flex-end;\n align-items: flex-start;\n\n padding: 0;\n}\nalspotron-nowplaying::after {\n content: '';\n position: absolute;\n width: 100%;\n height: 100%;\n top: 0;\n left: 0;\n\n background-image: linear-gradient(\n 0deg,\n rgba(0, 0, 0, 0.75) 0%,\n rgba(0, 0, 0, 0.5) 24px,\n rgba(0, 0, 0, 0) 48px\n );\n z-index: 20;\n}\n\n\nalspotron-nowplaying-progress-bar {\n background: none !important;\n z-index: 21;\n}\nalspotron-nowplaying-progress {\n --color: 29, 29, 29;\n\n opacity: 0.75;\n \n background-image: linear-gradient(\n 135deg,\n rgba(var(--color), 1) 0%,\n rgba(var(--color), 1) 25%,\n rgba(var(--color), 0) 25%,\n rgba(var(--color), 0) 50%,\n rgba(var(--color), 1) 50%,\n rgba(var(--color), 1) 75%,\n rgba(var(--color), 0) 75%,\n rgba(var(--color), 0) 100%\n );\n background-size: 24px 24px;\n\n animation: progress 1.5s linear infinite;\n}\n\n@keyframes progress {\n 0% {\n background-position-x: 0;\n }\n 100% {\n background-position-x: 24px;\n }\n}\n\nalspotron-nowplaying-cover {\n position: absolute;\n width: 100%;\n height: 100%;\n top: 0;\n left: 0;\n z-index: -1;\n\n object-fit: cover;\n border-radius: 12px;\n padding: 4px;\n\n}\n\nalspotron-wrapper--stopped alspotron-nowplaying-cover {\n transform: scale(0.9);\n}\n\nalspotron-nowplaying-playing-text {\n position: relative;\n z-index: 50;\n margin-top: auto;\n padding: 12px 16px;\n overflow: visible !important;\n}\n\nalspotron-marquee {\n z-index: 50;\n}\n" +} \ No newline at end of file diff --git a/common/presets/index.ts b/common/presets/index.ts new file mode 100644 index 00000000..2bfcc957 --- /dev/null +++ b/common/presets/index.ts @@ -0,0 +1,11 @@ +import albumCover from './album-cover.json'; +import multipleLyric from './multiple-lyric.json'; + +import { DEFAULT_STYLE } from '../constants'; +import { StyleConfig } from '../schema'; + +export default { + 'default': DEFAULT_STYLE, + 'album-cover': albumCover, + 'multiple-lyric': multipleLyric, +} as Record; diff --git a/common/presets/multiple-lyric.json b/common/presets/multiple-lyric.json new file mode 100644 index 00000000..bba894bf --- /dev/null +++ b/common/presets/multiple-lyric.json @@ -0,0 +1,36 @@ +{ + "font": "Pretendard Variable", + "fontWeight": "400", + "animation": "fade", + "animationAtOnce": true, + "maxHeight": 400, + "proximityOpacity": 0, + "proximitySensitivity": 2, + "rowGap": 1, + "nowPlaying": { + "color": "#FFFFFF", + "background": "rgba(29, 29, 29, .50)", + "backgroundProgress": "rgba(29, 29, 29, .80)", + "fontSize": 15, + "maxWidth": 600, + "visible": true, + "stoppedOpacity": 0.5 + }, + "lyric": { + "color": "#FFFFFF", + "background": "rgba(29, 29, 29, .70)", + "fontSize": 12, + "maxWidth": 700, + "stoppedOpacity": 0.5, + "containerRowGap": 0.25, + "multipleContainerRowGap": 1, + "direction": "column", + "nextLyric": 1, + "previousLyric": 1, + "nextLyricScale": 0.9, + "previousLyricScale": 0.9, + "nextLyricOpacity": 0.5, + "previousLyricOpacity": 0.5 + }, + "userCSS": "" +} \ No newline at end of file diff --git a/renderer/hooks/useStyle.ts b/renderer/hooks/useStyle.ts index 47cf8430..9054efc9 100644 --- a/renderer/hooks/useStyle.ts +++ b/renderer/hooks/useStyle.ts @@ -3,7 +3,9 @@ import { Accessor, createMemo } from 'solid-js'; import useConfig from './useConfig'; import useThemeList from './useThemeList'; -import { DEFAULT_STYLE } from '../../common/constants'; +import { DEFAULT_STYLE, PRESET_PREFIX } from '../../common/constants'; + +import presetThemes from '../../common/presets'; import type { StyleConfig } from '../../common/schema'; @@ -15,11 +17,13 @@ const useStyle = (): Accessor => { const list = themeList(); const configData = config(); - return ( - list[configData?.selectedTheme ?? ''] - // ?? configData?.style - ?? DEFAULT_STYLE - ); + let result = list[configData?.selectedTheme ?? ''] ?? DEFAULT_STYLE; + if (configData?.selectedTheme.startsWith(PRESET_PREFIX)) { + const name = configData?.selectedTheme.replace(PRESET_PREFIX, ''); + result = presetThemes[name] ?? DEFAULT_STYLE; + } + + return result; }); return style; diff --git a/renderer/settings/containers/ThemeListContainer.tsx b/renderer/settings/containers/ThemeListContainer.tsx index 0a1b993e..0b2a3b0b 100644 --- a/renderer/settings/containers/ThemeListContainer.tsx +++ b/renderer/settings/containers/ThemeListContainer.tsx @@ -5,9 +5,11 @@ import { useNavigate } from '@solidjs/router'; import Card from '../../components/Card'; import Modal from '../../components/Modal'; import useConfig from '../../hooks/useConfig'; -import { DEFAULT_STYLE } from '../../../common/constants'; import useThemeList from '../../hooks/useThemeList'; + +import presetThemes from '../../../common/presets'; import { StyleConfig } from '../../../common/schema'; +import { DEFAULT_STYLE, PRESET_PREFIX } from '../../../common/constants'; const ThemeListContainer = () => { const [config, setConfig] = useConfig(); @@ -79,7 +81,7 @@ const ThemeListContainer = () => { const isSelected = config()?.selectedTheme === name; setTheme(name, null); setConfig({ - selectedTheme: isSelected ? Object.keys(themeList())[0] : config()?.selectedTheme, + selectedTheme: isSelected ? `${PRESET_PREFIX}${Object.keys(presetThemes)[0]}` : config()?.selectedTheme, }); setDeleteOpen(false); }; @@ -106,9 +108,48 @@ const ThemeListContainer = () => {
+
+ +
+ + {(name) => ( + + } + > + + + + +
+ +
+
+ + + )} +
+ +
+ +
+
{(name) => ( { subCards={[