feat: trim-VOD dialog i18n + Twitch API help link + log file shortcut
Three small UX wins.
1. Trim-VOD dialog: every inner label was hardcoded German in
index.html (Start:, Ende:, Startzeit (HH:MM:SS):, Dauer:, Start
Part-Nummer..., Leer lassen = Teil 1, Dateinamen-Format:, Zur
Queue hinzufuegen). EN-mode users had a German dialog. Each
element now has an id + setText wiring + DE/EN locale strings.
2. Settings -> Twitch API card now opens with a help line + link
to dev.twitch.tv/console/apps. Uses window.api.openExternal so
the link opens in the user's default browser instead of the
Electron renderer (which has nodeIntegration off / no native
navigation). Fixes the "no idea how to set this up" first-run
friction.
3. Settings -> Live Debug Log gets an "Open log file" button next
to Refresh. Uses a new ipcMain handle (open-debug-log-file ->
shell.showItemInFolder on DEBUG_LOG_FILE) so users no longer
have to navigate manually to ProgramData. As a small defensive
bundle:
- get-debug-log: lines parameter capped at [1, 5000] so a
misbehaving renderer (or future feature) cannot ask main to
slice millions of lines.
- export-runtime-metrics: now uses writeFileAtomicSync (the
fsync+rename helper from cycle 1) instead of plain
writeFileSync so a power loss mid-export cannot leave a
half-written metrics file at the user-chosen path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9dcdb8086e
commit
16d2456770
@ -53,12 +53,12 @@
|
|||||||
|
|
||||||
<!-- Start Zeit mit Slider -->
|
<!-- Start Zeit mit Slider -->
|
||||||
<div style="margin-bottom: 15px;">
|
<div style="margin-bottom: 15px;">
|
||||||
<label style="display: block; margin-bottom: 5px;">Start:</label>
|
<label id="clipDialogStartLabel" style="display: block; margin-bottom: 5px;">Start:</label>
|
||||||
<input type="range" id="clipStartSlider" min="0" max="100" value="0"
|
<input type="range" id="clipStartSlider" min="0" max="100" value="0"
|
||||||
style="width: 100%; height: 6px; -webkit-appearance: none; background: #1a1a1a; border-radius: 3px; cursor: pointer;"
|
style="width: 100%; height: 6px; -webkit-appearance: none; background: #1a1a1a; border-radius: 3px; cursor: pointer;"
|
||||||
oninput="updateFromSlider('start')">
|
oninput="updateFromSlider('start')">
|
||||||
<div style="display: flex; align-items: center; gap: 10px; margin-top: 8px;">
|
<div style="display: flex; align-items: center; gap: 10px; margin-top: 8px;">
|
||||||
<label style="color: #888;">Startzeit (HH:MM:SS):</label>
|
<label id="clipDialogStartTimeLabel" style="color: #888;">Startzeit (HH:MM:SS):</label>
|
||||||
<input type="text" id="clipStartTime" value="00:00:00"
|
<input type="text" id="clipStartTime" value="00:00:00"
|
||||||
style="width: 100px; background: #333; border: 1px solid #444; border-radius: 4px; padding: 6px 10px; color: white; font-family: monospace; text-align: center;"
|
style="width: 100px; background: #333; border: 1px solid #444; border-radius: 4px; padding: 6px 10px; color: white; font-family: monospace; text-align: center;"
|
||||||
onchange="updateFromInput('start')">
|
onchange="updateFromInput('start')">
|
||||||
@ -67,12 +67,12 @@
|
|||||||
|
|
||||||
<!-- End Zeit mit Slider -->
|
<!-- End Zeit mit Slider -->
|
||||||
<div style="margin-bottom: 15px;">
|
<div style="margin-bottom: 15px;">
|
||||||
<label style="display: block; margin-bottom: 5px;">Ende:</label>
|
<label id="clipDialogEndLabel" style="display: block; margin-bottom: 5px;">Ende:</label>
|
||||||
<input type="range" id="clipEndSlider" min="0" max="100" value="60"
|
<input type="range" id="clipEndSlider" min="0" max="100" value="60"
|
||||||
style="width: 100%; height: 6px; -webkit-appearance: none; background: #1a1a1a; border-radius: 3px; cursor: pointer;"
|
style="width: 100%; height: 6px; -webkit-appearance: none; background: #1a1a1a; border-radius: 3px; cursor: pointer;"
|
||||||
oninput="updateFromSlider('end')">
|
oninput="updateFromSlider('end')">
|
||||||
<div style="display: flex; align-items: center; gap: 10px; margin-top: 8px;">
|
<div style="display: flex; align-items: center; gap: 10px; margin-top: 8px;">
|
||||||
<label style="color: #888;">Endzeit (HH:MM:SS):</label>
|
<label id="clipDialogEndTimeLabel" style="color: #888;">Endzeit (HH:MM:SS):</label>
|
||||||
<input type="text" id="clipEndTime" value="00:01:00"
|
<input type="text" id="clipEndTime" value="00:01:00"
|
||||||
style="width: 100px; background: #333; border: 1px solid #444; border-radius: 4px; padding: 6px 10px; color: white; font-family: monospace; text-align: center;"
|
style="width: 100px; background: #333; border: 1px solid #444; border-radius: 4px; padding: 6px 10px; color: white; font-family: monospace; text-align: center;"
|
||||||
onchange="updateFromInput('end')">
|
onchange="updateFromInput('end')">
|
||||||
@ -81,22 +81,22 @@
|
|||||||
|
|
||||||
<!-- Dauer Anzeige -->
|
<!-- Dauer Anzeige -->
|
||||||
<div style="text-align: center; margin-bottom: 20px;">
|
<div style="text-align: center; margin-bottom: 20px;">
|
||||||
<span style="color: #888;">Dauer: </span>
|
<span id="clipDialogDurationLabel" style="color: #888;">Dauer: </span>
|
||||||
<span id="clipDurationDisplay" style="color: #00c853;">00:01:00</span>
|
<span id="clipDurationDisplay" style="color: #00c853;">00:01:00</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Teil Nummer -->
|
<!-- Teil Nummer -->
|
||||||
<div style="margin-bottom: 15px;">
|
<div style="margin-bottom: 15px;">
|
||||||
<label style="display: block; margin-bottom: 8px;">Start Part-Nummer (optional, fur Fortsetzung):</label>
|
<label id="clipDialogPartLabel" style="display: block; margin-bottom: 8px;">Start Part-Nummer (optional, fur Fortsetzung):</label>
|
||||||
<input type="text" id="clipStartPart" placeholder="z.B. 42"
|
<input type="text" id="clipStartPart" placeholder="z.B. 42"
|
||||||
style="width: 100px; background: #333; border: 1px solid #444; border-radius: 4px; padding: 8px 12px; color: white;"
|
style="width: 100px; background: #333; border: 1px solid #444; border-radius: 4px; padding: 8px 12px; color: white;"
|
||||||
oninput="updateFilenameExamples()">
|
oninput="updateFilenameExamples()">
|
||||||
<div style="color: #888; font-size: 12px; margin-top: 5px;">Leer lassen = Teil 1</div>
|
<div id="clipDialogPartHint" style="color: #888; font-size: 12px; margin-top: 5px;">Leer lassen = Teil 1</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Dateinamen Format -->
|
<!-- Dateinamen Format -->
|
||||||
<div style="margin-bottom: 20px;">
|
<div style="margin-bottom: 20px;">
|
||||||
<label style="display: block; margin-bottom: 10px;">Dateinamen-Format:</label>
|
<label id="clipDialogFormatLabel" style="display: block; margin-bottom: 10px;">Dateinamen-Format:</label>
|
||||||
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer; margin-bottom: 8px;">
|
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer; margin-bottom: 8px;">
|
||||||
<input type="radio" name="filenameFormat" value="simple" checked onchange="updateFilenameExamples()"
|
<input type="radio" name="filenameFormat" value="simple" checked onchange="updateFilenameExamples()"
|
||||||
style="width: 18px; height: 18px; accent-color: #9146FF;">
|
style="width: 18px; height: 18px; accent-color: #9146FF;">
|
||||||
@ -131,7 +131,7 @@
|
|||||||
|
|
||||||
<!-- Button -->
|
<!-- Button -->
|
||||||
<div style="text-align: center;">
|
<div style="text-align: center;">
|
||||||
<button class="btn-primary" style="background: #00c853; padding: 12px 30px; border: none; border-radius: 4px; color: white; font-weight: 600; cursor: pointer;" onclick="confirmClipDialog()">Zur Queue hinzufugen</button>
|
<button class="btn-primary" id="clipDialogConfirmBtn" style="background: #00c853; padding: 12px 30px; border: none; border-radius: 4px; color: white; font-weight: 600; cursor: pointer;" onclick="confirmClipDialog()">Zur Queue hinzufugen</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -429,6 +429,10 @@
|
|||||||
|
|
||||||
<div class="settings-card">
|
<div class="settings-card">
|
||||||
<h3 id="apiTitle">Twitch API</h3>
|
<h3 id="apiTitle">Twitch API</h3>
|
||||||
|
<p id="apiHelpText" style="color: var(--text-secondary); font-size:13px; margin-bottom:12px; line-height:1.5;">
|
||||||
|
<span id="apiHelpIntro">Du brauchst eine Client-ID und ein Client-Secret von Twitch.</span>
|
||||||
|
<a href="#" id="apiHelpLink" onclick="event.preventDefault(); openTwitchDevConsole()" style="color: var(--accent); text-decoration: underline; cursor: pointer;">dev.twitch.tv/console/apps</a>
|
||||||
|
</p>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label id="clientIdLabel">Client ID</label>
|
<label id="clientIdLabel">Client ID</label>
|
||||||
<input type="text" id="clientId" placeholder="Twitch Client ID">
|
<input type="text" id="clientId" placeholder="Twitch Client ID">
|
||||||
@ -541,6 +545,7 @@
|
|||||||
<h3 id="debugLogTitle">Live Debug-Log</h3>
|
<h3 id="debugLogTitle">Live Debug-Log</h3>
|
||||||
<div class="form-row" style="margin-bottom: 10px; align-items: center;">
|
<div class="form-row" style="margin-bottom: 10px; align-items: center;">
|
||||||
<button class="btn-secondary" id="btnRefreshLog" onclick="refreshDebugLog()">Aktualisieren</button>
|
<button class="btn-secondary" id="btnRefreshLog" onclick="refreshDebugLog()">Aktualisieren</button>
|
||||||
|
<button class="btn-secondary" id="btnOpenDebugLogFile" onclick="openDebugLogFile()">Log-Datei oeffnen</button>
|
||||||
<label style="display:flex; align-items:center; gap:6px; font-size:13px; color: var(--text-secondary);">
|
<label style="display:flex; align-items:center; gap:6px; font-size:13px; color: var(--text-secondary);">
|
||||||
<input type="checkbox" id="debugAutoRefresh" onchange="toggleDebugAutoRefresh(this.checked)">
|
<input type="checkbox" id="debugAutoRefresh" onchange="toggleDebugAutoRefresh(this.checked)">
|
||||||
<span id="autoRefreshText">Auto-Refresh</span>
|
<span id="autoRefreshText">Auto-Refresh</span>
|
||||||
|
|||||||
16
src/main.ts
16
src/main.ts
@ -4146,7 +4146,16 @@ ipcMain.handle('run-preflight', async (_, autoFix: boolean = false) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('get-debug-log', async (_, lines: number = 200) => {
|
ipcMain.handle('get-debug-log', async (_, lines: number = 200) => {
|
||||||
return readDebugLog(lines);
|
// Cap so a misbehaving renderer (or future feature) cannot ask the
|
||||||
|
// main process to slice millions of lines from a multi-MB log.
|
||||||
|
const safeLines = Number.isFinite(lines) ? Math.max(1, Math.min(5000, Math.floor(lines))) : 200;
|
||||||
|
return readDebugLog(safeLines);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('open-debug-log-file', (): boolean => {
|
||||||
|
if (!fs.existsSync(DEBUG_LOG_FILE)) return false;
|
||||||
|
shell.showItemInFolder(DEBUG_LOG_FILE);
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('is-downloading', () => isDownloading);
|
ipcMain.handle('is-downloading', () => isDownloading);
|
||||||
@ -4169,7 +4178,10 @@ ipcMain.handle('export-runtime-metrics', async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const snapshot = getRuntimeMetricsSnapshot();
|
const snapshot = getRuntimeMetricsSnapshot();
|
||||||
fs.writeFileSync(dialogResult.filePath, JSON.stringify(snapshot, null, 2), 'utf-8');
|
// Atomic write: same fsync+rename pattern used for config/queue
|
||||||
|
// (cycle 1) so a power loss mid-export can't leave a half-written
|
||||||
|
// metrics file at the user's chosen path.
|
||||||
|
writeFileAtomicSync(dialogResult.filePath, JSON.stringify(snapshot, null, 2));
|
||||||
return { success: true, filePath: dialogResult.filePath };
|
return { success: true, filePath: dialogResult.filePath };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
appendDebugLog('runtime-metrics-export-failed', String(e));
|
appendDebugLog('runtime-metrics-export-failed', String(e));
|
||||||
|
|||||||
@ -85,6 +85,7 @@ contextBridge.exposeInMainWorld('api', {
|
|||||||
openFolder: (path: string) => ipcRenderer.invoke('open-folder', path),
|
openFolder: (path: string) => ipcRenderer.invoke('open-folder', path),
|
||||||
openFile: (path: string) => ipcRenderer.invoke('open-file', path),
|
openFile: (path: string) => ipcRenderer.invoke('open-file', path),
|
||||||
showInFolder: (path: string) => ipcRenderer.invoke('show-in-folder', path),
|
showInFolder: (path: string) => ipcRenderer.invoke('show-in-folder', path),
|
||||||
|
openDebugLogFile: () => ipcRenderer.invoke('open-debug-log-file'),
|
||||||
|
|
||||||
// Video Cutter
|
// Video Cutter
|
||||||
getVideoInfo: (filePath: string): Promise<VideoInfo | null> => ipcRenderer.invoke('get-video-info', filePath),
|
getVideoInfo: (filePath: string): Promise<VideoInfo | null> => ipcRenderer.invoke('get-video-info', filePath),
|
||||||
|
|||||||
1
src/renderer-globals.d.ts
vendored
1
src/renderer-globals.d.ts
vendored
@ -200,6 +200,7 @@ interface ApiBridge {
|
|||||||
openFolder(path: string): Promise<void>;
|
openFolder(path: string): Promise<void>;
|
||||||
openFile(path: string): Promise<boolean>;
|
openFile(path: string): Promise<boolean>;
|
||||||
showInFolder(path: string): Promise<boolean>;
|
showInFolder(path: string): Promise<boolean>;
|
||||||
|
openDebugLogFile(): Promise<boolean>;
|
||||||
getVideoInfo(filePath: string): Promise<VideoInfo | null>;
|
getVideoInfo(filePath: string): Promise<VideoInfo | null>;
|
||||||
extractFrame(filePath: string, timeSeconds: number): Promise<string | null>;
|
extractFrame(filePath: string, timeSeconds: number): Promise<string | null>;
|
||||||
cutVideo(inputFile: string, startTime: number, endTime: number): Promise<{ success: boolean; outputFile: string | null }>;
|
cutVideo(inputFile: string, startTime: number, endTime: number): Promise<{ success: boolean; outputFile: string | null }>;
|
||||||
|
|||||||
@ -51,6 +51,9 @@ const UI_TEXT_DE = {
|
|||||||
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.',
|
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).',
|
streamerInvalid: 'Twitch-Username ungueltig (4-25 Zeichen, Buchstaben/Zahlen/Unterstrich).',
|
||||||
|
apiHelpIntro: 'Du brauchst eine Client-ID und ein Client-Secret von Twitch.',
|
||||||
|
apiHelpLinkText: 'dev.twitch.tv/console/apps',
|
||||||
|
openDebugLogFile: 'Log-Datei oeffnen',
|
||||||
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)',
|
||||||
@ -190,6 +193,15 @@ const UI_TEXT_DE = {
|
|||||||
},
|
},
|
||||||
clips: {
|
clips: {
|
||||||
dialogTitle: 'VOD zuschneiden',
|
dialogTitle: 'VOD zuschneiden',
|
||||||
|
dialogStart: 'Start:',
|
||||||
|
dialogStartTime: 'Startzeit (HH:MM:SS):',
|
||||||
|
dialogEnd: 'Ende:',
|
||||||
|
dialogEndTime: 'Endzeit (HH:MM:SS):',
|
||||||
|
dialogDuration: 'Dauer: ',
|
||||||
|
dialogPartLabel: 'Start Part-Nummer (optional, fur Fortsetzung):',
|
||||||
|
dialogPartHint: 'Leer lassen = Teil 1',
|
||||||
|
dialogFormatLabel: 'Dateinamen-Format:',
|
||||||
|
dialogConfirm: 'Zur Queue hinzufuegen',
|
||||||
invalidDuration: 'Ungultig!',
|
invalidDuration: 'Ungultig!',
|
||||||
endBeforeStart: 'Endzeit muss grosser als Startzeit sein!',
|
endBeforeStart: 'Endzeit muss grosser als Startzeit sein!',
|
||||||
outOfRange: 'Zeit ausserhalb des VOD-Bereichs!',
|
outOfRange: 'Zeit ausserhalb des VOD-Bereichs!',
|
||||||
|
|||||||
@ -51,6 +51,9 @@ const UI_TEXT_EN = {
|
|||||||
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.',
|
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).',
|
streamerInvalid: 'Invalid Twitch username (4-25 chars, letters/digits/underscore).',
|
||||||
|
apiHelpIntro: 'You need a Client ID and Client Secret from Twitch.',
|
||||||
|
apiHelpLinkText: 'dev.twitch.tv/console/apps',
|
||||||
|
openDebugLogFile: 'Open log file',
|
||||||
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)',
|
||||||
@ -190,6 +193,15 @@ const UI_TEXT_EN = {
|
|||||||
},
|
},
|
||||||
clips: {
|
clips: {
|
||||||
dialogTitle: 'Trim VOD',
|
dialogTitle: 'Trim VOD',
|
||||||
|
dialogStart: 'Start:',
|
||||||
|
dialogStartTime: 'Start time (HH:MM:SS):',
|
||||||
|
dialogEnd: 'End:',
|
||||||
|
dialogEndTime: 'End time (HH:MM:SS):',
|
||||||
|
dialogDuration: 'Duration: ',
|
||||||
|
dialogPartLabel: 'Start part number (optional, for continuation):',
|
||||||
|
dialogPartHint: 'Leave empty = part 1',
|
||||||
|
dialogFormatLabel: 'Filename format:',
|
||||||
|
dialogConfirm: 'Add to queue',
|
||||||
invalidDuration: 'Invalid!',
|
invalidDuration: 'Invalid!',
|
||||||
endBeforeStart: 'End time must be greater than start time!',
|
endBeforeStart: 'End time must be greater than start time!',
|
||||||
outOfRange: 'Time is outside VOD range!',
|
outOfRange: 'Time is outside VOD range!',
|
||||||
|
|||||||
@ -265,6 +265,14 @@ async function runPreflight(autoFix = false): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openDebugLogFile(): Promise<void> {
|
||||||
|
const ok = await window.api.openDebugLogFile();
|
||||||
|
if (!ok) {
|
||||||
|
const toast = (window as unknown as { showAppToast?: (msg: string, kind?: 'info' | 'warn') => void }).showAppToast;
|
||||||
|
if (toast) toast('Debug log file not yet present.', 'warn');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function refreshDebugLog(): Promise<void> {
|
async function refreshDebugLog(): Promise<void> {
|
||||||
const text = await window.api.getDebugLog(250);
|
const text = await window.api.getDebugLog(250);
|
||||||
const panel = byId('debugLogOutput');
|
const panel = byId('debugLogOutput');
|
||||||
|
|||||||
@ -61,6 +61,15 @@ function applyLanguageToStaticUI(): void {
|
|||||||
setText('clipsInfoText', UI_TEXT.static.clipsInfoText);
|
setText('clipsInfoText', UI_TEXT.static.clipsInfoText);
|
||||||
setText('clipTemplateHelp', UI_TEXT.clips.templateHelp);
|
setText('clipTemplateHelp', UI_TEXT.clips.templateHelp);
|
||||||
setPlaceholder('clipFilenameTemplate', UI_TEXT.clips.templatePlaceholder);
|
setPlaceholder('clipFilenameTemplate', UI_TEXT.clips.templatePlaceholder);
|
||||||
|
setText('clipDialogStartLabel', UI_TEXT.clips.dialogStart);
|
||||||
|
setText('clipDialogStartTimeLabel', UI_TEXT.clips.dialogStartTime);
|
||||||
|
setText('clipDialogEndLabel', UI_TEXT.clips.dialogEnd);
|
||||||
|
setText('clipDialogEndTimeLabel', UI_TEXT.clips.dialogEndTime);
|
||||||
|
setText('clipDialogDurationLabel', UI_TEXT.clips.dialogDuration);
|
||||||
|
setText('clipDialogPartLabel', UI_TEXT.clips.dialogPartLabel);
|
||||||
|
setText('clipDialogPartHint', UI_TEXT.clips.dialogPartHint);
|
||||||
|
setText('clipDialogFormatLabel', UI_TEXT.clips.dialogFormatLabel);
|
||||||
|
setText('clipDialogConfirmBtn', UI_TEXT.clips.dialogConfirm);
|
||||||
setText('cutterSelectTitle', UI_TEXT.static.cutterSelectTitle);
|
setText('cutterSelectTitle', UI_TEXT.static.cutterSelectTitle);
|
||||||
setText('cutterBrowseBtn', UI_TEXT.static.cutterBrowse);
|
setText('cutterBrowseBtn', UI_TEXT.static.cutterBrowse);
|
||||||
setText('mergeTitle', UI_TEXT.static.mergeTitle);
|
setText('mergeTitle', UI_TEXT.static.mergeTitle);
|
||||||
@ -73,6 +82,8 @@ function applyLanguageToStaticUI(): void {
|
|||||||
setText('languageDeText', UI_TEXT.static.languageDe);
|
setText('languageDeText', UI_TEXT.static.languageDe);
|
||||||
setText('languageEnText', UI_TEXT.static.languageEn);
|
setText('languageEnText', UI_TEXT.static.languageEn);
|
||||||
setText('apiTitle', UI_TEXT.static.apiTitle);
|
setText('apiTitle', UI_TEXT.static.apiTitle);
|
||||||
|
setText('apiHelpIntro', UI_TEXT.static.apiHelpIntro);
|
||||||
|
setText('apiHelpLink', UI_TEXT.static.apiHelpLinkText);
|
||||||
setText('clientIdLabel', UI_TEXT.static.clientIdLabel);
|
setText('clientIdLabel', UI_TEXT.static.clientIdLabel);
|
||||||
setText('clientSecretLabel', UI_TEXT.static.clientSecretLabel);
|
setText('clientSecretLabel', UI_TEXT.static.clientSecretLabel);
|
||||||
setText('saveSettingsBtn', UI_TEXT.static.saveSettings);
|
setText('saveSettingsBtn', UI_TEXT.static.saveSettings);
|
||||||
@ -129,6 +140,7 @@ function applyLanguageToStaticUI(): void {
|
|||||||
setText('preflightResult', UI_TEXT.static.preflightEmpty);
|
setText('preflightResult', UI_TEXT.static.preflightEmpty);
|
||||||
setText('debugLogTitle', UI_TEXT.static.debugLogTitle);
|
setText('debugLogTitle', UI_TEXT.static.debugLogTitle);
|
||||||
setText('btnRefreshLog', UI_TEXT.static.refreshLog);
|
setText('btnRefreshLog', UI_TEXT.static.refreshLog);
|
||||||
|
setText('btnOpenDebugLogFile', UI_TEXT.static.openDebugLogFile);
|
||||||
setText('autoRefreshText', UI_TEXT.static.autoRefresh);
|
setText('autoRefreshText', UI_TEXT.static.autoRefresh);
|
||||||
setText('runtimeMetricsTitle', UI_TEXT.static.runtimeMetricsTitle);
|
setText('runtimeMetricsTitle', UI_TEXT.static.runtimeMetricsTitle);
|
||||||
setText('btnRefreshMetrics', UI_TEXT.static.runtimeMetricsRefresh);
|
setText('btnRefreshMetrics', UI_TEXT.static.runtimeMetricsRefresh);
|
||||||
|
|||||||
@ -208,6 +208,10 @@ async function init(): Promise<void> {
|
|||||||
scheduleQueueSync(QUEUE_SYNC_DEFAULT_MS);
|
scheduleQueueSync(QUEUE_SYNC_DEFAULT_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openTwitchDevConsole(): void {
|
||||||
|
void window.api.openExternal('https://dev.twitch.tv/console/apps');
|
||||||
|
}
|
||||||
|
|
||||||
function closeTopmostOpenModal(): boolean {
|
function closeTopmostOpenModal(): boolean {
|
||||||
// Try each known modal in priority order: clip dialog, template guide, update modal
|
// Try each known modal in priority order: clip dialog, template guide, update modal
|
||||||
const clipModal = document.getElementById('clipModal');
|
const clipModal = document.getElementById('clipModal');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user