Skip to content

Commit

Permalink
In-app updater
Browse files Browse the repository at this point in the history
  • Loading branch information
abrenneke committed Oct 17, 2023
1 parent 47730b6 commit 991da3f
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 3 deletions.
2 changes: 1 addition & 1 deletion packages/app/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ tauri-build = { version = "1.2.1", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.2.4", features = [ "shell-execute", "http-all", "path-all", "updater", "shell-open", "dialog-all", "fs-all", "global-shortcut-all", "shell-sidecar", "window-all", "devtools"] }
tauri = { version = "1.2.4", features = [ "process-relaunch", "shell-execute", "http-all", "path-all", "updater", "shell-open", "dialog-all", "fs-all", "global-shortcut-all", "shell-sidecar", "window-all", "devtools"] }
tauri-plugin-persisted-scope = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" }
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
tar = "0.4.40"
Expand Down
5 changes: 4 additions & 1 deletion packages/app/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
"dialog": {
"all": true
},
"process": {
"relaunch": true
},
"shell": {
"sidecar": true,
"open": true,
Expand Down Expand Up @@ -89,7 +92,7 @@
"updater": {
"active": true,
"endpoints": ["https://github.com/Ironclad/rivet/releases/latest/download/latest.json"],
"dialog": true,
"dialog": false,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDQwRTgxNDEzODJDREJDNzkKUldSNXZNMkNFeFRvUU5VK0NXdEIwNlc3NTREUWpNSmpaeGFwWjFwc21ZT0U5cFlzcy81QndDOXYK"
},
"windows": [
Expand Down
13 changes: 13 additions & 0 deletions packages/app/src/components/RivetApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import { useLoadStaticData } from '../hooks/useLoadStaticData';
import { DataStudioRenderer } from './dataStudio/DataStudio';
import { StatusBar } from './StatusBar';
import { PluginsOverlayRenderer } from './PluginsOverlay';
import { useCheckForUpdate } from '../hooks/useCheckForUpdate';
import useAsyncEffect from 'use-async-effect';
import { UpdateModalRenderer } from './UpdateModal';
import { useMonitorUpdateStatus } from '../hooks/useMonitorUpdateStatus';

const styles = css`
overflow: hidden;
Expand All @@ -40,6 +44,14 @@ export const RivetApp: FC = () => {
onRunGraph: tryRunGraph,
});

const checkForUpdate = useCheckForUpdate();

useAsyncEffect(async () => {
await checkForUpdate();
}, []);

useMonitorUpdateStatus();

return (
<div className={clsx('app', theme ? `theme-${theme}` : 'theme-default')} css={styles}>
<OverlayTabs />
Expand All @@ -60,6 +72,7 @@ export const RivetApp: FC = () => {
<ChatViewerRenderer />
<DataStudioRenderer />
<PluginsOverlayRenderer />
<UpdateModalRenderer />
<ToastContainer position="bottom-right" hideProgressBar newestOnTop />
</div>
);
Expand Down
105 changes: 105 additions & 0 deletions packages/app/src/components/UpdateModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { useState, type FC, useEffect } from 'react';

import Modal, { ModalTransition, ModalBody, ModalFooter, ModalHeader, ModalTitle } from '@atlaskit/modal-dialog';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { updateModalOpenState, updateStatusState } from '../state/settings';
import Button from '@atlaskit/button';
import useAsyncEffect from 'use-async-effect';
import { checkUpdate, installUpdate, onUpdaterEvent } from '@tauri-apps/api/updater';
import { getVersion } from '@tauri-apps/api/app';
import { css } from '@emotion/react';
import { relaunch } from '@tauri-apps/api/process';

const bodyStyle = css`
pre {
font-family: var(--font-family);
}
`;

export const UpdateModalRenderer: FC = () => {
const [modalOpen] = useRecoilState(updateModalOpenState);

return <ModalTransition>{modalOpen && <UpdateModal />}</ModalTransition>;
};

export const UpdateModal: FC = () => {
const setModalOpen = useSetRecoilState(updateModalOpenState);
const [isUpdating, setIsUpdating] = useState(false);
const [updateStatus, setUpdateStatus] = useRecoilState(updateStatusState);

const [currentVersion, setCurrentVersion] = useState('');
const [latestVersion, setLatestVersion] = useState('');
const [updateBody, setUpdateBody] = useState('');

useAsyncEffect(async () => {
setCurrentVersion(await getVersion());
const { manifest } = await checkUpdate();
if (manifest) {
setLatestVersion(manifest.version);
setUpdateBody(manifest.body);
}
}, []);

const doUpdate = async () => {
try {
setUpdateStatus('Starting update...');
setIsUpdating(true);

await installUpdate();
} catch (err) {
console.error(err);
}
};

const handleModalClose = () => {
if (isUpdating) {
return;
}
setModalOpen(false);
};

useAsyncEffect(async () => {
if (updateStatus === 'Installed.') {
await relaunch();
}
}, [updateStatus]);

const skipUpdate = () => {};

const canRender = currentVersion && latestVersion && updateBody;

return (
canRender && (
<Modal onClose={handleModalClose}>
<ModalHeader>
<ModalTitle>🎉 Update Available</ModalTitle>
</ModalHeader>
<ModalBody>
<div css={bodyStyle}>
<p>
A new version <strong>{latestVersion}</strong> of Rivet is available. You are on currently on version{' '}
<strong>{currentVersion}</strong>. Would you like to install it now?
</p>
<h4>Update Notes:</h4>
<pre>{updateBody}</pre>
</div>
</ModalBody>
<ModalFooter>
{isUpdating ? (
<div>{updateStatus}</div>
) : (
<>
<Button appearance="primary" onClick={doUpdate}>
Update
</Button>
<Button appearance="subtle" onClick={skipUpdate}>
Skip this update
</Button>
<Button onClick={() => setModalOpen(false)}>Cancel</Button>
</>
)}
</ModalFooter>
</Modal>
)
);
};
68 changes: 68 additions & 0 deletions packages/app/src/hooks/useCheckForUpdate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { useEffect } from 'react';
import { checkUpdate, installUpdate, onUpdaterEvent } from '@tauri-apps/api/updater';
import useAsyncEffect from 'use-async-effect';
import { toast } from 'react-toastify';
import { css } from '@emotion/react';
import { isInTauri } from '../utils/tauri';
import { useSetRecoilState } from 'recoil';
import { updateModalOpenState } from '../state/settings';

const toastStyle = css`
display: flex;
flex-direction: column;
.actions {
display: flex;
flex-direction: row;
justify-content: flex-end;
}
button {
background-color: var(--grey);
color: var(--grey-lightest);
font-family: apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
'Droid Sans', 'Helvetica Neue', sans-serif;
border: 1px solid var(--grey-lightest);
padding: 8px 12px;
display: flex;
align-items: center;
justify-content: center;
white-space: nowrap;
font-size: 14px;
cursor: pointer;
&.primary {
background-color: var(--primary);
color: var(--foreground-on-primary);
}
}
`;

export function useCheckForUpdate() {
const setUpdateModalOpen = useSetRecoilState(updateModalOpenState);

return async () => {
const { shouldUpdate, manifest } = await checkUpdate();

if (shouldUpdate) {
toast.success(
({ closeToast }) => (
<div css={toastStyle}>
<div className="info">Rivet version {manifest?.version} is now available!</div>
<div className="actions">
<button className="primary" onClick={() => setUpdateModalOpen(true)}>
Install
</button>
<button onClick={() => installUpdate()}>Skip</button>
<button onClick={() => closeToast?.()}>Not Now</button>
</div>
</div>
),
{
autoClose: false,
closeButton: false,
},
);
}
};
}
34 changes: 34 additions & 0 deletions packages/app/src/hooks/useMonitorUpdateStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useEffect } from 'react';
import { isInTauri } from '../utils/tauri';
import { onUpdaterEvent } from '@tauri-apps/api/updater';
import { useSetRecoilState } from 'recoil';
import { updateStatusState } from '../state/settings';
import { match } from 'ts-pattern';
import useAsyncEffect from 'use-async-effect';
import { relaunch } from '@tauri-apps/api/process';

export function useMonitorUpdateStatus() {
const setUpdateStatus = useSetRecoilState(updateStatusState);

useAsyncEffect(async () => {
let unlisten: any | undefined = undefined;

if (isInTauri()) {
unlisten = await onUpdaterEvent(({ error, status }) => {
match(status as typeof status | 'DOWNLOADED') // -.-
.with('PENDING', async () => setUpdateStatus('Downloading...'))
.with('DONE', async () => setUpdateStatus('Installed.'))
.with('ERROR', async () => setUpdateStatus(`Error - ${error}`))
.with('UPTODATE', async () => setUpdateStatus('Up to date.'))
.with('DOWNLOADED', async () => setUpdateStatus('Installing...'))
.exhaustive();
});
}

return () => {
if (unlisten) {
unlisten();
}
};
}, []);
}
4 changes: 3 additions & 1 deletion packages/app/src/index.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',

--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
'Droid Sans', 'Helvetica Neue', sans-serif;
font-family: var(--font-family);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
box-sizing: border-box;
Expand Down
16 changes: 16 additions & 0 deletions packages/app/src/state/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,19 @@ export const previousDataPerNodeToKeepState = atom<number>({
default: -1,
effects_UNSTABLE: [persistAtom],
});

export const skippedMaxVersion = atom<string | undefined>({
key: 'skippedMaxVersion',
default: undefined,
effects_UNSTABLE: [persistAtom],
});

export const updateModalOpenState = atom<boolean>({
key: 'updateModalOpen',
default: false,
});

export const updateStatusState = atom<string | undefined>({
key: 'updateStatus',
default: undefined,
});

0 comments on commit 991da3f

Please sign in to comment.