Release 1.0.3: Windows sharp in CI, manual update check, updater IPC
Release / release (push) Failing after 29s
Release / release (push) Failing after 29s
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { startTransition, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import { ipcChannels } from '../../shared/ipc/contracts';
|
||||
import { ipcChannels, type UpdaterCheckResponse } from '../../shared/ipc/contracts';
|
||||
import { EULA_CURRENT_VERSION } from '../../shared/license/eulaVersion';
|
||||
import type { LicenseSnapshot } from '../../shared/license/licenseSnapshot';
|
||||
import type { AssetId, MediaAsset, Project, ProjectId, SceneAudioRef, SceneId } from '../../shared/types';
|
||||
@@ -76,6 +76,8 @@ export function EditorApp() {
|
||||
const [previewBusy, setPreviewBusy] = useState(false);
|
||||
const [presentationOpen, setPresentationOpen] = useState(false);
|
||||
const [licenseSnap, setLicenseSnap] = useState<LicenseSnapshot | null>(null);
|
||||
const [checkUpdatesOpen, setCheckUpdatesOpen] = useState(false);
|
||||
const [appPackaged, setAppPackaged] = useState(false);
|
||||
const [licenseKeyModalOpen, setLicenseKeyModalOpen] = useState(false);
|
||||
const [eulaModalOpen, setEulaModalOpen] = useState(false);
|
||||
const [aboutLicenseOpen, setAboutLicenseOpen] = useState(false);
|
||||
@@ -305,6 +307,7 @@ export function EditorApp() {
|
||||
const r = await getDndApi().invoke(ipcChannels.app.getVersion, {});
|
||||
const label = r.buildNumber ? `v${r.version} · ${r.buildNumber}` : `v${r.version}`;
|
||||
setAppVersionText(label);
|
||||
setAppPackaged(r.packaged);
|
||||
} catch {
|
||||
setAppVersionText(null);
|
||||
}
|
||||
@@ -647,6 +650,20 @@ export function EditorApp() {
|
||||
>
|
||||
{t('menu.aboutLicense')}
|
||||
</button>
|
||||
{licenseActive && appPackaged ? (
|
||||
<button
|
||||
type="button"
|
||||
role="menuitem"
|
||||
className={styles.fileMenuItem}
|
||||
onClick={() => {
|
||||
setSettingsMenuOpen(false);
|
||||
setSettingsLangSubOpen(false);
|
||||
setCheckUpdatesOpen(true);
|
||||
}}
|
||||
>
|
||||
{t('menu.checkUpdates')}
|
||||
</button>
|
||||
) : null}
|
||||
<div className={styles.fileMenuSubHost} role="presentation">
|
||||
<button
|
||||
type="button"
|
||||
@@ -819,6 +836,7 @@ export function EditorApp() {
|
||||
await actions.exportProject(projectId);
|
||||
}}
|
||||
/>
|
||||
<CheckUpdatesModal open={checkUpdatesOpen} onClose={() => setCheckUpdatesOpen(false)} />
|
||||
<SimpleMessageModal
|
||||
open={appNotice !== null}
|
||||
title={appNotice?.title ?? t('common.message')}
|
||||
@@ -941,6 +959,136 @@ function ExportProjectModal({
|
||||
);
|
||||
}
|
||||
|
||||
type CheckUpdatesModalProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
function CheckUpdatesModal({ open, onClose }: CheckUpdatesModalProps) {
|
||||
const { t } = useEditorI18n();
|
||||
const [phase, setPhase] = useState<'idle' | 'checking' | 'done'>('idle');
|
||||
const [res, setRes] = useState<UpdaterCheckResponse | null>(null);
|
||||
const [downloadBusy, setDownloadBusy] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
startTransition(() => {
|
||||
setPhase('checking');
|
||||
setRes(null);
|
||||
});
|
||||
void getDndApi()
|
||||
.invoke(ipcChannels.updater.check, {})
|
||||
.then((r) => {
|
||||
setRes(r);
|
||||
setPhase('done');
|
||||
})
|
||||
.catch((e: unknown) => {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
setRes({ outcome: 'error', message });
|
||||
setPhase('done');
|
||||
});
|
||||
}, [open]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape' && !downloadBusy) onClose();
|
||||
};
|
||||
window.addEventListener('keydown', onKey);
|
||||
return () => window.removeEventListener('keydown', onKey);
|
||||
}, [downloadBusy, onClose, open]);
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
const body =
|
||||
phase === 'checking' || res === null ? (
|
||||
<div className={styles.muted}>{t('updates.checking')}</div>
|
||||
) : res.outcome === 'available' ? (
|
||||
<div className={styles.muted}>{t('updates.available', { version: res.version })}</div>
|
||||
) : res.outcome === 'current' ? (
|
||||
<div className={styles.muted}>{t('updates.current', { version: res.currentVersion })}</div>
|
||||
) : res.outcome === 'error' ? (
|
||||
<div className={styles.muted}>{t('updates.error', { message: res.message })}</div>
|
||||
) : res.outcome === 'not_packaged' ? (
|
||||
<div className={styles.muted}>{t('updates.notPackaged')}</div>
|
||||
) : (
|
||||
<div className={styles.muted}>{t('updates.noLicense')}</div>
|
||||
);
|
||||
|
||||
const showUpdateIdle = phase === 'done' && res !== null && res.outcome === 'available' && !downloadBusy;
|
||||
const showUpdateBusy = phase === 'done' && res !== null && res.outcome === 'available' && downloadBusy;
|
||||
|
||||
return createPortal(
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={t('common.close')}
|
||||
onClick={() => {
|
||||
if (!downloadBusy) onClose();
|
||||
}}
|
||||
className={styles.modalBackdrop}
|
||||
/>
|
||||
<div role="dialog" aria-modal="true" className={styles.modalDialog}>
|
||||
<div className={styles.modalHeader}>
|
||||
<div className={styles.modalTitle}>{t('updates.dialogTitle')}</div>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={t('common.close')}
|
||||
disabled={downloadBusy}
|
||||
onClick={onClose}
|
||||
className={styles.modalClose}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<div className={styles.fieldGrid}>{body}</div>
|
||||
<div className={styles.modalFooter}>
|
||||
{showUpdateIdle ? (
|
||||
<>
|
||||
<Button variant="ghost" disabled={downloadBusy} onClick={onClose}>
|
||||
{t('common.close')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
disabled={downloadBusy}
|
||||
onClick={() => {
|
||||
setDownloadBusy(true);
|
||||
void getDndApi()
|
||||
.invoke(ipcChannels.updater.downloadAndRestart, {})
|
||||
.then((r) => {
|
||||
if (!r.ok) {
|
||||
setDownloadBusy(false);
|
||||
setRes({ outcome: 'error', message: r.message });
|
||||
}
|
||||
})
|
||||
.catch((e: unknown) => {
|
||||
setDownloadBusy(false);
|
||||
setRes({
|
||||
outcome: 'error',
|
||||
message: e instanceof Error ? e.message : String(e),
|
||||
});
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('updates.download')}
|
||||
</Button>
|
||||
</>
|
||||
) : showUpdateBusy ? (
|
||||
<Button variant="primary" disabled>
|
||||
{t('updates.downloading')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant="primary" disabled={downloadBusy} onClick={onClose}>
|
||||
{t('common.close')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>,
|
||||
document.body,
|
||||
);
|
||||
}
|
||||
|
||||
type SimpleMessageModalProps = {
|
||||
open: boolean;
|
||||
title?: string;
|
||||
|
||||
@@ -114,10 +114,21 @@ export const EDITOR_MESSAGES: Record<EditorLocale, Record<string, string>> = {
|
||||
|
||||
'menu.enterKey': 'Указать ключ',
|
||||
'menu.aboutLicense': 'О лицензии',
|
||||
'menu.checkUpdates': 'Проверить обновления',
|
||||
'menu.language': 'Язык',
|
||||
'menu.langRu': 'Русский',
|
||||
'menu.langEn': 'English',
|
||||
|
||||
'updates.dialogTitle': 'Обновления',
|
||||
'updates.checking': 'Проверка наличия обновлений…',
|
||||
'updates.available': 'Доступна новая версия {version}.',
|
||||
'updates.current': 'У вас установлена актуальная версия ({version}).',
|
||||
'updates.error': 'Не удалось проверить обновления: {message}',
|
||||
'updates.notPackaged': 'Проверка доступна только в установленной версии приложения.',
|
||||
'updates.noLicense': 'Нужна активная лицензия.',
|
||||
'updates.download': 'Обновить',
|
||||
'updates.downloading': 'Загрузка…',
|
||||
|
||||
'projectMenu.home': 'Начальный экран',
|
||||
'projectMenu.import': 'Импорт',
|
||||
'projectMenu.export': 'Экспорт',
|
||||
@@ -330,10 +341,21 @@ export const EDITOR_MESSAGES: Record<EditorLocale, Record<string, string>> = {
|
||||
|
||||
'menu.enterKey': 'Enter license key',
|
||||
'menu.aboutLicense': 'About license',
|
||||
'menu.checkUpdates': 'Check for updates',
|
||||
'menu.language': 'Language',
|
||||
'menu.langRu': 'Русский',
|
||||
'menu.langEn': 'English',
|
||||
|
||||
'updates.dialogTitle': 'Updates',
|
||||
'updates.checking': 'Checking for updates…',
|
||||
'updates.available': 'A new version is available: {version}.',
|
||||
'updates.current': 'You have the latest version ({version}).',
|
||||
'updates.error': 'Could not check for updates: {message}',
|
||||
'updates.notPackaged': 'Updates can only be checked in the installed application.',
|
||||
'updates.noLicense': 'An active license is required.',
|
||||
'updates.download': 'Update',
|
||||
'updates.downloading': 'Downloading…',
|
||||
|
||||
'projectMenu.home': 'Home',
|
||||
'projectMenu.import': 'Import',
|
||||
'projectMenu.export': 'Export',
|
||||
|
||||
Reference in New Issue
Block a user