fix(release): harden updater status and cutter layout
Windows CI / verify (push) Failing after 3m37s
Windows CI / verify (push) Failing after 3m37s
Ignore malformed update notifications that do not carry a version, preventing unusable release prompts in installed builds. Keep the loaded cutter workspace free of the source selector and give cutter export selects an explicit non-repeating indicator after workspace styling. Add regression coverage, bump the patch release to 1.0.10, and update public installation documentation.
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 1.0.10 - 2026-08-12
|
||||||
|
|
||||||
|
- Ignore malformed updater events without a version instead of showing an unusable update prompt.
|
||||||
|
- Keep the cutter source selection out of the loaded editor layout and render each export profile indicator exactly once.
|
||||||
|
|
||||||
## 1.0.9 - 2026-08-12
|
## 1.0.9 - 2026-08-12
|
||||||
|
|
||||||
- Waited for managed-tool checksum streams to close before promoting verified installations, preventing intermittent Windows repair failures caused by open file handles.
|
- Waited for managed-tool checksum streams to close before promoting verified installations, preventing intermittent Windows repair failures caused by open file handles.
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ The application works in public mode without a Twitch login. Connecting a Twitch
|
|||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Open the [latest GitHub release](https://github.com/Sucukdeluxe/Twitch-VOD-Manager/releases/latest).
|
1. Open the [latest GitHub release](https://github.com/Sucukdeluxe/Twitch-VOD-Manager/releases/latest).
|
||||||
2. Download `Twitch-VOD-Manager-Setup-1.0.9.exe`.
|
2. Download `Twitch-VOD-Manager-Setup-1.0.10.exe`.
|
||||||
3. Run the installer and choose the installation directory.
|
3. Run the installer and choose the installation directory.
|
||||||
4. Start Twitch VOD Manager and add a streamer.
|
4. Start Twitch VOD Manager and add a streamer.
|
||||||
|
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "twitch-vod-manager",
|
"name": "twitch-vod-manager",
|
||||||
"version": "1.0.9",
|
"version": "1.0.10",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "twitch-vod-manager",
|
"name": "twitch-vod-manager",
|
||||||
"version": "1.0.9",
|
"version": "1.0.10",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.16.1",
|
"axios": "^1.16.1",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "twitch-vod-manager",
|
"name": "twitch-vod-manager",
|
||||||
"version": "1.0.9",
|
"version": "1.0.10",
|
||||||
"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": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
|
|||||||
+1
-1
@@ -93,7 +93,7 @@ if (process.platform === 'win32') {
|
|||||||
sourcePath: electronSourceExecutable,
|
sourcePath: electronSourceExecutable,
|
||||||
destinationPath: resolve(rootDirectory, 'node_modules', 'electron', 'dist', 'Twitch VOD Manager.exe'),
|
destinationPath: resolve(rootDirectory, 'node_modules', 'electron', 'dist', 'Twitch VOD Manager.exe'),
|
||||||
iconPath: resolve(rootDirectory, 'build', 'icon.ico'),
|
iconPath: resolve(rootDirectory, 'build', 'icon.ico'),
|
||||||
version: '1.0.9',
|
version: '1.0.10',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -132,6 +132,7 @@
|
|||||||
"src/renderer-command-palette.ts",
|
"src/renderer-command-palette.ts",
|
||||||
"src/renderer-cutter.ts",
|
"src/renderer-cutter.ts",
|
||||||
"src/renderer-cutter.production-path.test.ts",
|
"src/renderer-cutter.production-path.test.ts",
|
||||||
|
"src/cutter-workspace-styles.production-path.test.ts",
|
||||||
"src/renderer-globals.d.ts",
|
"src/renderer-globals.d.ts",
|
||||||
"src/renderer-locale-de.ts",
|
"src/renderer-locale-de.ts",
|
||||||
"src/renderer-locale-en.ts",
|
"src/renderer-locale-en.ts",
|
||||||
@@ -146,6 +147,7 @@
|
|||||||
"src/renderer-streamers.ts",
|
"src/renderer-streamers.ts",
|
||||||
"src/renderer-texts.ts",
|
"src/renderer-texts.ts",
|
||||||
"src/renderer-updates.ts",
|
"src/renderer-updates.ts",
|
||||||
|
"src/renderer-updates.production-path.test.ts",
|
||||||
"src/renderer-vod-hover.ts",
|
"src/renderer-vod-hover.ts",
|
||||||
"src/renderer.ts",
|
"src/renderer.ts",
|
||||||
"src/styles.css",
|
"src/styles.css",
|
||||||
|
|||||||
@@ -200,6 +200,34 @@ async function run() {
|
|||||||
await win.setViewportSize({ width: 1440, height: 900 });
|
await win.setViewportSize({ width: 1440, height: 900 });
|
||||||
await win.emulateMedia({ reducedMotion: 'reduce' });
|
await win.emulateMedia({ reducedMotion: 'reduce' });
|
||||||
await win.evaluate(() => window.showTab('cutter'));
|
await win.evaluate(() => window.showTab('cutter'));
|
||||||
|
await win.evaluate(() => {
|
||||||
|
document.getElementById('cutterWorkspace').classList.add('shown');
|
||||||
|
document.getElementById('cutterExportProfile').disabled = false;
|
||||||
|
});
|
||||||
|
await win.locator('#cutterExportProfile').waitFor({ state: 'visible' });
|
||||||
|
const cutterExportProfileBox = await win.locator('#cutterExportProfile').boundingBox();
|
||||||
|
if (!cutterExportProfileBox) throw new Error('Export profile is not visible');
|
||||||
|
await win.mouse.move(cutterExportProfileBox.x + cutterExportProfileBox.width / 2, cutterExportProfileBox.y + cutterExportProfileBox.height / 2);
|
||||||
|
const cutterExportProfilePresentation = await win.evaluate(() => {
|
||||||
|
const style = getComputedStyle(document.getElementById('cutterExportProfile'));
|
||||||
|
return {
|
||||||
|
backgroundImage: style.backgroundImage,
|
||||||
|
backgroundRepeat: style.backgroundRepeat,
|
||||||
|
backgroundPosition: style.backgroundPosition,
|
||||||
|
sourceDisplay: getComputedStyle(document.querySelector('.cutter-source-bar')).display,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
check(
|
||||||
|
cutterExportProfilePresentation.backgroundImage !== 'none'
|
||||||
|
&& cutterExportProfilePresentation.backgroundRepeat === 'no-repeat'
|
||||||
|
&& cutterExportProfilePresentation.backgroundPosition.includes('8px')
|
||||||
|
&& cutterExportProfilePresentation.sourceDisplay === 'none',
|
||||||
|
`Export profile indicator is tiled or misplaced: ${JSON.stringify(cutterExportProfilePresentation)}`
|
||||||
|
);
|
||||||
|
await win.evaluate(() => {
|
||||||
|
document.getElementById('cutterWorkspace').classList.remove('shown');
|
||||||
|
document.getElementById('cutterExportProfile').disabled = true;
|
||||||
|
});
|
||||||
const additionalContainerCapabilities = await Promise.all(Object.entries(additionalContainerFiles).map(async ([extension, filePath]) => ({
|
const additionalContainerCapabilities = await Promise.all(Object.entries(additionalContainerFiles).map(async ([extension, filePath]) => ({
|
||||||
extension,
|
extension,
|
||||||
capability: await createCutterCapability(win, filePath),
|
capability: await createCutterCapability(win, filePath),
|
||||||
@@ -316,7 +344,7 @@ async function run() {
|
|||||||
`Disabled empty-player volume control still expands on hover: ${JSON.stringify({ emptyVolumeBefore, emptyVolumeAfter })}`
|
`Disabled empty-player volume control still expands on hover: ${JSON.stringify({ emptyVolumeBefore, emptyVolumeAfter })}`
|
||||||
);
|
);
|
||||||
if (process.env.TWITCH_VOD_MANAGER_CUTTER_EMPTY_ONLY === '1') {
|
if (process.env.TWITCH_VOD_MANAGER_CUTTER_EMPTY_ONLY === '1') {
|
||||||
console.log(JSON.stringify({ failures, runtimeIssues, emptyLayout, emptyFullscreenLayout, emptyVolumeBefore, emptyVolumeAfter }, null, 2));
|
console.log(JSON.stringify({ failures, runtimeIssues, cutterExportProfilePresentation, emptyLayout, emptyFullscreenLayout, emptyVolumeBefore, emptyVolumeAfter }, null, 2));
|
||||||
if (failures.length > 0) process.exitCode = 1;
|
if (failures.length > 0) process.exitCode = 1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ function check(condition, message) {
|
|||||||
if (!condition) failures.push(message);
|
if (!condition) failures.push(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
check(packageJson.version === '1.0.9', `package version is ${packageJson.version}`);
|
check(packageJson.version === '1.0.10', `package version is ${packageJson.version}`);
|
||||||
check(packageLock.version === '1.0.9', `lockfile version is ${packageLock.version}`);
|
check(packageLock.version === '1.0.10', `lockfile version is ${packageLock.version}`);
|
||||||
check(packageLock.packages?.['']?.version === '1.0.9', `lockfile root package version is ${packageLock.packages?.['']?.version}`);
|
check(packageLock.packages?.['']?.version === '1.0.10', `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?.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?.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}`);
|
check(packageJson.build?.publish?.url === 'https://github.com/Sucukdeluxe/Twitch-VOD-Manager/releases/latest/download/', `publish URL is ${packageJson.build?.publish?.url}`);
|
||||||
@@ -62,7 +62,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://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(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(!/storyboards\/\d{8,12}(?:-|\/)/.test(mainSource), 'numeric Twitch VOD example remains in the public source');
|
||||||
check(indexSource.includes('Version: v1.0.9'), 'initial version label is not 1.0.9');
|
check(indexSource.includes('Version: v1.0.10'), 'initial version label is not 1.0.10');
|
||||||
check(!indexSource.includes('Version: v4.1.13'), 'legacy version label is still present');
|
check(!indexSource.includes('Version: v4.1.13'), 'legacy version label is still present');
|
||||||
check(fs.existsSync(manifestPath), 'public release manifest is missing');
|
check(fs.existsSync(manifestPath), 'public release manifest is missing');
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
|
||||||
|
const styles = readFileSync(join(__dirname, 'styles.css'), 'utf8');
|
||||||
|
const workspaceStyles = readFileSync(join(__dirname, 'workspace.css'), 'utf8');
|
||||||
|
|
||||||
|
describe('cutter workspace style production paths', () => {
|
||||||
|
test('hides the source bar when the non-adjacent workspace is shown', () => {
|
||||||
|
expect(styles).toContain('.cutter-source-bar:has(~ .cutter-workspace.shown)');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('keeps the export profile indicator from tiling after workspace background styling', () => {
|
||||||
|
const selector = '#cutterTab .cutter-export-options select {';
|
||||||
|
const start = workspaceStyles.indexOf(selector);
|
||||||
|
const end = workspaceStyles.indexOf('}', start);
|
||||||
|
expect(workspaceStyles.slice(start, end)).toMatch(/background-repeat:\s*no-repeat/);
|
||||||
|
});
|
||||||
|
});
|
||||||
+1
-1
@@ -940,7 +940,7 @@
|
|||||||
|
|
||||||
<div class="settings-card" data-settings-pane="updates" hidden>
|
<div class="settings-card" data-settings-pane="updates" hidden>
|
||||||
<h3 id="updateTitle">Updates</h3>
|
<h3 id="updateTitle">Updates</h3>
|
||||||
<p id="versionInfo" class="card-intro">Version: v1.0.9</p>
|
<p id="versionInfo" class="card-intro">Version: v1.0.10</p>
|
||||||
<button type="button" class="btn-secondary" id="checkUpdateBtn" onclick="checkUpdate()">Nach Updates suchen</button>
|
<button type="button" class="btn-secondary" id="checkUpdateBtn" onclick="checkUpdate()">Nach Updates suchen</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { runInNewContext } from 'node:vm';
|
||||||
|
import { ModuleKind, ScriptTarget, transpileModule } from 'typescript';
|
||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
|
||||||
|
function sourceFragment(start: string, end: string): string {
|
||||||
|
const source = readFileSync(join(__dirname, 'renderer-updates.ts'), 'utf8');
|
||||||
|
const from = source.indexOf(start);
|
||||||
|
const to = source.indexOf(end, from);
|
||||||
|
if (from < 0 || to < 0) throw new Error('Missing renderer updates production fragment');
|
||||||
|
return source.slice(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
function evaluate(source: string, context: Record<string, unknown>): { rememberUpdateInfo: (info?: { version?: string } | null) => unknown } {
|
||||||
|
context.globalThis = context;
|
||||||
|
const compiled = transpileModule(`${source}\nObject.assign(globalThis, { __updatesProductionPath: { rememberUpdateInfo } });`, {
|
||||||
|
compilerOptions: { module: ModuleKind.None, target: ScriptTarget.ES2022 },
|
||||||
|
}).outputText;
|
||||||
|
runInNewContext(compiled, context);
|
||||||
|
return (context as { __updatesProductionPath: { rememberUpdateInfo: (info?: { version?: string } | null) => unknown } }).__updatesProductionPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('renderer update production paths', () => {
|
||||||
|
test('does not create an update state without a version', () => {
|
||||||
|
const api = evaluate(sourceFragment('function rememberUpdateInfo', 'function getActiveUpdateInfo'), {
|
||||||
|
latestUpdateVersion: '',
|
||||||
|
latestUpdateInfo: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(api.rememberUpdateInfo({})).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
+23
-8
@@ -33,8 +33,9 @@ function notifyUpdate(message: string, type: 'info' | 'warn' = 'info'): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function rememberUpdateInfo(info?: UpdateInfo | null): UpdateInfo {
|
function rememberUpdateInfo(info?: UpdateInfo | null): UpdateInfo | null {
|
||||||
const version = info?.version || latestUpdateVersion || latestUpdateInfo?.version || '?';
|
const version = (info?.version || latestUpdateVersion || latestUpdateInfo?.version || '').trim();
|
||||||
|
if (!version) return null;
|
||||||
latestUpdateVersion = version;
|
latestUpdateVersion = version;
|
||||||
latestUpdateInfo = {
|
latestUpdateInfo = {
|
||||||
...(latestUpdateInfo || { version }),
|
...(latestUpdateInfo || { version }),
|
||||||
@@ -44,7 +45,7 @@ function rememberUpdateInfo(info?: UpdateInfo | null): UpdateInfo {
|
|||||||
return latestUpdateInfo;
|
return latestUpdateInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getActiveUpdateInfo(): UpdateInfo {
|
function getActiveUpdateInfo(): UpdateInfo | null {
|
||||||
return rememberUpdateInfo();
|
return rememberUpdateInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +183,8 @@ function handleWorkspaceUpdateAction(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (updateBannerState === 'available' || updateBannerState === 'ready') {
|
if (updateBannerState === 'available' || updateBannerState === 'ready') {
|
||||||
openUpdateModal(getActiveUpdateInfo());
|
const info = getActiveUpdateInfo();
|
||||||
|
if (info) openUpdateModal(info);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,6 +193,7 @@ function handleWorkspaceUpdateAction(): void {
|
|||||||
|
|
||||||
function setUpdateBannerAvailableUi(info: UpdateInfo, reveal = true): void {
|
function setUpdateBannerAvailableUi(info: UpdateInfo, reveal = true): void {
|
||||||
const activeInfo = rememberUpdateInfo(info);
|
const activeInfo = rememberUpdateInfo(info);
|
||||||
|
if (!activeInfo) return;
|
||||||
updateReady = false;
|
updateReady = false;
|
||||||
updateDownloadInProgress = false;
|
updateDownloadInProgress = false;
|
||||||
latestDownloadProgress = null;
|
latestDownloadProgress = null;
|
||||||
@@ -240,6 +243,7 @@ function setDownloadPendingUi(): void {
|
|||||||
|
|
||||||
function setDownloadReadyUi(info?: UpdateInfo): void {
|
function setDownloadReadyUi(info?: UpdateInfo): void {
|
||||||
const activeInfo = rememberUpdateInfo(info);
|
const activeInfo = rememberUpdateInfo(info);
|
||||||
|
if (!activeInfo) return;
|
||||||
updateReady = true;
|
updateReady = true;
|
||||||
updateDownloadInProgress = false;
|
updateDownloadInProgress = false;
|
||||||
updateBannerState = 'ready';
|
updateBannerState = 'ready';
|
||||||
@@ -390,6 +394,7 @@ function refreshUpdateChangelogToggleText(): void {
|
|||||||
|
|
||||||
function refreshUpdateModalTexts(): void {
|
function refreshUpdateModalTexts(): void {
|
||||||
const info = getActiveUpdateInfo();
|
const info = getActiveUpdateInfo();
|
||||||
|
if (!info) return;
|
||||||
const isReady = updateReady;
|
const isReady = updateReady;
|
||||||
|
|
||||||
byId('updateModalTitle').textContent = isReady
|
byId('updateModalTitle').textContent = isReady
|
||||||
@@ -421,7 +426,7 @@ function refreshUpdateModalTexts(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function openUpdateModal(info?: UpdateInfo): void {
|
function openUpdateModal(info?: UpdateInfo): void {
|
||||||
rememberUpdateInfo(info);
|
if (!rememberUpdateInfo(info)) return;
|
||||||
updateChangelogExpanded = false;
|
updateChangelogExpanded = false;
|
||||||
RendererAccessibility.openDialog('updateModal', { onEscape: dismissUpdateModal });
|
RendererAccessibility.openDialog('updateModal', { onEscape: dismissUpdateModal });
|
||||||
refreshUpdateModalTexts();
|
refreshUpdateModalTexts();
|
||||||
@@ -546,7 +551,8 @@ async function checkUpdate(): Promise<void> {
|
|||||||
updateCheckInProgress = false;
|
updateCheckInProgress = false;
|
||||||
setCheckButtonCheckingState(false);
|
setCheckButtonCheckingState(false);
|
||||||
if (latestUpdateInfo || updateReady) {
|
if (latestUpdateInfo || updateReady) {
|
||||||
openUpdateModal(getActiveUpdateInfo());
|
const info = getActiveUpdateInfo();
|
||||||
|
if (info) openUpdateModal(info);
|
||||||
} else {
|
} else {
|
||||||
notifyUpdate(UI_TEXT.updates.readyToInstall, 'info');
|
notifyUpdate(UI_TEXT.updates.readyToInstall, 'info');
|
||||||
}
|
}
|
||||||
@@ -611,8 +617,11 @@ function downloadUpdate(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (result?.skipped === 'ready-to-install') {
|
if (result?.skipped === 'ready-to-install') {
|
||||||
setDownloadReadyUi(getActiveUpdateInfo());
|
const info = getActiveUpdateInfo();
|
||||||
openUpdateModal(getActiveUpdateInfo());
|
if (info) {
|
||||||
|
setDownloadReadyUi(info);
|
||||||
|
openUpdateModal(info);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -646,6 +655,11 @@ window.api.onUpdateAvailable((info: UpdateInfo) => {
|
|||||||
latestDownloadProgress = null;
|
latestDownloadProgress = null;
|
||||||
setCheckButtonCheckingState(false);
|
setCheckButtonCheckingState(false);
|
||||||
|
|
||||||
|
if (!activeInfo) {
|
||||||
|
shouldOpenUpdateModalOnAvailable = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If the user explicitly skipped this exact version, suppress the auto
|
// If the user explicitly skipped this exact version, suppress the auto
|
||||||
// notification entirely — banner stays hidden, no modal popup. A manual
|
// notification entirely — banner stays hidden, no modal popup. A manual
|
||||||
// "Check for updates" click overrides the skip so the user can change
|
// "Check for updates" click overrides the skip so the user can change
|
||||||
@@ -707,6 +721,7 @@ window.api.onUpdateDownloaded((info: UpdateInfo) => {
|
|||||||
// by a stale entry.
|
// by a stale entry.
|
||||||
clearSkippedUpdateVersion();
|
clearSkippedUpdateVersion();
|
||||||
const activeInfo = rememberUpdateInfo(info);
|
const activeInfo = rememberUpdateInfo(info);
|
||||||
|
if (!activeInfo) return;
|
||||||
setDownloadReadyUi(activeInfo);
|
setDownloadReadyUi(activeInfo);
|
||||||
openUpdateModal(activeInfo);
|
openUpdateModal(activeInfo);
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -3091,7 +3091,7 @@ input[type="checkbox"].vod-select-checkbox {
|
|||||||
grid-template-rows: minmax(0, 1fr) auto;
|
grid-template-rows: minmax(0, 1fr) auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#cutterTab .cutter-source-bar:has(+ .cutter-workspace.shown) {
|
#cutterTab .cutter-source-bar:has(~ .cutter-workspace.shown) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1420,6 +1420,21 @@ textarea:hover:not(:disabled) {
|
|||||||
border-color: var(--workspace-border-strong);
|
border-color: var(--workspace-border-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#cutterTab .cutter-export-options select {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
min-width: 0;
|
||||||
|
padding-right: 28px;
|
||||||
|
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23999'%3E%3Cpath d='M4 6l4 4 4-4z'/%3E%3C/svg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 8px center;
|
||||||
|
background-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cutterTab .cutter-export-options select:hover:not(:disabled) {
|
||||||
|
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%239146FF'%3E%3Cpath d='M4 6l4 4 4-4z'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
input:not([type="checkbox"]):not([type="radio"]):focus,
|
input:not([type="checkbox"]):not([type="radio"]):focus,
|
||||||
select:focus,
|
select:focus,
|
||||||
textarea:focus {
|
textarea:focus {
|
||||||
|
|||||||
Reference in New Issue
Block a user