mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
xxx
This commit is contained in:
379
gui/src/App.tsx
379
gui/src/App.tsx
File diff suppressed because one or more lines are too long
@@ -1,6 +1,7 @@
|
||||
import { DOCS_SITE, GH_REPO, VersionContext } from '@/App';
|
||||
import { DOCS_SITE, GH_REPO } from '@/App';
|
||||
import { useBreakpoint, useIsTauri } from '@/hooks/breakpoint';
|
||||
import { STABLE_CHANNEL, useConfig } from '@/hooks/config';
|
||||
import { useUpdateContext } from '@/hooks/update.js';
|
||||
import { useWebsocketAPI } from '@/hooks/websocket-api';
|
||||
import { connectedIMUTrackersAtom } from '@/store/app-store';
|
||||
import { error } from '@/utils/logging';
|
||||
@@ -15,7 +16,7 @@ import {
|
||||
import { open } from '@tauri-apps/plugin-shell';
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { ReactNode, useContext, useEffect, useState } from 'react';
|
||||
import { ReactNode, useEffect, useState } from 'react';
|
||||
import { NavLink, useMatch } from 'react-router-dom';
|
||||
import {
|
||||
RpcMessage,
|
||||
@@ -94,7 +95,7 @@ export function TopBar({
|
||||
const { useRPCPacket, sendRPCPacket } = useWebsocketAPI();
|
||||
const connectedIMUTrackers = useAtomValue(connectedIMUTrackersAtom);
|
||||
const { config, setConfig, saveConfig } = useConfig();
|
||||
const version = useContext(VersionContext);
|
||||
const { isUpToDate } = useUpdateContext();
|
||||
const [localIp, setLocalIp] = useState<string | null>(null);
|
||||
const [showConnectedTrackersWarning, setConnectedTrackerWarning] =
|
||||
useState(false);
|
||||
@@ -224,7 +225,7 @@ export function TopBar({
|
||||
</>
|
||||
)}
|
||||
|
||||
{version && (
|
||||
{!isUpToDate && (
|
||||
<div
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
import { GH_REPO } from '@/App';
|
||||
import { useUpdateContext } from '@/hooks/update.js';
|
||||
import { error } from '@/utils/logging';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
import { useContext, useState } from 'react';
|
||||
import { open } from '@tauri-apps/plugin-shell';
|
||||
import { useState } from 'react';
|
||||
import semver from 'semver';
|
||||
import { BaseModal } from './commons/BaseModal';
|
||||
import { Button } from './commons/Button';
|
||||
import { Typography } from './commons/Typography';
|
||||
import { open } from '@tauri-apps/plugin-shell';
|
||||
import semver from 'semver';
|
||||
import { GH_REPO, VersionContext } from '@/App';
|
||||
import { error } from '@/utils/logging';
|
||||
|
||||
export function VersionUpdateModal() {
|
||||
const { l10n } = useLocalization();
|
||||
const newVersion = useContext(VersionContext);
|
||||
const { latestVersionOnChannel: newVersion } = useUpdateContext();
|
||||
const [forceClose, setForceClose] = useState(false);
|
||||
const closeModal = () => {
|
||||
localStorage.setItem('lastVersionFound', newVersion);
|
||||
localStorage.setItem('lastVersionFound', newVersion ?? '');
|
||||
setForceClose(true);
|
||||
};
|
||||
let isVersionNew = false;
|
||||
try {
|
||||
// TODO(devminer): check over this, if this is still necessary
|
||||
if (newVersion) {
|
||||
isVersionNew = semver.gt(
|
||||
newVersion,
|
||||
@@ -28,6 +30,8 @@ export function VersionUpdateModal() {
|
||||
error('failed to parse new version');
|
||||
}
|
||||
|
||||
if (newVersion === null) return null;
|
||||
|
||||
return (
|
||||
<BaseModal
|
||||
isOpen={!forceClose && !!newVersion && isVersionNew}
|
||||
|
||||
427
gui/src/components/providers/UpdateContext.tsx
Normal file
427
gui/src/components/providers/UpdateContext.tsx
Normal file
File diff suppressed because one or more lines are too long
@@ -1,8 +1,7 @@
|
||||
import { UpdateManifestContext } from '@/App.js';
|
||||
import { Button } from '@/components/commons/Button.js';
|
||||
import { SteamIcon } from '@/components/commons/icon/SteamIcon';
|
||||
import { Input } from '@/components/commons/Input.js';
|
||||
import { MarkdownLink } from '@/components/commons/MarkdownLink.js';
|
||||
import { Tooltip } from '@/components/commons/Tooltip.js';
|
||||
import { Typography } from '@/components/commons/Typography';
|
||||
import { UpdateChannelOptions } from '@/components/settings/pages/components/UpdateChannelOptions.js';
|
||||
import { UpdateChannelVersionOptions } from '@/components/settings/pages/components/UpdateVersionOptions.js';
|
||||
@@ -12,10 +11,11 @@ import {
|
||||
} from '@/components/settings/SettingsPageLayout';
|
||||
import { useBreakpoint } from '@/hooks/breakpoint.js';
|
||||
import { defaultConfig, useConfig } from '@/hooks/config';
|
||||
import { Localized, useLocalization } from '@fluent/react';
|
||||
import { UpdateManifest, type ChannelName } from '@slimevr/update-manifest';
|
||||
import { useUpdateContext } from '@/hooks/update.js';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
import { type ChannelName, type Version } from '@slimevr/update-manifest';
|
||||
import classNames from 'classnames';
|
||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import Markdown from 'react-markdown';
|
||||
import remark from 'remark-gfm';
|
||||
@@ -30,13 +30,15 @@ export function UpdateSettings() {
|
||||
const { config, setConfig } = useConfig();
|
||||
const { isMobile } = useBreakpoint('mobile');
|
||||
|
||||
const updateManifest = useContext(UpdateManifestContext);
|
||||
const { manifest: updateManifest } = useUpdateContext();
|
||||
|
||||
const { reset, control, watch, handleSubmit, getValues, setValue } =
|
||||
useForm<SettingsForm>({
|
||||
defaultValues: {
|
||||
channel: config?.updateChannel ?? defaultConfig.updateChannel,
|
||||
autoUpdate: config?.autoUpdate ?? defaultConfig.autoUpdate,
|
||||
autoUpdate:
|
||||
config?.notifyOnAvailableUpdates ??
|
||||
defaultConfig.notifyOnAvailableUpdates,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -45,7 +47,7 @@ export function UpdateSettings() {
|
||||
const onSubmit = (values: SettingsForm) => {
|
||||
setConfig({
|
||||
updateChannel: values.channel,
|
||||
autoUpdate: values.autoUpdate,
|
||||
notifyOnAvailableUpdates: values.autoUpdate,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -88,11 +90,35 @@ export function UpdateSettings() {
|
||||
value={channel}
|
||||
variant={isMobile ? 'dropdown' : 'radio'}
|
||||
onSelect={(channel) => setValue('channel', channel)}
|
||||
asList={false}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
</SettingsPagePaneLayout>
|
||||
|
||||
<SettingsPagePaneLayout icon={<SteamIcon />} id="change_system">
|
||||
<>
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('settings-general-change_system')}
|
||||
</Typography>
|
||||
<Typography bold>
|
||||
{l10n.getString('settings-general-change_system-subtitle')}
|
||||
</Typography>
|
||||
<div className="flex flex-col py-2">
|
||||
{l10n
|
||||
.getString('settings-general-change_system-description')
|
||||
.split('\n')
|
||||
.map((line, i) => (
|
||||
<Typography color="secondary" key={i}>
|
||||
{line}
|
||||
</Typography>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<ChangeSystem />
|
||||
</>
|
||||
</SettingsPagePaneLayout>
|
||||
|
||||
<SettingsPagePaneLayout icon={<SteamIcon />} id="change_version">
|
||||
<>
|
||||
<Typography variant="main-title">
|
||||
@@ -112,7 +138,7 @@ export function UpdateSettings() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<ChangeVersion updateManifest={updateManifest} />
|
||||
<ChangeVersion />
|
||||
</>
|
||||
</SettingsPagePaneLayout>
|
||||
</form>
|
||||
@@ -120,28 +146,22 @@ export function UpdateSettings() {
|
||||
);
|
||||
}
|
||||
|
||||
function ChangeVersion({ updateManifest }: { updateManifest: UpdateManifest }) {
|
||||
function ChangeVersion() {
|
||||
const { isMobile } = useBreakpoint('mobile');
|
||||
|
||||
const { config } = useConfig();
|
||||
const currentChannel = config?.updateChannel ?? defaultConfig.updateChannel;
|
||||
|
||||
const {
|
||||
manifest,
|
||||
channel: currentChannel,
|
||||
checkCompatibility,
|
||||
} = useUpdateContext();
|
||||
const [channel, setChannel] = useState<ChannelName>(currentChannel);
|
||||
const [version, setVersion] = useState(
|
||||
__VERSION_TAG__ in updateManifest.channels[channel].versions
|
||||
? __VERSION_TAG__
|
||||
: ''
|
||||
const [version, setVersion] = useState<Version | null>(
|
||||
manifest && __VERSION_TAG__ in manifest.channels[channel].versions
|
||||
? (__VERSION_TAG__ as Version)
|
||||
: null
|
||||
);
|
||||
|
||||
const ch = updateManifest.channels[channel] ?? null;
|
||||
const v = ch?.versions[version] ?? null;
|
||||
|
||||
const isAlreadyInstalled =
|
||||
channel === currentChannel && version === __VERSION_TAG__;
|
||||
// TODO(devminer): correctly figure out the current platform
|
||||
const hasBuildAvailableForThisPlatform =
|
||||
v && 'windows' in v.builds && 'x86_64' in v.builds['windows'];
|
||||
const isInstallable = !isAlreadyInstalled && hasBuildAvailableForThisPlatform;
|
||||
const res = version && checkCompatibility(channel, version);
|
||||
|
||||
const install = useCallback(() => {}, []);
|
||||
|
||||
@@ -150,33 +170,41 @@ function ChangeVersion({ updateManifest }: { updateManifest: UpdateManifest }) {
|
||||
<div className="md:w-1/4">
|
||||
<Typography>Channel</Typography>
|
||||
<div className="flex flex-col md:gap-4 sm:gap-2 xs:gap-1 mobile:gap-4 max-h-[384px] md:max-h-[512px] pr-1">
|
||||
<UpdateChannelOptions
|
||||
manifest={updateManifest}
|
||||
value={channel}
|
||||
variant={isMobile ? 'dropdown' : 'radio'}
|
||||
onSelect={(channel) => {
|
||||
setChannel(channel);
|
||||
setVersion('');
|
||||
}}
|
||||
/>
|
||||
{manifest && (
|
||||
<UpdateChannelOptions
|
||||
manifest={manifest}
|
||||
value={channel}
|
||||
variant={isMobile ? 'dropdown' : 'radio'}
|
||||
onSelect={(channel) => {
|
||||
setChannel(channel);
|
||||
setVersion(null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="md:w-1/4">
|
||||
<Typography>Version</Typography>
|
||||
<div className="flex flex-col md:gap-4 sm:gap-2 xs:gap-1 mobile:gap-4 max-h-[384px] md:max-h-[512px] overflow-auto pr-1">
|
||||
<UpdateChannelVersionOptions
|
||||
manifest={updateManifest}
|
||||
channel={channel}
|
||||
value={version}
|
||||
variant={isMobile ? 'dropdown' : 'radio'}
|
||||
onSelect={setVersion}
|
||||
/>
|
||||
{config?.debug && (
|
||||
<div className="md:w-1/4">
|
||||
<Typography>Version</Typography>
|
||||
<div className="flex flex-col md:gap-4 sm:gap-2 xs:gap-1 mobile:gap-4 max-h-[384px] md:max-h-[512px] overflow-auto pr-1">
|
||||
{manifest && (
|
||||
<UpdateChannelVersionOptions
|
||||
manifest={manifest}
|
||||
channel={channel}
|
||||
value={version ?? ''}
|
||||
variant={isMobile ? 'dropdown' : 'radio'}
|
||||
onSelect={(version) =>
|
||||
setVersion(version === '' ? null : (version as Version))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="md:w-1/2 flex flex-col gap-2">
|
||||
{v && (
|
||||
{res?.version && (
|
||||
<>
|
||||
<Typography>Release Notes</Typography>
|
||||
<div className="bg-background-60 rounded-lg px-3 py-2 max-h-[512px] overflow-auto">
|
||||
@@ -189,27 +217,14 @@ function ChangeVersion({ updateManifest }: { updateManifest: UpdateManifest }) {
|
||||
'prose-code:text-background-20'
|
||||
)}
|
||||
>
|
||||
{v.release_notes}
|
||||
{res.version.release_notes}
|
||||
</Markdown>
|
||||
</div>
|
||||
<div className="inline ml-auto w-fit">
|
||||
<Tooltip
|
||||
content={
|
||||
<Localized id="X">
|
||||
<Typography
|
||||
variant="standard"
|
||||
whitespace="whitespace-pre-wrap"
|
||||
/>
|
||||
</Localized>
|
||||
}
|
||||
preferedDirection="bottom"
|
||||
mode="corner"
|
||||
>
|
||||
{/* TODO(devminer): add translations */}
|
||||
<Button variant="primary" disabled={!isInstallable}>
|
||||
Install
|
||||
</Button>
|
||||
</Tooltip>
|
||||
{/* TODO(devminer): add translations */}
|
||||
<Button variant="primary" disabled={!res.isInstallable}>
|
||||
Install
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@@ -217,3 +232,63 @@ function ChangeVersion({ updateManifest }: { updateManifest: UpdateManifest }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type ChangeSYstemForm = {
|
||||
platform: string;
|
||||
architecture: string;
|
||||
};
|
||||
|
||||
function ChangeSystem() {
|
||||
const { platform, architecture, changeSystem } = useUpdateContext();
|
||||
|
||||
const { reset, control, watch, handleSubmit, getValues, setValue } =
|
||||
useForm<ChangeSYstemForm>({
|
||||
defaultValues: {
|
||||
platform,
|
||||
architecture,
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = (values: ChangeSYstemForm) => {
|
||||
console.log(values);
|
||||
changeSystem(values.platform, values.architecture);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = watch(() => handleSubmit(onSubmit)());
|
||||
return () => subscription.unsubscribe();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<form className="flex flex-col gap-2" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="grid gap-2 items-center grid-rows-2 grid-cols-[fit-content(20%),1fr]">
|
||||
<Typography bold variant="vr-accessible">
|
||||
Platform
|
||||
</Typography>
|
||||
<Input control={control} name="platform" placeholder={platform} />
|
||||
<Typography bold variant="vr-accessible">
|
||||
Architecture
|
||||
</Typography>
|
||||
<Input
|
||||
control={control}
|
||||
name="architecture"
|
||||
placeholder={architecture}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="secondary"
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
reset();
|
||||
}}
|
||||
>
|
||||
Reset to real values
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Typography } from '@/components/commons/Typography';
|
||||
import { ASSIGNMENT_MODES } from '@/components/onboarding/BodyAssignment';
|
||||
import { AssignMode, defaultConfig } from '@/hooks/config';
|
||||
import { UpdateManifest, type ChannelName } from '@slimevr/update-manifest';
|
||||
import classNames from 'classnames';
|
||||
import { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
@@ -33,7 +34,9 @@ const ItemContent = ({
|
||||
|
||||
{channel === defaultConfig.updateChannel && (
|
||||
/* TODO(devminer): add translations */
|
||||
<div className="bg-background-70 px-1.5 py-1 rounded-md">default</div>
|
||||
<div className="bg-background-70 px-1.5 py-0.5 rounded-md leading-[1rem] text-[0.625rem]">
|
||||
default
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -47,13 +50,15 @@ const ItemContent = ({
|
||||
export function UpdateChannelOptions({
|
||||
manifest,
|
||||
value,
|
||||
variant = 'radio',
|
||||
onSelect,
|
||||
variant = 'radio',
|
||||
asList = true,
|
||||
}: {
|
||||
manifest: UpdateManifest;
|
||||
value: ChannelName;
|
||||
variant: 'radio' | 'dropdown';
|
||||
onSelect: (channel: ChannelName) => void;
|
||||
variant?: 'radio' | 'dropdown';
|
||||
asList?: boolean;
|
||||
}) {
|
||||
const { control, watch } = useForm<{
|
||||
updateChannel: ChannelName;
|
||||
@@ -91,7 +96,7 @@ export function UpdateChannelOptions({
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className={classNames('flex gap-2', asList && 'flex-col')}>
|
||||
{Object.entries(manifest.channels).map(([channel, data]) => (
|
||||
<Radio
|
||||
key={channel}
|
||||
|
||||
@@ -2,7 +2,12 @@ import { Dropdown } from '@/components/commons/Dropdown';
|
||||
import { WarningIcon } from '@/components/commons/icon/WarningIcon.js';
|
||||
import { Radio } from '@/components/commons/Radio';
|
||||
import { Typography } from '@/components/commons/Typography';
|
||||
import { UpdateManifest, type ChannelName } from '@slimevr/update-manifest';
|
||||
import { useUpdateContext } from '@/hooks/update.js';
|
||||
import {
|
||||
UpdateManifest,
|
||||
Version,
|
||||
type ChannelName,
|
||||
} from '@slimevr/update-manifest';
|
||||
import { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { compare } from 'semver';
|
||||
@@ -25,26 +30,30 @@ const ItemContent = ({
|
||||
|
||||
{isLatest && (
|
||||
/* TODO(devminer): add translations */
|
||||
<div className="bg-background-70 px-1.5 py-1 rounded-md">latest</div>
|
||||
<div className="bg-background-70 px-1.5 py-0.5 rounded-md leading-[1rem] text-[0.625rem]">
|
||||
latest
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Typography variant="standard" color="secondary">
|
||||
{isAlreadyInstalled && (
|
||||
<div className="text-yellow-background-300 flex gap-1 items-center">
|
||||
<WarningIcon className="size-5 min-w-5" />
|
||||
{/* TODO(devminer): add translations */}
|
||||
{isAlreadyInstalled && (
|
||||
<div className="text-yellow-background-300 flex gap-1 items-center">
|
||||
<WarningIcon className="size-5 min-w-5" />
|
||||
{/* TODO(devminer): add translations */}
|
||||
<Typography variant="standard" color="secondary">
|
||||
already installed
|
||||
</div>
|
||||
)}
|
||||
{!hasBuildsAvailableForThisPlatform && (
|
||||
<div className="text-yellow-background-300 flex gap-1 items-center">
|
||||
<WarningIcon className="size-5 min-w-5" />
|
||||
{/* TODO(devminer): add translations */}
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
{!hasBuildsAvailableForThisPlatform && (
|
||||
<div className="text-yellow-background-300 flex gap-1 items-center">
|
||||
<WarningIcon className="size-5 min-w-5" />
|
||||
{/* TODO(devminer): add translations */}
|
||||
<Typography variant="standard" color="secondary">
|
||||
no build available for your platform
|
||||
</div>
|
||||
)}
|
||||
</Typography>
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -62,6 +71,8 @@ export function UpdateChannelVersionOptions({
|
||||
variant: 'radio' | 'dropdown';
|
||||
onSelect: (version: string) => void;
|
||||
}) {
|
||||
const { channel: currentChannel, checkCompatibilityFromVersionInfo } =
|
||||
useUpdateContext();
|
||||
const { control, watch } = useForm<{
|
||||
version: string;
|
||||
}>({
|
||||
@@ -87,32 +98,27 @@ export function UpdateChannelVersionOptions({
|
||||
placeholder=""
|
||||
maxHeight="300px"
|
||||
items={Object.entries(ch.versions)
|
||||
.map(([tag, version]) => [tag, version] as const)
|
||||
.map(([tag, version]) => [tag as Version, version] as const)
|
||||
.sort(([a, _12], [b, _22]) => compare(b, a))
|
||||
.map(([tag, version]) => {
|
||||
const isAlreadyInstalled = tag === __VERSION_TAG__;
|
||||
// TODO(devminer): correctly figure out the current platform
|
||||
const hasBuildAvailableForThisPlatform =
|
||||
'windows' in version.builds &&
|
||||
'x86_64' in version.builds['windows'];
|
||||
const isInstallable =
|
||||
!isAlreadyInstalled && hasBuildAvailableForThisPlatform;
|
||||
.map(([version, versionInfo]) => {
|
||||
const res = checkCompatibilityFromVersionInfo(
|
||||
channel,
|
||||
version,
|
||||
versionInfo
|
||||
);
|
||||
|
||||
return {
|
||||
disabled: !isInstallable,
|
||||
value: version,
|
||||
component: (
|
||||
<div className="flex flex-row gap-2 py-1 text-left">
|
||||
<ItemContent
|
||||
version={tag}
|
||||
isLatest={ch.current_version === tag}
|
||||
isAlreadyInstalled={isAlreadyInstalled}
|
||||
hasBuildsAvailableForThisPlatform={
|
||||
hasBuildAvailableForThisPlatform
|
||||
}
|
||||
version={version}
|
||||
isLatest={ch.current_version === version}
|
||||
isAlreadyInstalled={res.alreadyInstalled}
|
||||
hasBuildsAvailableForThisPlatform={!!res.build}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
value: tag,
|
||||
};
|
||||
})}
|
||||
/>
|
||||
@@ -121,34 +127,29 @@ export function UpdateChannelVersionOptions({
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
{Object.entries(ch.versions)
|
||||
.map(([tag, version]) => [tag, version] as const)
|
||||
.map(([tag, version]) => [tag as Version, version] as const)
|
||||
.sort(([a, _12], [b, _22]) => compare(b, a))
|
||||
.map(([tag, version]) => {
|
||||
const isAlreadyInstalled = tag === __VERSION_TAG__;
|
||||
// TODO(devminer): correctly figure out the current platform
|
||||
const hasBuildAvailableForThisPlatform =
|
||||
'windows' in version.builds &&
|
||||
'x86_64' in version.builds['windows'];
|
||||
const isInstallable =
|
||||
!isAlreadyInstalled && hasBuildAvailableForThisPlatform;
|
||||
.map(([version, versionInfo]) => {
|
||||
const res = checkCompatibilityFromVersionInfo(
|
||||
channel,
|
||||
version,
|
||||
versionInfo
|
||||
);
|
||||
|
||||
return (
|
||||
<Radio
|
||||
key={tag}
|
||||
key={version}
|
||||
name="version"
|
||||
control={control}
|
||||
value={tag}
|
||||
value={version}
|
||||
className="hidden"
|
||||
disabled={!isInstallable}
|
||||
>
|
||||
<div className="flex flex-row md:gap-4 gap-2">
|
||||
<ItemContent
|
||||
version={tag}
|
||||
isLatest={ch.current_version === tag}
|
||||
isAlreadyInstalled={isAlreadyInstalled}
|
||||
hasBuildsAvailableForThisPlatform={
|
||||
hasBuildAvailableForThisPlatform
|
||||
}
|
||||
version={version}
|
||||
isLatest={ch.current_version === version}
|
||||
isAlreadyInstalled={res.alreadyInstalled}
|
||||
hasBuildsAvailableForThisPlatform={!!res.build}
|
||||
/>
|
||||
</div>
|
||||
</Radio>
|
||||
|
||||
@@ -30,7 +30,7 @@ export enum AssignMode {
|
||||
|
||||
export interface Config {
|
||||
updateChannel: ChannelName;
|
||||
autoUpdate: boolean;
|
||||
notifyOnAvailableUpdates: boolean;
|
||||
debug: boolean;
|
||||
lang: string;
|
||||
doneOnboarding: boolean;
|
||||
@@ -61,7 +61,7 @@ export interface ConfigContext {
|
||||
|
||||
export const defaultConfig: Config = {
|
||||
updateChannel: STABLE_CHANNEL,
|
||||
autoUpdate: true,
|
||||
notifyOnAvailableUpdates: true,
|
||||
lang: 'en',
|
||||
debug: false,
|
||||
doneOnboarding: false,
|
||||
|
||||
54
gui/src/hooks/update.ts
Normal file
54
gui/src/hooks/update.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import {
|
||||
UpdateManifestChannel,
|
||||
UpdateManifestChannelVersion,
|
||||
UpdateManifestChannelVersionBuild,
|
||||
type ChannelName,
|
||||
type UpdateManifest,
|
||||
type Version,
|
||||
} from '@slimevr/update-manifest';
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
export interface UpdateContext {
|
||||
channel: ChannelName;
|
||||
notifyOnAvailableUpdates: boolean;
|
||||
|
||||
isUpToDate: boolean;
|
||||
latestVersionOnChannel: Version | null;
|
||||
|
||||
manifest: UpdateManifest | null;
|
||||
|
||||
platform: string;
|
||||
architecture: string;
|
||||
changeSystem(platform: string, architecture: string): void;
|
||||
|
||||
checkCompatibility(
|
||||
channelName: ChannelName,
|
||||
version: Version
|
||||
): {
|
||||
alreadyInstalled: boolean;
|
||||
channel: UpdateManifestChannel | null;
|
||||
version: UpdateManifestChannelVersion | null;
|
||||
build: UpdateManifestChannelVersionBuild | null;
|
||||
isInstallable: boolean;
|
||||
};
|
||||
checkCompatibilityFromVersionInfo(
|
||||
channelName: ChannelName,
|
||||
version: Version,
|
||||
versionInfo: UpdateManifestChannelVersion
|
||||
): {
|
||||
alreadyInstalled: boolean;
|
||||
build: UpdateManifestChannelVersionBuild | null;
|
||||
isInstallable: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export const UpdateContextC = createContext<UpdateContext | null>(null);
|
||||
|
||||
export function useUpdateContext() {
|
||||
const context = useContext(UpdateContextC);
|
||||
if (!context) {
|
||||
throw new Error('useUpdateContext must be within a UpdateContext Provider');
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
90
gui/src/utils/update.ts
Normal file
90
gui/src/utils/update.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import {
|
||||
ChannelName,
|
||||
UpdateManifest,
|
||||
UpdateManifestChannel,
|
||||
UpdateManifestChannelVersion,
|
||||
UpdateManifestChannelVersionBuild,
|
||||
Version,
|
||||
} from '@slimevr/update-manifest';
|
||||
|
||||
type System = {
|
||||
platform: string;
|
||||
architecture: string;
|
||||
};
|
||||
|
||||
export function checkVersionCompatibility(
|
||||
manifest: UpdateManifest,
|
||||
channelName: ChannelName,
|
||||
version: Version,
|
||||
currentChannelName: ChannelName,
|
||||
system: System
|
||||
): {
|
||||
alreadyInstalled: boolean;
|
||||
isInstallable: boolean;
|
||||
channel: UpdateManifestChannel | null;
|
||||
version: UpdateManifestChannelVersion | null;
|
||||
build: UpdateManifestChannelVersionBuild | null;
|
||||
} {
|
||||
const alreadyInstalled = isAlreadyInstalled(channelName, version, currentChannelName);
|
||||
|
||||
const channel = manifest.channels[channelName];
|
||||
|
||||
const versionInfo = channel.versions[version];
|
||||
if (!versionInfo)
|
||||
return {
|
||||
alreadyInstalled,
|
||||
channel,
|
||||
version: null,
|
||||
build: null,
|
||||
isInstallable: false,
|
||||
};
|
||||
|
||||
const build = findBuildForPlatformAndArchitecture(versionInfo, system);
|
||||
|
||||
return {
|
||||
alreadyInstalled,
|
||||
channel,
|
||||
version: versionInfo,
|
||||
build,
|
||||
isInstallable: !alreadyInstalled && build !== null,
|
||||
};
|
||||
}
|
||||
|
||||
function isAlreadyInstalled(
|
||||
channelName: ChannelName,
|
||||
version: Version,
|
||||
currentChannelName: ChannelName
|
||||
) {
|
||||
return currentChannelName === channelName && __VERSION_TAG__ === version;
|
||||
}
|
||||
|
||||
export function checkVersionCompatibility2(
|
||||
channelName: ChannelName,
|
||||
version: Version,
|
||||
versionInfo: UpdateManifestChannelVersion,
|
||||
currentChannelName: ChannelName,
|
||||
system: System
|
||||
) {
|
||||
const alreadyInstalled = isAlreadyInstalled(channelName, version, currentChannelName);
|
||||
|
||||
const build = findBuildForPlatformAndArchitecture(versionInfo, system);
|
||||
|
||||
return {
|
||||
alreadyInstalled,
|
||||
build,
|
||||
isInstallable: !alreadyInstalled && build !== null,
|
||||
};
|
||||
}
|
||||
|
||||
function findBuildForPlatformAndArchitecture(
|
||||
versionInfo: UpdateManifestChannelVersion,
|
||||
system: System
|
||||
) {
|
||||
const buildsForPlatform = versionInfo.builds[system.platform];
|
||||
if (!buildsForPlatform) return null;
|
||||
|
||||
const buildForPlatformAndArchitecture = buildsForPlatform[system.architecture];
|
||||
if (!buildForPlatformAndArchitecture) return null;
|
||||
|
||||
return buildForPlatformAndArchitecture;
|
||||
}
|
||||
Reference in New Issue
Block a user