Compare commits

..

No commits in common. "a82a8f97f7751173c3d1fe3128031f3e873f67d1" and "6086cd51c1fabe554ab74f39af37917b0b11e6f4" have entirely different histories.

4 changed files with 20 additions and 65 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.6.51", "version": "4.6.50",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.6.51", "version": "4.6.50",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"axios": "^1.6.0", "axios": "^1.6.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.6.51", "version": "4.6.50",
"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",

View File

@ -482,72 +482,45 @@ function renderStreamers(): void {
nameSpan.className = 'streamer-name' + (isLive ? ' is-live' : ''); nameSpan.className = 'streamer-name' + (isLive ? ' is-live' : '');
nameSpan.textContent = streamer; nameSpan.textContent = streamer;
// Three streamer-row action chips (AUTO toggle / VOD toggle / REC
// one-shot). All share the same accessibility wiring:
// role="button", tabindex="0", aria-pressed for the toggles +
// aria-label for screen readers, plus Enter/Space keydown
// activation. wireChipButton centralises that so each chip only
// declares its own visual class + label + handler.
const wireChipButton = (el: HTMLElement, opts: {
handler: () => void;
ariaLabel: string;
pressed?: boolean;
}): void => {
el.setAttribute('role', 'button');
el.setAttribute('tabindex', '0');
el.setAttribute('aria-label', opts.ariaLabel);
if (opts.pressed !== undefined) el.setAttribute('aria-pressed', String(opts.pressed));
el.addEventListener('click', (e) => {
e.stopPropagation();
opts.handler();
});
el.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
e.stopPropagation();
opts.handler();
}
});
};
// AUTO toggle — when enabled, the main-process auto-record poller // AUTO toggle — when enabled, the main-process auto-record poller
// watches this channel for offline->live transitions and queues a // watches this channel for offline->live transitions and queues a
// live recording automatically. // live recording automatically. Off by default, click to toggle.
const autoList = (config.auto_record_streamers as string[] | undefined) || []; const autoList = (config.auto_record_streamers as string[] | undefined) || [];
const isAutoOn = autoList.includes(streamer); const isAutoOn = autoList.includes(streamer);
const autoBtn = document.createElement('span'); const autoBtn = document.createElement('span');
autoBtn.className = 'streamer-auto' + (isAutoOn ? ' active' : ''); autoBtn.className = 'streamer-auto' + (isAutoOn ? ' active' : '');
autoBtn.textContent = 'AUTO'; autoBtn.textContent = 'AUTO';
autoBtn.title = UI_TEXT.streamers?.autoRecordTitle || 'Auto-record when this streamer goes live'; autoBtn.title = UI_TEXT.streamers?.autoRecordTitle || 'Auto-record when this streamer goes live';
wireChipButton(autoBtn, { autoBtn.addEventListener('click', (e) => {
handler: () => { void toggleAutoRecord(streamer); }, e.stopPropagation();
ariaLabel: UI_TEXT.streamers?.autoRecordTitle || 'Auto-record', void toggleAutoRecord(streamer);
pressed: isAutoOn
}); });
// VOD-auto-download toggle — periodic scan of this streamer's // VOD-auto-download toggle — when enabled, the main-process auto-VOD
// VOD list, auto-queues anything new within the age window. // poller scans this streamer's VOD list periodically and queues new
// VODs published in the rolling window automatically. Complements
// AUTO (live capture): VOD covers downtime + transcoded archive,
// AUTO covers a stream as it happens. Useful for both.
const vodList = (config.auto_vod_download_streamers as string[] | undefined) || []; const vodList = (config.auto_vod_download_streamers as string[] | undefined) || [];
const isVodOn = vodList.includes(streamer); const isVodOn = vodList.includes(streamer);
const vodBtn = document.createElement('span'); const vodBtn = document.createElement('span');
vodBtn.className = 'streamer-vod' + (isVodOn ? ' active' : ''); vodBtn.className = 'streamer-vod' + (isVodOn ? ' active' : '');
vodBtn.textContent = 'VOD'; vodBtn.textContent = 'VOD';
vodBtn.title = UI_TEXT.streamers?.autoVodTitle || 'Auto-download new VODs'; vodBtn.title = UI_TEXT.streamers?.autoVodTitle || 'Auto-download new VODs';
wireChipButton(vodBtn, { vodBtn.addEventListener('click', (e) => {
handler: () => { void toggleAutoVodDownload(streamer); }, e.stopPropagation();
ariaLabel: UI_TEXT.streamers?.autoVodTitle || 'Auto-download new VODs', void toggleAutoVodDownload(streamer);
pressed: isVodOn
}); });
// Live-record one-shot — triggers a recording immediately (server // Live-record button — small red dot, only triggers a live capture
// verifies the streamer is online before honoring the request). // when the streamer is currently online (server checks via Helix).
const recBtn = document.createElement('span'); const recBtn = document.createElement('span');
recBtn.className = 'streamer-rec'; recBtn.className = 'streamer-rec';
recBtn.textContent = 'REC'; recBtn.textContent = 'REC';
recBtn.title = UI_TEXT.streamers?.recordLiveTitle || 'Record live now'; recBtn.title = UI_TEXT.streamers?.recordLiveTitle || 'Record live now';
wireChipButton(recBtn, { recBtn.addEventListener('click', (e) => {
handler: () => { void triggerLiveRecording(streamer); }, e.stopPropagation();
ariaLabel: UI_TEXT.streamers?.recordLiveTitle || 'Record live now' void triggerLiveRecording(streamer);
}); });
const removeSpan = document.createElement('span'); const removeSpan = document.createElement('span');
removeSpan.className = 'remove'; removeSpan.className = 'remove';

View File

@ -455,24 +455,6 @@ body {
border-radius: 3px; border-radius: 3px;
} }
/* Keyboard focus rings for the AUTO / VOD / REC chip buttons.
Each picks up its own accent: AUTO + VOD use semantic green/blue
tints matching their active state, REC the red of the live dot. */
.streamer-auto:focus-visible {
outline: none;
box-shadow: 0 0 0 2px rgba(0, 200, 83, 0.55);
}
.streamer-vod:focus-visible {
outline: none;
box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.55);
}
.streamer-rec:focus-visible {
outline: none;
box-shadow: 0 0 0 2px rgba(255, 68, 68, 0.55);
}
/* Live-dot red pulsing indicator shown next to a streamer's name in /* Live-dot red pulsing indicator shown next to a streamer's name in
the sidebar when they are currently broadcasting on Twitch. */ the sidebar when they are currently broadcasting on Twitch. */
.streamer-live-dot { .streamer-live-dot {