mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-24 01:28:35 +02:00
Stacks and InlineCode
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import type { BatchUpsertResult } from '@yaakapp-internal/models';
|
||||
import { VStack } from '@yaakapp-internal/ui';
|
||||
import { Button } from '../components/core/Button';
|
||||
import { FormattedError } from '../components/core/FormattedError';
|
||||
import { VStack } from '../components/core/Stacks';
|
||||
import { ImportDataDialog } from '../components/ImportDataDialog';
|
||||
import { activeWorkspaceAtom } from '../hooks/useActiveWorkspace';
|
||||
import { createFastMutation } from '../hooks/useFastMutation';
|
||||
|
||||
@@ -1,65 +1,86 @@
|
||||
import { emit } from "@tauri-apps/api/event";
|
||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||
import { debounce } from "@yaakapp-internal/lib";
|
||||
import { emit } from '@tauri-apps/api/event';
|
||||
import { openUrl } from '@tauri-apps/plugin-opener';
|
||||
import { debounce } from '@yaakapp-internal/lib';
|
||||
import type {
|
||||
FormInput,
|
||||
InternalEvent,
|
||||
JsonPrimitive,
|
||||
ShowToastRequest,
|
||||
} from "@yaakapp-internal/plugins";
|
||||
import { updateAllPlugins } from "@yaakapp-internal/plugins";
|
||||
} from '@yaakapp-internal/plugins';
|
||||
import { updateAllPlugins } from '@yaakapp-internal/plugins';
|
||||
import type {
|
||||
PluginUpdateNotification,
|
||||
UpdateInfo,
|
||||
UpdateResponse,
|
||||
YaakNotification,
|
||||
} from "@yaakapp-internal/tauri-client";
|
||||
import { openSettings } from "../commands/openSettings";
|
||||
import { Button } from "../components/core/Button";
|
||||
import { ButtonInfiniteLoading } from "../components/core/ButtonInfiniteLoading";
|
||||
import { Icon } from "@yaakapp-internal/ui";
|
||||
import { HStack, VStack } from "../components/core/Stacks";
|
||||
} from '@yaakapp-internal/tauri-client';
|
||||
import { HStack, Icon, VStack } from '@yaakapp-internal/ui';
|
||||
import { openSettings } from '../commands/openSettings';
|
||||
import { Button } from '../components/core/Button';
|
||||
import { ButtonInfiniteLoading } from '../components/core/ButtonInfiniteLoading';
|
||||
|
||||
// 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 { showPromptForm } from "./prompt-form";
|
||||
import { invokeCmd } from "./tauri";
|
||||
import { showToast } from "./toast";
|
||||
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 { showPromptForm } from './prompt-form';
|
||||
import { invokeCmd } from './tauri';
|
||||
import { showToast } from './toast';
|
||||
|
||||
export function initGlobalListeners() {
|
||||
listenToTauriEvent<ShowToastRequest>("show_toast", (event) => {
|
||||
listenToTauriEvent<ShowToastRequest>('show_toast', (event) => {
|
||||
showToast({ ...event.payload });
|
||||
});
|
||||
|
||||
// Show errors for any plugins that failed to load during startup
|
||||
invokeCmd<[string, string][]>("cmd_plugin_init_errors").then((errors) => {
|
||||
invokeCmd<[string, string][]>('cmd_plugin_init_errors').then((errors) => {
|
||||
for (const [dir, err] of errors) {
|
||||
const name = dir.split(/[/\\]/).pop() ?? dir;
|
||||
showToast({
|
||||
id: `plugin-init-error-${name}`,
|
||||
color: "danger",
|
||||
color: 'danger',
|
||||
timeout: null,
|
||||
message: `Failed to load plugin "${name}": ${err}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
listenToTauriEvent("settings", () => openSettings.mutate(null));
|
||||
listenToTauriEvent('settings', () => openSettings.mutate(null));
|
||||
|
||||
// Track active dynamic form dialogs so follow-up input updates can reach them
|
||||
const activeForms = new Map<string, (inputs: FormInput[]) => void>();
|
||||
|
||||
// Listen for plugin events
|
||||
listenToTauriEvent<InternalEvent>(
|
||||
"plugin_event",
|
||||
async ({ payload: event }) => {
|
||||
if (event.payload.type === "prompt_text_request") {
|
||||
const value = await showPrompt(event.payload);
|
||||
listenToTauriEvent<InternalEvent>('plugin_event', async ({ payload: event }) => {
|
||||
if (event.payload.type === 'prompt_text_request') {
|
||||
const value = await showPrompt(event.payload);
|
||||
const result: InternalEvent = {
|
||||
id: generateId(),
|
||||
replyId: event.id,
|
||||
pluginName: event.pluginName,
|
||||
pluginRefId: event.pluginRefId,
|
||||
context: event.context,
|
||||
payload: {
|
||||
type: 'prompt_text_response',
|
||||
value,
|
||||
},
|
||||
};
|
||||
await emit(event.id, result);
|
||||
} else if (event.payload.type === 'prompt_form_request') {
|
||||
if (event.replyId != null) {
|
||||
// Follow-up update from plugin runtime — update the active dialog's inputs
|
||||
const updateInputs = activeForms.get(event.replyId);
|
||||
if (updateInputs) {
|
||||
updateInputs(event.payload.inputs);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Initial request — show the dialog with bidirectional support
|
||||
const emitFormResponse = (values: Record<string, JsonPrimitive> | null, done: boolean) => {
|
||||
const result: InternalEvent = {
|
||||
id: generateId(),
|
||||
replyId: event.id,
|
||||
@@ -67,105 +88,66 @@ export function initGlobalListeners() {
|
||||
pluginRefId: event.pluginRefId,
|
||||
context: event.context,
|
||||
payload: {
|
||||
type: "prompt_text_response",
|
||||
value,
|
||||
type: 'prompt_form_response',
|
||||
values,
|
||||
done,
|
||||
},
|
||||
};
|
||||
await emit(event.id, result);
|
||||
} else if (event.payload.type === "prompt_form_request") {
|
||||
if (event.replyId != null) {
|
||||
// Follow-up update from plugin runtime — update the active dialog's inputs
|
||||
const updateInputs = activeForms.get(event.replyId);
|
||||
if (updateInputs) {
|
||||
updateInputs(event.payload.inputs);
|
||||
}
|
||||
return;
|
||||
}
|
||||
emit(event.id, result);
|
||||
};
|
||||
|
||||
// Initial request — show the dialog with bidirectional support
|
||||
const emitFormResponse = (
|
||||
values: Record<string, JsonPrimitive> | null,
|
||||
done: boolean,
|
||||
) => {
|
||||
const result: InternalEvent = {
|
||||
id: generateId(),
|
||||
replyId: event.id,
|
||||
pluginName: event.pluginName,
|
||||
pluginRefId: event.pluginRefId,
|
||||
context: event.context,
|
||||
payload: {
|
||||
type: "prompt_form_response",
|
||||
values,
|
||||
done,
|
||||
},
|
||||
};
|
||||
emit(event.id, result);
|
||||
};
|
||||
const values = await showPromptForm({
|
||||
id: event.payload.id,
|
||||
title: event.payload.title,
|
||||
description: event.payload.description,
|
||||
size: event.payload.size,
|
||||
inputs: event.payload.inputs,
|
||||
confirmText: event.payload.confirmText,
|
||||
cancelText: event.payload.cancelText,
|
||||
onValuesChange: debounce((values) => emitFormResponse(values, false), 150),
|
||||
onInputsUpdated: (cb) => activeForms.set(event.id, cb),
|
||||
});
|
||||
|
||||
const values = await showPromptForm({
|
||||
id: event.payload.id,
|
||||
title: event.payload.title,
|
||||
description: event.payload.description,
|
||||
size: event.payload.size,
|
||||
inputs: event.payload.inputs,
|
||||
confirmText: event.payload.confirmText,
|
||||
cancelText: event.payload.cancelText,
|
||||
onValuesChange: debounce(
|
||||
(values) => emitFormResponse(values, false),
|
||||
150,
|
||||
),
|
||||
onInputsUpdated: (cb) => activeForms.set(event.id, cb),
|
||||
});
|
||||
// Clean up and send final response
|
||||
activeForms.delete(event.id);
|
||||
emitFormResponse(values, true);
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up and send final response
|
||||
activeForms.delete(event.id);
|
||||
emitFormResponse(values, true);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
listenToTauriEvent<string>(
|
||||
"update_installed",
|
||||
async ({ payload: version }) => {
|
||||
console.log("Got update installed event", version);
|
||||
showUpdateInstalledToast(version);
|
||||
},
|
||||
);
|
||||
listenToTauriEvent<string>('update_installed', async ({ payload: version }) => {
|
||||
console.log('Got update installed event', version);
|
||||
showUpdateInstalledToast(version);
|
||||
});
|
||||
|
||||
// Listen for update events
|
||||
listenToTauriEvent<UpdateInfo>("update_available", async ({ payload }) => {
|
||||
console.log("Got update available", payload);
|
||||
listenToTauriEvent<UpdateInfo>('update_available', async ({ payload }) => {
|
||||
console.log('Got update available', payload);
|
||||
showUpdateAvailableToast(payload);
|
||||
});
|
||||
|
||||
listenToTauriEvent<YaakNotification>("notification", ({ payload }) => {
|
||||
console.log("Got notification event", payload);
|
||||
listenToTauriEvent<YaakNotification>('notification', ({ payload }) => {
|
||||
console.log('Got notification event', payload);
|
||||
showNotificationToast(payload);
|
||||
});
|
||||
|
||||
// Listen for plugin update events
|
||||
listenToTauriEvent<PluginUpdateNotification>(
|
||||
"plugin_updates_available",
|
||||
({ payload }) => {
|
||||
console.log("Got plugin updates event", payload);
|
||||
showPluginUpdatesToast(payload);
|
||||
},
|
||||
);
|
||||
listenToTauriEvent<PluginUpdateNotification>('plugin_updates_available', ({ payload }) => {
|
||||
console.log('Got plugin updates event', payload);
|
||||
showPluginUpdatesToast(payload);
|
||||
});
|
||||
}
|
||||
|
||||
function showUpdateInstalledToast(version: string) {
|
||||
const UPDATE_TOAST_ID = "update-info";
|
||||
const UPDATE_TOAST_ID = 'update-info';
|
||||
|
||||
showToast({
|
||||
id: UPDATE_TOAST_ID,
|
||||
color: "primary",
|
||||
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>
|
||||
<p className="text-text-subtle text-sm">Start using the new version now?</p>
|
||||
</VStack>
|
||||
),
|
||||
action: ({ hide }) => (
|
||||
@@ -176,7 +158,7 @@ function showUpdateInstalledToast(version: string) {
|
||||
loadingChildren="Restarting..."
|
||||
onClick={() => {
|
||||
hide();
|
||||
setTimeout(() => invokeCmd("cmd_restart", {}), 200);
|
||||
setTimeout(() => invokeCmd('cmd_restart', {}), 200);
|
||||
}}
|
||||
>
|
||||
Relaunch Yaak
|
||||
@@ -186,24 +168,23 @@ function showUpdateInstalledToast(version: string) {
|
||||
}
|
||||
|
||||
async function showUpdateAvailableToast(updateInfo: UpdateInfo) {
|
||||
const UPDATE_TOAST_ID = "update-info";
|
||||
const UPDATE_TOAST_ID = 'update-info';
|
||||
const { version, replyEventId, downloaded } = updateInfo;
|
||||
|
||||
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" });
|
||||
await emit<UpdateResponse>(replyEventId, { type: 'ack' });
|
||||
|
||||
showToast({
|
||||
id: UPDATE_TOAST_ID,
|
||||
color: "info",
|
||||
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?
|
||||
{downloaded ? 'Do you want to install' : 'Download and install'} the update?
|
||||
</p>
|
||||
</VStack>
|
||||
),
|
||||
@@ -213,15 +194,15 @@ async function showUpdateAvailableToast(updateInfo: UpdateInfo) {
|
||||
size="xs"
|
||||
color="info"
|
||||
className="min-w-[10rem]"
|
||||
loadingChildren={downloaded ? "Installing..." : "Downloading..."}
|
||||
loadingChildren={downloaded ? 'Installing...' : 'Downloading...'}
|
||||
onClick={async () => {
|
||||
await emit<UpdateResponse>(replyEventId, {
|
||||
type: "action",
|
||||
action: "install",
|
||||
type: 'action',
|
||||
action: 'install',
|
||||
});
|
||||
}}
|
||||
>
|
||||
{downloaded ? "Install Now" : "Download and Install"}
|
||||
{downloaded ? 'Install Now' : 'Download and Install'}
|
||||
</ButtonInfiniteLoading>
|
||||
<Button
|
||||
size="xs"
|
||||
@@ -240,24 +221,23 @@ async function showUpdateAvailableToast(updateInfo: UpdateInfo) {
|
||||
}
|
||||
|
||||
function showPluginUpdatesToast(updateInfo: PluginUpdateNotification) {
|
||||
const PLUGIN_UPDATE_TOAST_ID = "plugin-updates";
|
||||
const PLUGIN_UPDATE_TOAST_ID = 'plugin-updates';
|
||||
const count = updateInfo.updateCount;
|
||||
const pluginNames = updateInfo.plugins.map((p: { name: string }) => p.name);
|
||||
|
||||
showToast({
|
||||
id: PLUGIN_UPDATE_TOAST_ID,
|
||||
color: "info",
|
||||
color: 'info',
|
||||
timeout: null,
|
||||
message: (
|
||||
<VStack>
|
||||
<h2 className="font-semibold">
|
||||
{count === 1 ? "1 plugin update" : `${count} plugin updates`}{" "}
|
||||
available
|
||||
{count === 1 ? '1 plugin update' : `${count} plugin updates`} available
|
||||
</h2>
|
||||
<p className="text-text-subtle text-sm">
|
||||
{count === 1
|
||||
? pluginNames[0]
|
||||
: `${pluginNames.slice(0, 2).join(", ")}${count > 2 ? `, and ${count - 2} more` : ""}`}
|
||||
: `${pluginNames.slice(0, 2).join(', ')}${count > 2 ? `, and ${count - 2} more` : ''}`}
|
||||
</p>
|
||||
</VStack>
|
||||
),
|
||||
@@ -273,8 +253,8 @@ function showPluginUpdatesToast(updateInfo: PluginUpdateNotification) {
|
||||
hide();
|
||||
if (updated.length > 0) {
|
||||
showToast({
|
||||
color: "success",
|
||||
message: `Successfully updated ${updated.length} plugin${updated.length === 1 ? "" : "s"}`,
|
||||
color: 'success',
|
||||
message: `Successfully updated ${updated.length} plugin${updated.length === 1 ? '' : 's'}`,
|
||||
});
|
||||
}
|
||||
}}
|
||||
@@ -287,7 +267,7 @@ function showPluginUpdatesToast(updateInfo: PluginUpdateNotification) {
|
||||
variant="border"
|
||||
onClick={() => {
|
||||
hide();
|
||||
openSettings.mutate("plugins:installed");
|
||||
openSettings.mutate('plugins:installed');
|
||||
}}
|
||||
>
|
||||
View Updates
|
||||
@@ -311,9 +291,7 @@ function showNotificationToast(n: YaakNotification) {
|
||||
</VStack>
|
||||
),
|
||||
onClose: () => {
|
||||
invokeCmd("cmd_dismiss_notification", { notificationId: n.id }).catch(
|
||||
console.error,
|
||||
);
|
||||
invokeCmd('cmd_dismiss_notification', { notificationId: n.id }).catch(console.error);
|
||||
},
|
||||
action: ({ hide }) => {
|
||||
return actionLabel && actionUrl ? (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { VStack } from '../components/core/Stacks';
|
||||
import { VStack } from '@yaakapp-internal/ui';
|
||||
import { WorkspaceEncryptionSetting } from '../components/WorkspaceEncryptionSetting';
|
||||
import { activeWorkspaceMetaAtom } from '../hooks/useActiveWorkspace';
|
||||
import { showDialog } from './dialog';
|
||||
|
||||
Reference in New Issue
Block a user