refactor(ui): simplify update dialog

This commit is contained in:
Siyuan 2025-11-07 16:01:09 +00:00
parent 882eb703c5
commit ba76d5bbc9
5 changed files with 91 additions and 120 deletions

View File

@ -220,6 +220,8 @@ type UpdateParams struct {
ResetConfig bool `json:"resetConfig"` ResetConfig bool `json:"resetConfig"`
} }
// getUpdateStatus gets the update status for the given components
// and updates the componentUpdateStatuses map
func (s *State) getUpdateStatus( func (s *State) getUpdateStatus(
ctx context.Context, ctx context.Context,
params UpdateParams, params UpdateParams,
@ -239,7 +241,7 @@ func (s *State) getUpdateStatus(
systemUpdate = &currentSystemUpdate systemUpdate = &currentSystemUpdate
} }
err = s.doGetUpdateStatus(ctx, params, appUpdate, systemUpdate) err = s.checkUpdateStatus(ctx, params, appUpdate, systemUpdate)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -250,21 +252,20 @@ func (s *State) getUpdateStatus(
return appUpdate, systemUpdate, nil return appUpdate, systemUpdate, nil
} }
// doGetUpdateStatus is the internal function that gets the update status // checkUpdateStatus checks the update status for the given components
// it WON'T change the state of the OTA state func (s *State) checkUpdateStatus(
func (s *State) doGetUpdateStatus(
ctx context.Context, ctx context.Context,
params UpdateParams, params UpdateParams,
appUpdate *componentUpdateStatus, appUpdateStatus *componentUpdateStatus,
systemUpdate *componentUpdateStatus, systemUpdateStatus *componentUpdateStatus,
) error { ) error {
// Get local versions // Get local versions
systemVersionLocal, appVersionLocal, err := s.getLocalVersion() systemVersionLocal, appVersionLocal, err := s.getLocalVersion()
if err != nil { if err != nil {
return fmt.Errorf("error getting local version: %w", err) return fmt.Errorf("error getting local version: %w", err)
} }
appUpdate.localVersion = appVersionLocal.String() appUpdateStatus.localVersion = appVersionLocal.String()
systemUpdate.localVersion = systemVersionLocal.String() systemUpdateStatus.localVersion = systemVersionLocal.String()
// Get remote metadata // Get remote metadata
remoteMetadata, err := s.fetchUpdateMetadata(ctx, params) remoteMetadata, err := s.fetchUpdateMetadata(ctx, params)
@ -276,13 +277,13 @@ func (s *State) doGetUpdateStatus(
} }
return err return err
} }
appUpdate.url = remoteMetadata.AppURL appUpdateStatus.url = remoteMetadata.AppURL
appUpdate.hash = remoteMetadata.AppHash appUpdateStatus.hash = remoteMetadata.AppHash
appUpdate.version = remoteMetadata.AppVersion appUpdateStatus.version = remoteMetadata.AppVersion
systemUpdate.url = remoteMetadata.SystemURL systemUpdateStatus.url = remoteMetadata.SystemURL
systemUpdate.hash = remoteMetadata.SystemHash systemUpdateStatus.hash = remoteMetadata.SystemHash
systemUpdate.version = remoteMetadata.SystemVersion systemUpdateStatus.version = remoteMetadata.SystemVersion
// Get remote versions // Get remote versions
systemVersionRemote, err := semver.NewVersion(remoteMetadata.SystemVersion) systemVersionRemote, err := semver.NewVersion(remoteMetadata.SystemVersion)
@ -290,26 +291,26 @@ func (s *State) doGetUpdateStatus(
err = fmt.Errorf("error parsing remote system version: %w", err) err = fmt.Errorf("error parsing remote system version: %w", err)
return err return err
} }
systemUpdate.available = systemVersionRemote.GreaterThan(systemVersionLocal) systemUpdateStatus.available = systemVersionRemote.GreaterThan(systemVersionLocal)
systemUpdate.downgradeAvailable = systemVersionRemote.LessThan(systemVersionLocal) systemUpdateStatus.downgradeAvailable = systemVersionRemote.LessThan(systemVersionLocal)
appVersionRemote, err := semver.NewVersion(remoteMetadata.AppVersion) appVersionRemote, err := semver.NewVersion(remoteMetadata.AppVersion)
if err != nil { if err != nil {
err = fmt.Errorf("error parsing remote app version: %w, %s", err, remoteMetadata.AppVersion) err = fmt.Errorf("error parsing remote app version: %w, %s", err, remoteMetadata.AppVersion)
return err return err
} }
appUpdate.available = appVersionRemote.GreaterThan(appVersionLocal) appUpdateStatus.available = appVersionRemote.GreaterThan(appVersionLocal)
appUpdate.downgradeAvailable = appVersionRemote.LessThan(appVersionLocal) appUpdateStatus.downgradeAvailable = appVersionRemote.LessThan(appVersionLocal)
// Handle pre-release updates // Handle pre-release updates
isRemoteSystemPreRelease := systemVersionRemote.Prerelease() != "" isRemoteSystemPreRelease := systemVersionRemote.Prerelease() != ""
isRemoteAppPreRelease := appVersionRemote.Prerelease() != "" isRemoteAppPreRelease := appVersionRemote.Prerelease() != ""
if isRemoteSystemPreRelease && !params.IncludePreRelease { if isRemoteSystemPreRelease && !params.IncludePreRelease {
systemUpdate.available = false systemUpdateStatus.available = false
} }
if isRemoteAppPreRelease && !params.IncludePreRelease { if isRemoteAppPreRelease && !params.IncludePreRelease {
appUpdate.available = false appUpdateStatus.available = false
} }
return nil return nil
@ -317,12 +318,12 @@ func (s *State) doGetUpdateStatus(
// GetUpdateStatus returns the current update status (for backwards compatibility) // GetUpdateStatus returns the current update status (for backwards compatibility)
func (s *State) GetUpdateStatus(ctx context.Context, params UpdateParams) (*UpdateStatus, error) { func (s *State) GetUpdateStatus(ctx context.Context, params UpdateParams) (*UpdateStatus, error) {
appUpdate := &componentUpdateStatus{} appUpdateStatus := componentUpdateStatus{}
systemUpdate := &componentUpdateStatus{} systemUpdateStatus := componentUpdateStatus{}
err := s.doGetUpdateStatus(ctx, params, appUpdate, systemUpdate) err := s.checkUpdateStatus(ctx, params, &appUpdateStatus, &systemUpdateStatus)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting update status: %w", err) return nil, fmt.Errorf("error getting update status: %w", err)
} }
return toUpdateStatus(appUpdate, systemUpdate, ""), nil return toUpdateStatus(&appUpdateStatus, &systemUpdateStatus, ""), nil
} }

4
ota.go
View File

@ -135,8 +135,8 @@ func rpcGetLocalVersion() (*ota.LocalMetadata, error) {
} }
type updateParams struct { type updateParams struct {
AppTargetVersion string `json:"app"` AppTargetVersion string `json:"appTargetVersion"`
SystemTargetVersion string `json:"system"` SystemTargetVersion string `json:"systemTargetVersion"`
Components string `json:"components,omitempty"` // components is a comma-separated list of components to update Components string `json:"components,omitempty"` // components is a comma-separated list of components to update
} }

View File

@ -204,9 +204,8 @@ export default function SettingsAdvancedRoute() {
setVersionUpdateLoading(true); setVersionUpdateLoading(true);
versionInfo = await checkUpdateComponents({ versionInfo = await checkUpdateComponents({
components: components.join(","), components: components.join(","),
// TODO: Rename to appTargetVersion and systemTargetVersion appTargetVersion: appVersion,
app: appVersion, systemTargetVersion: systemVersion,
system: systemVersion,
}, devChannel); }, devChannel);
console.log("versionInfo", versionInfo); console.log("versionInfo", versionInfo);
} catch (error: unknown) { } catch (error: unknown) {
@ -216,7 +215,6 @@ export default function SettingsAdvancedRoute() {
} }
const pageParams = new URLSearchParams(); const pageParams = new URLSearchParams();
pageParams.set("downgrade", "true");
if (components.includes("app") && versionInfo.remote?.appVersion && versionInfo.appDowngradeAvailable) { if (components.includes("app") && versionInfo.remote?.appVersion && versionInfo.appDowngradeAvailable) {
pageParams.set("app", versionInfo.remote?.appVersion); pageParams.set("app", versionInfo.remote?.appVersion);
} }

View File

@ -11,7 +11,7 @@ import LoadingSpinner from "@components/LoadingSpinner";
import UpdatingStatusCard, { type UpdatePart } from "@components/UpdatingStatusCard"; import UpdatingStatusCard, { type UpdatePart } from "@components/UpdatingStatusCard";
import { m } from "@localizations/messages.js"; import { m } from "@localizations/messages.js";
import { sleep } from "@/utils"; import { sleep } from "@/utils";
import { SystemVersionInfo } from "@/utils/jsonrpc"; import { checkUpdateComponents, SystemVersionInfo, updateParams } from "@/utils/jsonrpc";
export default function SettingsGeneralUpdateRoute() { export default function SettingsGeneralUpdateRoute() {
const navigate = useNavigate(); const navigate = useNavigate();
@ -38,20 +38,16 @@ export default function SettingsGeneralUpdateRoute() {
setModalView("updating"); setModalView("updating");
}, [send, setModalView]); }, [send, setModalView]);
const onConfirmDowngrade = useCallback(() => { const onConfirmCustomUpdate = useCallback((appTargetVersion?: string, systemTargetVersion?: string) => {
const components = []; const components = [];
if (customSystemVersion) { if (appTargetVersion) components.push("system");
components.push("system"); if (systemTargetVersion) components.push("app");
}
if (customAppVersion) {
components.push("app");
}
send("tryUpdateComponents", { send("tryUpdateComponents", {
params: { params: {
components: components.join(","), components: components.join(","),
app: customAppVersion, appTargetVersion,
system: customSystemVersion, systemTargetVersion,
}, },
includePreRelease: false, includePreRelease: false,
resetConfig, resetConfig,
@ -59,7 +55,7 @@ export default function SettingsGeneralUpdateRoute() {
if ("error" in resp) return; if ("error" in resp) return;
setModalView("updating"); setModalView("updating");
}); });
}, [send, setModalView, customAppVersion, customSystemVersion, resetConfig]); }, [send, setModalView, resetConfig]);
useEffect(() => { useEffect(() => {
if (otaState.updating) { if (otaState.updating) {
@ -76,7 +72,7 @@ export default function SettingsGeneralUpdateRoute() {
return <Dialog return <Dialog
onClose={onClose} onClose={onClose}
onConfirmUpdate={onConfirmUpdate} onConfirmUpdate={onConfirmUpdate}
onConfirmDowngrade={onConfirmDowngrade} onConfirmCustomUpdate={onConfirmCustomUpdate}
customAppVersion={customAppVersion} customAppVersion={customAppVersion}
customSystemVersion={customSystemVersion} customSystemVersion={customSystemVersion}
/>; />;
@ -85,13 +81,13 @@ export default function SettingsGeneralUpdateRoute() {
export function Dialog({ export function Dialog({
onClose, onClose,
onConfirmUpdate, onConfirmUpdate,
onConfirmDowngrade, onConfirmCustomUpdate: onConfirmCustomUpdateCallback,
customAppVersion, customAppVersion,
customSystemVersion, customSystemVersion,
}: Readonly<{ }: Readonly<{
onClose: () => void; onClose: () => void;
onConfirmUpdate: () => void; onConfirmUpdate: () => void;
onConfirmDowngrade: () => void; onConfirmCustomUpdate: (appVersion?: string, systemVersion?: string) => void;
customAppVersion?: string; customAppVersion?: string;
customSystemVersion?: string; customSystemVersion?: string;
}>) { }>) {
@ -99,32 +95,30 @@ export function Dialog({
const [versionInfo, setVersionInfo] = useState<null | SystemVersionInfo>(null); const [versionInfo, setVersionInfo] = useState<null | SystemVersionInfo>(null);
const { modalView, setModalView, otaState } = useUpdateStore(); const { modalView, setModalView, otaState } = useUpdateStore();
const { send } = useJsonRpc(); const forceCustomUpdate = customSystemVersion !== undefined || customAppVersion !== undefined;
const onConfirmCustomUpdate = useCallback(() => {
onConfirmCustomUpdateCallback(
customAppVersion !== undefined ? customAppVersion : versionInfo?.remote?.appVersion,
customSystemVersion !== undefined ? customSystemVersion : versionInfo?.remote?.systemVersion,
);
}, [onConfirmCustomUpdateCallback, customAppVersion, customSystemVersion, versionInfo]);
const onFinishedLoading = useCallback( const onFinishedLoading = useCallback(
(versionInfo: SystemVersionInfo) => { (versionInfo: SystemVersionInfo) => {
const hasUpdate = const hasUpdate =
versionInfo?.systemUpdateAvailable || versionInfo?.appUpdateAvailable; versionInfo?.systemUpdateAvailable || versionInfo?.appUpdateAvailable;
const forceCustomUpdate = customSystemVersion !== undefined || customAppVersion !== undefined;
setVersionInfo(versionInfo); setVersionInfo(versionInfo);
if (forceCustomUpdate) { if (hasUpdate || forceCustomUpdate) {
setModalView("confirmCustomUpdate");
} else if (hasUpdate) {
setModalView("updateAvailable"); setModalView("updateAvailable");
} else { } else {
setModalView("upToDate"); setModalView("upToDate");
} }
}, },
[setModalView, customAppVersion, customSystemVersion], [setModalView, forceCustomUpdate],
); );
const onCancelDowngrade = useCallback(() => {
send("cancelDowngrade", {});
onClose();
}, [onClose, send]);
return ( return (
<div className="pointer-events-auto relative mx-auto text-left"> <div className="pointer-events-auto relative mx-auto text-left">
<div> <div>
@ -137,24 +131,22 @@ export function Dialog({
)} )}
{modalView === "loading" && ( {modalView === "loading" && (
<LoadingState onFinished={onFinishedLoading} onCancelCheck={onClose} /> <LoadingState
onFinished={onFinishedLoading}
onCancelCheck={onClose}
customAppVersion={customAppVersion}
customSystemVersion={customSystemVersion}
/>
)} )}
{modalView === "updateAvailable" && ( {modalView === "updateAvailable" && (
<UpdateAvailableState <UpdateAvailableState
onConfirmUpdate={onConfirmUpdate} forceCustomUpdate={forceCustomUpdate}
onConfirm={forceCustomUpdate ? onConfirmCustomUpdate : onConfirmUpdate}
onClose={onClose} onClose={onClose}
versionInfo={versionInfo!} versionInfo={versionInfo!}
/> />
)} )}
{modalView === "confirmCustomUpdate" && (
<ConfirmCustomUpdate
appVersion={customAppVersion}
systemVersion={customSystemVersion}
onConfirm={onConfirmDowngrade}
onCancel={onCancelDowngrade}
/>
)}
{modalView === "updating" && ( {modalView === "updating" && (
<UpdatingDeviceState <UpdatingDeviceState
@ -179,9 +171,13 @@ export function Dialog({
function LoadingState({ function LoadingState({
onFinished, onFinished,
onCancelCheck, onCancelCheck,
customAppVersion,
customSystemVersion,
}: { }: {
onFinished: (versionInfo: SystemVersionInfo) => void; onFinished: (versionInfo: SystemVersionInfo) => void;
onCancelCheck: () => void; onCancelCheck: () => void;
customAppVersion?: string;
customSystemVersion?: string;
}) { }) {
const [progressWidth, setProgressWidth] = useState("0%"); const [progressWidth, setProgressWidth] = useState("0%");
const abortControllerRef = useRef<AbortController | null>(null); const abortControllerRef = useRef<AbortController | null>(null);
@ -190,6 +186,23 @@ function LoadingState({
const { setModalView } = useUpdateStore(); const { setModalView } = useUpdateStore();
const progressBarRef = useRef<HTMLDivElement>(null); const progressBarRef = useRef<HTMLDivElement>(null);
const checkUpdate = useCallback(async () => {
if (!customAppVersion && !customSystemVersion) {
return await getVersionInfo();
}
const params : updateParams = {
components: "",
appTargetVersion: customAppVersion,
systemTargetVersion: customSystemVersion,
};
if (customAppVersion) params.components += ",app";
if (customSystemVersion) params.components += ",system";
params.components = params.components?.replace(/^,+/, "");
return await checkUpdateComponents(params, false);
}, [customAppVersion, customSystemVersion, getVersionInfo]);
useEffect(() => { useEffect(() => {
abortControllerRef.current = new AbortController(); abortControllerRef.current = new AbortController();
const signal = abortControllerRef.current.signal; const signal = abortControllerRef.current.signal;
@ -199,8 +212,7 @@ function LoadingState({
setProgressWidth("100%"); setProgressWidth("100%");
}, 0); }, 0);
// TODO: CHECK FOR QUERY PARAMS checkUpdate()
getVersionInfo()
.then(async versionInfo => { .then(async versionInfo => {
// Add a small delay to ensure it's not just flickering // Add a small delay to ensure it's not just flickering
await sleep(600); await sleep(600);
@ -222,7 +234,7 @@ function LoadingState({
clearTimeout(animationTimer); clearTimeout(animationTimer);
abortControllerRef.current?.abort(); abortControllerRef.current?.abort();
}; };
}, [getVersionInfo, onFinished, setModalView]); }, [checkUpdate, onFinished, setModalView]);
return ( return (
<div className="flex flex-col items-start justify-start space-y-4 text-left"> <div className="flex flex-col items-start justify-start space-y-4 text-left">
@ -430,11 +442,13 @@ function SystemUpToDateState({
function UpdateAvailableState({ function UpdateAvailableState({
versionInfo, versionInfo,
onConfirmUpdate, forceCustomUpdate,
onConfirm,
onClose, onClose,
}: { }: {
versionInfo: SystemVersionInfo; versionInfo: SystemVersionInfo;
onConfirmUpdate: () => void; forceCustomUpdate: boolean;
onConfirm: () => void;
onClose: () => void; onClose: () => void;
}) { }) {
return ( return (
@ -444,23 +458,23 @@ function UpdateAvailableState({
{m.general_update_available_title()} {m.general_update_available_title()}
</p> </p>
<p className="mb-2 text-sm text-slate-600 dark:text-slate-300"> <p className="mb-2 text-sm text-slate-600 dark:text-slate-300">
{m.general_update_available_description()} {forceCustomUpdate ? m.general_update_downgrade_available_description() : m.general_update_available_description()}
</p> </p>
<p className="mb-4 text-sm text-slate-600 dark:text-slate-300"> <p className="mb-4 text-sm text-slate-600 dark:text-slate-300">
{versionInfo?.systemUpdateAvailable ? ( {(forceCustomUpdate ? versionInfo?.systemDowngradeAvailable : versionInfo?.systemUpdateAvailable) ? (
<> <>
<span className="font-semibold">{m.general_update_system_type()}</span>: {versionInfo?.remote?.systemVersion} <span className="font-semibold">{m.general_update_system_type()}</span>: {versionInfo?.local?.systemVersion} <span className="text-slate-600 dark:text-slate-300"></span> {versionInfo?.remote?.systemVersion}
<br /> <br />
</> </>
) : null} ) : null}
{versionInfo?.appUpdateAvailable ? ( {(forceCustomUpdate ? versionInfo?.appDowngradeAvailable : versionInfo?.appUpdateAvailable) ? (
<> <>
<span className="font-semibold">{m.general_update_application_type()}</span>: {versionInfo?.remote?.appVersion} <span className="font-semibold">{m.general_update_application_type()}</span>: {versionInfo?.local?.appVersion} <span className="text-slate-600 dark:text-slate-300"></span> {versionInfo?.remote?.appVersion}
</> </>
) : null} ) : null}
</p> </p>
<div className="flex items-center justify-start gap-x-2"> <div className="flex items-center justify-start gap-x-2">
<Button size="SM" theme="primary" text={m.general_update_now_button()} onClick={onConfirmUpdate} /> <Button size="SM" theme={forceCustomUpdate ? "danger" : "primary"} text={forceCustomUpdate ? m.general_update_downgrade_button() : m.general_update_now_button()} onClick={onConfirm} />
<Button size="SM" theme="light" text={m.general_update_later_button()} onClick={onClose} /> <Button size="SM" theme="light" text={m.general_update_later_button()} onClick={onClose} />
</div> </div>
</div> </div>
@ -468,48 +482,6 @@ function UpdateAvailableState({
); );
} }
function ConfirmCustomUpdate({
appVersion,
systemVersion,
onConfirm,
onCancel,
}: {
appVersion?: string;
systemVersion?: string;
onConfirm: () => void;
onCancel: () => void;
}) {
return (
<div className="flex flex-col items-start justify-start space-y-4 text-left">
<div className="text-left">
<p className="text-base font-semibold text-black dark:text-white">
{m.general_update_downgrade_available_title()}
</p>
<p className="mb-2 text-sm text-slate-600 dark:text-slate-300">
{m.general_update_downgrade_available_description()}
</p>
<p className="mb-4 text-sm text-slate-600 dark:text-slate-300">
{systemVersion ? (
<>
<span className="font-semibold">{m.general_update_system_type()}</span>: {systemVersion}
<br />
</>
) : null}
{appVersion ? (
<>
<span className="font-semibold">{m.general_update_application_type()}</span>: {appVersion}
</>
) : null}
</p>
<div className="flex items-center justify-start gap-x-2">
<Button size="SM" theme="primary" text={m.general_update_downgrade_button()} onClick={onConfirm} />
<Button size="SM" theme="light" text={m.general_update_keep_current_button()} onClick={onCancel} />
</div>
</div>
</div>
);
}
function UpdateCompletedState({ onClose }: { onClose: () => void }) { function UpdateCompletedState({ onClose }: { onClose: () => void }) {
return ( return (
<div className="flex flex-col items-start justify-start space-y-4 text-left"> <div className="flex flex-col items-start justify-start space-y-4 text-left">

View File

@ -246,8 +246,8 @@ export async function getLocalVersion() {
} }
export interface updateParams { export interface updateParams {
app?: string; appTargetVersion?: string;
system?: string; systemTargetVersion?: string;
components?: string; components?: string;
} }