diff --git a/CHANGELOG.md b/CHANGELOG.md index a95cb0b..25589ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 1.0.4 - 2026-08-11 + +- Added smooth entrance and exit motion for the VOD selection action dock, including Windows systems with reduced animations enabled. +- Fixed stale Windows desktop and Start menu icons with version-specific shortcut icon resources and an explicit Shell refresh after installation. +- Kept icon resources available for pinned and copied shortcuts across upgrades while cleaning them up during a full uninstall. +- Updated the public product overview with a real Twitch channel example. + ## 1.0.3 - 2026-08-10 - Added animated selection markers across the main navigation, sidebar modes, language control, streamer list and settings pages. diff --git a/README.md b/README.md index 2e51e84..ad91ef1 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ The application works in public mode without a Twitch login. Connecting a Twitch ## Installation 1. Open the [latest GitHub release](https://github.com/Sucukdeluxe/Twitch-VOD-Manager/releases/latest). -2. Download `Twitch-VOD-Manager-Setup-1.0.3.exe`. +2. Download `Twitch-VOD-Manager-Setup-1.0.4.exe`. 3. Run the installer and choose the installation directory. 4. Start Twitch VOD Manager and add a streamer. diff --git a/build/installer.nsh b/build/installer.nsh index 4ee8fa1..e538b72 100644 --- a/build/installer.nsh +++ b/build/installer.nsh @@ -2,3 +2,26 @@ ; Kill running Twitch VOD Manager process before installation nsExec::ExecToLog 'taskkill /F /IM "Twitch VOD Manager.exe"' !macroend + +!macro customInstall + CreateDirectory "$LOCALAPPDATA\Twitch VOD Manager\Shortcut Icons" + CopyFiles /SILENT "$INSTDIR\resources\app-icons\icon-${VERSION}.ico" "$LOCALAPPDATA\Twitch VOD Manager\Shortcut Icons" + StrCpy $0 "$LOCALAPPDATA\Twitch VOD Manager\Shortcut Icons\icon-${VERSION}.ico" + ${if} ${FileExists} "$newDesktopLink" + CreateShortCut "$newDesktopLink" "$appExe" "" "$0" 0 "" "" "${APP_DESCRIPTION}" + ClearErrors + WinShell::SetLnkAUMI "$newDesktopLink" "${APP_ID}" + ${endIf} + ${if} ${FileExists} "$newStartMenuLink" + CreateShortCut "$newStartMenuLink" "$appExe" "" "$0" 0 "" "" "${APP_DESCRIPTION}" + ClearErrors + WinShell::SetLnkAUMI "$newStartMenuLink" "${APP_ID}" + ${endIf} + System::Call 'shell32::SHChangeNotify(i 0x08000000, i 0x1000, i 0, i 0)' +!macroend + +!macro customUnInstall + ${ifNot} ${isUpdated} + RMDir /r "$LOCALAPPDATA\Twitch VOD Manager\Shortcut Icons" + ${endIf} +!macroend diff --git a/package-lock.json b/package-lock.json index ce103cb..6facec2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "twitch-vod-manager", - "version": "1.0.3", + "version": "1.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "twitch-vod-manager", - "version": "1.0.3", + "version": "1.0.4", "license": "MIT", "dependencies": { "axios": "^1.16.1", diff --git a/package.json b/package.json index 6f91874..589755a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "twitch-vod-manager", - "version": "1.0.3", + "version": "1.0.4", "description": "Twitch VOD Manager - Download Twitch VODs easily", "main": "dist/main.js", "author": "Sucukdeluxe", @@ -58,6 +58,12 @@ "build/icon.png", "package.json" ], + "extraResources": [ + { + "from": "build/icon.ico", + "to": "app-icons/icon-${version}.ico" + } + ], "directories": { "output": "release" }, diff --git a/scripts/dev.mjs b/scripts/dev.mjs index 65d4475..29117fc 100644 --- a/scripts/dev.mjs +++ b/scripts/dev.mjs @@ -83,7 +83,7 @@ if (process.platform === 'win32') { sourcePath: electronSourceExecutable, destinationPath: resolve(rootDirectory, 'node_modules', 'electron', 'dist', 'Twitch VOD Manager.exe'), iconPath: resolve(rootDirectory, 'build', 'icon.ico'), - version: '1.0.3', + version: '1.0.4', }); } diff --git a/scripts/smoke-test-public-release-config.js b/scripts/smoke-test-public-release-config.js index b87c4b2..1eda54a 100644 --- a/scripts/smoke-test-public-release-config.js +++ b/scripts/smoke-test-public-release-config.js @@ -6,6 +6,7 @@ const packageJson = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), const packageLock = JSON.parse(fs.readFileSync(path.join(root, 'package-lock.json'), 'utf8')); const mainSource = fs.readFileSync(path.join(root, 'src', 'main.ts'), 'utf8'); const indexSource = fs.readFileSync(path.join(root, 'src', 'index.html'), 'utf8'); +const installerSource = fs.readFileSync(path.join(root, 'build', 'installer.nsh'), 'utf8'); const manifestPath = path.join(root, 'scripts', 'public-release-files.json'); const failures = []; @@ -13,9 +14,9 @@ function check(condition, message) { if (!condition) failures.push(message); } -check(packageJson.version === '1.0.3', `package version is ${packageJson.version}`); -check(packageLock.version === '1.0.3', `lockfile version is ${packageLock.version}`); -check(packageLock.packages?.['']?.version === '1.0.3', `lockfile root package version is ${packageLock.packages?.['']?.version}`); +check(packageJson.version === '1.0.4', `package version is ${packageJson.version}`); +check(packageLock.version === '1.0.4', `lockfile version is ${packageLock.version}`); +check(packageLock.packages?.['']?.version === '1.0.4', `lockfile root package version is ${packageLock.packages?.['']?.version}`); check(packageJson.build?.appId === 'io.github.sucukdeluxe.twitch-vod-manager', `appId is ${packageJson.build?.appId}`); check(packageJson.build?.publish?.provider === 'generic', `publish provider is ${packageJson.build?.publish?.provider}`); check(packageJson.build?.publish?.url === 'https://github.com/Sucukdeluxe/Twitch-VOD-Manager/releases/latest/download/', `publish URL is ${packageJson.build?.publish?.url}`); @@ -23,6 +24,14 @@ check(JSON.stringify(packageJson.build?.files) === JSON.stringify(['dist/**/*', check(packageJson.build?.win?.icon === 'build/icon.ico', `Windows icon is ${packageJson.build?.win?.icon}`); check(packageJson.build?.nsis?.installerIcon === 'build/icon.ico', `installer icon is ${packageJson.build?.nsis?.installerIcon}`); check(packageJson.build?.nsis?.uninstallerIcon === 'build/icon.ico', `uninstaller icon is ${packageJson.build?.nsis?.uninstallerIcon}`); +const shortcutIconResource = packageJson.build?.extraResources?.find((entry) => entry?.from === 'build/icon.ico'); +check(shortcutIconResource?.to === 'app-icons/icon-${version}.ico', `versioned shortcut icon resource is ${shortcutIconResource?.to}`); +check(installerSource.includes('"$LOCALAPPDATA\\Twitch VOD Manager\\Shortcut Icons\\icon-${VERSION}.ico"'), 'installed shortcuts do not use the persistent versioned icon resource'); +check(installerSource.includes('CopyFiles /SILENT "$INSTDIR\\resources\\app-icons\\icon-${VERSION}.ico"'), 'versioned shortcut icon is not copied to persistent storage'); +check(installerSource.includes('CreateShortCut "$newDesktopLink"'), 'desktop shortcut is not refreshed with the versioned icon resource'); +check(installerSource.includes('CreateShortCut "$newStartMenuLink"'), 'start menu shortcut is not refreshed with the versioned icon resource'); +check(installerSource.includes('SHChangeNotify(i 0x08000000, i 0x1000'), 'Windows shell icon cache is not flushed after shortcut refresh'); +check(installerSource.includes('${ifNot} ${isUpdated}') && installerSource.includes('RMDir /r "$LOCALAPPDATA\\Twitch VOD Manager\\Shortcut Icons"'), 'persistent shortcut icons are not cleaned up on a real uninstall'); check(packageJson.build?.win?.signAndEditExecutable !== false, 'Windows executable resource editing is enabled'); check(packageJson.build?.win?.signExecutable === false, 'Windows code signing remains disabled without suppressing icon resources'); check(fs.existsSync(path.join(root, 'build', 'icon.png')), 'application PNG icon is missing'); @@ -36,7 +45,7 @@ check(mainSource.includes('GITHUB_RELEASES_DOWNLOAD_BASE_URL'), 'GitHub releases check(mainSource.includes('https://api.github.com/repos/Sucukdeluxe/Twitch-VOD-Manager/releases/latest'), 'GitHub latest release API URL is missing'); check(mainSource.includes('https://github.com/Sucukdeluxe/Twitch-VOD-Manager/releases/download'), 'GitHub release download URL is missing'); check(!/storyboards\/\d{8,12}(?:-|\/)/.test(mainSource), 'numeric Twitch VOD example remains in the public source'); -check(indexSource.includes('Version: v1.0.3'), 'initial version label is not 1.0.3'); +check(indexSource.includes('Version: v1.0.4'), 'initial version label is not 1.0.4'); check(!indexSource.includes('Version: v4.1.13'), 'legacy version label is still present'); check(fs.existsSync(manifestPath), 'public release manifest is missing'); diff --git a/scripts/smoke-test-workspace-ui.js b/scripts/smoke-test-workspace-ui.js index c30481c..11ab4de 100644 --- a/scripts/smoke-test-workspace-ui.js +++ b/scripts/smoke-test-workspace-ui.js @@ -1353,6 +1353,7 @@ async function run() { }); const hoverCard = win.locator('.vod-card[data-vod-id="hover-hd-fixture"]'); const gridTopBeforeSelection = await win.locator('#vodGrid').evaluate((grid) => grid.getBoundingClientRect().top); + await win.emulateMedia({ reducedMotion: 'reduce' }); await hoverCard.locator('.vod-thumbnail').click(); const clickSelection = await win.evaluate(() => { const card = document.querySelector('.vod-card[data-vod-id="hover-hd-fixture"]'); @@ -1401,8 +1402,41 @@ async function run() { check(Math.abs(dockWithOneSelection.width - dockWithTwoSelections.width) <= 0.25, `VOD bulk dock width changes between one and two selections: ${dockWithOneSelection.width}/${dockWithTwoSelections.width}`); check(Math.abs(dockWithOneSelection.left - dockWithTwoSelections.left) <= 0.25, `VOD bulk dock shifts between one and two selections: ${dockWithOneSelection.left}/${dockWithTwoSelections.left}`); await secondDockCard.locator('.vod-thumbnail').click(); + await hoverCard.locator('.vod-thumbnail').click(); + await win.waitForTimeout(80); + const dockDuringExit = await win.locator('#vodBulkBar').evaluate((dock) => { + const style = getComputedStyle(dock); + return { + display: style.display, + opacity: Number(style.opacity), + visibility: style.visibility, + pointerEvents: style.pointerEvents, + animationCount: dock.getAnimations().length, + translateY: new DOMMatrix(style.transform).m42 + }; + }); + await win.waitForFunction(() => getComputedStyle(document.getElementById('vodBulkBar')).visibility === 'hidden', null, { timeout: 1000 }); + const dockAfterExit = await win.locator('#vodBulkBar').evaluate((dock) => { + const style = getComputedStyle(dock); + return { + display: style.display, + opacity: Number(style.opacity), + visibility: style.visibility, + pointerEvents: style.pointerEvents + }; + }); + checks.vodBulkDockExit = { during: dockDuringExit, after: dockAfterExit }; + check(dockDuringExit.display === 'flex' && dockDuringExit.visibility === 'visible', `VOD bulk dock disappears before its exit motion: ${JSON.stringify(dockDuringExit)}`); + check(dockDuringExit.opacity > 0 && dockDuringExit.opacity < 1, `VOD bulk dock has no visible exit opacity frame: ${JSON.stringify(dockDuringExit)}`); + check(dockDuringExit.animationCount > 0, `VOD bulk dock exits without a running transition: ${JSON.stringify(dockDuringExit)}`); + check(dockDuringExit.translateY > 1, `VOD bulk dock exit has no downward movement: ${JSON.stringify(dockDuringExit)}`); + check(dockAfterExit.display === 'flex' && dockAfterExit.visibility === 'hidden' && dockAfterExit.pointerEvents === 'none' && dockAfterExit.opacity === 0, `VOD bulk dock does not finish in a non-interactive hidden state: ${JSON.stringify(dockAfterExit)}`); + await hoverCard.locator('.vod-thumbnail').click(); + await win.waitForTimeout(280); check(openCallsAfterClick.length === 0, `VOD image click still opens Twitch: ${JSON.stringify(openCallsAfterClick)}`); check(openCallsAfterContextMenu.length === 1 && openCallsAfterContextMenu[0] === 'https://example.invalid/hover-hd-fixture', `VOD context menu does not exclusively open Twitch: ${JSON.stringify(openCallsAfterContextMenu)}`); + await win.locator('#toolbarRefreshVodsBtn').hover(); + await win.waitForTimeout(40); await hoverCard.hover(); await win.waitForTimeout(320); const hdHoverFirst = await win.evaluate(() => { diff --git a/src/index.html b/src/index.html index 9eb1447..41f4e40 100644 --- a/src/index.html +++ b/src/index.html @@ -395,7 +395,7 @@ -