Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 49 additions & 49 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -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'
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.

Expand Down
53 changes: 39 additions & 14 deletions media/scripts/lyricsScript.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
let lastLyricsHash = '';
let lastPickId = null;
let mobileMode = false;

let lyricsNotFound = [
'Looks like we dont have the lyrics for this song yet.',
'Looks like we dont 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.",
];
Expand All @@ -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') {
Expand Down Expand Up @@ -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;
Expand All @@ -78,7 +88,7 @@ function clearLyrics() {
}

function pickLyrics(id, textColor) {
if (id === lastPickId) {
if (id === lastPickId || id === null || id === undefined) {
return;
}
console.log(textColor);
Expand All @@ -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 = '';
}
}
});

Expand Down
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand All @@ -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."
}
}
},
Expand All @@ -46,6 +52,10 @@
{
"command": "spotilyrics.setTracksCacheMaxSize",
"title": "Set Tracks Cache Max Size"
},
{
"command": "spotilyrics.toggleMobileMode",
"title": "Toggle Mobile Mode"
}
]
},
Expand Down
23 changes: 23 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean>('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() {
Expand Down Expand Up @@ -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
);
Expand Down Expand Up @@ -337,13 +353,15 @@ async function updateLyrics() {
lyrics: trackCache.plainLyricsStrs,
color: trackCache.coverColor,
textColor: '#' + trackCache.textColor,
mobileMode: mobileMode,
});
} else if (trackCache.synchronizedLyricsMap) {
panel.webview.postMessage({
command: 'addLyrics',
lyrics: trackCache.synchronizedLyricsStrs,
color: trackCache.coverColor,
textColor: '#' + trackCache.textColor,
mobileMode: mobileMode,
});
}
}
Expand Down Expand Up @@ -409,6 +427,7 @@ async function updateLyrics() {
lyrics: currentPlayingState.plainLyricsStrs,
color: currentPlayingState.coverColor,
textColor: '#' + currentPlayingState.textColor,
mobileMode: mobileMode,
});
}
} else {
Expand All @@ -427,6 +446,7 @@ async function updateLyrics() {
lyrics: currentPlayingState.synchronizedLyricsStrs,
color: currentPlayingState.coverColor,
textColor: '#' + currentPlayingState.textColor,
mobileMode: mobileMode,
});
}
}
Expand All @@ -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,
});
}
}
Expand Down
Loading