merge: update from dev branch
This commit is contained in:
parent
74d783b5d5
commit
ab8c32ffb8
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@ -1,4 +0,0 @@
|
|||||||
- name: Upload coverage reports to Codecov
|
|
||||||
uses: codecov/codecov-action@v4.0.1
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
|
5
.idea/.gitignore
vendored
Normal file
5
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
12
.idea/AquaVox.iml
Normal file
12
.idea/AquaVox.iml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
48
.idea/codeStyles/Project.xml
Normal file
48
.idea/codeStyles/Project.xml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<HTMLCodeStyleSettings>
|
||||||
|
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||||
|
</HTMLCodeStyleSettings>
|
||||||
|
<JSCodeStyleSettings version="0">
|
||||||
|
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||||
|
<option name="USE_DOUBLE_QUOTES" value="false" />
|
||||||
|
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||||
|
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||||
|
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||||
|
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||||
|
</JSCodeStyleSettings>
|
||||||
|
<TypeScriptCodeStyleSettings version="0">
|
||||||
|
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||||
|
<option name="USE_DOUBLE_QUOTES" value="false" />
|
||||||
|
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||||
|
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||||
|
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||||
|
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||||
|
</TypeScriptCodeStyleSettings>
|
||||||
|
<VueCodeStyleSettings>
|
||||||
|
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
|
||||||
|
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
|
||||||
|
</VueCodeStyleSettings>
|
||||||
|
<codeStyleSettings language="HTML">
|
||||||
|
<option name="SOFT_MARGINS" value="120" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="JavaScript">
|
||||||
|
<option name="SOFT_MARGINS" value="120" />
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="TypeScript">
|
||||||
|
<option name="SOFT_MARGINS" value="120" />
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="Vue">
|
||||||
|
<option name="SOFT_MARGINS" value="120" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="4" />
|
||||||
|
<option name="TAB_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
|
</state>
|
||||||
|
</component>
|
6
.idea/jsLibraryMappings.xml
Normal file
6
.idea/jsLibraryMappings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JavaScriptLibraryMappings">
|
||||||
|
<includedPredefinedLibrary name="Node.js Core" />
|
||||||
|
</component>
|
||||||
|
</project>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/AquaVox.iml" filepath="$PROJECT_DIR$/.idea/AquaVox.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
17
.idea/runConfigurations/Run_Dev_Server.xml
Normal file
17
.idea/runConfigurations/Run_Dev_Server.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Run Dev Server" type="ShConfigurationType" focusToolWindowBeforeRun="true">
|
||||||
|
<option name="SCRIPT_TEXT" value="bun dev" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||||
|
<option name="SCRIPT_PATH" value="" />
|
||||||
|
<option name="SCRIPT_OPTIONS" value="" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
|
||||||
|
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||||
|
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
|
||||||
|
<option name="INTERPRETER_PATH" value="/opt/homebrew/bin/nu" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="EXECUTE_IN_TERMINAL" value="true" />
|
||||||
|
<option name="EXECUTE_SCRIPT_FILE" value="false" />
|
||||||
|
<envs />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "aquavox",
|
"name": "aquavox",
|
||||||
"version": "1.12.8",
|
"version": "1.12.12",
|
||||||
"private": false,
|
"private": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
<div>
|
|
||||||
|
|
||||||
</div>
|
|
@ -35,11 +35,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function volumeBarChangeTouch(e: TouchEvent) {
|
function volumeBarChangeTouch(e: TouchEvent) {
|
||||||
const value = turncate(
|
const value = truncate(
|
||||||
e.touches[0].clientX - volumeBar.getBoundingClientRect().x,
|
e.touches[0].clientX - volumeBar.getBoundingClientRect().x,
|
||||||
0,
|
0,
|
||||||
volumeBar.getBoundingClientRect().width
|
volumeBar.getBoundingClientRect().width
|
||||||
) / volumeBar.getBoundingClientRect().width;
|
) / volumeBar.getBoundingClientRect().width;
|
||||||
adjustVolume(value);
|
adjustVolume(value);
|
||||||
localStorage.setItem('volume', value.toString());
|
localStorage.setItem('volume', value.toString());
|
||||||
}
|
}
|
||||||
@ -49,10 +49,14 @@
|
|||||||
progressBarSlideValue.set((e.offsetX / progressBar.getBoundingClientRect().width) * duration);
|
progressBarSlideValue.set((e.offsetX / progressBar.getBoundingClientRect().width) * duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
function turncate(value: number, min: number, max: number) {
|
function truncate(value: number, min: number, max: number) {
|
||||||
return Math.min(Math.max(value, min), max);
|
return Math.min(Math.max(value, min), max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function progressBarMouseUp(offsetX: number) {
|
||||||
|
adjustDisplayProgress(offsetX / progressBar.getBoundingClientRect().width);
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
mql.addEventListener('change', (e) => {
|
mql.addEventListener('change', (e) => {
|
||||||
showInfoTop = e.matches && hasLyrics;
|
showInfoTop = e.matches && hasLyrics;
|
||||||
@ -85,7 +89,7 @@
|
|||||||
>
|
>
|
||||||
{#if !showInfoTop}
|
{#if !showInfoTop}
|
||||||
<div class="song-info">
|
<div class="song-info">
|
||||||
<div class="song-info-top {isInfoTopOverflowing ? 'animate' : ''}" bind:this={songInfoTopContainer}>
|
<div class="song-info-regular {isInfoTopOverflowing ? 'animate' : ''}" bind:this={songInfoTopContainer}>
|
||||||
<span
|
<span
|
||||||
class="song-name text-shadow {isInfoTopOverflowing ? 'animate' : ''}"
|
class="song-name text-shadow {isInfoTopOverflowing ? 'animate' : ''}"
|
||||||
bind:this={songInfoTopContent}>{name}</span
|
bind:this={songInfoTopContent}>{name}</span
|
||||||
@ -100,51 +104,34 @@
|
|||||||
{formatDuration(progress)}
|
{formatDuration(progress)}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="progress-bar shadow-md"
|
aria-valuemax={duration}
|
||||||
on:click={(e) => progressBarOnClick(e)}
|
aria-valuemin="0"
|
||||||
|
aria-valuenow={progress}
|
||||||
bind:this={progressBar}
|
bind:this={progressBar}
|
||||||
|
class="progress-bar shadow-md"
|
||||||
|
on:keydown
|
||||||
|
on:keyup
|
||||||
on:mousedown={() => {
|
on:mousedown={() => {
|
||||||
userAdjustingProgress.set(true);
|
userAdjustingProgress.set(true);
|
||||||
}}
|
}}
|
||||||
on:mousemove={(e) => {
|
on:mousemove={(e) => {
|
||||||
if ($userAdjustingProgress) {
|
if ($userAdjustingProgress) {
|
||||||
|
console.log(e.offsetX )
|
||||||
adjustDisplayProgress(e.offsetX / progressBar.getBoundingClientRect().width);
|
adjustDisplayProgress(e.offsetX / progressBar.getBoundingClientRect().width);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
on:touchstart={(e) => {
|
on:mouseup={(e) => {
|
||||||
if (e.cancelable) {
|
const offsetX = e.offsetX;
|
||||||
e.preventDefault();
|
progressBarOnClick(e);
|
||||||
}
|
// Q: why it needs delay?
|
||||||
userAdjustingProgress.set(true);
|
// A: I do not know.
|
||||||
}}
|
setTimeout(()=> {
|
||||||
on:touchmove={(e) => {
|
userAdjustingProgress.set(false);
|
||||||
e.preventDefault();
|
progressBarMouseUp(offsetX);
|
||||||
userAdjustingProgress.set(true);
|
}, 50);
|
||||||
if ($userAdjustingProgress) {
|
|
||||||
lastTouchProgress =
|
|
||||||
turncate(
|
|
||||||
e.touches[0].clientX - progressBar.getBoundingClientRect().x,
|
|
||||||
0,
|
|
||||||
progressBar.getBoundingClientRect().width
|
|
||||||
) / progressBar.getBoundingClientRect().width;
|
|
||||||
adjustDisplayProgress(lastTouchProgress);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
on:touchend={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
userAdjustingProgress.set(false);
|
|
||||||
adjustProgress(lastTouchProgress);
|
|
||||||
}}
|
|
||||||
on:mouseup={() => {
|
|
||||||
userAdjustingProgress.set(false);
|
|
||||||
}}
|
}}
|
||||||
role="slider"
|
role="slider"
|
||||||
aria-valuemin="0"
|
|
||||||
aria-valuemax={duration}
|
|
||||||
aria-valuenow={progress}
|
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
on:keydown
|
|
||||||
on:keyup
|
|
||||||
>
|
>
|
||||||
<div class="bar" style={`width: ${(progress / (duration + 0.001)) * 100}%;`}></div>
|
<div class="bar" style={`width: ${(progress / (duration + 0.001)) * 100}%;`}></div>
|
||||||
</div>
|
</div>
|
||||||
@ -152,26 +139,49 @@
|
|||||||
<div class="time-indicator text-shadow-md time-total">{formatDuration(duration)}</div>
|
<div class="time-indicator text-shadow-md time-total">{formatDuration(duration)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="controls top-32 flex h-16 overflow-hidden items-center justify-center">
|
<div class="controls top-32 flex h-16 overflow-hidden items-center justify-center">
|
||||||
<button style="filter: drop-shadow( 0 4px 8px rgba(0, 0, 0, 0.12) );" class="control-btn previous">
|
<button class="control-btn previous" style="filter: drop-shadow( 0 4px 8px rgba(0, 0, 0, 0.12) );">
|
||||||
<img class="control-img switch-song-img" src="/previous.svg" alt="上一曲" />
|
<img alt="上一曲" class="control-img switch-song-img" src="/previous.svg" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
style="filter: drop-shadow( 0 4px 8px rgba(0, 0, 0, 0.12) );"
|
|
||||||
class="control-btn play-btn"
|
class="control-btn play-btn"
|
||||||
on:click={() => clickPlay()}
|
on:click={(e) => clickPlay()}
|
||||||
|
on:focus={null}
|
||||||
|
on:mouseleave={(e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = '';
|
||||||
|
}}
|
||||||
|
on:mouseover={(e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = 'rgba(0, 0, 0, 0.2)';
|
||||||
|
}}
|
||||||
|
on:touchend={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.currentTarget.style.backgroundColor = '';
|
||||||
|
e.currentTarget.style.scale = '1';
|
||||||
|
clickPlay();
|
||||||
|
}}
|
||||||
|
on:touchstart={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.currentTarget.style.backgroundColor = 'rgba(0, 0, 0, 0.2)';
|
||||||
|
e.currentTarget.style.scale = '0.8';
|
||||||
|
}}
|
||||||
|
style="filter: drop-shadow( 0 4px 8px rgba(0, 0, 0, 0.12) );"
|
||||||
>
|
>
|
||||||
<img class="control-img" src={paused ? '/play.svg' : '/pause.svg'} alt="暂停或播放" />
|
<img alt={paused ? '播放' : '暂停'} class="control-img" src={paused ? '/play.svg' : '/pause.svg'} />
|
||||||
</button>
|
</button>
|
||||||
<button style="filter: drop-shadow( 0 4px 8px rgba(0, 0, 0, 0.12) );" class="control-btn next">
|
<button class="control-btn next" style="filter: drop-shadow( 0 4px 8px rgba(0, 0, 0, 0.12) );">
|
||||||
<img class="control-img switch-song-img" src="/next.svg" alt="下一曲" />
|
<img alt="下一曲" class="control-img switch-song-img" src="/next.svg" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="relative top-52 h-6 flex">
|
<div class="relative top-52 h-6 flex">
|
||||||
<img class="scale-75" src="/volumeDown.svg" alt="最小音量" />
|
<img alt="最小音量" class="scale-75" src="/volumeDown.svg" />
|
||||||
<div
|
<div
|
||||||
|
aria-valuemax="1"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuenow={volume}
|
||||||
|
bind:this={volumeBar}
|
||||||
class="progress-bar shadow-md !top-1/2 !translate-y-[-50%]"
|
class="progress-bar shadow-md !top-1/2 !translate-y-[-50%]"
|
||||||
on:click={(e) => volumeBarOnChange(e)}
|
on:click={(e) => volumeBarOnChange(e)}
|
||||||
bind:this={volumeBar}
|
on:keydown
|
||||||
|
on:keyup
|
||||||
on:mousedown={() => {
|
on:mousedown={() => {
|
||||||
userAdjustingVolume = true;
|
userAdjustingVolume = true;
|
||||||
}}
|
}}
|
||||||
@ -180,11 +190,12 @@
|
|||||||
volumeBarOnChange(e);
|
volumeBarOnChange(e);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
on:touchstart={(e) => {
|
on:mouseup={() => {
|
||||||
if (e.cancelable) {
|
userAdjustingVolume = false;
|
||||||
e.preventDefault();
|
}}
|
||||||
}
|
on:touchend={(e) => {
|
||||||
userAdjustingVolume = true;
|
e.preventDefault();
|
||||||
|
userAdjustingVolume = false;
|
||||||
}}
|
}}
|
||||||
on:touchmove={(e) => {
|
on:touchmove={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -193,24 +204,18 @@
|
|||||||
volumeBarChangeTouch(e);
|
volumeBarChangeTouch(e);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
on:touchend={(e) => {
|
on:touchstart={(e) => {
|
||||||
e.preventDefault();
|
if (e.cancelable) {
|
||||||
userAdjustingVolume = false;
|
e.preventDefault();
|
||||||
}}
|
}
|
||||||
on:mouseup={() => {
|
userAdjustingVolume = true;
|
||||||
userAdjustingVolume = false;
|
|
||||||
}}
|
}}
|
||||||
role="slider"
|
role="slider"
|
||||||
aria-valuemin="0"
|
|
||||||
aria-valuemax="1"
|
|
||||||
aria-valuenow={volume}
|
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
on:keydown
|
|
||||||
on:keyup
|
|
||||||
>
|
>
|
||||||
<div class="bar" style={`width: ${volume * 100}%;`}></div>
|
<div class="bar" style={`width: ${volume * 100}%;`}></div>
|
||||||
</div>
|
</div>
|
||||||
<img class="scale-75" src="/volumeUp.svg" alt="最大音量" />
|
<img alt="最大音量" class="scale-75" src="/volumeUp.svg" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -221,6 +226,7 @@
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, 0);
|
transform: translate(-50%, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-btn {
|
.control-btn {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 3.7rem;
|
height: 3.7rem;
|
||||||
@ -228,11 +234,10 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin: 0 0.5rem;
|
margin: 0 0.5rem;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
transition: 0.1s;
|
transition: 0.45s;
|
||||||
}
|
scale: 1;
|
||||||
.control-btn:hover {
|
|
||||||
background-color: rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-img {
|
.control-img {
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
@ -240,6 +245,7 @@
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.switch-song-img {
|
.switch-song-img {
|
||||||
width: auto !important;
|
width: auto !important;
|
||||||
height: 1.7rem !important;
|
height: 1.7rem !important;
|
||||||
@ -256,19 +262,21 @@
|
|||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.song-info-top {
|
|
||||||
|
.song-info-regular {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
height: 2.375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.song-info-top.animate {
|
.song-info-regular.animate {
|
||||||
mask-image: linear-gradient(
|
mask-image: linear-gradient(
|
||||||
90deg,
|
90deg,
|
||||||
rgba(0, 0, 0, 0) 0%,
|
rgba(0, 0, 0, 0) 0%,
|
||||||
rgba(0, 0, 0, 1) 2rem,
|
rgba(0, 0, 0, 1) 2rem,
|
||||||
rgba(0, 0, 0, 1) calc(100% - 5rem),
|
rgba(0, 0, 0, 1) calc(100% - 5rem),
|
||||||
rgba(0, 0, 0, 0) 100%
|
rgba(0, 0, 0, 0) 100%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,12 +291,15 @@
|
|||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.song-name.animate {
|
.song-name.animate {
|
||||||
animation: scroll 10s linear infinite;
|
animation: scroll 10s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
.song-name::-webkit-scrollbar {
|
.song-name::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes scroll {
|
@keyframes scroll {
|
||||||
0% {
|
0% {
|
||||||
transform: translateX(100%);
|
transform: translateX(100%);
|
||||||
@ -300,10 +311,12 @@
|
|||||||
transform: translateX(-100%);
|
transform: translateX(-100%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.song-author {
|
.song-author {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
color: rgba(255, 255, 255, 0.8);
|
color: rgba(255, 255, 255, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -311,6 +324,7 @@
|
|||||||
transform: translate(-50%, 0);
|
transform: translate(-50%, 0);
|
||||||
height: 2.4rem;
|
height: 2.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-bar {
|
.progress-bar {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
@ -325,9 +339,11 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: 0.3s;
|
transition: 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-bar:hover {
|
.progress-bar:hover {
|
||||||
height: 0.7rem;
|
height: 0.7rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bar {
|
.bar {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -351,10 +367,18 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
top: 0.2rem;
|
top: 0.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-current {
|
.time-current {
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-total {
|
.time-total {
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.control-btn {
|
||||||
|
transition: 0.1s
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
import progressBarRaw from '$lib/state/progressBarRaw';
|
import progressBarRaw from '$lib/state/progressBarRaw';
|
||||||
import type { LrcJsonData } from 'lrc-parser-ts';
|
import type { LrcJsonData } from 'lrc-parser-ts';
|
||||||
import progressBarSlideValue from '$lib/state/progressBarSlideValue';
|
import progressBarSlideValue from '$lib/state/progressBarSlideValue';
|
||||||
|
import nextUpdate from '$lib/state/nextUpdate';
|
||||||
|
|
||||||
// Component input properties
|
// Component input properties
|
||||||
export let lyrics: string[];
|
export let lyrics: string[];
|
||||||
@ -12,18 +13,25 @@
|
|||||||
|
|
||||||
// Local state and variables
|
// Local state and variables
|
||||||
let getLyricIndex: Function;
|
let getLyricIndex: Function;
|
||||||
const debugMode = false;
|
let debugMode = false;
|
||||||
|
if (localStorage.getItem('debugMode') == null) {
|
||||||
|
localStorage.setItem('debugMode', 'false');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
debugMode = localStorage.getItem('debugMode')!.toLowerCase() === "true";
|
||||||
|
}
|
||||||
let currentLyricIndex = -1;
|
let currentLyricIndex = -1;
|
||||||
let currentPositionIndex = -1;
|
let currentPositionIndex = -1;
|
||||||
let currentAnimationIndex = -1;
|
let currentAnimationIndex = -1;
|
||||||
let lyricsContainer: HTMLDivElement;
|
let lyricsContainer: HTMLDivElement | null;
|
||||||
let nextUpdate = -1;
|
|
||||||
let lastAdjustProgress = 0;
|
let lastAdjustProgress = 0;
|
||||||
let localProgress = 0;
|
let localProgress = 0;
|
||||||
let lastScroll = 0;
|
let lastScroll = 0;
|
||||||
let scrolling = false;
|
let scrolling = false;
|
||||||
let scriptScrolling = false;
|
let scriptScrolling = false;
|
||||||
|
|
||||||
|
let currentLyricTopMargin = 288;
|
||||||
|
|
||||||
// References to lyric elements
|
// References to lyric elements
|
||||||
let refs: HTMLParagraphElement[] = [];
|
let refs: HTMLParagraphElement[] = [];
|
||||||
let _refs: any[] = [];
|
let _refs: any[] = [];
|
||||||
@ -38,73 +46,37 @@
|
|||||||
else return 'previous-lyric';
|
else return 'previous-lyric';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll to corresponding lyric while adjusting progress
|
|
||||||
$: {
|
|
||||||
if ($userAdjustingProgress == true) {
|
|
||||||
const currentLyric = refs[getLyricIndex(progress)];
|
|
||||||
scrollToLyric(currentLyric);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the current lyric and apply blur effect based on the progress
|
|
||||||
$: {
|
|
||||||
(() => {
|
|
||||||
if (!lyricsContainer || !originalLyrics.scripts) return;
|
|
||||||
|
|
||||||
const scripts = originalLyrics.scripts;
|
|
||||||
currentPositionIndex = getLyricIndex(progress);
|
|
||||||
const cl = scripts[currentPositionIndex];
|
|
||||||
|
|
||||||
if (cl.start <= progress && progress <= cl.end) {
|
|
||||||
currentLyricIndex = currentPositionIndex;
|
|
||||||
nextUpdate = cl.end;
|
|
||||||
} else {
|
|
||||||
currentLyricIndex = -1;
|
|
||||||
nextUpdate = cl.start;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentLyric = refs[currentPositionIndex];
|
|
||||||
if ($userAdjustingProgress || scrolling || currentLyric.getBoundingClientRect().top < 0) return;
|
|
||||||
|
|
||||||
for (let i = 0; i < scripts.length; i++) {
|
|
||||||
const offset = Math.abs(i - currentPositionIndex);
|
|
||||||
const blurRadius = Math.min(offset * 1, 16);
|
|
||||||
if (refs[i]) {
|
|
||||||
refs[i].style.filter = `blur(${blurRadius}px)`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility function to create a sleep/delay
|
|
||||||
function sleep(ms: number) {
|
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to move the lyrics up smoothly
|
// Function to move the lyrics up smoothly
|
||||||
async function moveToNextLine(h: number) {
|
async function moveToNextLine(h: number) {
|
||||||
let pos = currentPositionIndex + 2;
|
console.debug(new Date().getTime() , 'moveToNextLine', h);
|
||||||
for (let i = currentPositionIndex + 2; i < refs.length; i++) {
|
// the line that's going to process (like a pointer)
|
||||||
|
// by default, it's "the next line" after the lift
|
||||||
|
let processingLineIndex = currentPositionIndex + 2;
|
||||||
|
|
||||||
|
// modify translateY of all lines in viewport one by one to lift them up
|
||||||
|
for (let i = processingLineIndex; i < refs.length; i++) {
|
||||||
const lyric = refs[i];
|
const lyric = refs[i];
|
||||||
lyric.style.transition =
|
lyric.style.transition =
|
||||||
'transform .6s cubic-bezier(.28,.01,.29,.99), filter 200ms ease, opacity 200ms ease, font-size 200ms ease, scale 250ms ease';
|
`transform .6s cubic-bezier(.28,.01,.29,.99), filter 200ms ease, opacity 200ms ease,
|
||||||
|
font-size 200ms ease, scale 250ms ease`;
|
||||||
lyric.style.transform = `translateY(${-h}px)`;
|
lyric.style.transform = `translateY(${-h}px)`;
|
||||||
pos = i;
|
processingLineIndex = i;
|
||||||
await sleep(75);
|
await sleep(75);
|
||||||
if (refs[i - 2].getBoundingClientRect().top > lyricsContainer.getBoundingClientRect().height) break;
|
const twoLinesAhead = refs[i - 2];
|
||||||
|
if (lyricsContainer && twoLinesAhead.getBoundingClientRect().top > lyricsContainer.getBoundingClientRect().height) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (refs.length - pos < 3) {
|
if (refs.length - processingLineIndex < 3) {
|
||||||
for (let i = pos; i < refs.length; i++) {
|
for (let i = processingLineIndex; i < refs.length; i++) {
|
||||||
const lyric = refs[i];
|
const lyric = refs[i];
|
||||||
lyric.style.transition =
|
lyric.style.transition =
|
||||||
'transform .6s cubic-bezier(.28,.01,.29,.99), filter 200ms ease, opacity 200ms ease, font-size 200ms ease, scale 250ms ease';
|
'transform .6s cubic-bezier(.28,.01,.29,.99), filter 200ms ease, opacity 200ms ease, font-size 200ms ease, scale 250ms ease';
|
||||||
lyric.style.transform = `translateY(${-h}px)`;
|
lyric.style.transform = `translateY(${-h}px)`;
|
||||||
pos = i;
|
processingLineIndex = i;
|
||||||
await sleep(75);
|
await sleep(75);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (let i = pos; i < refs.length; i++) {
|
for (let i = processingLineIndex; i < refs.length; i++) {
|
||||||
refs[i].style.transition =
|
refs[i].style.transition =
|
||||||
'transform 0s, filter 200ms ease, opacity 200ms ease, font-size 200ms ease, scale 250ms ease';
|
'transform 0s, filter 200ms ease, opacity 200ms ease, font-size 200ms ease, scale 250ms ease';
|
||||||
const height = refs[i].getBoundingClientRect().height;
|
const height = refs[i].getBoundingClientRect().height;
|
||||||
@ -112,37 +84,45 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wait until the animation end
|
||||||
await sleep(650);
|
await sleep(650);
|
||||||
|
|
||||||
|
// clear the transition to let the following style changes could be done without animation
|
||||||
for (let i = 0; i < refs.length; i++) {
|
for (let i = 0; i < refs.length; i++) {
|
||||||
refs[i].style.transition =
|
refs[i].style.transition =
|
||||||
'transform 0s, filter 200ms ease, opacity 200ms ease, font-size 200ms ease, scale 250ms ease';
|
'transform 0s, filter 200ms ease, opacity 200ms ease, font-size 200ms ease, scale 250ms ease';
|
||||||
}
|
}
|
||||||
|
// reset the translateY, and immediately scroll down to provide visual stability
|
||||||
for (let i = 0; i < refs.length; i++) {
|
for (let i = 0; i < refs.length; i++) {
|
||||||
refs[i].style.transform = `translateY(0px)`;
|
refs[i].style.transform = `translateY(0px)`;
|
||||||
}
|
}
|
||||||
scriptScrolling = true;
|
scriptScrolling = true;
|
||||||
lyricsContainer.scrollTop += h;
|
if (lyricsContainer !== null) {
|
||||||
setTimeout(() => {
|
lyricsContainer.scrollTop += h;
|
||||||
scriptScrolling = false;
|
}
|
||||||
}, 500);
|
await sleep(500);
|
||||||
|
scriptScrolling = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll the lyrics container to the given lyric
|
// Scroll the lyrics container to the given lyric
|
||||||
async function scrollToLyric(currentLyric: HTMLParagraphElement) {
|
async function scrollToLyric(currentLyric: HTMLParagraphElement) {
|
||||||
if (!originalLyrics || !originalLyrics.scripts || !lyricsContainer) return;
|
if (!originalLyrics || !originalLyrics.scripts || !lyricsContainer) return;
|
||||||
scriptScrolling = true;
|
scriptScrolling = true;
|
||||||
lyricsContainer.scrollTop += currentLyric.getBoundingClientRect().top - 144;
|
lyricsContainer.scrollTop += currentLyric.getBoundingClientRect().top - currentLyricTopMargin;
|
||||||
|
for (let i = 0; i < refs.length; i++) {
|
||||||
|
refs[i].style.transform = 'translateY(0px)';
|
||||||
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
scriptScrolling = false;
|
scriptScrolling = false;
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle user adjusting progress state changes
|
// Handle user adjusting progress state changes
|
||||||
userAdjustingProgress.subscribe((v) => {
|
userAdjustingProgress.subscribe((adjusting) => {
|
||||||
if (!originalLyrics) return;
|
if (!originalLyrics) return;
|
||||||
const scripts = originalLyrics.scripts;
|
const scripts = originalLyrics.scripts;
|
||||||
if (!scripts) return;
|
if (!scripts) return;
|
||||||
if (v) {
|
if (adjusting) {
|
||||||
for (let i = 0; i < scripts.length; i++) {
|
for (let i = 0; i < scripts.length; i++) {
|
||||||
refs[i].style.filter = `blur(0px)`;
|
refs[i].style.filter = `blur(0px)`;
|
||||||
}
|
}
|
||||||
@ -193,73 +173,132 @@
|
|||||||
}, 5500);
|
}, 5500);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update lyrics position based on progress
|
// Utility function to create a sleep/delay
|
||||||
|
function sleep(ms: number) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll to corresponding lyric while adjusting progress
|
||||||
|
$: {
|
||||||
|
if ($userAdjustingProgress == true) {
|
||||||
|
const currentLyric = refs[getLyricIndex(progress)];
|
||||||
|
scrollToLyric(currentLyric);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the current lyric and apply blur effect based on the progress
|
||||||
$: {
|
$: {
|
||||||
(() => {
|
(() => {
|
||||||
if ($userAdjustingProgress) {
|
if (!lyricsContainer || !originalLyrics.scripts) return;
|
||||||
nextUpdate = progress;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextUpdate - progress >= 0.05) return;
|
const scripts = originalLyrics.scripts;
|
||||||
if (
|
currentPositionIndex = getLyricIndex(progress);
|
||||||
currentPositionIndex < 0 ||
|
const cl = scripts[currentPositionIndex];
|
||||||
currentPositionIndex === currentAnimationIndex ||
|
|
||||||
currentPositionIndex === lastAdjustProgress ||
|
if (cl.start <= progress && progress <= cl.end) {
|
||||||
scrolling
|
currentLyricIndex = currentPositionIndex;
|
||||||
)
|
nextUpdate.set(cl.end);
|
||||||
return;
|
} else {
|
||||||
|
currentLyricIndex = -1;
|
||||||
|
nextUpdate.set(cl.start);
|
||||||
|
}
|
||||||
|
|
||||||
const currentLyric = refs[currentPositionIndex];
|
const currentLyric = refs[currentPositionIndex];
|
||||||
|
if ($userAdjustingProgress || scrolling || currentLyric.getBoundingClientRect().top < 0) return;
|
||||||
|
|
||||||
if (originalLyrics.scripts && currentLyric.getBoundingClientRect().top < 0) return;
|
for (let i = 0; i < scripts.length; i++) {
|
||||||
|
const offset = Math.abs(i - currentPositionIndex);
|
||||||
const offsetHeight =
|
const blurRadius = Math.min(offset * 0.96, 16);
|
||||||
refs[currentPositionIndex].getBoundingClientRect().height +
|
if (refs[i]) {
|
||||||
refs[currentPositionIndex].getBoundingClientRect().top -
|
refs[i].style.filter = `blur(${blurRadius}px)`;
|
||||||
144;
|
}
|
||||||
|
|
||||||
currentLyric.style.transition =
|
|
||||||
'transform .6s cubic-bezier(.28,.01,.29,.99), filter 200ms ease, opacity 200ms ease, font-size 200ms ease, scale 250ms ease';
|
|
||||||
currentLyric.style.transform = `translateY(${-offsetHeight}px)`;
|
|
||||||
|
|
||||||
for (let i = currentPositionIndex - 1; i >= 0; i--) {
|
|
||||||
refs[i].style.transition =
|
|
||||||
'transform .6s cubic-bezier(.28,.01,.29,.99), filter 200ms ease, opacity 200ms ease, font-size 200ms ease, scale 250ms ease';
|
|
||||||
const height = refs[i].getBoundingClientRect().height;
|
|
||||||
refs[i].style.transform = `translateY(${-offsetHeight}px)`;
|
|
||||||
}
|
}
|
||||||
if (currentPositionIndex + 1 < refs.length) {
|
|
||||||
const nextLyric = refs[currentPositionIndex + 1];
|
|
||||||
nextLyric.style.transition =
|
|
||||||
'transform .6s cubic-bezier(.28,.01,.29,.99), filter 200ms ease, opacity 200ms ease, font-size 200ms ease, scale 250ms ease';
|
|
||||||
nextLyric.style.transform = `translateY(${-offsetHeight}px)`;
|
|
||||||
moveToNextLine(offsetHeight);
|
|
||||||
}
|
|
||||||
currentAnimationIndex = currentPositionIndex;
|
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nextUpdate.subscribe(async (nextUpdate) => {
|
||||||
|
if (
|
||||||
|
currentPositionIndex < 0 ||
|
||||||
|
currentPositionIndex === currentAnimationIndex ||
|
||||||
|
currentPositionIndex === lastAdjustProgress ||
|
||||||
|
$userAdjustingProgress === true ||
|
||||||
|
scrolling
|
||||||
|
) return;
|
||||||
|
|
||||||
|
const currentLyric = refs[currentPositionIndex];
|
||||||
|
|
||||||
|
if (originalLyrics.scripts && currentLyric.getBoundingClientRect().top < 0) return;
|
||||||
|
|
||||||
|
const offsetHeight =
|
||||||
|
refs[currentPositionIndex].getBoundingClientRect().top -
|
||||||
|
currentLyricTopMargin;
|
||||||
|
|
||||||
|
// prepare current line
|
||||||
|
currentLyric.style.transition = `transform .6s cubic-bezier(.28,.01,.29,.99), filter 200ms ease,
|
||||||
|
opacity 200ms ease, font-size 200ms ease, scale 250ms ease`;
|
||||||
|
currentLyric.style.transform = `translateY(${-offsetHeight}px)`;
|
||||||
|
|
||||||
|
for (let i = currentPositionIndex - 1; i >= 0; i--) {
|
||||||
|
refs[i].style.transition = `transform .6s cubic-bezier(.28,.01,.29,.99), filter 200ms ease,
|
||||||
|
opacity 200ms ease, font-size 200ms ease, scale 250ms ease`;
|
||||||
|
refs[i].style.transform = `translateY(${-offsetHeight}px)`;
|
||||||
|
}
|
||||||
|
if (currentPositionIndex + 1 < refs.length) {
|
||||||
|
const nextLyric = refs[currentPositionIndex + 1];
|
||||||
|
nextLyric.style.transition = `transform .6s cubic-bezier(.28,.01,.29,.99), filter 200ms ease,
|
||||||
|
opacity 200ms ease, font-size 200ms ease, scale 250ms ease`;
|
||||||
|
nextLyric.style.transform = `translateY(${-offsetHeight}px)`;
|
||||||
|
await moveToNextLine(offsetHeight);
|
||||||
|
}
|
||||||
|
currentAnimationIndex = currentPositionIndex;
|
||||||
|
})
|
||||||
|
|
||||||
|
function onKeyDown(e: KeyboardEvent) {
|
||||||
|
if (e.altKey && e.shiftKey && (e.metaKey || e.key === 'OS') && e.key === 'Enter') {
|
||||||
|
debugMode = !debugMode;
|
||||||
|
localStorage.setItem('debugMode', debugMode ? 'true' : 'false');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractTranslateValue(s: string): string | null {
|
||||||
|
const regex = /translateY\((-?\d*px)\)/;
|
||||||
|
let arr = regex.exec(s);
|
||||||
|
return arr==null ? null : arr[1];
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if lyrics && originalLyrics}
|
<svelte:window on:keydown={onKeyDown} />
|
||||||
|
|
||||||
|
{#if debugMode && lyricsContainer}
|
||||||
|
<div
|
||||||
|
class="absolute top-6 right-10 font-mono text-sm backdrop-blur-md z-20 bg-[rgba(255,255,255,0.15)] px-2 rounded-xl">
|
||||||
|
<p>
|
||||||
|
LyricIndex: {currentLyricIndex} PositionIndex: {currentPositionIndex}
|
||||||
|
AnimationIndex:{currentAnimationIndex}
|
||||||
|
NextUpdate: {$nextUpdate}
|
||||||
|
Progress: {progress.toFixed(2)}
|
||||||
|
lastAdjustProgress: {lastAdjustProgress}
|
||||||
|
scrollPosition: {lyricsContainer.scrollTop}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
|
||||||
|
{#if lyrics && originalLyrics && originalLyrics.scripts}
|
||||||
<div
|
<div
|
||||||
class="absolute top-[6.5rem] md:top-36 xl:top-0 w-screen xl:w-[52vw] px-6 md:px-12 lg:px-[7.5rem] xl:left-[45vw] xl:px-[3vw] h-[calc(100vh-17rem)] xl:h-screen font-sans
|
class="absolute top-[6.5rem] md:top-36 xl:top-0 w-screen xl:w-[52vw] px-6 md:px-12 lg:px-[7.5rem] xl:left-[45vw] xl:px-[3vw] h-[calc(100vh-17rem)] xl:h-screen font-sans
|
||||||
text-left no-scrollbar overflow-y-auto z-[1] pt-16 lyrics"
|
text-left no-scrollbar overflow-y-auto z-[1] pt-16 lyrics"
|
||||||
bind:this={lyricsContainer}
|
bind:this={lyricsContainer}
|
||||||
on:scroll={scrollHandler}
|
|
||||||
>
|
>
|
||||||
{#if debugMode}
|
|
||||||
<p class="fixed top-6 right-20 font-mono text-sm">
|
|
||||||
LyricIndex: {currentLyricIndex} PositionIndex: {currentPositionIndex} AnimationIndex:{currentAnimationIndex}
|
|
||||||
NextUpdate: {nextUpdate}
|
|
||||||
Progress: {progress.toFixed(2)}
|
|
||||||
lastAdjustProgress: {lastAdjustProgress}
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
{#each lyrics as lyric, i}
|
{#each lyrics as lyric, i}
|
||||||
<p bind:this={_refs[i]} class={`${getClass(i, progress)} text-shadow-lg`}>
|
<p bind:this={_refs[i]} class={`${getClass(i, progress)} text-shadow-lg`}>
|
||||||
{#if debugMode}
|
{#if debugMode && refs[i] && refs[i].style !== undefined}
|
||||||
<span class="text-lg absolute">{i}</span>
|
<span class="text-lg absolute -translate-y-4">{i}
|
||||||
|
{originalLyrics.scripts[i].start} ~ {originalLyrics.scripts[i].end}
|
||||||
|
tY: {extractTranslateValue(refs[i].style.transform)}
|
||||||
|
top: {Math.round(refs[i].getBoundingClientRect().top)}px
|
||||||
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
{lyric}
|
{lyric}
|
||||||
</p>
|
</p>
|
||||||
@ -268,82 +307,103 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<!--suppress CssUnusedSymbol -->
|
||||||
<style>
|
<style>
|
||||||
|
:root {
|
||||||
|
--lyric-mobile-font-size: 2rem;
|
||||||
|
--lyric-mobile-line-height: 2.4rem;
|
||||||
|
--lyric-mobile-margin: 1.5rem 0;
|
||||||
|
--lyric-mobile-font-weight: 700;
|
||||||
|
--lyric-desktop-font-size: 3.5rem;
|
||||||
|
--lyric-desktop-line-height: 4.5rem;
|
||||||
|
--lyric-desktop-margin: 1.75rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
.lyrics {
|
.lyrics {
|
||||||
mask-image: linear-gradient(
|
mask-image: linear-gradient(
|
||||||
rgba(0, 0, 0, 0) 0%,
|
rgba(0, 0, 0, 0) 0%,
|
||||||
rgba(0, 0, 0, 1) 2rem,
|
rgba(0, 0, 0, 1) 2rem,
|
||||||
rgba(0, 0, 0, 1) calc(100% - 5rem),
|
rgba(0, 0, 0, 1) calc(100% - 5rem),
|
||||||
rgba(0, 0, 0, 0) 100%
|
rgba(0, 0, 0, 0) 100%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-scrollbar {
|
.no-scrollbar {
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-scrollbar::-webkit-scrollbar {
|
.no-scrollbar::-webkit-scrollbar {
|
||||||
width: 0px;
|
width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.current-lyric {
|
.current-lyric {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: 600;
|
font-weight: var(--lyric-mobile-font-weight);
|
||||||
font-size: 2.1rem;
|
font-size: var(--lyric-mobile-font-size);
|
||||||
line-height: 2.7rem;
|
line-height: var(--lyric-mobile-line-height);
|
||||||
margin: 1rem 0rem;
|
margin: var(--lyric-mobile-margin);
|
||||||
scale: 1.02 1;
|
scale: 1.02 1;
|
||||||
top: 1rem;
|
top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.previous-lyric {
|
.previous-lyric {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: rgba(255, 255, 255, 0.7);
|
color: rgba(255, 255, 255, 0.48);
|
||||||
font-weight: 600;
|
font-weight: var(--lyric-mobile-font-weight);
|
||||||
font-size: 2.1rem;
|
font-size: var(--lyric-mobile-font-size);
|
||||||
line-height: 2.7rem;
|
line-height: var(--lyric-mobile-line-height);
|
||||||
margin: 1rem 0rem;
|
margin: var(--lyric-mobile-margin);
|
||||||
top: 1rem;
|
top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.after-lyric {
|
.after-lyric {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: rgba(255, 255, 255, 0.7);
|
color: rgba(255, 255, 255, 0.48);
|
||||||
font-weight: 600;
|
font-weight: var(--lyric-mobile-font-weight);
|
||||||
font-size: 2.1rem;
|
font-size: var(--lyric-mobile-font-size);
|
||||||
line-height: 2.7rem;
|
line-height: var(--lyric-mobile-line-height);
|
||||||
margin: 1rem 0rem;
|
margin: var(--lyric-mobile-margin);
|
||||||
top: 1rem;
|
top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.current-lyric {
|
.current-lyric {
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
line-height: 4rem;
|
line-height: 4rem;
|
||||||
margin: 2.4rem 0rem;
|
margin: 2.4rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.after-lyric {
|
.after-lyric {
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
line-height: 3.3rem;
|
line-height: 3.3rem;
|
||||||
margin: 2.4rem 0rem;
|
margin: 2.4rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.previous-lyric {
|
.previous-lyric {
|
||||||
font-size: 3em;
|
font-size: 3em;
|
||||||
line-height: 3.3rem;
|
line-height: 3.3rem;
|
||||||
margin: 2.4rem 0rem;
|
margin: 2.4rem 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
.current-lyric {
|
.current-lyric {
|
||||||
font-size: 3.5rem;
|
font-size: var(--lyric-desktop-font-size);
|
||||||
line-height: 6.5rem;
|
line-height: var(--lyric-desktop-line-height);
|
||||||
margin: 0rem 0rem;
|
margin: var(--lyric-desktop-margin);
|
||||||
}
|
}
|
||||||
|
|
||||||
.after-lyric {
|
.after-lyric {
|
||||||
font-size: 3.5rem;
|
font-size: var(--lyric-desktop-font-size);
|
||||||
line-height: 6.5rem;
|
line-height: var(--lyric-desktop-line-height);
|
||||||
margin: 0rem 0rem;
|
margin: var(--lyric-desktop-margin);
|
||||||
}
|
}
|
||||||
|
|
||||||
.previous-lyric {
|
.previous-lyric {
|
||||||
font-size: 3.5rem;
|
font-size: var(--lyric-desktop-font-size);
|
||||||
line-height: 6.5rem;
|
line-height: var(--lyric-desktop-line-height);
|
||||||
margin: 0rem 0rem;
|
margin: var(--lyric-desktop-margin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
3
src/lib/state/nextUpdate.ts
Normal file
3
src/lib/state/nextUpdate.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { writable } from 'svelte/store';
|
||||||
|
const nextUpdate = writable(-1);
|
||||||
|
export default nextUpdate;
|
@ -1,27 +1,28 @@
|
|||||||
import { songNameCache } from '$lib/server/cache.js';
|
import { songNameCache } from '$lib/server/cache.js';
|
||||||
import { loadData } from '$lib/server/database/loadData';
|
import { loadData } from '$lib/server/database/loadData';
|
||||||
import { json, error } from '@sveltejs/kit';
|
import { error, json } from '@sveltejs/kit';
|
||||||
|
import type { RequestHandler } from './$types';
|
||||||
|
|
||||||
export async function GET({ url }) {
|
export const GET: RequestHandler = async ({ url }) => {
|
||||||
const keyword = url.searchParams.get("keyword");
|
const keyword = url.searchParams.get('keyword');
|
||||||
|
|
||||||
loadData();
|
await loadData();
|
||||||
|
|
||||||
if (keyword === null) {
|
if (keyword === null) {
|
||||||
return error(400, {
|
return error(400, {
|
||||||
"message": "Miss parameter: keyword"
|
'message': 'Miss parameter: keyword'
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const resultList: MusicMetadata[] = [];
|
const resultList: MusicMetadata[] = [];
|
||||||
|
|
||||||
for (const songName of songNameCache.keys()){
|
for (const songName of songNameCache.keys()) {
|
||||||
if (songName.toLocaleLowerCase().includes(keyword.toLocaleLowerCase())) {
|
if (songName.toLocaleLowerCase().includes(keyword.toLocaleLowerCase())) {
|
||||||
resultList.push(songNameCache.get(songName)!);
|
resultList.push(songNameCache.get(songName)!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return json({
|
return json({
|
||||||
"result": resultList
|
'result': resultList
|
||||||
});
|
});
|
||||||
}
|
};
|
@ -1,19 +1,20 @@
|
|||||||
import { getCurrentFormattedDateTime } from '$lib/songUpdateTime';
|
import { getCurrentFormattedDateTime } from '$lib/songUpdateTime';
|
||||||
import { json, error } from '@sveltejs/kit';
|
import { json, error } from '@sveltejs/kit';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import type { RequestHandler } from './$types';
|
||||||
|
|
||||||
export async function GET({ params }) {
|
export const GET: RequestHandler = async ({ params }) => {
|
||||||
const filePath = `./data/song/${params.id}.json`;
|
const filePath = `./data/song/${params.id}.json`;
|
||||||
if (!fs.existsSync(filePath)) {
|
if (!fs.existsSync(filePath)) {
|
||||||
return error(404, {
|
return error(404, {
|
||||||
message: "No correspoding song."
|
message: "No corresponding song."
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const data = fs.readFileSync(filePath);
|
const data = fs.readFileSync(filePath);
|
||||||
return json(JSON.parse(data.toString()));
|
return json(JSON.parse(data.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function POST({ params, request }) {
|
export const POST: RequestHandler = async ({ request, params }) => {
|
||||||
const timeStamp = new Date().getTime();
|
const timeStamp = new Date().getTime();
|
||||||
if (!fs.existsSync("./data/pending/")) {
|
if (!fs.existsSync("./data/pending/")) {
|
||||||
fs.mkdirSync("./data/pending");
|
fs.mkdirSync("./data/pending");
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { songData } from '$lib/server/cache.js';
|
import { songData } from '$lib/server/cache.js';
|
||||||
import { loadData } from '$lib/server/database/loadData.js';
|
import { loadData } from '$lib/server/database/loadData.js';
|
||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
|
import type { RequestHandler } from './$types';
|
||||||
|
|
||||||
export async function GET({ url }) {
|
export const GET: RequestHandler = async ({ url }) => {
|
||||||
const limit = parseInt(url.searchParams.get("limit") ?? "20");
|
const limit = parseInt(url.searchParams.get("limit") ?? "20");
|
||||||
const offset = parseInt(url.searchParams.get("offset") ?? "0");
|
const offset = parseInt(url.searchParams.get("offset") ?? "0");
|
||||||
loadData();
|
loadData();
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
/** @type {import('./$types').PageLoad} */
|
import type { PageServerLoad } from './$types';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
|
||||||
|
|
||||||
export function load({ params }) {
|
export const load: PageServerLoad = ({ params }) => {
|
||||||
const filePath = `./data/song/${params.id}.json`;
|
const filePath = `./data/song/${params.id}.json`;
|
||||||
if (!fs.existsSync(filePath)) {
|
if (!fs.existsSync(filePath)) {
|
||||||
return {
|
return {
|
||||||
|
Loading…
Reference in New Issue
Block a user