Compare commits
2 Commits
83647c264b
...
f04c0b64cc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f04c0b64cc | ||
|
|
d6e513d70d |
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "twitch-vod-manager",
|
"name": "twitch-vod-manager",
|
||||||
"version": "4.5.15",
|
"version": "4.5.16",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "twitch-vod-manager",
|
"name": "twitch-vod-manager",
|
||||||
"version": "4.5.15",
|
"version": "4.5.16",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "twitch-vod-manager",
|
"name": "twitch-vod-manager",
|
||||||
"version": "4.5.15",
|
"version": "4.5.16",
|
||||||
"description": "Twitch VOD Manager - Download Twitch VODs easily",
|
"description": "Twitch VOD Manager - Download Twitch VODs easily",
|
||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
"author": "xRangerDE",
|
"author": "xRangerDE",
|
||||||
|
|||||||
@ -39,6 +39,7 @@
|
|||||||
|
|
||||||
<div class="modal-actions update-modal-actions">
|
<div class="modal-actions update-modal-actions">
|
||||||
<button class="btn-secondary" id="updateModalDismissBtn" type="button" onclick="dismissUpdateModal()">Nein</button>
|
<button class="btn-secondary" id="updateModalDismissBtn" type="button" onclick="dismissUpdateModal()">Nein</button>
|
||||||
|
<button class="btn-secondary" id="updateModalSkipBtn" type="button" onclick="skipUpdateVersion()">Diese Version ueberspringen</button>
|
||||||
<button class="btn-primary" id="updateModalConfirmBtn" type="button" onclick="confirmUpdateModal()">Ja, herunterladen</button>
|
<button class="btn-primary" id="updateModalConfirmBtn" type="button" onclick="confirmUpdateModal()">Ja, herunterladen</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -49,6 +49,8 @@ const UI_TEXT_DE = {
|
|||||||
performanceModeBalanced: 'Ausgewogen',
|
performanceModeBalanced: 'Ausgewogen',
|
||||||
performanceModeSpeed: 'Max Geschwindigkeit',
|
performanceModeSpeed: 'Max Geschwindigkeit',
|
||||||
smartSchedulerLabel: 'Smart Queue Scheduler aktivieren',
|
smartSchedulerLabel: 'Smart Queue Scheduler aktivieren',
|
||||||
|
smartSchedulerHint: 'Bevorzugt kuerzere VODs und aeltere Queue-Eintraege zuerst, damit der Durchsatz gleichmaessig bleibt. Deaktivieren = strikte Einfuegereihenfolge.',
|
||||||
|
streamerInvalid: 'Twitch-Username ungueltig (4-25 Zeichen, Buchstaben/Zahlen/Unterstrich).',
|
||||||
duplicatePreventionLabel: 'Duplikate in Queue verhindern',
|
duplicatePreventionLabel: 'Duplikate in Queue verhindern',
|
||||||
persistQueueLabel: 'Queue zwischen App-Starts speichern',
|
persistQueueLabel: 'Queue zwischen App-Starts speichern',
|
||||||
metadataCacheMinutesLabel: 'Metadata-Cache (Minuten)',
|
metadataCacheMinutesLabel: 'Metadata-Cache (Minuten)',
|
||||||
@ -245,6 +247,7 @@ const UI_TEXT_DE = {
|
|||||||
modalDismiss: 'Nein',
|
modalDismiss: 'Nein',
|
||||||
modalDownloadConfirm: 'Ja, herunterladen',
|
modalDownloadConfirm: 'Ja, herunterladen',
|
||||||
modalInstallConfirm: 'Ja, installieren',
|
modalInstallConfirm: 'Ja, installieren',
|
||||||
|
modalSkipVersion: 'Diese Version ueberspringen',
|
||||||
changelogLabel: 'Changelog',
|
changelogLabel: 'Changelog',
|
||||||
showChangelog: 'Changelog anzeigen',
|
showChangelog: 'Changelog anzeigen',
|
||||||
hideChangelog: 'Changelog ausblenden',
|
hideChangelog: 'Changelog ausblenden',
|
||||||
|
|||||||
@ -49,6 +49,8 @@ const UI_TEXT_EN = {
|
|||||||
performanceModeBalanced: 'Balanced',
|
performanceModeBalanced: 'Balanced',
|
||||||
performanceModeSpeed: 'Max Speed',
|
performanceModeSpeed: 'Max Speed',
|
||||||
smartSchedulerLabel: 'Enable smart queue scheduler',
|
smartSchedulerLabel: 'Enable smart queue scheduler',
|
||||||
|
smartSchedulerHint: 'Prefers shorter VODs and older queue entries first so the queue throughput stays steady. Disable to drain in strict insertion order.',
|
||||||
|
streamerInvalid: 'Invalid Twitch username (4-25 chars, letters/digits/underscore).',
|
||||||
duplicatePreventionLabel: 'Prevent duplicate queue entries',
|
duplicatePreventionLabel: 'Prevent duplicate queue entries',
|
||||||
persistQueueLabel: 'Keep queue between app restarts',
|
persistQueueLabel: 'Keep queue between app restarts',
|
||||||
metadataCacheMinutesLabel: 'Metadata Cache (Minutes)',
|
metadataCacheMinutesLabel: 'Metadata Cache (Minutes)',
|
||||||
@ -245,6 +247,7 @@ const UI_TEXT_EN = {
|
|||||||
modalDismiss: 'No',
|
modalDismiss: 'No',
|
||||||
modalDownloadConfirm: 'Yes, download',
|
modalDownloadConfirm: 'Yes, download',
|
||||||
modalInstallConfirm: 'Yes, install',
|
modalInstallConfirm: 'Yes, install',
|
||||||
|
modalSkipVersion: 'Skip this version',
|
||||||
changelogLabel: 'Changelog',
|
changelogLabel: 'Changelog',
|
||||||
showChangelog: 'Show changelog',
|
showChangelog: 'Show changelog',
|
||||||
hideChangelog: 'Hide changelog',
|
hideChangelog: 'Hide changelog',
|
||||||
|
|||||||
@ -210,7 +210,19 @@ function renderStreamers(): void {
|
|||||||
async function addStreamer(): Promise<void> {
|
async function addStreamer(): Promise<void> {
|
||||||
const input = byId<HTMLInputElement>('newStreamer');
|
const input = byId<HTMLInputElement>('newStreamer');
|
||||||
const name = input.value.trim().toLowerCase();
|
const name = input.value.trim().toLowerCase();
|
||||||
if (!name || (config.streamers ?? []).includes(name)) {
|
if (!name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Twitch usernames: 4-25 characters, alphanumeric + underscore.
|
||||||
|
// Catch typos / invalid input before it hits the API and silently
|
||||||
|
// returns "streamer not found".
|
||||||
|
if (!/^[a-zA-Z0-9_]{4,25}$/.test(name)) {
|
||||||
|
showAppToast(UI_TEXT.static.streamerInvalid, 'warn');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((config.streamers ?? []).includes(name)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -91,6 +91,8 @@ function applyLanguageToStaticUI(): void {
|
|||||||
setText('performanceModeBalanced', UI_TEXT.static.performanceModeBalanced);
|
setText('performanceModeBalanced', UI_TEXT.static.performanceModeBalanced);
|
||||||
setText('performanceModeSpeed', UI_TEXT.static.performanceModeSpeed);
|
setText('performanceModeSpeed', UI_TEXT.static.performanceModeSpeed);
|
||||||
setText('smartSchedulerLabel', UI_TEXT.static.smartSchedulerLabel);
|
setText('smartSchedulerLabel', UI_TEXT.static.smartSchedulerLabel);
|
||||||
|
setTitle('smartSchedulerLabel', UI_TEXT.static.smartSchedulerHint);
|
||||||
|
setTitle('smartSchedulerToggle', UI_TEXT.static.smartSchedulerHint);
|
||||||
setText('duplicatePreventionLabel', UI_TEXT.static.duplicatePreventionLabel);
|
setText('duplicatePreventionLabel', UI_TEXT.static.duplicatePreventionLabel);
|
||||||
setText('persistQueueLabel', UI_TEXT.static.persistQueueLabel);
|
setText('persistQueueLabel', UI_TEXT.static.persistQueueLabel);
|
||||||
setText('metadataCacheMinutesLabel', UI_TEXT.static.metadataCacheMinutesLabel);
|
setText('metadataCacheMinutesLabel', UI_TEXT.static.metadataCacheMinutesLabel);
|
||||||
@ -139,6 +141,7 @@ function applyLanguageToStaticUI(): void {
|
|||||||
setText('updateModalTitle', UI_TEXT.updates.modalAvailableTitle);
|
setText('updateModalTitle', UI_TEXT.updates.modalAvailableTitle);
|
||||||
setText('updateModalDismissBtn', UI_TEXT.updates.modalDismiss);
|
setText('updateModalDismissBtn', UI_TEXT.updates.modalDismiss);
|
||||||
setText('updateModalConfirmBtn', UI_TEXT.updates.modalDownloadConfirm);
|
setText('updateModalConfirmBtn', UI_TEXT.updates.modalDownloadConfirm);
|
||||||
|
setText('updateModalSkipBtn', UI_TEXT.updates.modalSkipVersion);
|
||||||
setText('updateChangelogLabel', UI_TEXT.updates.changelogLabel);
|
setText('updateChangelogLabel', UI_TEXT.updates.changelogLabel);
|
||||||
setText('updateChangelogToggle', UI_TEXT.updates.showChangelog);
|
setText('updateChangelogToggle', UI_TEXT.updates.showChangelog);
|
||||||
setText('updateChangelogEmpty', UI_TEXT.updates.noChangelog);
|
setText('updateChangelogEmpty', UI_TEXT.updates.noChangelog);
|
||||||
|
|||||||
@ -9,6 +9,20 @@ let updateBannerState: 'idle' | 'available' | 'downloading' | 'ready' = 'idle';
|
|||||||
let updateChangelogExpanded = false;
|
let updateChangelogExpanded = false;
|
||||||
let shouldOpenUpdateModalOnAvailable = false;
|
let shouldOpenUpdateModalOnAvailable = false;
|
||||||
|
|
||||||
|
const SKIPPED_UPDATE_VERSION_KEY = 'twitch-vod-manager:skipped-update-version';
|
||||||
|
|
||||||
|
function getSkippedUpdateVersion(): string {
|
||||||
|
try { return localStorage.getItem(SKIPPED_UPDATE_VERSION_KEY) || ''; } catch { return ''; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function persistSkippedUpdateVersion(version: string): void {
|
||||||
|
try { localStorage.setItem(SKIPPED_UPDATE_VERSION_KEY, version); } catch { /* localStorage may be unavailable */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSkippedUpdateVersion(): void {
|
||||||
|
try { localStorage.removeItem(SKIPPED_UPDATE_VERSION_KEY); } catch { /* localStorage may be unavailable */ }
|
||||||
|
}
|
||||||
|
|
||||||
function notifyUpdate(message: string, type: 'info' | 'warn' = 'info'): void {
|
function notifyUpdate(message: string, type: 'info' | 'warn' = 'info'): void {
|
||||||
const toastFn = (window as unknown as { showAppToast?: (msg: string, kind?: 'info' | 'warn') => void }).showAppToast;
|
const toastFn = (window as unknown as { showAppToast?: (msg: string, kind?: 'info' | 'warn') => void }).showAppToast;
|
||||||
if (typeof toastFn === 'function') {
|
if (typeof toastFn === 'function') {
|
||||||
@ -278,6 +292,11 @@ function refreshUpdateModalTexts(): void {
|
|||||||
byId('updateModalConfirmBtn').textContent = isReady
|
byId('updateModalConfirmBtn').textContent = isReady
|
||||||
? UI_TEXT.updates.modalInstallConfirm
|
? UI_TEXT.updates.modalInstallConfirm
|
||||||
: UI_TEXT.updates.modalDownloadConfirm;
|
: UI_TEXT.updates.modalDownloadConfirm;
|
||||||
|
// Skip-version only makes sense before the download. Once the .exe is
|
||||||
|
// already on disk and ready to install, hide the button.
|
||||||
|
const skipBtn = byId<HTMLButtonElement>('updateModalSkipBtn');
|
||||||
|
skipBtn.textContent = UI_TEXT.updates.modalSkipVersion;
|
||||||
|
skipBtn.style.display = isReady ? 'none' : '';
|
||||||
byId('updateChangelogLabel').textContent = UI_TEXT.updates.changelogLabel;
|
byId('updateChangelogLabel').textContent = UI_TEXT.updates.changelogLabel;
|
||||||
byId('updateChangelogEmpty').textContent = UI_TEXT.updates.noChangelog;
|
byId('updateChangelogEmpty').textContent = UI_TEXT.updates.noChangelog;
|
||||||
|
|
||||||
@ -301,6 +320,19 @@ function dismissUpdateModal(): void {
|
|||||||
byId('updateModal').classList.remove('show');
|
byId('updateModal').classList.remove('show');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function skipUpdateVersion(): void {
|
||||||
|
const v = (latestUpdateInfo?.version || latestUpdateVersion || '').trim();
|
||||||
|
if (v) {
|
||||||
|
persistSkippedUpdateVersion(v);
|
||||||
|
}
|
||||||
|
dismissUpdateModal();
|
||||||
|
hideUpdateBanner();
|
||||||
|
updateBannerState = 'idle';
|
||||||
|
// Note: latestUpdateInfo is intentionally kept so a manual "Check for
|
||||||
|
// updates" can still re-surface the same version if the user changes
|
||||||
|
// their mind (manual checks bypass the skip-version filter).
|
||||||
|
}
|
||||||
|
|
||||||
function confirmUpdateModal(): void {
|
function confirmUpdateModal(): void {
|
||||||
dismissUpdateModal();
|
dismissUpdateModal();
|
||||||
|
|
||||||
@ -495,11 +527,22 @@ window.api.onUpdateAvailable((info: UpdateInfo) => {
|
|||||||
updateCheckInProgress = false;
|
updateCheckInProgress = false;
|
||||||
updateReady = false;
|
updateReady = false;
|
||||||
updateDownloadInProgress = false;
|
updateDownloadInProgress = false;
|
||||||
|
const wasManual = manualUpdateCheckPending;
|
||||||
manualUpdateCheckPending = false;
|
manualUpdateCheckPending = false;
|
||||||
manualUpdateOutcomeHandled = true;
|
manualUpdateOutcomeHandled = true;
|
||||||
latestDownloadProgress = null;
|
latestDownloadProgress = null;
|
||||||
setCheckButtonCheckingState(false);
|
setCheckButtonCheckingState(false);
|
||||||
|
|
||||||
|
// If the user explicitly skipped this exact version, suppress the auto
|
||||||
|
// notification entirely — banner stays hidden, no modal popup. A manual
|
||||||
|
// "Check for updates" click overrides the skip so the user can change
|
||||||
|
// their mind.
|
||||||
|
const isSkipped = getSkippedUpdateVersion() === activeInfo.version;
|
||||||
|
if (isSkipped && !wasManual) {
|
||||||
|
shouldOpenUpdateModalOnAvailable = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setUpdateBannerAvailableUi(activeInfo);
|
setUpdateBannerAvailableUi(activeInfo);
|
||||||
|
|
||||||
if (shouldOpenUpdateModalOnAvailable) {
|
if (shouldOpenUpdateModalOnAvailable) {
|
||||||
@ -509,6 +552,7 @@ window.api.onUpdateAvailable((info: UpdateInfo) => {
|
|||||||
shouldOpenUpdateModalOnAvailable = false;
|
shouldOpenUpdateModalOnAvailable = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
window.api.onUpdateNotAvailable(() => {
|
window.api.onUpdateNotAvailable(() => {
|
||||||
updateCheckInProgress = false;
|
updateCheckInProgress = false;
|
||||||
setCheckButtonCheckingState(false);
|
setCheckButtonCheckingState(false);
|
||||||
@ -540,6 +584,10 @@ window.api.onUpdateDownloadProgress((progress: UpdateDownloadProgress) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
window.api.onUpdateDownloaded((info: UpdateInfo) => {
|
window.api.onUpdateDownloaded((info: UpdateInfo) => {
|
||||||
|
// Once a version is actually downloaded the user clearly stopped
|
||||||
|
// skipping it — clear the skip flag so future updates aren't masked
|
||||||
|
// by a stale entry.
|
||||||
|
clearSkippedUpdateVersion();
|
||||||
const activeInfo = rememberUpdateInfo(info);
|
const activeInfo = rememberUpdateInfo(info);
|
||||||
setDownloadReadyUi(activeInfo);
|
setDownloadReadyUi(activeInfo);
|
||||||
openUpdateModal(activeInfo);
|
openUpdateModal(activeInfo);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user