mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-27 03:41:11 +01:00
Integrated update experience (#259)
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import deepEqual from '@gilbarbara/deep-equal';
|
||||
import type { UpdateInfo } from '@yaakapp-internal/tauri';
|
||||
import type { Atom } from 'jotai';
|
||||
import { atom } from 'jotai';
|
||||
import { selectAtom } from 'jotai/utils';
|
||||
import type { SplitLayoutLayout } from '../components/core/SplitLayout';
|
||||
import { atomWithKVStorage } from './atoms/atomWithKVStorage';
|
||||
@@ -16,3 +18,5 @@ export const workspaceLayoutAtom = atomWithKVStorage<SplitLayoutLayout>(
|
||||
'workspace_layout',
|
||||
'horizontal',
|
||||
);
|
||||
|
||||
export const updateAvailableAtom = atom<Omit<UpdateInfo, 'replyEventId'> | null>(null);
|
||||
|
||||
20
src-web/lib/color.ts
Normal file
20
src-web/lib/color.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { Color } from '@yaakapp-internal/plugins';
|
||||
|
||||
const colors: Record<Color, boolean> = {
|
||||
primary: true,
|
||||
secondary: true,
|
||||
success: true,
|
||||
notice: true,
|
||||
warning: true,
|
||||
danger: true,
|
||||
info: true,
|
||||
};
|
||||
|
||||
export function stringToColor(str: string | null): Color | null {
|
||||
if (!str) return null;
|
||||
const strLower = str.toLowerCase();
|
||||
if (strLower in colors) {
|
||||
return strLower as Color;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1,12 +1,19 @@
|
||||
import { emit } from '@tauri-apps/api/event';
|
||||
import { openUrl } from '@tauri-apps/plugin-opener';
|
||||
import type { InternalEvent, ShowToastRequest } from '@yaakapp-internal/plugins';
|
||||
import type { UpdateInfo, UpdateResponse, YaakNotification } from '@yaakapp-internal/tauri';
|
||||
import { openSettings } from '../commands/openSettings';
|
||||
import { Button } from '../components/core/Button';
|
||||
import { ButtonInfiniteLoading } from '../components/core/ButtonInfiniteLoading';
|
||||
import { Icon } from '../components/core/Icon';
|
||||
import { HStack, VStack } from '../components/core/Stacks';
|
||||
|
||||
// Listen for toasts
|
||||
import { listenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||
import { updateAvailableAtom } from './atoms';
|
||||
import { stringToColor } from './color';
|
||||
import { generateId } from './generateId';
|
||||
import { jotaiStore } from './jotai';
|
||||
import { showPrompt } from './prompt';
|
||||
import { invokeCmd } from './tauri';
|
||||
import { showToast } from './toast';
|
||||
@@ -37,40 +44,125 @@ export function initGlobalListeners() {
|
||||
}
|
||||
});
|
||||
|
||||
listenToTauriEvent<{
|
||||
id: string;
|
||||
timestamp: string;
|
||||
message: string;
|
||||
timeout?: number | null;
|
||||
action?: null | {
|
||||
url: string;
|
||||
label: string;
|
||||
};
|
||||
}>('notification', ({ payload }) => {
|
||||
console.log('Got notification event', payload);
|
||||
const actionUrl = payload.action?.url;
|
||||
const actionLabel = payload.action?.label;
|
||||
const UPDATE_TOAST_ID = 'update-info'; // Share the ID to replace the toast
|
||||
|
||||
listenToTauriEvent<string>('update_installed', async ({ payload: version }) => {
|
||||
showToast({
|
||||
id: payload.id,
|
||||
timeout: payload.timeout ?? undefined,
|
||||
message: payload.message,
|
||||
onClose: () => {
|
||||
invokeCmd('cmd_dismiss_notification', { notificationId: payload.id }).catch(console.error);
|
||||
},
|
||||
action: ({ hide }) =>
|
||||
actionLabel && actionUrl ? (
|
||||
<Button
|
||||
size="xs"
|
||||
color="secondary"
|
||||
className="mr-auto min-w-[5rem]"
|
||||
onClick={() => {
|
||||
hide();
|
||||
return openUrl(actionUrl);
|
||||
}}
|
||||
>
|
||||
{actionLabel}
|
||||
</Button>
|
||||
) : null,
|
||||
id: UPDATE_TOAST_ID,
|
||||
color: 'primary',
|
||||
timeout: null,
|
||||
message: (
|
||||
<VStack>
|
||||
<h2 className="font-semibold">Yaak {version} was installed</h2>
|
||||
<p className="text-text-subtle text-sm">Start using the new version now?</p>
|
||||
</VStack>
|
||||
),
|
||||
action: ({ hide }) => (
|
||||
<ButtonInfiniteLoading
|
||||
size="xs"
|
||||
className="mr-auto min-w-[5rem]"
|
||||
color="primary"
|
||||
loadingChildren="Restarting..."
|
||||
onClick={() => {
|
||||
hide();
|
||||
setTimeout(() => invokeCmd('cmd_restart', {}), 200);
|
||||
}}
|
||||
>
|
||||
Relaunch Yaak
|
||||
</ButtonInfiniteLoading>
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
// Listen for update events
|
||||
listenToTauriEvent<UpdateInfo>(
|
||||
'update_available',
|
||||
async ({ payload: { version, replyEventId, downloaded } }) => {
|
||||
console.log('Received update available event', { replyEventId, version, downloaded });
|
||||
jotaiStore.set(updateAvailableAtom, { version, downloaded });
|
||||
|
||||
// Acknowledge the event, so we don't time out and try the fallback update logic
|
||||
await emit<UpdateResponse>(replyEventId, { type: 'ack' });
|
||||
|
||||
showToast({
|
||||
id: UPDATE_TOAST_ID,
|
||||
color: 'info',
|
||||
timeout: null,
|
||||
message: (
|
||||
<VStack>
|
||||
<h2 className="font-semibold">Yaak {version} is available</h2>
|
||||
<p className="text-text-subtle text-sm">
|
||||
{downloaded ? 'Do you want to install' : 'Download and install'} the update?
|
||||
</p>
|
||||
</VStack>
|
||||
),
|
||||
action: () => (
|
||||
<HStack space={1.5}>
|
||||
<ButtonInfiniteLoading
|
||||
size="xs"
|
||||
color="info"
|
||||
className="min-w-[10rem]"
|
||||
loadingChildren={downloaded ? 'Installing...' : 'Downloading...'}
|
||||
onClick={async () => {
|
||||
await emit<UpdateResponse>(replyEventId, { type: 'action', action: 'install' });
|
||||
}}
|
||||
>
|
||||
{downloaded ? 'Install Now' : 'Download and Install'}
|
||||
</ButtonInfiniteLoading>
|
||||
<Button
|
||||
size="xs"
|
||||
color="info"
|
||||
variant="border"
|
||||
rightSlot={<Icon icon="external_link" />}
|
||||
onClick={async () => {
|
||||
await openUrl('https://yaak.app/changelog/' + version);
|
||||
}}
|
||||
>
|
||||
What's New
|
||||
</Button>
|
||||
</HStack>
|
||||
),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
listenToTauriEvent<YaakNotification>('notification', ({ payload }) => {
|
||||
console.log('Got notification event', payload);
|
||||
showNotificationToast(payload);
|
||||
});
|
||||
}
|
||||
|
||||
function showNotificationToast(n: YaakNotification) {
|
||||
const actionUrl = n.action?.url;
|
||||
const actionLabel = n.action?.label;
|
||||
showToast({
|
||||
id: n.id,
|
||||
timeout: n.timeout ?? null,
|
||||
color: stringToColor(n.color) ?? undefined,
|
||||
message: (
|
||||
<VStack>
|
||||
{n.title && <h2 className="font-semibold">{n.title}</h2>}
|
||||
<p className="text-text-subtle text-sm">{n.message}</p>
|
||||
</VStack>
|
||||
),
|
||||
onClose: () => {
|
||||
invokeCmd('cmd_dismiss_notification', { notificationId: n.id }).catch(console.error);
|
||||
},
|
||||
action: ({ hide }) => {
|
||||
return actionLabel && actionUrl ? (
|
||||
<Button
|
||||
size="xs"
|
||||
color={stringToColor(n.color) ?? undefined}
|
||||
className="mr-auto min-w-[5rem]"
|
||||
rightSlot={<Icon icon="external_link" />}
|
||||
onClick={() => {
|
||||
hide();
|
||||
return openUrl(actionUrl);
|
||||
}}
|
||||
>
|
||||
{actionLabel}
|
||||
</Button>
|
||||
) : null;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ type TauriCmd =
|
||||
| 'cmd_import_data'
|
||||
| 'cmd_install_plugin'
|
||||
| 'cmd_metadata'
|
||||
| 'cmd_restart'
|
||||
| 'cmd_new_child_window'
|
||||
| 'cmd_new_main_window'
|
||||
| 'cmd_plugin_info'
|
||||
|
||||
@@ -118,7 +118,7 @@ function toastColorVariables(color: YaakColor | null): Partial<CSSVariables> {
|
||||
|
||||
return {
|
||||
text: color.lift(0.8).css(),
|
||||
textSubtle: color.css(),
|
||||
textSubtle: color.lift(0.8).translucify(0.3).css(),
|
||||
surface: color.translucify(0.9).css(),
|
||||
surfaceHighlight: color.translucify(0.8).css(),
|
||||
border: color.lift(0.3).translucify(0.6).css(),
|
||||
|
||||
@@ -21,7 +21,6 @@ export function showToast({
|
||||
|
||||
let delay = 0;
|
||||
if (toastWithSameId) {
|
||||
console.log('HIDING TOAST', id);
|
||||
hideToast(toastWithSameId);
|
||||
// Allow enough time for old toast to animate out
|
||||
delay = 200;
|
||||
|
||||
Reference in New Issue
Block a user