From 380ba72123c5240c94abf155135cd6af6f503547 Mon Sep 17 00:00:00 2001 From: Andrey Litvitski Date: Fri, 21 Nov 2025 21:12:45 +0300 Subject: [PATCH] add mobile mode for lyrics --- .github/workflows/build.yml | 98 +++++++++++++++++------------------ README.md | 2 + media/scripts/lyricsScript.js | 53 ++++++++++++++----- package.json | 12 ++++- src/extension.ts | 23 ++++++++ 5 files changed, 124 insertions(+), 64 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 02fc58c..df51342 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,54 +1,54 @@ name: Build VSCode Extension (multi-platform) on: - push: - branches: [ main ] - pull_request: - workflow_dispatch: {} + push: + branches: [main] + pull_request: + workflow_dispatch: {} jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel] - node: [20] - - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node }} - cache: 'npm' - - - name: Install dependencies (including dev) - run: npm ci - - - name: Compile TypeScript - run: npm run compile - - - name: Package VSIX - shell: bash - run: | - case "${{ matrix.os }}" in - windows-latest) - npx @vscode/vsce package --target win32-x64 - ;; - ubuntu-latest) - npx @vscode/vsce package --target linux-x64 - ;; - macos-latest) - npx @vscode/vsce package --target darwin-arm64 - ;; - macos-15-intel) - npx @vscode/vsce package --target darwin-x64 - ;; - esac - - - name: Upload VSIX artifact - uses: actions/upload-artifact@v4 - with: - name: vscode-extension-${{ runner.os }} - path: '*.vsix' + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel] + node: [20] + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + cache: 'npm' + + - name: Install dependencies (including dev) + run: npm ci + + - name: Compile TypeScript + run: npm run compile + + - name: Package VSIX + shell: bash + run: | + case "${{ matrix.os }}" in + windows-latest) + npx @vscode/vsce package --target win32-x64 + ;; + ubuntu-latest) + npx @vscode/vsce package --target linux-x64 + ;; + macos-latest) + npx @vscode/vsce package --target darwin-arm64 + ;; + macos-15-intel) + npx @vscode/vsce package --target darwin-x64 + ;; + esac + + - name: Upload VSIX artifact + uses: actions/upload-artifact@v4 + with: + name: vscode-extension-${{ matrix.os }} + path: '*.vsix' diff --git a/README.md b/README.md index 1a522eb..7ae2a50 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ - πŸ“Œ **Live lyrics sync** with your Spotify playback. - 🎨 Lyrics colors auto-themed from album cover (via `colorthief`). - πŸ–₯️ Smooth **side panel view** – code on the left, lyrics on the right. +- πŸ“± **Mobile mode** – black unplayed lines, white played lines (like Spotify mobile app). - πŸ”‘ Simple **one-time login** using your own Spotify Client ID. - πŸšͺ Quick logout command to reset session. - ⚑ Set a **maximum tracks cache size** for lyrics syncing. @@ -55,6 +56,7 @@ Show Spotify Lyrics via Spotilyrics ## ⌨️ Commands - `Show Spotify Lyrics via Spotilyrics` (`spotilyrics.lyrics`) – open synced lyrics panel. +- `Toggle Mobile Mode` (`spotilyrics.toggleMobileMode`) – switch between normal and mobile mode. - `Logout from Spotilyrics` (`spotilyrics.logout`) – clear session and re-auth when needed. - `Set Tracks Cache Max Size` (`spotilyrics.setTracksCacheMaxSize`) – configure the maximum number of tracks cached for lyrics. diff --git a/media/scripts/lyricsScript.js b/media/scripts/lyricsScript.js index 3a6061e..046a610 100644 --- a/media/scripts/lyricsScript.js +++ b/media/scripts/lyricsScript.js @@ -1,9 +1,10 @@ let lastLyricsHash = ''; let lastPickId = null; +let mobileMode = false; let lyricsNotFound = [ - 'Looks like we don’t have the lyrics for this song yet.', - 'Looks like we don’t have the lyrics for this song.', + "Looks like we don't have the lyrics for this song yet.", + "Looks like we don't have the lyrics for this song.", "Hmm. We don't know the lyrics for this one.", "You'll have to guess the lyrics for this one.", ]; @@ -16,18 +17,23 @@ function getRandomForLyricsNotFound() { document.querySelector('.placeholder').textContent = getRandomForLyricsNotFound(); window.addEventListener('message', (event) => { - const { command, lyrics, pick, color, textColor } = event.data; + const { command, lyrics, pick, color, textColor, mobileMode: mobileModeFlag } = event.data; const body = document.body; if (color && body.style.backgroundColor !== color) { body.style.backgroundColor = color; } + if (mobileModeFlag !== undefined) { + mobileMode = mobileModeFlag; + } if (command === 'addLyrics') { const lyricsHash = JSON.stringify(lyrics); if (lyricsHash !== lastLyricsHash) { renderLyrics(lyrics, textColor); lastLyricsHash = lyricsHash; } - pickLyrics(pick, textColor); + if (pick !== undefined && pick !== null) { + pickLyrics(pick, textColor); + } } else if (command === 'clearLyrics') { clearLyrics(); } else if (command === 'pickLyrics') { @@ -56,7 +62,11 @@ function renderLyrics(lyrics, textColor) { } else { lyrics.forEach((line) => { const div = document.createElement('div'); - div.style.color = textColor; + if (mobileMode) { + div.style.color = '#000000'; + } else { + div.style.color = textColor; + } div.className = 'line future'; div.textContent = line.text || 'β™ͺ'; div.dataset.lyricsId = line.id; @@ -78,7 +88,7 @@ function clearLyrics() { } function pickLyrics(id, textColor) { - if (id === lastPickId) { + if (id === lastPickId || id === null || id === undefined) { return; } console.log(textColor); @@ -97,15 +107,30 @@ function pickLyrics(id, textColor) { lines.forEach((line, index) => { line.classList.remove('past', 'current', 'future'); - if (index < pickedIndex) { - line.classList.add('past'); - line.style.color = textColor; - } else if (index === pickedIndex) { - line.classList.add('current'); - line.style.color = 'white'; + if (mobileMode) { + if (index <= pickedIndex) { + line.classList.add('current'); + line.style.color = '#ffffff'; + line.style.opacity = '1'; + } else { + line.classList.add('future'); + line.style.color = '#000000'; + line.style.opacity = '1'; + } } else { - line.classList.add('future'); - line.style.color = textColor; + if (index < pickedIndex) { + line.classList.add('past'); + line.style.color = textColor; + line.style.opacity = ''; + } else if (index === pickedIndex) { + line.classList.add('current'); + line.style.color = 'white'; + line.style.opacity = ''; + } else { + line.classList.add('future'); + line.style.color = textColor; + line.style.opacity = ''; + } } }); diff --git a/package.json b/package.json index e11cf94..50bad9e 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "activationEvents": [ "onCommand:spotilyrics.lyrics", "onCommand:spotilyrics.logout", - "onCommand:spotilyrics.setTracksCacheMaxSize" + "onCommand:spotilyrics.setTracksCacheMaxSize", + "onCommand:spotilyrics.toggleMobileMode" ], "main": "./out/extension.js", "repository": { @@ -31,6 +32,11 @@ "minimum": 1, "maximum": 9007199254740991, "description": "Maximum cache size for tracks." + }, + "spotilyrics.mobileMode": { + "type": "boolean", + "default": false, + "description": "Mobile mode: black unplayed lines, white played lines." } } }, @@ -46,6 +52,10 @@ { "command": "spotilyrics.setTracksCacheMaxSize", "title": "Set Tracks Cache Max Size" + }, + { + "command": "spotilyrics.toggleMobileMode", + "title": "Toggle Mobile Mode" } ] }, diff --git a/src/extension.ts b/src/extension.ts index 7ffd3ec..830d224 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -146,6 +146,20 @@ export async function activate(context: vscode.ExtensionContext) { tracksCache = new LRUCache({ maxSize: value, sizeCalculation: () => 1 }); }) ); + context.subscriptions.push( + vscode.commands.registerCommand('spotilyrics.toggleMobileMode', async () => { + const config = vscode.workspace.getConfiguration('spotilyrics'); + const currentValue = config.get('mobileMode') ?? false; + const newValue = !currentValue; + await config.update('mobileMode', newValue, vscode.ConfigurationTarget.Global); + vscode.window.showInformationMessage( + `Mobile mode ${newValue ? 'enabled' : 'disabled'}` + ); + if (panel && currentPlayingState) { + await updateLyrics(); + } + }) + ); } export async function deactivate() { @@ -300,6 +314,8 @@ async function pollSpotifyStat(context: vscode.ExtensionContext) { async function updateLyrics() { if (authState) { + const mobileMode: boolean = + vscode.workspace.getConfiguration('spotilyrics').get('mobileMode') ?? false; const currentlyPlayingResponse = await SpotifyWebApi.getCurrentlyPlaying( authState.accessToken ); @@ -337,6 +353,7 @@ async function updateLyrics() { lyrics: trackCache.plainLyricsStrs, color: trackCache.coverColor, textColor: '#' + trackCache.textColor, + mobileMode: mobileMode, }); } else if (trackCache.synchronizedLyricsMap) { panel.webview.postMessage({ @@ -344,6 +361,7 @@ async function updateLyrics() { lyrics: trackCache.synchronizedLyricsStrs, color: trackCache.coverColor, textColor: '#' + trackCache.textColor, + mobileMode: mobileMode, }); } } @@ -409,6 +427,7 @@ async function updateLyrics() { lyrics: currentPlayingState.plainLyricsStrs, color: currentPlayingState.coverColor, textColor: '#' + currentPlayingState.textColor, + mobileMode: mobileMode, }); } } else { @@ -427,6 +446,7 @@ async function updateLyrics() { lyrics: currentPlayingState.synchronizedLyricsStrs, color: currentPlayingState.coverColor, textColor: '#' + currentPlayingState.textColor, + mobileMode: mobileMode, }); } } @@ -442,11 +462,14 @@ async function updateLyrics() { currentlyPlayingResponse.progress_ms ); if (value && panel) { + const mobileMode: boolean = + vscode.workspace.getConfiguration('spotilyrics').get('mobileMode') ?? false; panel.webview.postMessage({ command: 'pickLyrics', pick: value[1].id, color: currentPlayingState.coverColor, textColor: '#' + currentPlayingState.textColor, + mobileMode: mobileMode, }); } }