mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Basic version update checking (#690)
Co-authored-by: Uriel <urielfontan2002@gmail.com>
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2943,6 +2943,7 @@ dependencies = [
|
||||
"ignore",
|
||||
"objc",
|
||||
"once_cell",
|
||||
"open",
|
||||
"os_info",
|
||||
"os_pipe",
|
||||
"percent-encoding",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"react-hook-form": "^7.29.0",
|
||||
"react-modal": "3.15.1",
|
||||
"react-router-dom": "^6.2.2",
|
||||
"semver": "^7.5.0",
|
||||
"solarxr-protocol": "file:../solarxr-protocol",
|
||||
"three": "^0.148.0",
|
||||
"typescript": "^4.6.3"
|
||||
|
||||
@@ -9,6 +9,12 @@
|
||||
websocket-connecting = Connecting to the server
|
||||
websocket-connection_lost = Connection lost to the server. Trying to reconnect...
|
||||
|
||||
## Update notification
|
||||
version_update-title = New version available: { $version }
|
||||
version_update-description = Clicking "Update" will download the SlimeVR installer for you.
|
||||
version_update-update = Update
|
||||
version_update-close = Close
|
||||
|
||||
## Tips
|
||||
tips-find_tracker = Not sure which tracker is which? Shake a tracker and it will highlight the corresponding item.
|
||||
tips-do_not_move_heels = Ensure your heels do not move during recording!
|
||||
|
||||
@@ -28,7 +28,7 @@ shadow-rs = "0.21"
|
||||
[dependencies]
|
||||
serde_json = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
tauri = { version = "1.2", features = ["devtools", "dialog", "fs-all", "os-all", "path-all", "shell-execute", "window-close", "window-maximize", "window-minimize", "window-set-resizable", "window-set-size", "window-set-title", "window-start-dragging", "window-unmaximize", "window-unminimize"] }
|
||||
tauri = { version = "1.2", features = ["devtools", "dialog", "fs-all", "os-all", "path-all", "shell-execute", "shell-open", "window-close", "window-maximize", "window-minimize", "window-set-resizable", "window-set-size", "window-set-title", "window-start-dragging", "window-unmaximize", "window-unminimize"] }
|
||||
tauri-runtime = "0.12.1"
|
||||
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" }
|
||||
pretty_env_logger = "0.4"
|
||||
|
||||
@@ -56,7 +56,8 @@
|
||||
"allowlist": {
|
||||
"shell": {
|
||||
"all": false,
|
||||
"execute": true
|
||||
"execute": true,
|
||||
"open": true
|
||||
},
|
||||
"fs": {
|
||||
"scope": ["$APP/*", "$APP"],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect } from 'react';
|
||||
import { createContext, useEffect, useState } from 'react';
|
||||
import {
|
||||
BrowserRouter as Router,
|
||||
Outlet,
|
||||
@@ -43,6 +43,10 @@ import { VMCSettings } from './components/settings/pages/VMCSettings';
|
||||
import { MountingChoose } from './components/onboarding/pages/mounting/MountingChoose';
|
||||
import { ProportionsChoose } from './components/onboarding/pages/body-proportions/ProportionsChoose';
|
||||
import { LogicalSize, appWindow } from '@tauri-apps/api/window';
|
||||
import { Release, VersionUpdateModal } from './components/VersionUpdateModal';
|
||||
|
||||
export const GH_REPO = 'SlimeVR/SlimeVR-Server';
|
||||
export const VersionContext = createContext('');
|
||||
import { CalibrationTutorialPage } from './components/onboarding/pages/CalibrationTutorial';
|
||||
|
||||
function Layout() {
|
||||
@@ -52,6 +56,7 @@ function Layout() {
|
||||
return (
|
||||
<>
|
||||
<SerialDetectionModal></SerialDetectionModal>
|
||||
<VersionUpdateModal></VersionUpdateModal>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
@@ -129,6 +134,19 @@ const MIN_SIZE = { width: 880, height: 740 };
|
||||
export default function App() {
|
||||
const websocketAPI = useProvideWebsocketApi();
|
||||
const { l10n } = useLocalization();
|
||||
const [updateFound, setUpdateFound] = useState('');
|
||||
useEffect(() => {
|
||||
async function fetchReleases() {
|
||||
const releases: Release[] = await fetch(
|
||||
`https://api.github.com/repos/${GH_REPO}/releases`
|
||||
).then((res) => res.json());
|
||||
|
||||
if (__VERSION_TAG__ && releases[0].tag_name !== __VERSION_TAG__) {
|
||||
setUpdateFound(releases[0].tag_name);
|
||||
}
|
||||
}
|
||||
fetchReleases().catch(() => console.error('failed to fetch releases'));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
os.type()
|
||||
@@ -211,21 +229,23 @@ export default function App() {
|
||||
<WebSocketApiContext.Provider value={websocketAPI}>
|
||||
<AppContextProvider>
|
||||
<OnboardingContextProvider>
|
||||
<div className="h-full w-full text-standard bg-background-80 text-background-10">
|
||||
<div className="flex-col h-full">
|
||||
{!websocketAPI.isConnected && (
|
||||
<>
|
||||
<TopBar></TopBar>
|
||||
<div className="flex w-full h-full justify-center items-center p-2">
|
||||
{websocketAPI.isFirstConnection
|
||||
? l10n.getString('websocket-connecting')
|
||||
: l10n.getString('websocket-connection_lost')}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{websocketAPI.isConnected && <Layout></Layout>}
|
||||
<VersionContext.Provider value={updateFound}>
|
||||
<div className="h-full w-full text-standard bg-background-80 text-background-10">
|
||||
<div className="flex-col h-full">
|
||||
{!websocketAPI.isConnected && (
|
||||
<>
|
||||
<TopBar></TopBar>
|
||||
<div className="flex w-full h-full justify-center items-center p-2">
|
||||
{websocketAPI.isFirstConnection
|
||||
? l10n.getString('websocket-connecting')
|
||||
: l10n.getString('websocket-connection_lost')}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{websocketAPI.isConnected && <Layout></Layout>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</VersionContext.Provider>
|
||||
</OnboardingContextProvider>
|
||||
</AppContextProvider>
|
||||
</WebSocketApiContext.Provider>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { appWindow } from '@tauri-apps/api/window';
|
||||
import { ReactNode, useEffect, useState } from 'react';
|
||||
import { ReactNode, useContext, useEffect, useState } from 'react';
|
||||
import { NavLink, useMatch } from 'react-router-dom';
|
||||
import {
|
||||
RpcMessage,
|
||||
@@ -13,6 +13,10 @@ import { MinimiseIcon } from './commons/icon/MinimiseIcon';
|
||||
import { SlimeVRIcon } from './commons/icon/SimevrIcon';
|
||||
import { ProgressBar } from './commons/ProgressBar';
|
||||
import { Typography } from './commons/Typography';
|
||||
import { DownloadIcon } from './commons/icon/DownloadIcon';
|
||||
import { open } from '@tauri-apps/api/shell';
|
||||
import { GH_REPO, VersionContext } from '../App';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export function TopBar({
|
||||
progress,
|
||||
@@ -21,6 +25,7 @@ export function TopBar({
|
||||
progress?: number;
|
||||
}) {
|
||||
const { useRPCPacket, sendRPCPacket } = useWebsocketAPI();
|
||||
const version = useContext(VersionContext);
|
||||
const [localIp, setLocalIp] = useState<string | null>(null);
|
||||
const doesMatchSettings = useMatch({
|
||||
path: '/settings/*',
|
||||
@@ -54,15 +59,43 @@ export function TopBar({
|
||||
<div className="flex justify-around flex-col" data-tauri-drag-region>
|
||||
<Typography>SlimeVR</Typography>
|
||||
</div>
|
||||
<div className="flex justify-around flex-col text-standard-bold text-status-success bg-status-success bg-opacity-20 rounded-lg px-3 select-text">
|
||||
<div
|
||||
className={classNames(
|
||||
'flex justify-around flex-col text-standard-bold',
|
||||
'text-status-success bg-status-success bg-opacity-20 rounded-lg',
|
||||
'px-3 select-text cursor-pointer'
|
||||
)}
|
||||
onClick={() => {
|
||||
const url = `https://github.com/${GH_REPO}/releases`;
|
||||
open(url).catch(() => window.open(url, '_blank'));
|
||||
}}
|
||||
>
|
||||
{(__VERSION_TAG__ || __COMMIT_HASH__) +
|
||||
(__GIT_CLEAN__ ? '' : '-dirty')}
|
||||
</div>
|
||||
{doesMatchSettings && (
|
||||
<div className="flex justify-around flex-col text-standard-bold text-status-special bg-status-special bg-opacity-20 rounded-lg px-3 select-text">
|
||||
<div
|
||||
className={classNames(
|
||||
'flex justify-around flex-col text-standard-bold text-status-special',
|
||||
'bg-status-special bg-opacity-20 rounded-lg px-3 select-text'
|
||||
)}
|
||||
>
|
||||
{localIp || 'unknown local ip'}
|
||||
</div>
|
||||
)}
|
||||
{version && (
|
||||
<div
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
const url = document.body.classList.contains('windows_nt')
|
||||
? 'https://slimevr.dev/download'
|
||||
: `https://github.com/${GH_REPO}/releases/latest`;
|
||||
open(url).catch(() => window.open(url, '_blank'));
|
||||
}}
|
||||
>
|
||||
<DownloadIcon></DownloadIcon>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
||||
206
gui/src/components/VersionUpdateModal.tsx
Normal file
206
gui/src/components/VersionUpdateModal.tsx
Normal file
@@ -0,0 +1,206 @@
|
||||
import { useLocalization } from '@fluent/react';
|
||||
import { useContext, useState } from 'react';
|
||||
import { BaseModal } from './commons/BaseModal';
|
||||
import { Button } from './commons/Button';
|
||||
import { Typography } from './commons/Typography';
|
||||
import { open } from '@tauri-apps/api/shell';
|
||||
import semver from 'semver';
|
||||
import { GH_REPO, VersionContext } from '../App';
|
||||
|
||||
export function VersionUpdateModal() {
|
||||
const { l10n } = useLocalization();
|
||||
const newVersion = useContext(VersionContext);
|
||||
const [forceClose, setForceClose] = useState(false);
|
||||
const closeModal = () => {
|
||||
localStorage.setItem('lastVersionFound', newVersion);
|
||||
setForceClose(true);
|
||||
};
|
||||
let isVersionNew = false;
|
||||
try {
|
||||
if (newVersion) {
|
||||
isVersionNew = semver.gt(
|
||||
newVersion,
|
||||
localStorage.getItem('lastVersionFound') || 'v0.0.0'
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
console.error('failed to parse new version');
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseModal
|
||||
isOpen={!forceClose && !!newVersion && isVersionNew}
|
||||
onRequestClose={closeModal}
|
||||
>
|
||||
<div className="flex flex-col gap-3">
|
||||
<>
|
||||
<div className="flex flex-col items-center gap-3 fill-accent-background-20">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('version_update-title', {
|
||||
version: newVersion,
|
||||
})}
|
||||
</Typography>
|
||||
<Typography variant="standard">
|
||||
{l10n.getString('version_update-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={async () => {
|
||||
const url = document.body.classList.contains('windows_nt')
|
||||
? 'https://slimevr.dev/download'
|
||||
: `https://github.com/${GH_REPO}/releases/latest`;
|
||||
await open(url).catch(() => window.open(url, '_blank'));
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
{l10n.getString('version_update-update')}
|
||||
</Button>
|
||||
<Button variant="tertiary" onClick={closeModal}>
|
||||
{l10n.getString('version_update-close')}
|
||||
</Button>
|
||||
</>
|
||||
</div>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A GitHub release.
|
||||
*/
|
||||
export interface Release {
|
||||
url: string;
|
||||
html_url: string;
|
||||
assets_url: string;
|
||||
upload_url: string;
|
||||
tarball_url: string | null;
|
||||
zipball_url: string | null;
|
||||
id: number;
|
||||
node_id: string;
|
||||
/**
|
||||
* The name of the tag.
|
||||
*/
|
||||
tag_name: string;
|
||||
/**
|
||||
* Specifies the commitish value that determines where the Git tag is created from.
|
||||
*/
|
||||
target_commitish: string;
|
||||
name: string | null;
|
||||
body?: string | null;
|
||||
/**
|
||||
* true to create a draft (unpublished) release, false to create a published one.
|
||||
*/
|
||||
draft: boolean;
|
||||
/**
|
||||
* Whether to identify the release as a prerelease or a full release.
|
||||
*/
|
||||
prerelease: boolean;
|
||||
created_at: string;
|
||||
published_at: string | null;
|
||||
author: SimpleUser;
|
||||
assets: ReleaseAsset[];
|
||||
body_html?: string;
|
||||
body_text?: string;
|
||||
mentions_count?: number;
|
||||
/**
|
||||
* The URL of the release discussion.
|
||||
*/
|
||||
discussion_url?: string;
|
||||
reactions?: ReactionRollup;
|
||||
[k: string]: unknown;
|
||||
}
|
||||
/**
|
||||
* A GitHub user.
|
||||
*/
|
||||
export interface SimpleUser {
|
||||
name?: string | null;
|
||||
email?: string | null;
|
||||
login: string;
|
||||
id: number;
|
||||
node_id: string;
|
||||
avatar_url: string;
|
||||
gravatar_id: string | null;
|
||||
url: string;
|
||||
html_url: string;
|
||||
followers_url: string;
|
||||
following_url: string;
|
||||
gists_url: string;
|
||||
starred_url: string;
|
||||
subscriptions_url: string;
|
||||
organizations_url: string;
|
||||
repos_url: string;
|
||||
events_url: string;
|
||||
received_events_url: string;
|
||||
type: string;
|
||||
site_admin: boolean;
|
||||
starred_at?: string;
|
||||
[k: string]: unknown;
|
||||
}
|
||||
/**
|
||||
* Data related to a release.
|
||||
*/
|
||||
export interface ReleaseAsset {
|
||||
url: string;
|
||||
browser_download_url: string;
|
||||
id: number;
|
||||
node_id: string;
|
||||
/**
|
||||
* The file name of the asset.
|
||||
*/
|
||||
name: string;
|
||||
label: string | null;
|
||||
/**
|
||||
* State of the release asset.
|
||||
*/
|
||||
state: 'uploaded' | 'open';
|
||||
content_type: string;
|
||||
size: number;
|
||||
download_count: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
uploader: null | SimpleUser1;
|
||||
[k: string]: unknown;
|
||||
}
|
||||
/**
|
||||
* A GitHub user.
|
||||
*/
|
||||
export interface SimpleUser1 {
|
||||
name?: string | null;
|
||||
email?: string | null;
|
||||
login: string;
|
||||
id: number;
|
||||
node_id: string;
|
||||
avatar_url: string;
|
||||
gravatar_id: string | null;
|
||||
url: string;
|
||||
html_url: string;
|
||||
followers_url: string;
|
||||
following_url: string;
|
||||
gists_url: string;
|
||||
starred_url: string;
|
||||
subscriptions_url: string;
|
||||
organizations_url: string;
|
||||
repos_url: string;
|
||||
events_url: string;
|
||||
received_events_url: string;
|
||||
type: string;
|
||||
site_admin: boolean;
|
||||
starred_at?: string;
|
||||
[k: string]: unknown;
|
||||
}
|
||||
export interface ReactionRollup {
|
||||
url: string;
|
||||
total_count: number;
|
||||
'+1': number;
|
||||
'-1': number;
|
||||
laugh: number;
|
||||
confused: number;
|
||||
heart: number;
|
||||
hooray: number;
|
||||
eyes: number;
|
||||
rocket: number;
|
||||
[k: string]: unknown;
|
||||
}
|
||||
16
gui/src/components/commons/icon/DownloadIcon.tsx
Normal file
16
gui/src/components/commons/icon/DownloadIcon.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
export function DownloadIcon({ width = 22 }: { width?: number }) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width={width}
|
||||
className="fill-status-success stroke-status-success"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M12 2.25a.75.75 0 01.75.75v11.69l3.22-3.22a.75.75 0 111.06 1.06l-4.5 4.5a.75.75 0 01-1.06 0l-4.5-4.5a.75.75 0 111.06-1.06l3.22 3.22V3a.75.75 0 01.75-.75zm-9 13.5a.75.75 0 01.75.75v2.25a1.5 1.5 0 001.5 1.5h13.5a1.5 1.5 0 001.5-1.5V16.5a.75.75 0 011.5 0v2.25a3 3 0 01-3 3H5.25a3 3 0 01-3-3V16.5a.75.75 0 01.75-.75z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -45,6 +45,7 @@
|
||||
"react-hook-form": "^7.29.0",
|
||||
"react-modal": "3.15.1",
|
||||
"react-router-dom": "^6.2.2",
|
||||
"semver": "^7.5.0",
|
||||
"solarxr-protocol": "file:../solarxr-protocol",
|
||||
"three": "^0.148.0",
|
||||
"typescript": "^4.6.3"
|
||||
@@ -8573,9 +8574,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.3.8",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
|
||||
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
|
||||
"version": "7.5.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz",
|
||||
"integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==",
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
@@ -15354,9 +15355,9 @@
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.8",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
|
||||
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
|
||||
"version": "7.5.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz",
|
||||
"integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==",
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
@@ -15461,6 +15462,7 @@
|
||||
"react-hook-form": "^7.29.0",
|
||||
"react-modal": "3.15.1",
|
||||
"react-router-dom": "^6.2.2",
|
||||
"semver": "^7.5.0",
|
||||
"solarxr-protocol": "file:../solarxr-protocol",
|
||||
"tailwind-gradient-mask-image": "^1.0.0",
|
||||
"tailwindcss": "^3.3.1",
|
||||
|
||||
Reference in New Issue
Block a user