Add a typed platform package to decouple the frontend from Tauri (#539)

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Gregory Schier
2026-08-14 12:44:01 -07:00
committed by GitHub
co-authored by Claude Opus 5
parent 0068be9ffc
commit 2383f06e71
107 changed files with 954 additions and 505 deletions
+2 -2
View File
@@ -5,7 +5,7 @@ import { createFastMutation } from "../hooks/useFastMutation";
import { wasUpdatedExternally } from "../hooks/useRequestUpdateKey"; import { wasUpdatedExternally } from "../hooks/useRequestUpdateKey";
import { createRequestAndNavigate } from "../lib/createRequestAndNavigate"; import { createRequestAndNavigate } from "../lib/createRequestAndNavigate";
import { jotaiStore } from "../lib/jotai"; import { jotaiStore } from "../lib/jotai";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
import { showToast } from "../lib/toast"; import { showToast } from "../lib/toast";
export function looksLikeCurl(text: string) { export function looksLikeCurl(text: string) {
@@ -20,7 +20,7 @@ export const importCurl = createFastMutation<
mutationKey: ["import_curl"], mutationKey: ["import_curl"],
mutationFn: async ({ overwriteRequestId, command }) => { mutationFn: async ({ overwriteRequestId, command }) => {
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom); const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
const importedRequest: HttpRequest = await invokeCmd("cmd_curl_to_request", { const importedRequest: HttpRequest = await rpc("cmd_curl_to_request", {
command, command,
workspaceId, workspaceId,
}); });
+2 -2
View File
@@ -3,7 +3,7 @@ import { activeWorkspaceIdAtom } from "../hooks/useActiveWorkspace";
import { createFastMutation } from "../hooks/useFastMutation"; import { createFastMutation } from "../hooks/useFastMutation";
import { jotaiStore } from "../lib/jotai"; import { jotaiStore } from "../lib/jotai";
import { router } from "../lib/router"; import { router } from "../lib/router";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
// Allow tab with optional subtab (e.g., "plugins:installed") // Allow tab with optional subtab (e.g., "plugins:installed")
type SettingsTabWithSubtab = SettingsTab | `${SettingsTab}:${string}` | null; type SettingsTabWithSubtab = SettingsTab | `${SettingsTab}:${string}` | null;
@@ -20,7 +20,7 @@ export const openSettings = createFastMutation<void, string, SettingsTabWithSubt
search: { tab: (tab ?? undefined) as SettingsTab | undefined }, search: { tab: (tab ?? undefined) as SettingsTab | undefined },
}); });
await invokeCmd("cmd_new_child_window", { await rpc("cmd_new_child_window", {
url: location.href, url: location.href,
label: "settings", label: "settings",
title: "Yaak Settings", title: "Yaak Settings",
@@ -3,7 +3,7 @@ import { getRecentCookieJars } from "../hooks/useRecentCookieJars";
import { getRecentEnvironments } from "../hooks/useRecentEnvironments"; import { getRecentEnvironments } from "../hooks/useRecentEnvironments";
import { getRecentRequests } from "../hooks/useRecentRequests"; import { getRecentRequests } from "../hooks/useRecentRequests";
import { router } from "../lib/router"; import { router } from "../lib/router";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
export const switchWorkspace = createFastMutation< export const switchWorkspace = createFastMutation<
void, void,
@@ -30,7 +30,7 @@ export const switchWorkspace = createFastMutation<
params: { workspaceId }, params: { workspaceId },
search, search,
}); });
await invokeCmd<void>("cmd_new_main_window", { url: location.href }); await rpc<void>("cmd_new_main_window", { url: location.href });
return; return;
} }
@@ -1,4 +1,3 @@
import { open } from "@tauri-apps/plugin-dialog";
import { gitClone } from "@yaakapp-internal/git"; import { gitClone } from "@yaakapp-internal/git";
import { Banner, VStack } from "@yaakapp-internal/ui"; import { Banner, VStack } from "@yaakapp-internal/ui";
import { useState } from "react"; import { useState } from "react";
@@ -11,6 +10,7 @@ import { Checkbox } from "./core/Checkbox";
import { IconButton } from "./core/IconButton"; import { IconButton } from "./core/IconButton";
import { PlainInput } from "./core/PlainInput"; import { PlainInput } from "./core/PlainInput";
import { promptCredentials } from "./git/credentials"; import { promptCredentials } from "./git/credentials";
import { platform } from "@yaakapp-internal/platform";
interface Props { interface Props {
hide: () => void; hide: () => void;
@@ -38,7 +38,7 @@ export function CloneGitRepositoryDialog({ hide }: Props) {
hasSubdirectory && subdirectory ? `${directory}${sep}${subdirectory}` : directory; hasSubdirectory && subdirectory ? `${directory}${sep}${subdirectory}` : directory;
const handleSelectDirectory = async () => { const handleSelectDirectory = async () => {
const dir = await open({ const dir = await platform.dialog.open({
title: "Select Directory", title: "Select Directory",
directory: true, directory: true,
multiple: false, multiple: false,
@@ -1,11 +1,11 @@
import { invoke } from "@tauri-apps/api/core";
import { openUrl } from "@tauri-apps/plugin-opener";
import type { LicenseCheckStatus } from "@yaakapp-internal/license"; import type { LicenseCheckStatus } from "@yaakapp-internal/license";
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { useKeyValue } from "../hooks/useKeyValue"; import { useKeyValue } from "../hooks/useKeyValue";
import { appInfo } from "../lib/appInfo"; import { appInfo } from "../lib/appInfo";
import { pricingUrl } from "../lib/pricingUrl"; import { pricingUrl } from "../lib/pricingUrl";
import { DismissibleBanner } from "./core/DismissibleBanner"; import { DismissibleBanner } from "./core/DismissibleBanner";
import { rpc } from "../lib/rpc";
import { platform } from "@yaakapp-internal/platform";
const COMMERCIAL_USE_SNOOZE_MS = 7 * 24 * 60 * 60 * 1000; const COMMERCIAL_USE_SNOOZE_MS = 7 * 24 * 60 * 60 * 1000;
const COMMERCIAL_USE_BANNER_MESSAGE = const COMMERCIAL_USE_BANNER_MESSAGE =
@@ -95,7 +95,7 @@ async function shouldShowCommercialUsePrompt(): Promise<boolean> {
} }
try { try {
const license = await invoke<LicenseCheckStatus>("plugin:yaak-license|check"); const license = await rpc<LicenseCheckStatus>("plugin:yaak-license|check");
return license.status === "personal_use"; return license.status === "personal_use";
} catch (err) { } catch (err) {
console.log("Failed to check license before commercial-use prompt", err); console.log("Failed to check license before commercial-use prompt", err);
@@ -104,7 +104,7 @@ async function shouldShowCommercialUsePrompt(): Promise<boolean> {
} }
async function openCommercialUsePricing(source: string): Promise<void> { async function openCommercialUsePricing(source: string): Promise<void> {
await openUrl(pricingUrl(`app.commercial-use.${source}`)).catch(console.error); await platform.openUrl(pricingUrl(`app.commercial-use.${source}`)).catch(console.error);
} }
function isSnoozed(value: string | null, ms: number): boolean { function isSnoozed(value: string | null, ms: number): boolean {
@@ -5,7 +5,7 @@ import { VStack } from "@yaakapp-internal/ui";
import { useState } from "react"; import { useState } from "react";
import { router } from "../lib/router"; import { router } from "../lib/router";
import { setupOrConfigureEncryption } from "../lib/setupOrConfigureEncryption"; import { setupOrConfigureEncryption } from "../lib/setupOrConfigureEncryption";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
import { showErrorToast } from "../lib/toast"; import { showErrorToast } from "../lib/toast";
import { Button } from "./core/Button"; import { Button } from "./core/Button";
import { Checkbox } from "./core/Checkbox"; import { Checkbox } from "./core/Checkbox";
@@ -39,7 +39,7 @@ export function CreateWorkspaceDialog({ hide }: Props) {
// Do getWorkspaceMeta instead of naively creating one because it might have // Do getWorkspaceMeta instead of naively creating one because it might have
// been created already when the store refreshes the workspace meta after // been created already when the store refreshes the workspace meta after
const workspaceMeta = await invokeCmd<WorkspaceMeta>("cmd_get_workspace_meta", { const workspaceMeta = await rpc<WorkspaceMeta>("cmd_get_workspace_meta", {
workspaceId, workspaceId,
}); });
await updateModel({ await updateModel({
@@ -1,4 +1,3 @@
import { save } from "@tauri-apps/plugin-dialog";
import type { Workspace } from "@yaakapp-internal/models"; import type { Workspace } from "@yaakapp-internal/models";
import { workspacesAtom } from "@yaakapp-internal/models"; import { workspacesAtom } from "@yaakapp-internal/models";
import { HStack, VStack } from "@yaakapp-internal/ui"; import { HStack, VStack } from "@yaakapp-internal/ui";
@@ -7,12 +6,13 @@ import { useCallback, useMemo, useState } from "react";
import slugify from "slugify"; import slugify from "slugify";
import { activeWorkspaceAtom } from "../hooks/useActiveWorkspace"; import { activeWorkspaceAtom } from "../hooks/useActiveWorkspace";
import { pluralizeCount } from "../lib/pluralize"; import { pluralizeCount } from "../lib/pluralize";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
import { CommercialUseBanner } from "./CommercialUseBanner"; import { CommercialUseBanner } from "./CommercialUseBanner";
import { Button } from "./core/Button"; import { Button } from "./core/Button";
import { Checkbox } from "./core/Checkbox"; import { Checkbox } from "./core/Checkbox";
import { DetailsBanner } from "./core/DetailsBanner"; import { DetailsBanner } from "./core/DetailsBanner";
import { Link } from "./core/Link"; import { Link } from "./core/Link";
import { platform } from "@yaakapp-internal/platform";
interface Props { interface Props {
onHide: () => void; onHide: () => void;
@@ -65,7 +65,7 @@ function ExportDataDialogContent({
const ids = Object.keys(selectedWorkspaces).filter((k) => selectedWorkspaces[k]); const ids = Object.keys(selectedWorkspaces).filter((k) => selectedWorkspaces[k]);
const workspace = ids.length === 1 ? workspaces.find((w) => w.id === ids[0]) : undefined; const workspace = ids.length === 1 ? workspaces.find((w) => w.id === ids[0]) : undefined;
const slug = workspace ? slugify(workspace.name, { lower: true }) : "workspaces"; const slug = workspace ? slugify(workspace.name, { lower: true }) : "workspaces";
const exportPath = await save({ const exportPath = await platform.dialog.save({
title: "Export Data", title: "Export Data",
defaultPath: `yaak.${slug}.json`, defaultPath: `yaak.${slug}.json`,
}); });
@@ -73,7 +73,7 @@ function ExportDataDialogContent({
return; return;
} }
await invokeCmd("cmd_export_data", { await rpc("cmd_export_data", {
workspaceIds: ids, workspaceIds: ids,
exportPath, exportPath,
includePrivateEnvironments: includePrivateEnvironments, includePrivateEnvironments: includePrivateEnvironments,
@@ -2,7 +2,7 @@ import { HStack, VStack } from "@yaakapp-internal/ui";
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import type { FeedbackFeature } from "../lib/featureFeedbackConstants"; import type { FeedbackFeature } from "../lib/featureFeedbackConstants";
import { FEEDBACK_FEATURES } from "../lib/featureFeedbackConstants"; import { FEEDBACK_FEATURES } from "../lib/featureFeedbackConstants";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
import { hideToastById, showToast } from "../lib/toast"; import { hideToastById, showToast } from "../lib/toast";
import { Button } from "./core/Button"; import { Button } from "./core/Button";
import { Input } from "./core/Input"; import { Input } from "./core/Input";
@@ -31,7 +31,7 @@ export function FeedbackToast({ feature, onDone }: Props) {
onDone(); onDone();
// Fire-and-forget; failures are intentionally ignored // Fire-and-forget; failures are intentionally ignored
invokeCmd("cmd_send_feedback", { feature, text: trimmedText }).catch(() => {}); rpc("cmd_send_feedback", { feature, text: trimmedText }).catch(() => {});
showToast({ showToast({
id: `feature-feedback-${feature}`, id: `feature-feedback-${feature}`,
timeout: 3000, timeout: 3000,
@@ -1,4 +1,3 @@
import { open } from "@tauri-apps/plugin-dialog";
import type { GrpcRequest } from "@yaakapp-internal/models"; import type { GrpcRequest } from "@yaakapp-internal/models";
import { Banner, HStack, Icon, InlineCode, VStack } from "@yaakapp-internal/ui"; import { Banner, HStack, Icon, InlineCode, VStack } from "@yaakapp-internal/ui";
import { useActiveRequest } from "../hooks/useActiveRequest"; import { useActiveRequest } from "../hooks/useActiveRequest";
@@ -8,6 +7,7 @@ import { pluralizeCount } from "../lib/pluralize";
import { Button } from "./core/Button"; import { Button } from "./core/Button";
import { IconButton } from "./core/IconButton"; import { IconButton } from "./core/IconButton";
import { Link } from "./core/Link"; import { Link } from "./core/Link";
import { platform } from "@yaakapp-internal/platform";
interface Props { interface Props {
onDone: () => void; onDone: () => void;
@@ -45,7 +45,7 @@ function GrpcProtoSelectionDialogWithRequest({ request }: Props & { request: Grp
color="primary" color="primary"
variant="border" variant="border"
onClick={async () => { onClick={async () => {
const selected = await open({ const selected = await platform.dialog.open({
title: "Select Proto Files", title: "Select Proto Files",
multiple: true, multiple: true,
filters: [{ name: "Proto Files", extensions: ["proto"] }], filters: [{ name: "Proto Files", extensions: ["proto"] }],
@@ -63,7 +63,7 @@ function GrpcProtoSelectionDialogWithRequest({ request }: Props & { request: Grp
variant="border" variant="border"
color="primary" color="primary"
onClick={async () => { onClick={async () => {
const selected = await open({ const selected = await platform.dialog.open({
title: "Select Proto Directory", title: "Select Proto Directory",
directory: true, directory: true,
}); });
+6 -7
View File
@@ -1,5 +1,4 @@
import { isTauri } from "@tauri-apps/api/core"; import { platform, useCapability } from "@yaakapp-internal/platform";
import { readText } from "@tauri-apps/plugin-clipboard-manager";
import { Icon } from "@yaakapp-internal/ui"; import { Icon } from "@yaakapp-internal/ui";
import * as m from "motion/react-m"; import * as m from "motion/react-m";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
@@ -9,12 +8,12 @@ import { showToast } from "../lib/toast";
import { Button } from "./core/Button"; import { Button } from "./core/Button";
/** /**
* Offers to create a request from a Curl command on the clipboard. The desktop app can * Offers to create a request from a Curl command on the clipboard. A host that can read
* read the clipboard whenever the window is focused, but a browser can't without * the clipboard on its own offers it whenever the window is focused; one that would have
* prompting for permission, so it waits for the user to paste instead. * to prompt for permission first waits for the user to paste instead.
*/ */
export function ImportCurl() { export function ImportCurl() {
return isTauri() ? <ImportCurlButton /> : <ImportCurlOnPaste />; return useCapability("clipboardRead") ? <ImportCurlButton /> : <ImportCurlOnPaste />;
} }
function ImportCurlButton() { function ImportCurlButton() {
@@ -24,7 +23,7 @@ function ImportCurlButton() {
// oxlint-disable-next-line react-hooks/exhaustive-deps -- none // oxlint-disable-next-line react-hooks/exhaustive-deps -- none
useEffect(() => { useEffect(() => {
void readText().then(setClipboardText); void platform.clipboard.readText().then(setClipboardText);
}, [focused]); }, [focused]);
if (!looksLikeCurl(clipboardText)) { if (!looksLikeCurl(clipboardText)) {
+3 -3
View File
@@ -1,4 +1,3 @@
import { openUrl } from "@tauri-apps/plugin-opener";
import type { LicenseCheckStatus } from "@yaakapp-internal/license"; import type { LicenseCheckStatus } from "@yaakapp-internal/license";
import { useLicense } from "@yaakapp-internal/license"; import { useLicense } from "@yaakapp-internal/license";
import { settingsAtom } from "@yaakapp-internal/models"; import { settingsAtom } from "@yaakapp-internal/models";
@@ -14,6 +13,7 @@ import type { ButtonProps } from "./core/Button";
import { Dropdown, type DropdownItem } from "./core/Dropdown"; import { Dropdown, type DropdownItem } from "./core/Dropdown";
import { Icon } from "@yaakapp-internal/ui"; import { Icon } from "@yaakapp-internal/ui";
import { PillButton } from "./core/PillButton"; import { PillButton } from "./core/PillButton";
import { platform } from "@yaakapp-internal/platform";
const dismissedAtom = atomWithKVStorage<string | null>("dismissed_license_expired", null); const dismissedAtom = atomWithKVStorage<string | null>("dismissed_license_expired", null);
@@ -52,7 +52,7 @@ function getDetail(
leftSlot: <Icon icon="gift" />, leftSlot: <Icon icon="gift" />,
rightSlot: <Icon icon="external_link" size="sm" className="opacity-disabled" />, rightSlot: <Icon icon="external_link" size="sm" className="opacity-disabled" />,
hidden: data.data.changes === 0 || data.data.changesUrl == null, hidden: data.data.changes === 0 || data.data.changesUrl == null,
onSelect: () => openUrl(data.data.changesUrl ?? ""), onSelect: () => platform.openUrl(data.data.changesUrl ?? ""),
}, },
{ {
type: "separator", type: "separator",
@@ -63,7 +63,7 @@ function getDetail(
leftSlot: <Icon icon="refresh" />, leftSlot: <Icon icon="refresh" />,
rightSlot: <Icon icon="external_link" size="sm" className="opacity-disabled" />, rightSlot: <Icon icon="external_link" size="sm" className="opacity-disabled" />,
hidden: data.data.changesUrl == null, hidden: data.data.changesUrl == null,
onSelect: () => openUrl(data.data.billingUrl), onSelect: () => platform.openUrl(data.data.billingUrl),
}, },
{ {
label: "Enter License Key", label: "Enter License Key",
+3 -4
View File
@@ -1,7 +1,6 @@
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { convertFileSrc } from "@tauri-apps/api/core";
import { resolveResource } from "@tauri-apps/api/path";
import classNames from "classnames"; import classNames from "classnames";
import { platform } from "@yaakapp-internal/platform";
interface Props { interface Props {
src: string; src: string;
@@ -12,8 +11,8 @@ export function LocalImage({ src: srcPath, className }: Props) {
const src = useQuery({ const src = useQuery({
queryKey: ["local-image", srcPath], queryKey: ["local-image", srcPath],
queryFn: async () => { queryFn: async () => {
const p = await resolveResource(srcPath); const p = await platform.files.resolveResource(srcPath);
return convertFileSrc(p); return platform.files.url(p);
}, },
}); });
@@ -1,4 +1,3 @@
import { openUrl } from "@tauri-apps/plugin-opener";
import type { HttpResponse } from "@yaakapp-internal/models"; import type { HttpResponse } from "@yaakapp-internal/models";
import { format, formatDistanceToNowStrict } from "date-fns"; import { format, formatDistanceToNowStrict } from "date-fns";
import { useMemo } from "react"; import { useMemo } from "react";
@@ -6,6 +5,7 @@ import { CountBadge } from "./core/CountBadge";
import { DetailsBanner } from "./core/DetailsBanner"; import { DetailsBanner } from "./core/DetailsBanner";
import { IconButton } from "./core/IconButton"; import { IconButton } from "./core/IconButton";
import { KeyValueRow, KeyValueRows } from "./core/KeyValueRow"; import { KeyValueRow, KeyValueRows } from "./core/KeyValueRow";
import { platform } from "@yaakapp-internal/platform";
interface Props { interface Props {
response: HttpResponse; response: HttpResponse;
@@ -45,7 +45,7 @@ export function ResponseHeaders({ response }: Props) {
iconSize="sm" iconSize="sm"
className="inline-block w-auto h-auto! opacity-50 hover:opacity-100" className="inline-block w-auto h-auto! opacity-50 hover:opacity-100"
icon="external_link" icon="external_link"
onClick={() => openUrl(response.url)} onClick={() => platform.openUrl(response.url)}
title="Open in browser" title="Open in browser"
/> />
</div> </div>
+2 -2
View File
@@ -1,7 +1,7 @@
import { openUrl } from "@tauri-apps/plugin-opener";
import type { HttpResponse } from "@yaakapp-internal/models"; import type { HttpResponse } from "@yaakapp-internal/models";
import { IconButton } from "./core/IconButton"; import { IconButton } from "./core/IconButton";
import { KeyValueRow, KeyValueRows } from "./core/KeyValueRow"; import { KeyValueRow, KeyValueRows } from "./core/KeyValueRow";
import { platform } from "@yaakapp-internal/platform";
interface Props { interface Props {
response: HttpResponse; response: HttpResponse;
@@ -26,7 +26,7 @@ export function ResponseInfo({ response }: Props) {
iconSize="sm" iconSize="sm"
className="inline-block w-auto ml-1 h-auto! opacity-50 hover:opacity-100" className="inline-block w-auto ml-1 h-auto! opacity-50 hover:opacity-100"
icon="external_link" icon="external_link"
onClick={() => openUrl(response.url)} onClick={() => platform.openUrl(response.url)}
title="Open in browser" title="Open in browser"
/> />
</div> </div>
+17 -29
View File
@@ -1,5 +1,4 @@
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"; import { platform } from "@yaakapp-internal/platform";
import { open } from "@tauri-apps/plugin-dialog";
import { HStack } from "@yaakapp-internal/ui"; import { HStack } from "@yaakapp-internal/ui";
import classNames from "classnames"; import classNames from "classnames";
import mime from "mime"; import mime from "mime";
@@ -41,7 +40,7 @@ export function SelectFile({
...props ...props
}: Props) { }: Props) {
const handleClick = async () => { const handleClick = async () => {
const filePath = await open({ const filePath = await platform.dialog.open({
title: directory ? "Select Folder" : "Select File", title: directory ? "Select Folder" : "Select File",
multiple: false, multiple: false,
directory, directory,
@@ -64,32 +63,21 @@ export function SelectFile({
// NOTE: This doesn't work for Windows since native drag-n-drop can't work at the same tmie // NOTE: This doesn't work for Windows since native drag-n-drop can't work at the same tmie
// as browser drag-n-drop. // as browser drag-n-drop.
useEffect(() => { useEffect(() => {
let unlisten: (() => void) | undefined; return platform.window.onDragDrop((event) => {
const setup = async () => { if (event.type === "over") {
const webview = getCurrentWebviewWindow(); const p = event.position;
unlisten = await webview.onDragDropEvent((event) => { const r = ref.current?.getBoundingClientRect();
if (event.payload.type === "over") { if (r == null) return;
const p = event.payload.position; const isOver = p.x >= r.left && p.x <= r.right && p.y >= r.top && p.y <= r.bottom;
const r = ref.current?.getBoundingClientRect(); setIsHovering(isOver);
if (r == null) return; } else if (event.type === "drop" && isHovering) {
const isOver = p.x >= r.left && p.x <= r.right && p.y >= r.top && p.y <= r.bottom; const p = event.paths[0];
console.log("IS OVER", isOver); if (p) onChange({ filePath: p, contentType: null });
setIsHovering(isOver); setIsHovering(false);
} else if (event.payload.type === "drop" && isHovering) { } else {
console.log("User dropped", event.payload.paths); setIsHovering(false);
const p = event.payload.paths[0]; }
if (p) onChange({ filePath: p, contentType: null }); });
setIsHovering(false);
} else {
console.log("File drop cancelled");
setIsHovering(false);
}
});
};
setup().catch(console.error);
return () => {
if (unlisten) unlisten();
};
}, [isHovering, onChange]); }, [isHovering, onChange]);
const filePathWithNameOverride = nameOverride ? `${filePath} (${nameOverride})` : filePath; const filePathWithNameOverride = nameOverride ? `${filePath} (${nameOverride})` : filePath;
@@ -1,6 +1,5 @@
import { useSearch } from "@tanstack/react-router"; import { useSearch } from "@tanstack/react-router";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"; import { platform } from "@yaakapp-internal/platform";
import { type } from "@tauri-apps/plugin-os";
import { useLicense } from "@yaakapp-internal/license"; import { useLicense } from "@yaakapp-internal/license";
import { pluginsAtom, settingsAtom } from "@yaakapp-internal/models"; import { pluginsAtom, settingsAtom } from "@yaakapp-internal/models";
import { HeaderSize, HStack, Icon } from "@yaakapp-internal/ui"; import { HeaderSize, HStack, Icon } from "@yaakapp-internal/ui";
@@ -60,7 +59,7 @@ export default function Settings({ hide }: Props) {
hide(); hide();
} else { } else {
// It's being shown in a window, so close the window // It's being shown in a window, so close the window
await getCurrentWebviewWindow().close(); await platform.window.close();
} }
}); });
@@ -75,7 +74,7 @@ export default function Settings({ hide }: Props) {
onlyXWindowControl onlyXWindowControl
size="md" size="md"
className="x-theme-appHeader bg-surface text-text-subtle flex items-center justify-center border-b border-border-subtle text-sm font-semibold" className="x-theme-appHeader bg-surface text-text-subtle flex items-center justify-center border-b border-border-subtle text-sm font-semibold"
osType={type()} osType={platform.osType()}
hideWindowControls={settings.hideWindowControls} hideWindowControls={settings.hideWindowControls}
useNativeTitlebar={settings.useNativeTitlebar} useNativeTitlebar={settings.useNativeTitlebar}
interfaceScale={settings.interfaceScale} interfaceScale={settings.interfaceScale}
@@ -85,7 +84,7 @@ export default function Settings({ hide }: Props) {
justifyContent="center" justifyContent="center"
className="w-full h-full grid grid-cols-[1fr_auto] pointer-events-none" className="w-full h-full grid grid-cols-[1fr_auto] pointer-events-none"
> >
<div className={classNames(type() === "macos" ? "text-center" : "pl-2")}>Settings</div> <div className={classNames(platform.osType() === "macos" ? "text-center" : "pl-2")}>Settings</div>
</HStack> </HStack>
</HeaderSize> </HeaderSize>
)} )}
@@ -1,4 +1,3 @@
import { revealItemInDir } from "@tauri-apps/plugin-opener";
import { patchModel, settingsAtom } from "@yaakapp-internal/models"; import { patchModel, settingsAtom } from "@yaakapp-internal/models";
import { Heading, VStack } from "@yaakapp-internal/ui"; import { Heading, VStack } from "@yaakapp-internal/ui";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
@@ -9,6 +8,7 @@ import { CargoFeature } from "../CargoFeature";
import { CommercialUseBanner } from "../CommercialUseBanner"; import { CommercialUseBanner } from "../CommercialUseBanner";
import { DismissibleBanner } from "../core/DismissibleBanner"; import { DismissibleBanner } from "../core/DismissibleBanner";
import { IconButton } from "../core/IconButton"; import { IconButton } from "../core/IconButton";
import { platform } from "@yaakapp-internal/platform";
import { import {
ModelSettingRowBoolean, ModelSettingRowBoolean,
ModelSettingSelectControl, ModelSettingSelectControl,
@@ -152,7 +152,7 @@ export function SettingsGeneral() {
{ {
title: revealInFinderText, title: revealInFinderText,
icon: "folder_open", icon: "folder_open",
onClick: () => revealItemInDir(appInfo.appDataDir), onClick: () => platform.revealItemInDir(appInfo.appDataDir),
}, },
]} ]}
/> />
@@ -168,7 +168,7 @@ export function SettingsGeneral() {
{ {
title: revealInFinderText, title: revealInFinderText,
icon: "folder_open", icon: "folder_open",
onClick: () => revealItemInDir(appInfo.appLogDir), onClick: () => platform.revealItemInDir(appInfo.appLogDir),
}, },
]} ]}
/> />
@@ -1,4 +1,4 @@
import { type } from "@tauri-apps/plugin-os"; import { platform } from "@yaakapp-internal/platform";
import { useFonts } from "@yaakapp-internal/fonts"; import { useFonts } from "@yaakapp-internal/fonts";
import { useLicense } from "@yaakapp-internal/license"; import { useLicense } from "@yaakapp-internal/license";
import type { EditorKeymap, Settings } from "@yaakapp-internal/models"; import type { EditorKeymap, Settings } from "@yaakapp-internal/models";
@@ -9,7 +9,7 @@ import { useState } from "react";
import { activeWorkspaceAtom } from "../../hooks/useActiveWorkspace"; import { activeWorkspaceAtom } from "../../hooks/useActiveWorkspace";
import { showConfirm } from "../../lib/confirm"; import { showConfirm } from "../../lib/confirm";
import { pricingUrl } from "../../lib/pricingUrl"; import { pricingUrl } from "../../lib/pricingUrl";
import { invokeCmd } from "../../lib/tauri"; import { rpc } from "../../lib/rpc";
import { CargoFeature } from "../CargoFeature"; import { CargoFeature } from "../CargoFeature";
import { Button } from "../core/Button"; import { Button } from "../core/Button";
import { Checkbox } from "../core/Checkbox"; import { Checkbox } from "../core/Checkbox";
@@ -176,7 +176,7 @@ export function SettingsInterface() {
<SettingsSection title="Window"> <SettingsSection title="Window">
<NativeTitlebarSetting settings={settings} /> <NativeTitlebarSetting settings={settings} />
{type() !== "macos" && ( {platform.osType() !== "macos" && (
<ModelSettingRowBoolean <ModelSettingRowBoolean
model={settings} model={settings}
modelKey="hideWindowControls" modelKey="hideWindowControls"
@@ -216,7 +216,7 @@ function NativeTitlebarSetting({ settings }: { settings: Settings }) {
size="xs" size="xs"
onClick={async () => { onClick={async () => {
await patchModel(settings, { useNativeTitlebar: nativeTitlebar }); await patchModel(settings, { useNativeTitlebar: nativeTitlebar });
await invokeCmd("cmd_restart"); await rpc("cmd_restart");
}} }}
> >
Apply and Restart Apply and Restart
@@ -1,4 +1,3 @@
import { openUrl } from "@tauri-apps/plugin-opener";
import { useLicense } from "@yaakapp-internal/license"; import { useLicense } from "@yaakapp-internal/license";
import { Banner, HStack, Icon, VStack } from "@yaakapp-internal/ui"; import { Banner, HStack, Icon, VStack } from "@yaakapp-internal/ui";
import { differenceInDays } from "date-fns"; import { differenceInDays } from "date-fns";
@@ -12,6 +11,7 @@ import { Button } from "../core/Button";
import { Link } from "../core/Link"; import { Link } from "../core/Link";
import { PlainInput } from "../core/PlainInput"; import { PlainInput } from "../core/PlainInput";
import { Separator } from "../core/Separator"; import { Separator } from "../core/Separator";
import { platform } from "@yaakapp-internal/platform";
export function SettingsLicense() { export function SettingsLicense() {
return ( return (
@@ -135,7 +135,7 @@ function SettingsLicenseCmp() {
<Button <Button
color="secondary" color="secondary"
size="sm" size="sm"
onClick={() => openUrl("https://yaak.app/dashboard?intent=app.license.support")} onClick={() => platform.openUrl("https://yaak.app/dashboard?intent=app.license.support")}
rightSlot={<Icon icon="external_link" />} rightSlot={<Icon icon="external_link" />}
> >
Direct Support Direct Support
@@ -151,7 +151,7 @@ function SettingsLicenseCmp() {
color="primary" color="primary"
rightSlot={<Icon icon="external_link" />} rightSlot={<Icon icon="external_link" />}
onClick={() => onClick={() =>
openUrl(pricingUrl(`app.license.purchase.${check.data?.status ?? "unknown"}`)) platform.openUrl(pricingUrl(`app.license.purchase.${check.data?.status ?? "unknown"}`))
} }
> >
Purchase License Purchase License
@@ -1,5 +1,4 @@
import { useMutation, useQuery } from "@tanstack/react-query"; import { useMutation, useQuery } from "@tanstack/react-query";
import { openUrl } from "@tauri-apps/plugin-opener";
import type { Plugin } from "@yaakapp-internal/models"; import type { Plugin } from "@yaakapp-internal/models";
import { patchModel, pluginsAtom } from "@yaakapp-internal/models"; import { patchModel, pluginsAtom } from "@yaakapp-internal/models";
import type { PluginVersion } from "@yaakapp-internal/plugins"; import type { PluginVersion } from "@yaakapp-internal/plugins";
@@ -39,6 +38,7 @@ import { PlainInput } from "../core/PlainInput";
import { TabContent, Tabs } from "../core/Tabs/Tabs"; import { TabContent, Tabs } from "../core/Tabs/Tabs";
import { EmptyStateText } from "../EmptyStateText"; import { EmptyStateText } from "../EmptyStateText";
import { SelectFile } from "../SelectFile"; import { SelectFile } from "../SelectFile";
import { platform } from "@yaakapp-internal/platform";
interface SettingsPluginsProps { interface SettingsPluginsProps {
defaultSubtab?: string; defaultSubtab?: string;
@@ -113,7 +113,7 @@ export function SettingsPlugins({ defaultSubtab }: SettingsPluginsProps) {
icon="help" icon="help"
title="View documentation" title="View documentation"
onClick={() => onClick={() =>
openUrl("https://yaak.app/docs/plugin-development/plugins-quick-start") platform.openUrl("https://yaak.app/docs/plugin-development/plugins-quick-start")
} }
/> />
</HStack> </HStack>
@@ -1,4 +1,3 @@
import { openUrl } from "@tauri-apps/plugin-opener";
import { useLicense } from "@yaakapp-internal/license"; import { useLicense } from "@yaakapp-internal/license";
import { useRef } from "react"; import { useRef } from "react";
import { openSettings } from "../commands/openSettings"; import { openSettings } from "../commands/openSettings";
@@ -13,6 +12,7 @@ import { Dropdown } from "./core/Dropdown";
import { Icon } from "@yaakapp-internal/ui"; import { Icon } from "@yaakapp-internal/ui";
import { IconButton } from "./core/IconButton"; import { IconButton } from "./core/IconButton";
import { KeyboardShortcutsDialog } from "./KeyboardShortcutsDialog"; import { KeyboardShortcutsDialog } from "./KeyboardShortcutsDialog";
import { platform } from "@yaakapp-internal/platform";
export function SettingsDropdown() { export function SettingsDropdown() {
const exportData = useExportData(); const exportData = useExportData();
@@ -62,7 +62,7 @@ export function SettingsDropdown() {
{ {
label: "Create Run Button", label: "Create Run Button",
leftSlot: <Icon icon="rocket" />, leftSlot: <Icon icon="rocket" />,
onSelect: () => openUrl("https://yaak.app/button/new"), onSelect: () => platform.openUrl("https://yaak.app/button/new"),
}, },
{ type: "separator", label: `Yaak v${appInfo.version}` }, { type: "separator", label: `Yaak v${appInfo.version}` },
{ {
@@ -78,26 +78,26 @@ export function SettingsDropdown() {
leftSlot: <Icon icon="circle_dollar_sign" />, leftSlot: <Icon icon="circle_dollar_sign" />,
rightSlot: <Icon icon="external_link" color="success" className="opacity-60" />, rightSlot: <Icon icon="external_link" color="success" className="opacity-60" />,
onSelect: () => onSelect: () =>
openUrl(pricingUrl(`app.menu.purchase.${check.data?.status ?? "unknown"}`)), platform.openUrl(pricingUrl(`app.menu.purchase.${check.data?.status ?? "unknown"}`)),
}, },
{ {
label: "Install CLI", label: "Install CLI",
hidden: appInfo.cliVersion != null, hidden: appInfo.cliVersion != null,
leftSlot: <Icon icon="square_terminal" />, leftSlot: <Icon icon="square_terminal" />,
rightSlot: <Icon icon="external_link" color="secondary" />, rightSlot: <Icon icon="external_link" color="secondary" />,
onSelect: () => openUrl("https://yaak.app/docs/cli"), onSelect: () => platform.openUrl("https://yaak.app/docs/cli"),
}, },
{ {
label: "Feedback", label: "Feedback",
leftSlot: <Icon icon="chat" />, leftSlot: <Icon icon="chat" />,
rightSlot: <Icon icon="external_link" color="secondary" />, rightSlot: <Icon icon="external_link" color="secondary" />,
onSelect: () => openUrl("https://yaak.app/feedback"), onSelect: () => platform.openUrl("https://yaak.app/feedback"),
}, },
{ {
label: "Changelog", label: "Changelog",
leftSlot: <Icon icon="cake" />, leftSlot: <Icon icon="cake" />,
rightSlot: <Icon icon="external_link" color="secondary" />, rightSlot: <Icon icon="external_link" color="secondary" />,
onSelect: () => openUrl(`https://yaak.app/changelog/${appInfo.version}`), onSelect: () => platform.openUrl(`https://yaak.app/changelog/${appInfo.version}`),
}, },
]} ]}
> >
+4 -4
View File
@@ -1,6 +1,5 @@
import type { Extension } from "@codemirror/state"; import type { Extension } from "@codemirror/state";
import { Compartment } from "@codemirror/state"; import { Compartment } from "@codemirror/state";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import { debounce } from "@yaakapp-internal/lib"; import { debounce } from "@yaakapp-internal/lib";
import { gitMutations } from "@yaakapp-internal/git"; import { gitMutations } from "@yaakapp-internal/git";
import type { GitStatus } from "@yaakapp-internal/git"; import type { GitStatus } from "@yaakapp-internal/git";
@@ -44,7 +43,8 @@ import { getFolderActions } from "../hooks/useFolderActions";
import { getGrpcRequestActions } from "../hooks/useGrpcRequestActions"; import { getGrpcRequestActions } from "../hooks/useGrpcRequestActions";
import { useHotKey } from "../hooks/useHotKey"; import { useHotKey } from "../hooks/useHotKey";
import { getHttpRequestActions } from "../hooks/useHttpRequestActions"; import { getHttpRequestActions } from "../hooks/useHttpRequestActions";
import { useListenToTauriEvent } from "../hooks/useListenToTauriEvent"; import { platform } from "@yaakapp-internal/platform";
import { usePlatformEvent } from "../hooks/usePlatformEvent";
import { getModelAncestors } from "../hooks/useModelAncestors"; import { getModelAncestors } from "../hooks/useModelAncestors";
import { sendAnyHttpRequest } from "../hooks/useSendAnyHttpRequest"; import { sendAnyHttpRequest } from "../hooks/useSendAnyHttpRequest";
import { useSidebarHidden } from "../hooks/useSidebarHidden"; import { useSidebarHidden } from "../hooks/useSidebarHidden";
@@ -135,10 +135,10 @@ function Sidebar({ className }: { className?: string }) {
// Focus new sidebar models created by the user in this window. Writes from other // Focus new sidebar models created by the user in this window. Writes from other
// sources (import, sync, CLI) can carry thousands of models and shouldn't move // sources (import, sync, CLI) can carry thousands of models and shouldn't move
// the selection. // the selection.
useListenToTauriEvent<ModelPayload[]>("model_writes", ({ payload: payloads }) => { usePlatformEvent<ModelPayload[]>("model_writes", (payloads) => {
for (const payload of payloads) { for (const payload of payloads) {
if (payload.updateSource.type !== "window") continue; if (payload.updateSource.type !== "window") continue;
if (payload.updateSource.label !== getCurrentWebviewWindow().label) continue; if (payload.updateSource.label !== platform.window.label) continue;
if (!isSidebarLeafModel(payload.model)) continue; if (!isSidebarLeafModel(payload.model)) continue;
if (!(payload.change.type === "upsert" && payload.change.created)) continue; if (!(payload.change.type === "upsert" && payload.change.created)) continue;
treeRef.current?.selectItem(payload.model.id, true); treeRef.current?.selectItem(payload.model.id, true);
@@ -1,4 +1,3 @@
import { readDir } from "@tauri-apps/plugin-fs";
import { Banner, VStack } from "@yaakapp-internal/ui"; import { Banner, VStack } from "@yaakapp-internal/ui";
import { useState } from "react"; import { useState } from "react";
import { openWorkspaceFromSyncDir } from "../commands/openWorkspaceFromSyncDir"; import { openWorkspaceFromSyncDir } from "../commands/openWorkspaceFromSyncDir";
@@ -6,6 +5,7 @@ import { Button } from "./core/Button";
import { Checkbox } from "./core/Checkbox"; import { Checkbox } from "./core/Checkbox";
import { SettingRowBoolean, SettingRowDirectory } from "./core/SettingRow"; import { SettingRowBoolean, SettingRowDirectory } from "./core/SettingRow";
import { SelectFile } from "./SelectFile"; import { SelectFile } from "./SelectFile";
import { platform } from "@yaakapp-internal/platform";
export interface SyncToFilesystemSettingProps { export interface SyncToFilesystemSettingProps {
layout?: "form" | "settings"; layout?: "form" | "settings";
@@ -24,7 +24,7 @@ export function SyncToFilesystemSetting({
const handleFilePathChange = async (filePath: string | null) => { const handleFilePathChange = async (filePath: string | null) => {
if (filePath != null) { if (filePath != null) {
const files = await readDir(filePath); const files = await platform.files.readDir(filePath);
if (files.length > 0) { if (files.length > 0) {
setSyncDir(filePath); setSyncDir(filePath);
return; return;
+2 -2
View File
@@ -1,4 +1,4 @@
import { type } from "@tauri-apps/plugin-os"; import { platform } from "@yaakapp-internal/platform";
import { settingsAtom, workspacesAtom } from "@yaakapp-internal/models"; import { settingsAtom, workspacesAtom } from "@yaakapp-internal/models";
import { Banner, HeaderSize, HStack, SidebarLayout } from "@yaakapp-internal/ui"; import { Banner, HeaderSize, HStack, SidebarLayout } from "@yaakapp-internal/ui";
import classNames from "classnames"; import classNames from "classnames";
@@ -53,7 +53,7 @@ export function Workspace() {
const workspaces = useAtomValue(workspacesAtom); const workspaces = useAtomValue(workspacesAtom);
const settings = useAtomValue(settingsAtom); const settings = useAtomValue(settingsAtom);
const osType = type(); const osType = platform.osType();
const [width, setWidth] = useSidebarWidth(); const [width, setWidth] = useSidebarWidth();
const [sidebarHidden, setSidebarHidden] = useSidebarHidden(); const [sidebarHidden, setSidebarHidden] = useSidebarHidden();
const [floatingSidebarHidden, setFloatingSidebarHidden] = useFloatingSidebarHidden(); const [floatingSidebarHidden, setFloatingSidebarHidden] = useFloatingSidebarHidden();
@@ -1,5 +1,3 @@
import { open } from "@tauri-apps/plugin-dialog";
import { revealItemInDir } from "@tauri-apps/plugin-opener";
import { getModel, settingsAtom, workspacesAtom } from "@yaakapp-internal/models"; import { getModel, settingsAtom, workspacesAtom } from "@yaakapp-internal/models";
import classNames from "classnames"; import classNames from "classnames";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
@@ -27,6 +25,7 @@ import { Icon } from "@yaakapp-internal/ui";
import type { RadioDropdownItem } from "./core/RadioDropdown"; import type { RadioDropdownItem } from "./core/RadioDropdown";
import { RadioDropdown } from "./core/RadioDropdown"; import { RadioDropdown } from "./core/RadioDropdown";
import { SwitchWorkspaceDialog } from "./SwitchWorkspaceDialog"; import { SwitchWorkspaceDialog } from "./SwitchWorkspaceDialog";
import { platform } from "@yaakapp-internal/platform";
type Props = Pick<ButtonProps, "className" | "justify" | "forDropdown" | "leftSlot">; type Props = Pick<ButtonProps, "className" | "justify" | "forDropdown" | "leftSlot">;
@@ -76,7 +75,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
label: "Open Folder", label: "Open Folder",
leftSlot: <Icon icon="folder_open" />, leftSlot: <Icon icon="folder_open" />,
onSelect: async () => { onSelect: async () => {
const dir = await open({ const dir = await platform.dialog.open({
title: "Select Workspace Directory", title: "Select Workspace Directory",
directory: true, directory: true,
multiple: false, multiple: false,
@@ -120,7 +119,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
leftSlot: <Icon icon="folder_symlink" />, leftSlot: <Icon icon="folder_symlink" />,
onSelect: async () => { onSelect: async () => {
if (workspaceMeta?.settingSyncDir == null) return; if (workspaceMeta?.settingSyncDir == null) return;
await revealItemInDir(workspaceMeta.settingSyncDir); await platform.revealItemInDir(workspaceMeta.settingSyncDir);
}, },
}, },
{ {
@@ -9,7 +9,6 @@ import {
useSensor, useSensor,
useSensors, useSensors,
} from "@dnd-kit/core"; } from "@dnd-kit/core";
import { basename } from "@tauri-apps/api/path";
import classNames from "classnames"; import classNames from "classnames";
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { WrappedEnvironmentVariable } from "../../hooks/useEnvironmentVariables"; import type { WrappedEnvironmentVariable } from "../../hooks/useEnvironmentVariables";
@@ -34,6 +33,7 @@ import { Input } from "./Input";
import { ensurePairId } from "./PairEditor.util"; import { ensurePairId } from "./PairEditor.util";
import type { RadioDropdownItem } from "./RadioDropdown"; import type { RadioDropdownItem } from "./RadioDropdown";
import { RadioDropdown } from "./RadioDropdown"; import { RadioDropdown } from "./RadioDropdown";
import { platform } from "@yaakapp-internal/platform";
export interface PairEditorHandle { export interface PairEditorHandle {
/** /**
@@ -851,7 +851,7 @@ function FileActionsDropdown({
leftSlot: <Icon icon="file_code" />, leftSlot: <Icon icon="file_code" />,
onSelect: async () => { onSelect: async () => {
console.log("PAIR", pair); console.log("PAIR", pair);
const defaultFilename = await basename(pair.value ?? ""); const defaultFilename = await platform.files.basename(pair.value ?? "");
const filename = await showPrompt({ const filename = await showPrompt({
id: "filename", id: "filename",
title: "Override Filename", title: "Override Filename",
+2 -2
View File
@@ -1,4 +1,4 @@
import { type } from "@tauri-apps/plugin-os"; import { platform } from "@yaakapp-internal/platform";
import { HStack } from "@yaakapp-internal/ui"; import { HStack } from "@yaakapp-internal/ui";
import classNames from "classnames"; import classNames from "classnames";
import type { CSSProperties, ReactNode } from "react"; import type { CSSProperties, ReactNode } from "react";
@@ -66,7 +66,7 @@ export function Select<T extends string>({
<Label htmlFor={id} visuallyHidden={hideLabel} className={labelClassName} help={help}> <Label htmlFor={id} visuallyHidden={hideLabel} className={labelClassName} help={help}>
{label} {label}
</Label> </Label>
{type() === "macos" && !filterable ? ( {platform.osType() === "macos" && !filterable ? (
<HStack <HStack
space={2} space={2}
className={classNames( className={classNames(
@@ -1,5 +1,5 @@
import { convertFileSrc } from "@tauri-apps/api/core";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { platform } from "@yaakapp-internal/platform";
interface Props { interface Props {
bodyPath?: string; bodyPath?: string;
@@ -12,7 +12,7 @@ export function AudioViewer({ bodyPath, data, mimeType }: Props) {
useEffect(() => { useEffect(() => {
if (bodyPath) { if (bodyPath) {
setSrc(convertFileSrc(bodyPath)); setSrc(platform.files.url(bodyPath));
} else if (data) { } else if (data) {
// The type matters here in a way it doesn't for an image: a media element goes by what // The type matters here in a way it doesn't for an image: a media element goes by what
// the blob declares rather than sniffing it, so an Ogg labelled as MP3 won't play // the blob declares rather than sniffing it, so an Ogg labelled as MP3 won't play
@@ -1,6 +1,6 @@
import { convertFileSrc } from "@tauri-apps/api/core";
import classNames from "classnames"; import classNames from "classnames";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { platform } from "@yaakapp-internal/platform";
type Props = { className?: string; mimeType?: string } & ( type Props = { className?: string; mimeType?: string } & (
| { | {
@@ -18,7 +18,7 @@ export function ImageViewer({ className, mimeType, ...props }: Props) {
useEffect(() => { useEffect(() => {
if (bodyPath != null) { if (bodyPath != null) {
setSrc(convertFileSrc(bodyPath)); setSrc(platform.files.url(bodyPath));
} else if (data != null) { } else if (data != null) {
const blob = new Blob([data], { type: mimeType ?? "image/png" }); const blob = new Blob([data], { type: mimeType ?? "image/png" });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
@@ -1,12 +1,12 @@
import "react-pdf/dist/Page/TextLayer.css"; import "react-pdf/dist/Page/TextLayer.css";
import "react-pdf/dist/Page/AnnotationLayer.css"; import "react-pdf/dist/Page/AnnotationLayer.css";
import { convertFileSrc } from "@tauri-apps/api/core";
import "./PdfViewer.css"; import "./PdfViewer.css";
import type { PDFDocumentProxy } from "pdfjs-dist"; import type { PDFDocumentProxy } from "pdfjs-dist";
import { useMemo, useRef, useState } from "react"; import { useMemo, useRef, useState } from "react";
import { Document, Page } from "react-pdf"; import { Document, Page } from "react-pdf";
import { useContainerSize } from "@yaakapp-internal/ui"; import { useContainerSize } from "@yaakapp-internal/ui";
import { fireAndForget } from "../../lib/fireAndForget"; import { fireAndForget } from "../../lib/fireAndForget";
import { platform } from "@yaakapp-internal/platform";
fireAndForget( fireAndForget(
import("react-pdf").then(({ pdfjs }) => { import("react-pdf").then(({ pdfjs }) => {
@@ -37,7 +37,7 @@ export function PdfViewer({ bodyPath, data }: Props) {
// `Document` renders its "Failed to load PDF file" state for that frame before recovering // `Document` renders its "Failed to load PDF file" state for that frame before recovering
const src = useMemo(() => { const src = useMemo(() => {
if (bodyPath) { if (bodyPath) {
return convertFileSrc(bodyPath); return platform.files.url(bodyPath);
} }
if (data) { if (data) {
// Create a copy to avoid "Buffer is already detached" errors // Create a copy to avoid "Buffer is already detached" errors
@@ -1,5 +1,5 @@
import { convertFileSrc } from "@tauri-apps/api/core";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { platform } from "@yaakapp-internal/platform";
interface Props { interface Props {
bodyPath?: string; bodyPath?: string;
@@ -12,7 +12,7 @@ export function VideoViewer({ bodyPath, data, mimeType }: Props) {
useEffect(() => { useEffect(() => {
if (bodyPath) { if (bodyPath) {
setSrc(convertFileSrc(bodyPath)); setSrc(platform.files.url(bodyPath));
} else if (data) { } else if (data) {
// As in AudioViewer: a media element trusts the declared type instead of sniffing // As in AudioViewer: a media element trusts the declared type instead of sniffing
const blob = new Blob([new Uint8Array(data)], { type: mimeType ?? "video/mp4" }); const blob = new Blob([new Uint8Array(data)], { type: mimeType ?? "video/mp4" });
+4 -4
View File
@@ -1,5 +1,5 @@
// Listen for settings changes, the re-compute theme // Listen for settings changes, the re-compute theme
import { listen } from "@tauri-apps/api/event"; import { platform } from "@yaakapp-internal/platform";
import type { ModelPayload } from "@yaakapp-internal/models"; import type { ModelPayload } from "@yaakapp-internal/models";
import { fireAndForget } from "./lib/fireAndForget"; import { fireAndForget } from "./lib/fireAndForget";
import { getSettings } from "./lib/settings"; import { getSettings } from "./lib/settings";
@@ -8,12 +8,12 @@ function setFontSizeOnDocument(fontSize: number) {
document.documentElement.style.fontSize = `${fontSize}px`; document.documentElement.style.fontSize = `${fontSize}px`;
} }
listen<ModelPayload[]>("model_writes", async (event) => { platform.listen<ModelPayload[]>("model_writes", (payloads) => {
for (const payload of event.payload) { for (const payload of payloads) {
if (payload.change.type !== "upsert") continue; if (payload.change.type !== "upsert") continue;
if (payload.model.model !== "settings") continue; if (payload.model.model !== "settings") continue;
setFontSizeOnDocument(payload.model.interfaceFontSize); setFontSizeOnDocument(payload.model.interfaceFontSize);
} }
}).catch(console.error); });
fireAndForget(getSettings().then((settings) => setFontSizeOnDocument(settings.interfaceFontSize))); fireAndForget(getSettings().then((settings) => setFontSizeOnDocument(settings.interfaceFontSize)));
+4 -4
View File
@@ -1,5 +1,5 @@
// Listen for settings changes, the re-compute theme // Listen for settings changes, the re-compute theme
import { listen } from "@tauri-apps/api/event"; import { platform } from "@yaakapp-internal/platform";
import type { ModelPayload, Settings } from "@yaakapp-internal/models"; import type { ModelPayload, Settings } from "@yaakapp-internal/models";
import { fireAndForget } from "./lib/fireAndForget"; import { fireAndForget } from "./lib/fireAndForget";
import { getSettings } from "./lib/settings"; import { getSettings } from "./lib/settings";
@@ -12,12 +12,12 @@ function setFonts(settings: Settings) {
); );
} }
listen<ModelPayload[]>("model_writes", async (event) => { platform.listen<ModelPayload[]>("model_writes", (payloads) => {
for (const payload of event.payload) { for (const payload of payloads) {
if (payload.change.type !== "upsert") continue; if (payload.change.type !== "upsert") continue;
if (payload.model.model !== "settings") continue; if (payload.model.model !== "settings") continue;
setFonts(payload.model); setFonts(payload.model);
} }
}).catch(console.error); });
fireAndForget(getSettings().then((settings) => setFonts(settings))); fireAndForget(getSettings().then((settings) => setFonts(settings)));
@@ -1,9 +1,9 @@
import { event } from "@tauri-apps/api"; import { platform } from "@yaakapp-internal/platform";
import { useFastMutation } from "./useFastMutation"; import { useFastMutation } from "./useFastMutation";
export function useCancelHttpResponse(id: string | null) { export function useCancelHttpResponse(id: string | null) {
return useFastMutation<void>({ return useFastMutation<void>({
mutationKey: ["cancel_http_response", id], mutationKey: ["cancel_http_response", id],
mutationFn: () => event.emit(`cancel_http_response_${id}`), mutationFn: () => platform.emit(`cancel_http_response_${id}`),
}); });
} }
@@ -3,13 +3,13 @@ import { InlineCode } from "@yaakapp-internal/ui";
import { showAlert } from "../lib/alert"; import { showAlert } from "../lib/alert";
import { appInfo } from "../lib/appInfo"; import { appInfo } from "../lib/appInfo";
import { minPromiseMillis } from "../lib/minPromiseMillis"; import { minPromiseMillis } from "../lib/minPromiseMillis";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
export function useCheckForUpdates() { export function useCheckForUpdates() {
return useMutation({ return useMutation({
mutationKey: ["check_for_updates"], mutationKey: ["check_for_updates"],
mutationFn: async () => { mutationFn: async () => {
const hasUpdate: boolean = await minPromiseMillis(invokeCmd("cmd_check_for_updates"), 500); const hasUpdate: boolean = await minPromiseMillis(rpc("cmd_check_for_updates"), 500);
if (!hasUpdate) { if (!hasUpdate) {
showAlert({ showAlert({
id: "no-updates", id: "no-updates",
@@ -1,4 +1,4 @@
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
import { useFastMutation } from "./useFastMutation"; import { useFastMutation } from "./useFastMutation";
export function useDeleteGrpcConnections(requestId?: string) { export function useDeleteGrpcConnections(requestId?: string) {
@@ -6,7 +6,7 @@ export function useDeleteGrpcConnections(requestId?: string) {
mutationKey: ["delete_grpc_connections", requestId], mutationKey: ["delete_grpc_connections", requestId],
mutationFn: async () => { mutationFn: async () => {
if (requestId === undefined) return; if (requestId === undefined) return;
await invokeCmd("cmd_delete_all_grpc_connections", { requestId }); await rpc("cmd_delete_all_grpc_connections", { requestId });
}, },
}); });
} }
@@ -1,4 +1,4 @@
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
import { useFastMutation } from "./useFastMutation"; import { useFastMutation } from "./useFastMutation";
export function useDeleteHttpResponses(requestId?: string) { export function useDeleteHttpResponses(requestId?: string) {
@@ -6,7 +6,7 @@ export function useDeleteHttpResponses(requestId?: string) {
mutationKey: ["delete_http_responses", requestId], mutationKey: ["delete_http_responses", requestId],
mutationFn: async () => { mutationFn: async () => {
if (requestId === undefined) return; if (requestId === undefined) return;
await invokeCmd("cmd_delete_all_http_responses", { requestId }); await rpc("cmd_delete_all_http_responses", { requestId });
}, },
}); });
} }
@@ -8,7 +8,7 @@ import { showAlert } from "../lib/alert";
import { showConfirmDelete } from "../lib/confirm"; import { showConfirmDelete } from "../lib/confirm";
import { jotaiStore } from "../lib/jotai"; import { jotaiStore } from "../lib/jotai";
import { pluralizeCount } from "../lib/pluralize"; import { pluralizeCount } from "../lib/pluralize";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
import { activeWorkspaceIdAtom } from "./useActiveWorkspace"; import { activeWorkspaceIdAtom } from "./useActiveWorkspace";
import { useFastMutation } from "./useFastMutation"; import { useFastMutation } from "./useFastMutation";
@@ -45,7 +45,7 @@ export function useDeleteSendHistory() {
if (!confirmed) return false; if (!confirmed) return false;
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom); const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
await invokeCmd("cmd_delete_send_history", { workspaceId }); await rpc("cmd_delete_send_history", { workspaceId });
return true; return true;
}, },
}); });
+3 -3
View File
@@ -6,7 +6,7 @@ import type {
GetFolderActionsResponse, GetFolderActionsResponse,
} from "@yaakapp-internal/plugins"; } from "@yaakapp-internal/plugins";
import { useMemo } from "react"; import { useMemo } from "react";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
import { usePluginsKey } from "./usePlugins"; import { usePluginsKey } from "./usePlugins";
export type CallableFolderAction = Pick<FolderAction, "label" | "icon"> & { export type CallableFolderAction = Pick<FolderAction, "label" | "icon"> & {
@@ -30,7 +30,7 @@ export function useFolderActions() {
} }
export async function getFolderActions() { export async function getFolderActions() {
const responses = await invokeCmd<GetFolderActionsResponse[]>("cmd_folder_actions"); const responses = await rpc<GetFolderActionsResponse[]>("cmd_folder_actions");
const actions = responses.flatMap((r) => const actions = responses.flatMap((r) =>
r.actions.map((a, i) => ({ r.actions.map((a, i) => ({
label: a.label, label: a.label,
@@ -41,7 +41,7 @@ export async function getFolderActions() {
pluginRefId: r.pluginRefId, pluginRefId: r.pluginRefId,
args: { folder }, args: { folder },
}; };
await invokeCmd("cmd_call_folder_action", { req: payload }); await rpc("cmd_call_folder_action", { req: payload });
}, },
})), })),
); );
+7 -7
View File
@@ -1,10 +1,10 @@
import { useMutation, useQuery } from "@tanstack/react-query"; import { useMutation, useQuery } from "@tanstack/react-query";
import { emit } from "@tauri-apps/api/event"; import { platform } from "@yaakapp-internal/platform";
import type { GrpcConnection, GrpcRequest } from "@yaakapp-internal/models"; import type { GrpcConnection, GrpcRequest } from "@yaakapp-internal/models";
import { flushAllModelWrites } from "@yaakapp-internal/models"; import { flushAllModelWrites } from "@yaakapp-internal/models";
import { jotaiStore } from "../lib/jotai"; import { jotaiStore } from "../lib/jotai";
import { minPromiseMillis } from "../lib/minPromiseMillis"; import { minPromiseMillis } from "../lib/minPromiseMillis";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
import { activeEnvironmentIdAtom, useActiveEnvironment } from "./useActiveEnvironment"; import { activeEnvironmentIdAtom, useActiveEnvironment } from "./useActiveEnvironment";
import { useDebouncedValue } from "@yaakapp-internal/ui"; import { useDebouncedValue } from "@yaakapp-internal/ui";
@@ -25,7 +25,7 @@ export function useGrpc(
mutationKey: ["grpc_go", conn?.id], mutationKey: ["grpc_go", conn?.id],
mutationFn: async () => { mutationFn: async () => {
await flushAllModelWrites(); // The backend reads the request from the DB await flushAllModelWrites(); // The backend reads the request from the DB
return invokeCmd<void>("cmd_grpc_go", { return rpc<void>("cmd_grpc_go", {
requestId, requestId,
environmentId: environment?.id, environmentId: environment?.id,
protoFiles, protoFiles,
@@ -36,17 +36,17 @@ export function useGrpc(
const send = useMutation({ const send = useMutation({
mutationKey: ["grpc_send", conn?.id], mutationKey: ["grpc_send", conn?.id],
mutationFn: ({ message }: { message: string }) => mutationFn: ({ message }: { message: string }) =>
emit(`grpc_client_msg_${conn?.id ?? "none"}`, { Message: message }), platform.emit(`grpc_client_msg_${conn?.id ?? "none"}`, { Message: message }),
}); });
const cancel = useMutation({ const cancel = useMutation({
mutationKey: ["grpc_cancel", conn?.id ?? "n/a"], mutationKey: ["grpc_cancel", conn?.id ?? "n/a"],
mutationFn: () => emit(`grpc_client_msg_${conn?.id ?? "none"}`, "Cancel"), mutationFn: () => platform.emit(`grpc_client_msg_${conn?.id ?? "none"}`, "Cancel"),
}); });
const commit = useMutation({ const commit = useMutation({
mutationKey: ["grpc_commit", conn?.id ?? "n/a"], mutationKey: ["grpc_commit", conn?.id ?? "n/a"],
mutationFn: () => emit(`grpc_client_msg_${conn?.id ?? "none"}`, "Commit"), mutationFn: () => platform.emit(`grpc_client_msg_${conn?.id ?? "none"}`, "Commit"),
}); });
const debouncedUrl = useDebouncedValue<string>(req?.url ?? "", 1000); const debouncedUrl = useDebouncedValue<string>(req?.url ?? "", 1000);
@@ -61,7 +61,7 @@ export function useGrpc(
queryFn: () => { queryFn: () => {
const environmentId = jotaiStore.get(activeEnvironmentIdAtom); const environmentId = jotaiStore.get(activeEnvironmentIdAtom);
return minPromiseMillis<ReflectResponseService[]>( return minPromiseMillis<ReflectResponseService[]>(
invokeCmd("cmd_grpc_reflect", { requestId, protoFiles, environmentId }), rpc("cmd_grpc_reflect", { requestId, protoFiles, environmentId }),
300, 300,
); );
}, },
@@ -6,7 +6,7 @@ import type {
GrpcRequestAction, GrpcRequestAction,
} from "@yaakapp-internal/plugins"; } from "@yaakapp-internal/plugins";
import { useMemo } from "react"; import { useMemo } from "react";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
import { getGrpcProtoFiles } from "./useGrpcProtoFiles"; import { getGrpcProtoFiles } from "./useGrpcProtoFiles";
import { usePluginsKey } from "./usePlugins"; import { usePluginsKey } from "./usePlugins";
@@ -33,7 +33,7 @@ export function useGrpcRequestActions() {
} }
export async function getGrpcRequestActions() { export async function getGrpcRequestActions() {
const responses = await invokeCmd<GetGrpcRequestActionsResponse[]>("cmd_grpc_request_actions"); const responses = await rpc<GetGrpcRequestActionsResponse[]>("cmd_grpc_request_actions");
return responses.flatMap((r) => return responses.flatMap((r) =>
r.actions.map((a, i) => ({ r.actions.map((a, i) => ({
@@ -46,7 +46,7 @@ export async function getGrpcRequestActions() {
pluginRefId: r.pluginRefId, pluginRefId: r.pluginRefId,
args: { grpcRequest, protoFiles }, args: { grpcRequest, protoFiles },
}; };
await invokeCmd("cmd_call_grpc_request_action", { req: payload }); await rpc("cmd_call_grpc_request_action", { req: payload });
}, },
})), })),
); );
+3 -3
View File
@@ -1,4 +1,4 @@
import { type } from "@tauri-apps/plugin-os"; import { platform } from "@yaakapp-internal/platform";
import { debounce } from "@yaakapp-internal/lib"; import { debounce } from "@yaakapp-internal/lib";
import { settingsAtom } from "@yaakapp-internal/models"; import { settingsAtom } from "@yaakapp-internal/models";
import { atom, useAtomValue } from "jotai"; import { atom, useAtomValue } from "jotai";
@@ -102,7 +102,7 @@ const defaultHotkeysOther: Record<HotkeyAction, string[]> = {
/** Get the default hotkeys for the current platform */ /** Get the default hotkeys for the current platform */
export const defaultHotkeys: Record<HotkeyAction, string[]> = export const defaultHotkeys: Record<HotkeyAction, string[]> =
type() === "macos" ? defaultHotkeysMac : defaultHotkeysOther; platform.osType() === "macos" ? defaultHotkeysMac : defaultHotkeysOther;
/** Atom that provides the effective hotkeys by merging defaults with user settings */ /** Atom that provides the effective hotkeys by merging defaults with user settings */
export const hotkeysAtom = atom((get) => { export const hotkeysAtom = atom((get) => {
@@ -318,7 +318,7 @@ export function getHotkeyScope(action: HotkeyAction): string {
} }
export function formatHotkeyString(trigger: string): string[] { export function formatHotkeyString(trigger: string): string[] {
const os = type(); const os = platform.osType();
const parts = trigger.split("+"); const parts = trigger.split("+");
const labelParts: string[] = []; const labelParts: string[] = [];
@@ -3,7 +3,7 @@ import type { GetHttpAuthenticationSummaryResponse } from "@yaakapp-internal/plu
import { atom, useAtomValue } from "jotai"; import { atom, useAtomValue } from "jotai";
import { useState } from "react"; import { useState } from "react";
import { jotaiStore } from "../lib/jotai"; import { jotaiStore } from "../lib/jotai";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
import { showErrorToast } from "../lib/toast"; import { showErrorToast } from "../lib/toast";
import { usePluginsKey } from "./usePlugins"; import { usePluginsKey } from "./usePlugins";
@@ -31,7 +31,7 @@ export function useSubscribeHttpAuthentication() {
placeholderData: (prev) => prev, // Keep previous data on refetch placeholderData: (prev) => prev, // Keep previous data on refetch
queryFn: async () => { queryFn: async () => {
try { try {
const result = await invokeCmd<GetHttpAuthenticationSummaryResponse[]>( const result = await rpc<GetHttpAuthenticationSummaryResponse[]>(
"cmd_get_http_authentication_summaries", "cmd_get_http_authentication_summaries",
); );
setNumResults(result.length); setNumResults(result.length);
@@ -11,7 +11,7 @@ import type { GetHttpAuthenticationConfigResponse, JsonPrimitive } from "@yaakap
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { md5 } from "js-md5"; import { md5 } from "js-md5";
import { useState } from "react"; import { useState } from "react";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
import { activeEnvironmentIdAtom } from "./useActiveEnvironment"; import { activeEnvironmentIdAtom } from "./useActiveEnvironment";
import { activeWorkspaceIdAtom } from "./useActiveWorkspace"; import { activeWorkspaceIdAtom } from "./useActiveWorkspace";
@@ -48,7 +48,7 @@ export function useHttpAuthenticationConfig(
placeholderData: (prev) => prev, // Keep previous data on refetch placeholderData: (prev) => prev, // Keep previous data on refetch
queryFn: async () => { queryFn: async () => {
if (authName == null || authName === "inherit") return null; if (authName == null || authName === "inherit") return null;
const config = await invokeCmd<GetHttpAuthenticationConfigResponse>( const config = await rpc<GetHttpAuthenticationConfigResponse>(
"cmd_get_http_authentication_config", "cmd_get_http_authentication_config",
{ {
authName, authName,
@@ -65,7 +65,7 @@ export function useHttpAuthenticationConfig(
call: async ( call: async (
model: HttpRequest | GrpcRequest | WebsocketRequest | Folder | Workspace, model: HttpRequest | GrpcRequest | WebsocketRequest | Folder | Workspace,
) => { ) => {
await invokeCmd("cmd_call_http_authentication_action", { await rpc("cmd_call_http_authentication_action", {
pluginRefId: config.pluginRefId, pluginRefId: config.pluginRefId,
actionIndex: i, actionIndex: i,
authName, authName,
@@ -6,7 +6,7 @@ import type {
HttpRequestAction, HttpRequestAction,
} from "@yaakapp-internal/plugins"; } from "@yaakapp-internal/plugins";
import { useMemo } from "react"; import { useMemo } from "react";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
import { usePluginsKey } from "./usePlugins"; import { usePluginsKey } from "./usePlugins";
export type CallableHttpRequestAction = Pick<HttpRequestAction, "label" | "icon"> & { export type CallableHttpRequestAction = Pick<HttpRequestAction, "label" | "icon"> & {
@@ -30,7 +30,7 @@ export function useHttpRequestActions() {
} }
export async function getHttpRequestActions() { export async function getHttpRequestActions() {
const responses = await invokeCmd<GetHttpRequestActionsResponse[]>("cmd_http_request_actions"); const responses = await rpc<GetHttpRequestActionsResponse[]>("cmd_http_request_actions");
const actions = responses.flatMap((r) => const actions = responses.flatMap((r) =>
r.actions.map((a, i) => ({ r.actions.map((a, i) => ({
label: a.label, label: a.label,
@@ -41,7 +41,7 @@ export async function getHttpRequestActions() {
pluginRefId: r.pluginRefId, pluginRefId: r.pluginRefId,
args: { httpRequest }, args: { httpRequest },
}; };
await invokeCmd("cmd_call_http_request_action", { req: payload }); await rpc("cmd_call_http_request_action", { req: payload });
}, },
})), })),
); );
+2 -2
View File
@@ -1,6 +1,6 @@
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import type { HttpResponse } from "@yaakapp-internal/models"; import type { HttpResponse } from "@yaakapp-internal/models";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
export function useHttpRequestBody(response: HttpResponse | null) { export function useHttpRequestBody(response: HttpResponse | null) {
return useQuery({ return useQuery({
@@ -18,7 +18,7 @@ export async function getRequestBodyText(response: HttpResponse | null) {
return null; return null;
} }
const data = await invokeCmd<number[] | null>("cmd_http_request_body", { const data = await rpc<number[] | null>("cmd_http_request_body", {
responseId: response.id, responseId: response.id,
}); });
@@ -1,4 +1,3 @@
import { invoke } from "@tauri-apps/api/core";
import type { HttpResponse, HttpResponseEvent } from "@yaakapp-internal/models"; import type { HttpResponse, HttpResponseEvent } from "@yaakapp-internal/models";
import { import {
httpResponseEventsAtom, httpResponseEventsAtom,
@@ -8,6 +7,7 @@ import {
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { useEffect } from "react"; import { useEffect } from "react";
import { fireAndForget } from "../lib/fireAndForget"; import { fireAndForget } from "../lib/fireAndForget";
import { rpc } from "../lib/rpc";
export function useHttpResponseEvents(response: HttpResponse | null) { export function useHttpResponseEvents(response: HttpResponse | null) {
const allEvents = useAtomValue(httpResponseEventsAtom); const allEvents = useAtomValue(httpResponseEventsAtom);
@@ -20,7 +20,7 @@ export function useHttpResponseEvents(response: HttpResponse | null) {
// Fetch events from database, filtering out events from other responses and merging atomically // Fetch events from database, filtering out events from other responses and merging atomically
fireAndForget( fireAndForget(
invoke<HttpResponseEvent[]>("cmd_get_http_response_events", { responseId: response.id }).then( rpc<HttpResponseEvent[]>("cmd_get_http_response_events", { responseId: response.id }).then(
(events) => (events) =>
mergeModelsInStore("http_response_event", events, (e) => e.responseId === response.id), mergeModelsInStore("http_response_event", events, (e) => e.responseId === response.id),
), ),
@@ -1,5 +1,5 @@
import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useQuery, useQueryClient } from "@tanstack/react-query";
import { invoke } from "@tauri-apps/api/core";
import type { GraphQlIntrospection, HttpRequest } from "@yaakapp-internal/models"; import type { GraphQlIntrospection, HttpRequest } from "@yaakapp-internal/models";
import type { GraphQLSchema, IntrospectionQuery } from "graphql"; import type { GraphQLSchema, IntrospectionQuery } from "graphql";
import { buildClientSchema, getIntrospectionQuery } from "graphql"; import { buildClientSchema, getIntrospectionQuery } from "graphql";
@@ -9,6 +9,7 @@ import { getResponseBodyText } from "../lib/responseBody";
import { sendEphemeralRequest } from "../lib/sendEphemeralRequest"; import { sendEphemeralRequest } from "../lib/sendEphemeralRequest";
import { useActiveEnvironment } from "./useActiveEnvironment"; import { useActiveEnvironment } from "./useActiveEnvironment";
import { useDebouncedValue } from "@yaakapp-internal/ui"; import { useDebouncedValue } from "@yaakapp-internal/ui";
import { rpc } from "../lib/rpc";
const introspectionRequestBody = JSON.stringify({ const introspectionRequestBody = JSON.stringify({
query: getIntrospectionQuery(), query: getIntrospectionQuery(),
@@ -32,7 +33,7 @@ export function useIntrospectGraphQL(
const upsertIntrospection = useCallback( const upsertIntrospection = useCallback(
async (content: string | null) => { async (content: string | null) => {
const v = await invoke<GraphQlIntrospection>("models_upsert_graphql_introspection", { const v = await rpc<GraphQlIntrospection>("models_upsert_graphql_introspection", {
requestId: baseRequest.id, requestId: baseRequest.id,
workspaceId: baseRequest.workspaceId, workspaceId: baseRequest.workspaceId,
content: content ?? "", content: content ?? "",
@@ -119,7 +120,7 @@ function useIntrospectionResult(request: HttpRequest) {
return useQuery({ return useQuery({
queryKey: ["introspection", request.id], queryKey: ["introspection", request.id],
queryFn: async () => queryFn: async () =>
invoke<GraphQlIntrospection | null>("models_get_graphql_introspection", { rpc<GraphQlIntrospection | null>("models_get_graphql_introspection", {
requestId: request.id, requestId: request.id,
}), }),
}); });
@@ -1,28 +0,0 @@
import type { EventCallback, EventName } from "@tauri-apps/api/event";
import { listen } from "@tauri-apps/api/event";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import { useEffect, useRef } from "react";
export function useListenToTauriEvent<T>(event: EventName, fn: EventCallback<T>) {
const handlerRef = useRef(fn);
useEffect(() => {
handlerRef.current = fn;
}, [fn]);
useEffect(() => {
return listenToTauriEvent<T>(event, (p) => handlerRef.current(p));
}, [event]);
}
export function listenToTauriEvent<T>(event: EventName, fn: EventCallback<T>) {
const unsubPromise = listen<T>(
event,
fn,
// Listen to `emit_all()` events or events specific to the current window
{ target: { label: getCurrentWebviewWindow().label, kind: "Window" } },
);
return () => {
unsubPromise.then((unsub) => unsub()).catch(console.error);
};
}
@@ -1,4 +1,3 @@
import { invoke } from "@tauri-apps/api/core";
import type { GrpcConnection, GrpcEvent } from "@yaakapp-internal/models"; import type { GrpcConnection, GrpcEvent } from "@yaakapp-internal/models";
import { import {
grpcConnectionsAtom, grpcConnectionsAtom,
@@ -11,6 +10,7 @@ import { useEffect, useMemo } from "react";
import { fireAndForget } from "../lib/fireAndForget"; import { fireAndForget } from "../lib/fireAndForget";
import { atomWithKVStorage } from "../lib/atoms/atomWithKVStorage"; import { atomWithKVStorage } from "../lib/atoms/atomWithKVStorage";
import { activeRequestIdAtom } from "./useActiveRequestId"; import { activeRequestIdAtom } from "./useActiveRequestId";
import { rpc } from "../lib/rpc";
const pinnedGrpcConnectionIdsAtom = atomWithKVStorage<Record<string, string | null>>( const pinnedGrpcConnectionIdsAtom = atomWithKVStorage<Record<string, string | null>>(
"pinned-grpc-connection-ids", "pinned-grpc-connection-ids",
@@ -71,7 +71,7 @@ export function useGrpcEvents(connectionId: string | null) {
// Fetch events from database, filtering out events from other connections and merging atomically // Fetch events from database, filtering out events from other connections and merging atomically
fireAndForget( fireAndForget(
invoke<GrpcEvent[]>("models_grpc_events", { connectionId }).then((events) => rpc<GrpcEvent[]>("models_grpc_events", { connectionId }).then((events) =>
mergeModelsInStore("grpc_event", events, (e) => e.connectionId === connectionId), mergeModelsInStore("grpc_event", events, (e) => e.connectionId === connectionId),
), ),
); );
@@ -1,4 +1,3 @@
import { invoke } from "@tauri-apps/api/core";
import type { WebsocketConnection, WebsocketEvent } from "@yaakapp-internal/models"; import type { WebsocketConnection, WebsocketEvent } from "@yaakapp-internal/models";
import { import {
mergeModelsInStore, mergeModelsInStore,
@@ -12,6 +11,7 @@ import { fireAndForget } from "../lib/fireAndForget";
import { atomWithKVStorage } from "../lib/atoms/atomWithKVStorage"; import { atomWithKVStorage } from "../lib/atoms/atomWithKVStorage";
import { jotaiStore } from "../lib/jotai"; import { jotaiStore } from "../lib/jotai";
import { activeRequestIdAtom } from "./useActiveRequestId"; import { activeRequestIdAtom } from "./useActiveRequestId";
import { rpc } from "../lib/rpc";
const pinnedWebsocketConnectionIdAtom = atomWithKVStorage<Record<string, string | null>>( const pinnedWebsocketConnectionIdAtom = atomWithKVStorage<Record<string, string | null>>(
"pinned-websocket-connection-ids", "pinned-websocket-connection-ids",
@@ -58,7 +58,7 @@ export function useWebsocketEvents(connectionId: string | null) {
// Fetch events from database, filtering out events from other connections and merging atomically // Fetch events from database, filtering out events from other connections and merging atomically
fireAndForget( fireAndForget(
invoke<WebsocketEvent[]>("models_websocket_events", { connectionId }).then((events) => rpc<WebsocketEvent[]>("models_websocket_events", { connectionId }).then((events) =>
mergeModelsInStore("websocket_event", events, (e) => e.connectionId === connectionId), mergeModelsInStore("websocket_event", events, (e) => e.connectionId === connectionId),
), ),
); );
@@ -0,0 +1,12 @@
import { platform } from "@yaakapp-internal/platform";
import { useEffect, useRef } from "react";
/** Subscribe to a backend event for the lifetime of the component. */
export function usePlatformEvent<T>(event: string, fn: (payload: T) => void) {
const handlerRef = useRef(fn);
useEffect(() => {
handlerRef.current = fn;
}, [fn]);
useEffect(() => platform.listen<T>(event, (payload) => handlerRef.current(payload)), [event]);
}
+2 -2
View File
@@ -4,7 +4,7 @@ import { pluginsAtom } from "@yaakapp-internal/models";
import type { PluginMetadata } from "@yaakapp-internal/plugins"; import type { PluginMetadata } from "@yaakapp-internal/plugins";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { queryClient } from "../lib/queryClient"; import { queryClient } from "../lib/queryClient";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
function pluginInfoKey(id: string | null, plugin: Plugin | null) { function pluginInfoKey(id: string | null, plugin: Plugin | null) {
return ["plugin_info", id ?? "n/a", plugin?.updatedAt ?? "n/a"]; return ["plugin_info", id ?? "n/a", plugin?.updatedAt ?? "n/a"];
@@ -19,7 +19,7 @@ export function usePluginInfo(id: string | null) {
placeholderData: (prev) => prev, // Keep previous data on refetch placeholderData: (prev) => prev, // Keep previous data on refetch
queryFn: () => { queryFn: () => {
if (id == null) return null; if (id == null) return null;
return invokeCmd<PluginMetadata>("cmd_plugin_info", { id }); return rpc<PluginMetadata>("cmd_plugin_info", { id });
}, },
}); });
} }
+2 -2
View File
@@ -3,7 +3,7 @@ import { changeModelStoreWorkspace, pluginsAtom } from "@yaakapp-internal/models
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { jotaiStore } from "../lib/jotai"; import { jotaiStore } from "../lib/jotai";
import { minPromiseMillis } from "../lib/minPromiseMillis"; import { minPromiseMillis } from "../lib/minPromiseMillis";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
import { activeWorkspaceIdAtom } from "./useActiveWorkspace"; import { activeWorkspaceIdAtom } from "./useActiveWorkspace";
import { useDebouncedValue } from "@yaakapp-internal/ui"; import { useDebouncedValue } from "@yaakapp-internal/ui";
import { invalidateAllPluginInfo } from "./usePluginInfo"; import { invalidateAllPluginInfo } from "./usePluginInfo";
@@ -26,7 +26,7 @@ export function useRefreshPlugins() {
mutationFn: async () => { mutationFn: async () => {
await minPromiseMillis( await minPromiseMillis(
(async () => { (async () => {
await invokeCmd("cmd_reload_plugins"); await rpc("cmd_reload_plugins");
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom); const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
await changeModelStoreWorkspace(workspaceId); // Force refresh models await changeModelStoreWorkspace(workspaceId); // Force refresh models
invalidateAllPluginInfo(); invalidateAllPluginInfo();
+3 -3
View File
@@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query";
import type { RenderPurpose } from "@yaakapp-internal/plugins"; import type { RenderPurpose } from "@yaakapp-internal/plugins";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { minPromiseMillis } from "../lib/minPromiseMillis"; import { minPromiseMillis } from "../lib/minPromiseMillis";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
import { useActiveEnvironment } from "./useActiveEnvironment"; import { useActiveEnvironment } from "./useActiveEnvironment";
import { activeWorkspaceIdAtom } from "./useActiveWorkspace"; import { activeWorkspaceIdAtom } from "./useActiveWorkspace";
@@ -49,7 +49,7 @@ export async function renderTemplate({
purpose: RenderPurpose; purpose: RenderPurpose;
ignoreError?: boolean; ignoreError?: boolean;
}): Promise<string> { }): Promise<string> {
return invokeCmd("cmd_render_template", { return rpc("cmd_render_template", {
template, template,
workspaceId, workspaceId,
environmentId, environmentId,
@@ -67,5 +67,5 @@ export async function decryptTemplate({
workspaceId: string; workspaceId: string;
environmentId: string | null; environmentId: string | null;
}): Promise<string> { }): Promise<string> {
return invokeCmd("cmd_decrypt_template", { template, workspaceId, environmentId }); return rpc("cmd_decrypt_template", { template, workspaceId, environmentId });
} }
+17 -19
View File
@@ -1,4 +1,4 @@
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"; import { platform } from "@yaakapp-internal/platform";
import type { ModelPayload } from "@yaakapp-internal/models"; import type { ModelPayload } from "@yaakapp-internal/models";
import { atom, useAtomValue } from "jotai"; import { atom, useAtomValue } from "jotai";
import { generateId } from "../lib/generateId"; import { generateId } from "../lib/generateId";
@@ -6,26 +6,24 @@ import { jotaiStore } from "../lib/jotai";
const requestUpdateKeyAtom = atom<Record<string, string>>({}); const requestUpdateKeyAtom = atom<Record<string, string>>({});
getCurrentWebviewWindow() platform.listen<ModelPayload[]>("model_writes", (payloads) => {
.listen<ModelPayload[]>("model_writes", ({ payload: payloads }) => { const changedIds: string[] = [];
const changedIds: string[] = []; for (const payload of payloads) {
for (const payload of payloads) { if (payload.change.type !== "upsert") continue;
if (payload.change.type !== "upsert") continue;
if ( if (
(payload.model.model === "http_request" || (payload.model.model === "http_request" ||
payload.model.model === "grpc_request" || payload.model.model === "grpc_request" ||
payload.model.model === "websocket_request") && payload.model.model === "websocket_request") &&
((payload.updateSource.type === "window" && ((payload.updateSource.type === "window" &&
payload.updateSource.label !== getCurrentWebviewWindow().label) || payload.updateSource.label !== platform.window.label) ||
payload.updateSource.type !== "window") payload.updateSource.type !== "window")
) { ) {
changedIds.push(payload.model.id); changedIds.push(payload.model.id);
}
} }
if (changedIds.length > 0) wasUpdatedExternally(changedIds); }
}) if (changedIds.length > 0) wasUpdatedExternally(changedIds);
.catch(console.error); });
export function wasUpdatedExternally(changedRequestIds: string | string[]) { export function wasUpdatedExternally(changedRequestIds: string | string[]) {
const ids = Array.isArray(changedRequestIds) ? changedRequestIds : [changedRequestIds]; const ids = Array.isArray(changedRequestIds) ? changedRequestIds : [changedRequestIds];
+4 -4
View File
@@ -1,13 +1,13 @@
import { save } from "@tauri-apps/plugin-dialog";
import type { HttpResponse } from "@yaakapp-internal/models"; import type { HttpResponse } from "@yaakapp-internal/models";
import { getModel } from "@yaakapp-internal/models"; import { getModel } from "@yaakapp-internal/models";
import mime from "mime"; import mime from "mime";
import slugify from "slugify"; import slugify from "slugify";
import { InlineCode } from "@yaakapp-internal/ui"; import { InlineCode } from "@yaakapp-internal/ui";
import { getContentTypeFromHeaders } from "../lib/model_util"; import { getContentTypeFromHeaders } from "../lib/model_util";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
import { showToast } from "../lib/toast"; import { showToast } from "../lib/toast";
import { useFastMutation } from "./useFastMutation"; import { useFastMutation } from "./useFastMutation";
import { platform } from "@yaakapp-internal/platform";
export function useSaveResponse(response: HttpResponse | null) { export function useSaveResponse(response: HttpResponse | null) {
return useFastMutation({ return useFastMutation({
@@ -21,11 +21,11 @@ export function useSaveResponse(response: HttpResponse | null) {
const contentType = getContentTypeFromHeaders(response.headers) ?? "unknown"; const contentType = getContentTypeFromHeaders(response.headers) ?? "unknown";
const ext = mime.getExtension(contentType); const ext = mime.getExtension(contentType);
const slug = slugify(request.name || "response", { lower: true }); const slug = slugify(request.name || "response", { lower: true });
const filepath = await save({ const filepath = await platform.dialog.save({
defaultPath: ext ? `${slug}.${ext}` : slug, defaultPath: ext ? `${slug}.${ext}` : slug,
title: "Save Response", title: "Save Response",
}); });
await invokeCmd("cmd_save_response", { responseId: response.id, filepath }); await rpc("cmd_save_response", { responseId: response.id, filepath });
showToast({ showToast({
message: ( message: (
<> <>
@@ -1,6 +1,6 @@
import type { HttpResponse } from "@yaakapp-internal/models"; import type { HttpResponse } from "@yaakapp-internal/models";
import { flushAllModelWrites } from "@yaakapp-internal/models"; import { flushAllModelWrites } from "@yaakapp-internal/models";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
import { getActiveCookieJar } from "./useActiveCookieJar"; import { getActiveCookieJar } from "./useActiveCookieJar";
import { getActiveEnvironment } from "./useActiveEnvironment"; import { getActiveEnvironment } from "./useActiveEnvironment";
import { createFastMutation, useFastMutation } from "./useFastMutation"; import { createFastMutation, useFastMutation } from "./useFastMutation";
@@ -12,7 +12,7 @@ async function sendAnyHttpRequestById(id: string | null): Promise<HttpResponse |
await flushAllModelWrites(); await flushAllModelWrites();
return invokeCmd("cmd_send_http_request", { return rpc("cmd_send_http_request", {
requestId: id, requestId: id,
environmentId: getActiveEnvironment()?.id, environmentId: getActiveEnvironment()?.id,
cookieJarId: getActiveCookieJar()?.id, cookieJarId: getActiveCookieJar()?.id,
@@ -1,8 +1,8 @@
import { type } from "@tauri-apps/plugin-os"; import { platform } from "@yaakapp-internal/platform";
import { useIsFullscreen } from "@yaakapp-internal/ui"; import { useIsFullscreen } from "@yaakapp-internal/ui";
export function useStoplightsVisible() { export function useStoplightsVisible() {
const fullscreen = useIsFullscreen(); const fullscreen = useIsFullscreen();
const stoplightsVisible = type() === "macos" && !fullscreen; const stoplightsVisible = platform.osType() === "macos" && !fullscreen;
return stoplightsVisible; return stoplightsVisible;
} }
@@ -1,4 +1,4 @@
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"; import { platform } from "@yaakapp-internal/platform";
import { settingsAtom } from "@yaakapp-internal/models"; import { settingsAtom } from "@yaakapp-internal/models";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { useEffect } from "react"; import { useEffect } from "react";
@@ -11,7 +11,7 @@ export function useSyncFontSizeSetting() {
} }
const { interfaceScale, editorFontSize } = settings; const { interfaceScale, editorFontSize } = settings;
getCurrentWebviewWindow().setZoom(interfaceScale).catch(console.error); platform.window.setZoom(interfaceScale).catch(console.error);
document.documentElement.style.setProperty("--editor-font-size", `${editorFontSize}px`); document.documentElement.style.setProperty("--editor-font-size", `${editorFontSize}px`);
}, [settings]); }, [settings]);
} }
+4 -4
View File
@@ -1,5 +1,5 @@
import { useHotKey } from "./useHotKey"; import { useHotKey } from "./useHotKey";
import { useListenToTauriEvent } from "./useListenToTauriEvent"; import { usePlatformEvent } from "./usePlatformEvent";
import { useZoom } from "./useZoom"; import { useZoom } from "./useZoom";
export function useSyncZoomSetting() { export function useSyncZoomSetting() {
@@ -8,9 +8,9 @@ export function useSyncZoomSetting() {
// shortcuts for Windows/Linux // shortcuts for Windows/Linux
const zoom = useZoom(); const zoom = useZoom();
useHotKey("app.zoom_in", zoom.zoomIn); useHotKey("app.zoom_in", zoom.zoomIn);
useListenToTauriEvent("zoom_in", zoom.zoomIn); usePlatformEvent("zoom_in", zoom.zoomIn);
useHotKey("app.zoom_out", zoom.zoomOut); useHotKey("app.zoom_out", zoom.zoomOut);
useListenToTauriEvent("zoom_out", zoom.zoomOut); usePlatformEvent("zoom_out", zoom.zoomOut);
useHotKey("app.zoom_reset", zoom.zoomReset); useHotKey("app.zoom_reset", zoom.zoomReset);
useListenToTauriEvent("zoom_reset", zoom.zoomReset); usePlatformEvent("zoom_reset", zoom.zoomReset);
} }
@@ -12,7 +12,7 @@ import {
import type { GetTemplateFunctionConfigResponse, JsonPrimitive } from "@yaakapp-internal/plugins"; import type { GetTemplateFunctionConfigResponse, JsonPrimitive } from "@yaakapp-internal/plugins";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { md5 } from "js-md5"; import { md5 } from "js-md5";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
import { activeEnvironmentIdAtom } from "./useActiveEnvironment"; import { activeEnvironmentIdAtom } from "./useActiveEnvironment";
import { activeWorkspaceIdAtom } from "./useActiveWorkspace"; import { activeWorkspaceIdAtom } from "./useActiveWorkspace";
@@ -63,7 +63,7 @@ export async function getTemplateFunctionConfig(
model: HttpRequest | GrpcRequest | WebsocketRequest | Folder | Workspace, model: HttpRequest | GrpcRequest | WebsocketRequest | Folder | Workspace,
environmentId: string | undefined, environmentId: string | undefined,
) { ) {
const config = await invokeCmd<GetTemplateFunctionConfigResponse>( const config = await rpc<GetTemplateFunctionConfigResponse>(
"cmd_template_function_config", "cmd_template_function_config",
{ {
functionName, functionName,
@@ -6,7 +6,7 @@ import type {
import { atom, useAtomValue, useSetAtom } from "jotai"; import { atom, useAtomValue, useSetAtom } from "jotai";
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import type { TwigCompletionOption } from "../components/core/Editor/twig/completion"; import type { TwigCompletionOption } from "../components/core/Editor/twig/completion";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
import { usePluginsKey } from "./usePlugins"; import { usePluginsKey } from "./usePlugins";
const templateFunctionsAtom = atom<TemplateFunction[]>([]); const templateFunctionsAtom = atom<TemplateFunction[]>([]);
@@ -49,7 +49,7 @@ export function useSubscribeTemplateFunctions() {
refetchInterval: numFns > 0 ? Number.POSITIVE_INFINITY : 1000, refetchInterval: numFns > 0 ? Number.POSITIVE_INFINITY : 1000,
refetchOnMount: true, refetchOnMount: true,
queryFn: async () => { queryFn: async () => {
const result = await invokeCmd<GetTemplateFunctionSummaryResponse[]>( const result = await rpc<GetTemplateFunctionSummaryResponse[]>(
"cmd_template_function_summaries", "cmd_template_function_summaries",
); );
setNumFns(result.length); setNumFns(result.length);
@@ -1,6 +1,6 @@
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import type { Tokens } from "@yaakapp-internal/templates"; import type { Tokens } from "@yaakapp-internal/templates";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
export function useTemplateTokensToString(tokens: Tokens) { export function useTemplateTokensToString(tokens: Tokens) {
return useQuery<string>({ return useQuery<string>({
@@ -11,5 +11,5 @@ export function useTemplateTokensToString(tokens: Tokens) {
} }
export async function templateTokensToString(tokens: Tokens): Promise<string> { export async function templateTokensToString(tokens: Tokens): Promise<string> {
return invokeCmd("cmd_template_tokens_to_string", { tokens }); return rpc("cmd_template_tokens_to_string", { tokens });
} }
@@ -6,7 +6,7 @@ import type {
WebsocketRequestAction, WebsocketRequestAction,
} from "@yaakapp-internal/plugins"; } from "@yaakapp-internal/plugins";
import { useMemo } from "react"; import { useMemo } from "react";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
import { usePluginsKey } from "./usePlugins"; import { usePluginsKey } from "./usePlugins";
export type CallableWebSocketRequestAction = Pick<WebsocketRequestAction, "label" | "icon"> & { export type CallableWebSocketRequestAction = Pick<WebsocketRequestAction, "label" | "icon"> & {
@@ -30,7 +30,7 @@ export function useWebsocketRequestActions() {
} }
export async function getWebsocketRequestActions() { export async function getWebsocketRequestActions() {
const responses = await invokeCmd<GetWebsocketRequestActionsResponse[]>( const responses = await rpc<GetWebsocketRequestActionsResponse[]>(
"cmd_websocket_request_actions", "cmd_websocket_request_actions",
); );
const actions = responses.flatMap((r) => const actions = responses.flatMap((r) =>
@@ -43,7 +43,7 @@ export async function getWebsocketRequestActions() {
pluginRefId: r.pluginRefId, pluginRefId: r.pluginRefId,
args: { websocketRequest }, args: { websocketRequest },
}; };
await invokeCmd("cmd_call_websocket_request_action", { req: payload }); await rpc("cmd_call_websocket_request_action", { req: payload });
}, },
})), })),
); );
+2 -9
View File
@@ -1,18 +1,11 @@
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"; import { platform } from "@yaakapp-internal/platform";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { fireAndForget } from "../lib/fireAndForget";
export function useWindowFocus() { export function useWindowFocus() {
const [visible, setVisible] = useState(true); const [visible, setVisible] = useState(true);
useEffect(() => { useEffect(() => {
const unlisten = getCurrentWebviewWindow().onFocusChanged((e) => { return platform.window.onFocusChanged(setVisible);
setVisible(e.payload);
});
return () => {
fireAndForget(unlisten.then((fn) => fn()));
};
}, []); }, []);
return visible; return visible;
@@ -6,7 +6,7 @@ import type {
WorkspaceAction, WorkspaceAction,
} from "@yaakapp-internal/plugins"; } from "@yaakapp-internal/plugins";
import { useMemo } from "react"; import { useMemo } from "react";
import { invokeCmd } from "../lib/tauri"; import { rpc } from "../lib/rpc";
import { usePluginsKey } from "./usePlugins"; import { usePluginsKey } from "./usePlugins";
export type CallableWorkspaceAction = Pick<WorkspaceAction, "label" | "icon"> & { export type CallableWorkspaceAction = Pick<WorkspaceAction, "label" | "icon"> & {
@@ -30,7 +30,7 @@ export function useWorkspaceActions() {
} }
export async function getWorkspaceActions() { export async function getWorkspaceActions() {
const responses = await invokeCmd<GetWorkspaceActionsResponse[]>("cmd_workspace_actions"); const responses = await rpc<GetWorkspaceActionsResponse[]>("cmd_workspace_actions");
const actions = responses.flatMap((r) => const actions = responses.flatMap((r) =>
r.actions.map((a, i) => ({ r.actions.map((a, i) => ({
label: a.label, label: a.label,
@@ -41,7 +41,7 @@ export async function getWorkspaceActions() {
pluginRefId: r.pluginRefId, pluginRefId: r.pluginRefId,
args: { workspace }, args: { workspace },
}; };
await invokeCmd("cmd_call_workspace_action", { req: payload }); await rpc("cmd_call_workspace_action", { req: payload });
}, },
})), })),
); );
+3 -3
View File
@@ -3,7 +3,7 @@ import type { AnyModel, ModelPayload } from "@yaakapp-internal/models";
import { watchWorkspaceFiles } from "@yaakapp-internal/sync"; import { watchWorkspaceFiles } from "@yaakapp-internal/sync";
import { syncWorkspace } from "../commands/commands"; import { syncWorkspace } from "../commands/commands";
import { activeWorkspaceIdAtom, activeWorkspaceMetaAtom } from "../hooks/useActiveWorkspace"; import { activeWorkspaceIdAtom, activeWorkspaceMetaAtom } from "../hooks/useActiveWorkspace";
import { listenToTauriEvent } from "../hooks/useListenToTauriEvent"; import { platform } from "@yaakapp-internal/platform";
import { jotaiStore } from "../lib/jotai"; import { jotaiStore } from "../lib/jotai";
export function initSync() { export function initSync() {
@@ -33,8 +33,8 @@ const syncAfterModelWrite = eagerDebounceAsync(sync, 1000);
* simply add long-lived subscribers for the lifetime of the app. * simply add long-lived subscribers for the lifetime of the app.
*/ */
function initModelListeners() { function initModelListeners() {
listenToTauriEvent<ModelPayload[]>("model_writes", (p) => { platform.listen<ModelPayload[]>("model_writes", (payloads) => {
if (p.payload.some((payload) => isModelRelevant(payload.model))) syncAfterModelWrite(); if (payloads.some((payload) => isModelRelevant(payload.model))) syncAfterModelWrite();
}); });
} }
+4 -4
View File
@@ -1,5 +1,5 @@
import { getIdentifier } from "@tauri-apps/api/app"; import { platform } from "@yaakapp-internal/platform";
import { invokeCmd } from "./tauri"; import { rpc } from "./rpc";
export interface AppInfo { export interface AppInfo {
isDev: boolean; isDev: boolean;
@@ -16,8 +16,8 @@ export interface AppInfo {
} }
export const appInfo = { export const appInfo = {
...(await invokeCmd("cmd_metadata")), ...(await rpc("cmd_metadata")),
identifier: await getIdentifier(), identifier: await platform.appIdentifier(),
} as AppInfo; } as AppInfo;
console.log("App info", appInfo); console.log("App info", appInfo);
+3 -3
View File
@@ -1,14 +1,14 @@
import { clear, writeText } from "@tauri-apps/plugin-clipboard-manager";
import { showToast } from "./toast"; import { showToast } from "./toast";
import { platform } from "@yaakapp-internal/platform";
export function copyToClipboard( export function copyToClipboard(
text: string | null, text: string | null,
{ disableToast }: { disableToast?: boolean } = {}, { disableToast }: { disableToast?: boolean } = {},
) { ) {
if (text == null) { if (text == null) {
clear().catch(console.error); platform.clipboard.clear().catch(console.error);
} else { } else {
writeText(text).catch(console.error); platform.clipboard.writeText(text).catch(console.error);
} }
if (text !== "" && !disableToast) { if (text !== "" && !disableToast) {
+2 -2
View File
@@ -1,8 +1,8 @@
import type { HttpRequestHeader } from "@yaakapp-internal/models"; import type { HttpRequestHeader } from "@yaakapp-internal/models";
import { invokeCmd } from "./tauri"; import { rpc } from "./rpc";
/** /**
* Global default headers fetched from the backend. * Global default headers fetched from the backend.
* These are static and fetched once on module load. * These are static and fetched once on module load.
*/ */
export const defaultHeaders: HttpRequestHeader[] = await invokeCmd("cmd_default_headers"); export const defaultHeaders: HttpRequestHeader[] = await rpc("cmd_default_headers");
+3 -3
View File
@@ -2,7 +2,7 @@ import { parseTemplate } from "@yaakapp-internal/templates";
import { activeEnvironmentIdAtom } from "../hooks/useActiveEnvironment"; import { activeEnvironmentIdAtom } from "../hooks/useActiveEnvironment";
import { activeWorkspaceIdAtom } from "../hooks/useActiveWorkspace"; import { activeWorkspaceIdAtom } from "../hooks/useActiveWorkspace";
import { jotaiStore } from "./jotai"; import { jotaiStore } from "./jotai";
import { invokeCmd } from "./tauri"; import { rpc } from "./rpc";
export function analyzeTemplate(template: string): "global_secured" | "local_secured" | "insecure" { export function analyzeTemplate(template: string): "global_secured" | "local_secured" | "insecure" {
let secureTags = 0; let secureTags = 0;
@@ -39,7 +39,7 @@ export async function convertTemplateToInsecure(template: string) {
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom) ?? "n/a"; const workspaceId = jotaiStore.get(activeWorkspaceIdAtom) ?? "n/a";
const environmentId = jotaiStore.get(activeEnvironmentIdAtom) ?? null; const environmentId = jotaiStore.get(activeEnvironmentIdAtom) ?? null;
return invokeCmd<string>("cmd_decrypt_template", { template, workspaceId, environmentId }); return rpc<string>("cmd_decrypt_template", { template, workspaceId, environmentId });
} }
export async function convertTemplateToSecure(template: string): Promise<string> { export async function convertTemplateToSecure(template: string): Promise<string> {
@@ -53,5 +53,5 @@ export async function convertTemplateToSecure(template: string): Promise<string>
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom) ?? "n/a"; const workspaceId = jotaiStore.get(activeWorkspaceIdAtom) ?? "n/a";
const environmentId = jotaiStore.get(activeEnvironmentIdAtom) ?? null; const environmentId = jotaiStore.get(activeEnvironmentIdAtom) ?? null;
return invokeCmd<string>("cmd_secure_template", { template, workspaceId, environmentId }); return rpc<string>("cmd_secure_template", { template, workspaceId, environmentId });
} }
+3 -3
View File
@@ -1,11 +1,11 @@
import vkBeautify from "vkbeautify"; import vkBeautify from "vkbeautify";
import { invokeCmd } from "./tauri"; import { rpc } from "./rpc";
export async function tryFormatJson(text: string): Promise<string> { export async function tryFormatJson(text: string): Promise<string> {
if (text === "") return text; if (text === "") return text;
try { try {
const result = await invokeCmd<string>("cmd_format_json", { text }); const result = await rpc<string>("cmd_format_json", { text });
return result; return result;
} catch (err) { } catch (err) {
console.warn("Failed to format JSON", err); console.warn("Failed to format JSON", err);
@@ -24,7 +24,7 @@ export async function tryFormatGraphql(text: string): Promise<string> {
if (text === "") return text; if (text === "") return text;
try { try {
return await invokeCmd<string>("cmd_format_graphql", { text }); return await rpc<string>("cmd_format_graphql", { text });
} catch (err) { } catch (err) {
console.warn("Failed to format GraphQL", err); console.warn("Failed to format GraphQL", err);
} }
+2 -2
View File
@@ -9,7 +9,7 @@ import { showDialog } from "./dialog";
import { jotaiStore } from "./jotai"; import { jotaiStore } from "./jotai";
import { pluralizeCount } from "./pluralize"; import { pluralizeCount } from "./pluralize";
import { router } from "./router"; import { router } from "./router";
import { invokeCmd } from "./tauri"; import { rpc } from "./rpc";
export const importData = createFastMutation({ export const importData = createFastMutation({
mutationKey: ["import_data"], mutationKey: ["import_data"],
@@ -50,7 +50,7 @@ export const importData = createFastMutation({
async function performImport(filePath: string): Promise<boolean> { async function performImport(filePath: string): Promise<boolean> {
const activeWorkspace = jotaiStore.get(activeWorkspaceAtom); const activeWorkspace = jotaiStore.get(activeWorkspaceAtom);
const imported = await invokeCmd<BatchUpsertResult>("cmd_import_data", { const imported = await rpc<BatchUpsertResult>("cmd_import_data", {
filePath, filePath,
workspaceId: activeWorkspace?.id, workspaceId: activeWorkspace?.id,
}); });
+20 -22
View File
@@ -1,5 +1,3 @@
import { emit } from "@tauri-apps/api/event";
import { openUrl } from "@tauri-apps/plugin-opener";
import { debounce } from "@yaakapp-internal/lib"; import { debounce } from "@yaakapp-internal/lib";
import type { import type {
FormInput, FormInput,
@@ -20,23 +18,23 @@ import { Button } from "../components/core/Button";
import { ButtonInfiniteLoading } from "../components/core/ButtonInfiniteLoading"; import { ButtonInfiniteLoading } from "../components/core/ButtonInfiniteLoading";
// Listen for toasts // Listen for toasts
import { listenToTauriEvent } from "../hooks/useListenToTauriEvent"; import { platform } from "@yaakapp-internal/platform";
import { updateAvailableAtom } from "./atoms"; import { updateAvailableAtom } from "./atoms";
import { stringToColor } from "./color"; import { stringToColor } from "./color";
import { generateId } from "./generateId"; import { generateId } from "./generateId";
import { jotaiStore } from "./jotai"; import { jotaiStore } from "./jotai";
import { showPrompt } from "./prompt"; import { showPrompt } from "./prompt";
import { showPromptForm } from "./prompt-form"; import { showPromptForm } from "./prompt-form";
import { invokeCmd } from "./tauri"; import { rpc } from "./rpc";
import { showToast } from "./toast"; import { showToast } from "./toast";
export function initGlobalListeners() { export function initGlobalListeners() {
listenToTauriEvent<ShowToastRequest>("show_toast", (event) => { platform.listen<ShowToastRequest>("show_toast", (payload) => {
showToast({ ...event.payload }); showToast({ ...payload });
}); });
// Show errors for any plugins that failed to load during startup // Show errors for any plugins that failed to load during startup
void invokeCmd<[string, string][]>("cmd_plugin_init_errors").then((errors) => { void rpc<[string, string][]>("cmd_plugin_init_errors").then((errors) => {
for (const [dir, err] of errors) { for (const [dir, err] of errors) {
const name = dir.split(/[/\\]/).pop() ?? dir; const name = dir.split(/[/\\]/).pop() ?? dir;
showToast({ showToast({
@@ -61,13 +59,13 @@ export function initGlobalListeners() {
} }
}); });
listenToTauriEvent("settings", () => openSettings.mutate(null)); platform.listen("settings", () => openSettings.mutate(null));
// Track active dynamic form dialogs so follow-up input updates can reach them // Track active dynamic form dialogs so follow-up input updates can reach them
const activeForms = new Map<string, (inputs: FormInput[]) => void>(); const activeForms = new Map<string, (inputs: FormInput[]) => void>();
// Listen for plugin events // Listen for plugin events
listenToTauriEvent<InternalEvent>("plugin_event", async ({ payload: event }) => { platform.listen<InternalEvent>("plugin_event", async (event) => {
if (event.payload.type === "prompt_text_request") { if (event.payload.type === "prompt_text_request") {
const value = await showPrompt(event.payload); const value = await showPrompt(event.payload);
const result: InternalEvent = { const result: InternalEvent = {
@@ -81,7 +79,7 @@ export function initGlobalListeners() {
value, value,
}, },
}; };
await emit(event.id, result); await platform.emit(event.id, result);
} else if (event.payload.type === "prompt_form_request") { } else if (event.payload.type === "prompt_form_request") {
if (event.replyId != null) { if (event.replyId != null) {
// Follow-up update from plugin runtime — update the active dialog's inputs // Follow-up update from plugin runtime — update the active dialog's inputs
@@ -106,7 +104,7 @@ export function initGlobalListeners() {
done, done,
}, },
}; };
void emit(event.id, result); void platform.emit(event.id, result);
}; };
const values = await showPromptForm({ const values = await showPromptForm({
@@ -127,24 +125,24 @@ export function initGlobalListeners() {
} }
}); });
listenToTauriEvent<string>("update_installed", async ({ payload: version }) => { platform.listen<string>("update_installed", async (version) => {
console.log("Got update installed event", version); console.log("Got update installed event", version);
showUpdateInstalledToast(version); showUpdateInstalledToast(version);
}); });
// Listen for update events // Listen for update events
listenToTauriEvent<UpdateInfo>("update_available", async ({ payload }) => { platform.listen<UpdateInfo>("update_available", async (payload) => {
console.log("Got update available", payload); console.log("Got update available", payload);
void showUpdateAvailableToast(payload); void showUpdateAvailableToast(payload);
}); });
listenToTauriEvent<YaakNotification>("notification", ({ payload }) => { platform.listen<YaakNotification>("notification", (payload) => {
console.log("Got notification event", payload); console.log("Got notification event", payload);
showNotificationToast(payload); showNotificationToast(payload);
}); });
// Listen for plugin update events // Listen for plugin update events
listenToTauriEvent<PluginUpdateNotification>("plugin_updates_available", ({ payload }) => { platform.listen<PluginUpdateNotification>("plugin_updates_available", (payload) => {
console.log("Got plugin updates event", payload); console.log("Got plugin updates event", payload);
showPluginUpdatesToast(payload); showPluginUpdatesToast(payload);
}); });
@@ -171,7 +169,7 @@ function showUpdateInstalledToast(version: string) {
loadingChildren="Restarting..." loadingChildren="Restarting..."
onClick={() => { onClick={() => {
hide(); hide();
setTimeout(() => invokeCmd("cmd_restart", {}), 200); setTimeout(() => rpc("cmd_restart", {}), 200);
}} }}
> >
Relaunch Yaak Relaunch Yaak
@@ -187,7 +185,7 @@ async function showUpdateAvailableToast(updateInfo: UpdateInfo) {
jotaiStore.set(updateAvailableAtom, { version, downloaded }); jotaiStore.set(updateAvailableAtom, { version, downloaded });
// Acknowledge the event, so we don't time out and try the fallback update logic // Acknowledge the event, so we don't time out and try the fallback update logic
await emit<UpdateResponse>(replyEventId, { type: "ack" }); await platform.emit(replyEventId, { type: "ack" } satisfies UpdateResponse);
showToast({ showToast({
id: UPDATE_TOAST_ID, id: UPDATE_TOAST_ID,
@@ -209,10 +207,10 @@ async function showUpdateAvailableToast(updateInfo: UpdateInfo) {
className="min-w-40" className="min-w-40"
loadingChildren={downloaded ? "Installing..." : "Downloading..."} loadingChildren={downloaded ? "Installing..." : "Downloading..."}
onClick={async () => { onClick={async () => {
await emit<UpdateResponse>(replyEventId, { await platform.emit(replyEventId, {
type: "action", type: "action",
action: "install", action: "install",
}); } satisfies UpdateResponse);
}} }}
> >
{downloaded ? "Install Now" : "Download and Install"} {downloaded ? "Install Now" : "Download and Install"}
@@ -223,7 +221,7 @@ async function showUpdateAvailableToast(updateInfo: UpdateInfo) {
variant="border" variant="border"
rightSlot={<Icon icon="external_link" />} rightSlot={<Icon icon="external_link" />}
onClick={async () => { onClick={async () => {
await openUrl(`https://yaak.app/changelog/${version}`); await platform.openUrl(`https://yaak.app/changelog/${version}`);
}} }}
> >
What&apos;s New What&apos;s New
@@ -304,7 +302,7 @@ function showNotificationToast(n: YaakNotification) {
</VStack> </VStack>
), ),
onClose: () => { onClose: () => {
invokeCmd("cmd_dismiss_notification", { notificationId: n.id }).catch(console.error); rpc("cmd_dismiss_notification", { notificationId: n.id }).catch(console.error);
}, },
action: ({ hide }) => { action: ({ hide }) => {
return actionLabel && actionUrl ? ( return actionLabel && actionUrl ? (
@@ -315,7 +313,7 @@ function showNotificationToast(n: YaakNotification) {
rightSlot={<Icon icon="external_link" />} rightSlot={<Icon icon="external_link" />}
onClick={() => { onClick={() => {
hide(); hide();
return openUrl(actionUrl); return platform.openUrl(actionUrl);
}} }}
> >
{actionLabel} {actionLabel}
+4 -4
View File
@@ -1,4 +1,3 @@
import { save } from "@tauri-apps/plugin-dialog";
import { Icon } from "@yaakapp-internal/ui"; import { Icon } from "@yaakapp-internal/ui";
import mime from "mime"; import mime from "mime";
import { createElement } from "react"; import { createElement } from "react";
@@ -7,8 +6,9 @@ import type { SniffedValue } from "../components/core/Editor/sniffValue";
import { isEncodedRun } from "../components/core/Editor/sniffValue"; import { isEncodedRun } from "../components/core/Editor/sniffValue";
import { copyToClipboard } from "./copy"; import { copyToClipboard } from "./copy";
import { fireAndForget } from "./fireAndForget"; import { fireAndForget } from "./fireAndForget";
import { invokeCmd } from "./tauri"; import { rpc } from "./rpc";
import { showToast } from "./toast"; import { showToast } from "./toast";
import { platform } from "@yaakapp-internal/platform";
/** /**
* How the value is written, which is the thing worth knowing about it — that it is base64 * How the value is written, which is the thing worth knowing about it — that it is base64
@@ -229,7 +229,7 @@ function toBase64(bytes: Uint8Array): string {
*/ */
export async function saveValue(text: string, sniffed: SniffedValue | null, name: string) { export async function saveValue(text: string, sniffed: SniffedValue | null, name: string) {
const ext = sniffed == null ? "txt" : (mime.getExtension(sniffed.mime) ?? "bin"); const ext = sniffed == null ? "txt" : (mime.getExtension(sniffed.mime) ?? "bin");
const filepath = await save({ defaultPath: `${name}.${ext}`, title: "Save Value" }); const filepath = await platform.dialog.save({ defaultPath: `${name}.${ext}`, title: "Save Value" });
if (filepath == null) { if (filepath == null) {
return; // Cancelled return; // Cancelled
} }
@@ -241,6 +241,6 @@ export async function saveValue(text: string, sniffed: SniffedValue | null, name
? normalizeBase64(payloadOf(text, sniffed)) ? normalizeBase64(payloadOf(text, sniffed))
: toBase64(decodeValue(text, sniffed)); : toBase64(decodeValue(text, sniffed));
await invokeCmd("cmd_save_base64_to_binary", { filepath, data }); await rpc("cmd_save_base64_to_binary", { filepath, data });
showToast({ message: `Saved to ${filepath}` }); showToast({ message: `Saved to ${filepath}` });
} }
+7 -7
View File
@@ -1,9 +1,9 @@
import { readFile } from "@tauri-apps/plugin-fs";
import type { HttpResponse } from "@yaakapp-internal/models"; import type { HttpResponse } from "@yaakapp-internal/models";
import type { FilterResponse } from "@yaakapp-internal/plugins"; import type { FilterResponse } from "@yaakapp-internal/plugins";
import type { ServerSentEvent, SseSummary } from "@yaakapp-internal/sse"; import type { ServerSentEvent, SseSummary } from "@yaakapp-internal/sse";
import { candidateJsonPayloadsFromSseText, computeSseSummary } from "@yaakapp-internal/sse"; import { candidateJsonPayloadsFromSseText, computeSseSummary } from "@yaakapp-internal/sse";
import { invokeCmd } from "./tauri"; import { rpc } from "./rpc";
import { platform } from "@yaakapp-internal/platform";
export async function getResponseBodyText({ export async function getResponseBodyText({
response, response,
@@ -12,7 +12,7 @@ export async function getResponseBodyText({
response: HttpResponse; response: HttpResponse;
filter: string | null; filter: string | null;
}): Promise<string | null> { }): Promise<string | null> {
const result = await invokeCmd<FilterResponse>("cmd_http_response_body", { const result = await rpc<FilterResponse>("cmd_http_response_body", {
response, response,
filter, filter,
}); });
@@ -29,7 +29,7 @@ export async function getResponseBodyEventSource(
): Promise<ServerSentEvent[]> { ): Promise<ServerSentEvent[]> {
if (!response.bodyPath) return []; if (!response.bodyPath) return [];
try { try {
const events = await invokeCmd<ServerSentEvent[]>("cmd_get_sse_events", { const events = await rpc<ServerSentEvent[]>("cmd_get_sse_events", {
filePath: response.bodyPath, filePath: response.bodyPath,
}); });
if (events.length > 0) { if (events.length > 0) {
@@ -39,7 +39,7 @@ export async function getResponseBodyEventSource(
// Fall back to raw JSON frame parsing for non-standard SSE-like responses. // Fall back to raw JSON frame parsing for non-standard SSE-like responses.
} }
const bytes = await readFile(response.bodyPath); const bytes = await platform.files.readFile(response.bodyPath);
const text = new TextDecoder("utf-8").decode(bytes); const text = new TextDecoder("utf-8").decode(bytes);
return candidateJsonPayloadsFromSseText(text).map((data, index) => ({ return candidateJsonPayloadsFromSseText(text).map((data, index) => ({
data, data,
@@ -55,7 +55,7 @@ export async function getResponseBodySseSummary(
): Promise<SseSummary> { ): Promise<SseSummary> {
if (!response.bodyPath) return { fragmentCount: 0, summary: "" }; if (!response.bodyPath) return { fragmentCount: 0, summary: "" };
const bytes = await readFile(response.bodyPath); const bytes = await platform.files.readFile(response.bodyPath);
const text = new TextDecoder("utf-8").decode(bytes); const text = new TextDecoder("utf-8").decode(bytes);
return computeSseSummary(text, resultKeyPath); return computeSseSummary(text, resultKeyPath);
} }
@@ -64,5 +64,5 @@ export async function getResponseBodyBytes(
response: HttpResponse, response: HttpResponse,
): Promise<Uint8Array<ArrayBuffer> | null> { ): Promise<Uint8Array<ArrayBuffer> | null> {
if (!response.bodyPath) return null; if (!response.bodyPath) return null;
return readFile(response.bodyPath); return platform.files.readFile(response.bodyPath);
} }
+2 -2
View File
@@ -1,6 +1,6 @@
import { type } from "@tauri-apps/plugin-os"; import { platform } from "@yaakapp-internal/platform";
const os = type(); const os = platform.osType();
export const revealInFinderText = export const revealInFinderText =
os === "macos" os === "macos"
? "Reveal in Finder" ? "Reveal in Finder"
@@ -1,7 +1,15 @@
import type { InvokeArgs } from "@tauri-apps/api/core"; import type { RpcPayload } from "@yaakapp-internal/platform";
import { invoke } from "@tauri-apps/api/core"; import { platform } from "@yaakapp-internal/platform";
type TauriCmd = /**
* Every backend command the client sends.
*
* Listing them keeps typos out and gives us the inventory to check the Rust
* side against. Once the app's commands move onto `RpcRouter`, this union is
* replaced by the generated `RpcSchema` and the payload and result types come
* with it, the way `apps/yaak-proxy/lib/rpc.ts` already works.
*/
type AppCmd =
| "cmd_call_grpc_request_action" | "cmd_call_grpc_request_action"
| "cmd_call_http_authentication_action" | "cmd_call_http_authentication_action"
| "cmd_call_http_request_action" | "cmd_call_http_request_action"
@@ -53,14 +61,15 @@ type TauriCmd =
| "cmd_send_http_request" | "cmd_send_http_request"
| "cmd_template_function_summaries" | "cmd_template_function_summaries"
| "cmd_template_function_config" | "cmd_template_function_config"
| "cmd_template_tokens_to_string"; | "cmd_template_tokens_to_string"
| "models_get_graphql_introspection"
| "models_get_settings"
| "models_grpc_events"
| "models_upsert_graphql_introspection"
| "models_websocket_events"
| "plugin:yaak-license|check";
export async function invokeCmd<T>(cmd: TauriCmd, args?: InvokeArgs): Promise<T> { /** Call a backend command. */
// console.log('RUN COMMAND', cmd, args); export function rpc<T>(cmd: AppCmd, payload?: RpcPayload): Promise<T> {
try { return platform.rpc<T>(cmd, payload);
return await invoke(cmd, args);
} catch (err) {
console.warn("Tauri command error", cmd, err);
throw err;
}
} }
+2 -2
View File
@@ -1,6 +1,6 @@
import type { HttpRequest, HttpResponse } from "@yaakapp-internal/models"; import type { HttpRequest, HttpResponse } from "@yaakapp-internal/models";
import { getActiveCookieJar } from "../hooks/useActiveCookieJar"; import { getActiveCookieJar } from "../hooks/useActiveCookieJar";
import { invokeCmd } from "./tauri"; import { rpc } from "./rpc";
export async function sendEphemeralRequest( export async function sendEphemeralRequest(
request: HttpRequest, request: HttpRequest,
@@ -8,7 +8,7 @@ export async function sendEphemeralRequest(
): Promise<HttpResponse> { ): Promise<HttpResponse> {
// Remove some things that we don't want to associate // Remove some things that we don't want to associate
const newRequest = { ...request }; const newRequest = { ...request };
return invokeCmd("cmd_send_ephemeral_request", { return rpc("cmd_send_ephemeral_request", {
request: newRequest, request: newRequest,
environmentId, environmentId,
cookieJarId: getActiveCookieJar()?.id, cookieJarId: getActiveCookieJar()?.id,
+2 -2
View File
@@ -1,6 +1,6 @@
import { invoke } from "@tauri-apps/api/core";
import type { Settings } from "@yaakapp-internal/models"; import type { Settings } from "@yaakapp-internal/models";
import { rpc } from "./rpc";
export function getSettings(): Promise<Settings> { export function getSettings(): Promise<Settings> {
return invoke<Settings>("models_get_settings"); return rpc<Settings>("models_get_settings");
} }
+2 -2
View File
@@ -5,10 +5,10 @@ import {
resolveAppearance, resolveAppearance,
type Appearance, type Appearance,
} from "@yaakapp-internal/theme"; } from "@yaakapp-internal/theme";
import { invokeCmd } from "./tauri"; import { rpc } from "./rpc";
export async function getThemes() { export async function getThemes() {
const themes = (await invokeCmd<GetThemesResponse[]>("cmd_get_themes")).flatMap((t) => t.themes); const themes = (await rpc<GetThemesResponse[]>("cmd_get_themes")).flatMap((t) => t.themes);
themes.sort((a, b) => a.label.localeCompare(b.label)); themes.sort((a, b) => a.label.localeCompare(b.label));
// Remove duplicates, in case multiple plugins provide the same theme // Remove duplicates, in case multiple plugins provide the same theme
const uniqueThemes = Array.from(new Map(themes.map((t) => [t.id, t])).values()); const uniqueThemes = Array.from(new Map(themes.map((t) => [t.id, t])).values());
+2 -2
View File
@@ -1,6 +1,6 @@
import "./main.css"; import "./main.css";
import { RouterProvider } from "@tanstack/react-router"; import { RouterProvider } from "@tanstack/react-router";
import { type } from "@tauri-apps/plugin-os"; import { platform } from "@yaakapp-internal/platform";
import { changeModelStoreWorkspace, initModelStore } from "@yaakapp-internal/models"; import { changeModelStoreWorkspace, initModelStore } from "@yaakapp-internal/models";
import { setPlatformOnDocument } from "@yaakapp-internal/theme"; import { setPlatformOnDocument } from "@yaakapp-internal/theme";
import { StrictMode } from "react"; import { StrictMode } from "react";
@@ -11,7 +11,7 @@ import { initGlobalListeners } from "./lib/initGlobalListeners";
import { jotaiStore } from "./lib/jotai"; import { jotaiStore } from "./lib/jotai";
import { router } from "./lib/router"; import { router } from "./lib/router";
const osType = type(); const osType = platform.osType();
setPlatformOnDocument(osType); setPlatformOnDocument(osType);
window.addEventListener("keydown", (e) => { window.addEventListener("keydown", (e) => {
+2 -2
View File
@@ -1,6 +1,6 @@
import { QueryClientProvider } from "@tanstack/react-query"; import { QueryClientProvider } from "@tanstack/react-query";
import { createRootRoute, Outlet } from "@tanstack/react-router"; import { createRootRoute, Outlet } from "@tanstack/react-router";
import { type } from "@tauri-apps/plugin-os"; import { platform } from "@yaakapp-internal/platform";
import classNames from "classnames"; import classNames from "classnames";
import { Provider as JotaiProvider } from "jotai"; import { Provider as JotaiProvider } from "jotai";
import { LazyMotion, MotionConfig } from "motion/react"; import { LazyMotion, MotionConfig } from "motion/react";
@@ -46,7 +46,7 @@ function RouteComponent() {
function Layout() { function Layout() {
return ( return (
<div <div
className={classNames("w-full h-full", type() === "linux" && "border border-border-subtle")} className={classNames("w-full h-full", platform.osType() === "linux" && "border border-border-subtle")}
> >
<Outlet /> <Outlet />
</div> </div>
+6 -8
View File
@@ -1,6 +1,4 @@
import { listen } from "@tauri-apps/api/event"; import { platform } from "@yaakapp-internal/platform";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import { type as osType } from "@tauri-apps/plugin-os";
import { setWindowTheme } from "@yaakapp-internal/mac-window"; import { setWindowTheme } from "@yaakapp-internal/mac-window";
import type { ModelPayload } from "@yaakapp-internal/models"; import type { ModelPayload } from "@yaakapp-internal/models";
import type { Appearance } from "@yaakapp-internal/theme"; import type { Appearance } from "@yaakapp-internal/theme";
@@ -17,7 +15,7 @@ import { getResolvedTheme } from "./lib/themes";
// a good appearance guess so we're not waiting too long // a good appearance guess so we're not waiting too long
let preferredAppearance: Appearance = getInitialAppearance(); let preferredAppearance: Appearance = getInitialAppearance();
let linuxSystemAppearanceAvailable = let linuxSystemAppearanceAvailable =
osType() === "linux" && window.__YAAK_INITIAL_APPEARANCE_SOURCE__ === "linux-system"; platform.osType() === "linux" && window.__YAAK_INITIAL_APPEARANCE_SOURCE__ === "linux-system";
let configureThemeGeneration = 0; let configureThemeGeneration = 0;
let windowShown = false; let windowShown = false;
@@ -41,20 +39,20 @@ async function configureThemeAndShow() {
windowShown = true; windowShown = true;
// To prevent theme flashing, the backend hides new windows by default, so we // To prevent theme flashing, the backend hides new windows by default, so we
// need to show it here, after configuring the theme for the first time. // need to show it here, after configuring the theme for the first time.
await getCurrentWebviewWindow().show(); await platform.window.show();
} }
} }
// Listen for settings changes, the re-compute theme // Listen for settings changes, the re-compute theme
listen<ModelPayload[]>("model_writes", async (event) => { platform.listen<ModelPayload[]>("model_writes", async (payloads) => {
const relevant = event.payload.some( const relevant = payloads.some(
(p) => (p) =>
p.change.type === "upsert" && p.change.type === "upsert" &&
(p.model.model === "settings" || p.model.model === "plugin"), (p.model.model === "settings" || p.model.model === "plugin"),
); );
if (!relevant) return; if (!relevant) return;
await configureThemeAndShow(); await configureThemeAndShow();
}).catch(console.error); });
async function configureTheme(): Promise<boolean> { async function configureTheme(): Promise<boolean> {
const generation = ++configureThemeGeneration; const generation = ++configureThemeGeneration;
+2 -2
View File
@@ -1,9 +1,9 @@
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { invoke } from "@tauri-apps/api/core"; import { platform } from "@yaakapp-internal/platform";
import { Fonts } from "./bindings/gen_fonts"; import { Fonts } from "./bindings/gen_fonts";
export async function listFonts() { export async function listFonts() {
return invoke<Fonts>("plugin:yaak-fonts|list", {}); return platform.rpc<Fonts>("plugin:yaak-fonts|list", {});
} }
export function useFonts() { export function useFonts() {
+6 -10
View File
@@ -1,6 +1,5 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { invoke } from "@tauri-apps/api/core"; import { platform } from "@yaakapp-internal/platform";
import { listen } from "@tauri-apps/api/event";
import { appInfo } from "@yaakapp/yaak-client/lib/appInfo"; import { appInfo } from "@yaakapp/yaak-client/lib/appInfo";
import { useEffect } from "react"; import { useEffect } from "react";
import { LicenseCheckStatus } from "./bindings/license"; import { LicenseCheckStatus } from "./bindings/license";
@@ -13,24 +12,21 @@ export function useLicense() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const activate = useMutation<void, string, { licenseKey: string }>({ const activate = useMutation<void, string, { licenseKey: string }>({
mutationKey: ["license.activate"], mutationKey: ["license.activate"],
mutationFn: (payload) => invoke("plugin:yaak-license|activate", payload), mutationFn: (payload) => platform.rpc("plugin:yaak-license|activate", payload),
onSuccess: () => queryClient.invalidateQueries({ queryKey: CHECK_QUERY_KEY }), onSuccess: () => queryClient.invalidateQueries({ queryKey: CHECK_QUERY_KEY }),
}); });
const deactivate = useMutation<void, string, void>({ const deactivate = useMutation<void, string, void>({
mutationKey: ["license.deactivate"], mutationKey: ["license.deactivate"],
mutationFn: () => invoke("plugin:yaak-license|deactivate"), mutationFn: () => platform.rpc("plugin:yaak-license|deactivate"),
onSuccess: () => queryClient.invalidateQueries({ queryKey: CHECK_QUERY_KEY }), onSuccess: () => queryClient.invalidateQueries({ queryKey: CHECK_QUERY_KEY }),
}); });
// Check the license again after a license is activated // Check the license again after a license is activated
useEffect(() => { useEffect(() => {
const unlisten = listen("license-activated", async () => { return platform.listen("license-activated", () => {
await queryClient.invalidateQueries({ queryKey: CHECK_QUERY_KEY }); void queryClient.invalidateQueries({ queryKey: CHECK_QUERY_KEY });
}); });
return () => {
void unlisten.then((fn) => fn());
};
}, []); }, []);
const check = useQuery<LicenseCheckStatus | null, string>({ const check = useQuery<LicenseCheckStatus | null, string>({
@@ -41,7 +37,7 @@ export function useLicense() {
if (!appInfo.featureLicense) { if (!appInfo.featureLicense) {
return null; return null;
} }
return invoke<LicenseCheckStatus>("plugin:yaak-license|check"); return platform.rpc<LicenseCheckStatus>("plugin:yaak-license|check");
}, },
}); });
+3 -3
View File
@@ -1,9 +1,9 @@
import { invoke } from "@tauri-apps/api/core"; import { platform } from "@yaakapp-internal/platform";
export function setWindowTitle(title: string) { export function setWindowTitle(title: string) {
invoke("plugin:yaak-mac-window|set_title", { title }).catch(console.error); platform.rpc("plugin:yaak-mac-window|set_title", { title }).catch(console.error);
} }
export function setWindowTheme(bgColor: string) { export function setWindowTheme(bgColor: string) {
invoke("plugin:yaak-mac-window|set_theme", { bgColor }).catch(console.error); platform.rpc("plugin:yaak-mac-window|set_theme", { bgColor }).catch(console.error);
} }
+5 -5
View File
@@ -1,17 +1,17 @@
import { invoke } from "@tauri-apps/api/core"; import { platform } from "@yaakapp-internal/platform";
export function enableEncryption(workspaceId: string) { export function enableEncryption(workspaceId: string) {
return invoke<void>("cmd_enable_encryption", { workspaceId }); return platform.rpc<void>("cmd_enable_encryption", { workspaceId });
} }
export function revealWorkspaceKey(workspaceId: string) { export function revealWorkspaceKey(workspaceId: string) {
return invoke<string>("cmd_reveal_workspace_key", { workspaceId }); return platform.rpc<string>("cmd_reveal_workspace_key", { workspaceId });
} }
export function setWorkspaceKey(args: { workspaceId: string; key: string }) { export function setWorkspaceKey(args: { workspaceId: string; key: string }) {
return invoke<void>("cmd_set_workspace_key", args); return platform.rpc<void>("cmd_set_workspace_key", args);
} }
export function disableEncryption(workspaceId: string) { export function disableEncryption(workspaceId: string) {
return invoke<void>("cmd_disable_encryption", { workspaceId }); return platform.rpc<void>("cmd_disable_encryption", { workspaceId });
} }
+44 -46
View File
@@ -1,6 +1,5 @@
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { Channel, invoke } from "@tauri-apps/api/core"; import { platform } from "@yaakapp-internal/platform";
import { emit } from "@tauri-apps/api/event";
import { createFastMutation } from "@yaakapp/yaak-client/hooks/useFastMutation"; import { createFastMutation } from "@yaakapp/yaak-client/hooks/useFastMutation";
import { queryClient } from "@yaakapp/yaak-client/lib/queryClient"; import { queryClient } from "@yaakapp/yaak-client/lib/queryClient";
import { useMemo } from "react"; import { useMemo } from "react";
@@ -59,18 +58,17 @@ export function invalidateGitWorktreeStatus(dir?: string) {
export function useGitWorktreeStatus(dir: string, refreshKey?: string) { export function useGitWorktreeStatus(dir: string, refreshKey?: string) {
return useQuery<GitWorktreeStatus, string>({ return useQuery<GitWorktreeStatus, string>({
queryKey: gitWorktreeStatusQueryKey(dir, refreshKey), queryKey: gitWorktreeStatusQueryKey(dir, refreshKey),
queryFn: () => invoke("cmd_git_worktree_status", { dir }), queryFn: () => platform.rpc("cmd_git_worktree_status", { dir }),
placeholderData: (prev) => prev, placeholderData: (prev) => prev,
}); });
} }
export function watchGitWorktreeStatus(dir: string, callback: (status: GitWorktreeStatus) => void) { export function watchGitWorktreeStatus(dir: string, callback: (status: GitWorktreeStatus) => void) {
const channel = new Channel<GitWorktreeStatus>(); const unlistenPromise = platform.rpcStream<GitWatchResult, GitWorktreeStatus>(
channel.onmessage = callback; "cmd_git_watch_worktree_status",
const unlistenPromise = invoke<GitWatchResult>("cmd_git_watch_worktree_status", { { dir },
dir, callback,
channel, );
});
void unlistenPromise void unlistenPromise
.then(({ unlistenEvent }) => { .then(({ unlistenEvent }) => {
@@ -89,7 +87,7 @@ export function watchGitWorktreeStatus(dir: string, callback: (status: GitWorktr
function useGitFetchAll(dir: string, refreshKey?: string) { function useGitFetchAll(dir: string, refreshKey?: string) {
return useQuery<void, string>({ return useQuery<void, string>({
queryKey: ["git", "fetch_all", dir, refreshKey], queryKey: ["git", "fetch_all", dir, refreshKey],
queryFn: () => invoke("cmd_git_fetch_all", { dir }), queryFn: () => platform.rpc("cmd_git_fetch_all", { dir }),
refetchInterval: 10 * 60_000, refetchInterval: 10 * 60_000,
}); });
} }
@@ -98,7 +96,7 @@ function useGitBranchInfoQuery(dir: string, refreshKey?: string, fetchAllUpdated
return useQuery<GitBranchInfo, string>({ return useQuery<GitBranchInfo, string>({
refetchOnMount: true, refetchOnMount: true,
queryKey: ["git", "branch_info", dir, refreshKey, fetchAllUpdatedAt], queryKey: ["git", "branch_info", dir, refreshKey, fetchAllUpdatedAt],
queryFn: () => invoke("cmd_git_branch_info", { dir }), queryFn: () => platform.rpc("cmd_git_branch_info", { dir }),
placeholderData: (prev) => prev, placeholderData: (prev) => prev,
}); });
} }
@@ -113,8 +111,8 @@ export function useGitLog(dir: string, refreshKey?: string, relaPath?: string) {
queryKey: ["git", "log", dir, refreshKey, relaPath], queryKey: ["git", "log", dir, refreshKey, relaPath],
queryFn: () => queryFn: () =>
relaPath == null relaPath == null
? invoke("cmd_git_log", { dir }) ? platform.rpc("cmd_git_log", { dir })
: invoke("cmd_git_log_for_file", { dir, relaPath }), : platform.rpc("cmd_git_log_for_file", { dir, relaPath }),
placeholderData: (prev) => prev, placeholderData: (prev) => prev,
}); });
} }
@@ -129,7 +127,7 @@ export function useGitFileDiffForCommit(
queryKey: ["git", "file_diff_for_commit", dir, relaPath, commitOid], queryKey: ["git", "file_diff_for_commit", dir, relaPath, commitOid],
queryFn: () => { queryFn: () => {
if (commitOid == null) throw new Error("Missing commit oid"); if (commitOid == null) throw new Error("Missing commit oid");
return invoke("cmd_git_file_diff_for_commit", { dir, relaPath, commitOid }); return platform.rpc("cmd_git_file_diff_for_commit", { dir, relaPath, commitOid });
}, },
}); });
} }
@@ -149,7 +147,7 @@ export function useGit(dir: string, callbacks: GitCallbacks, refreshKey?: string
status: useQuery<GitStatusSummary, string>({ status: useQuery<GitStatusSummary, string>({
refetchOnMount: true, refetchOnMount: true,
queryKey: ["git", "status", dir, refreshKey, fetchAll.dataUpdatedAt], queryKey: ["git", "status", dir, refreshKey, fetchAll.dataUpdatedAt],
queryFn: () => invoke("cmd_git_status", { dir }), queryFn: () => platform.rpc("cmd_git_status", { dir }),
placeholderData: (prev) => prev, placeholderData: (prev) => prev,
}), }),
}, },
@@ -169,21 +167,21 @@ export const gitMutations = (dir: string, callbacks: GitCallbacks) => {
if (remote == null) throw new Error("No remote found"); if (remote == null) throw new Error("No remote found");
} }
const result = await invoke<PushResult>("cmd_git_push", { dir }); const result = await platform.rpc<PushResult>("cmd_git_push", { dir });
if (result.type !== "needs_credentials") return result; if (result.type !== "needs_credentials") return result;
// Needs credentials, prompt for them // Needs credentials, prompt for them
const creds = await callbacks.promptCredentials(result); const creds = await callbacks.promptCredentials(result);
if (creds == null) throw new Error("Canceled"); if (creds == null) throw new Error("Canceled");
await invoke("cmd_git_add_credential", { await platform.rpc("cmd_git_add_credential", {
remoteUrl: result.url, remoteUrl: result.url,
username: creds.username, username: creds.username,
password: creds.password, password: creds.password,
}); });
// Push again // Push again
return invoke<PushResult>("cmd_git_push", { dir }); return platform.rpc<PushResult>("cmd_git_push", { dir });
}; };
const handleError = (err: unknown) => { const handleError = (err: unknown) => {
@@ -198,32 +196,32 @@ export const gitMutations = (dir: string, callbacks: GitCallbacks) => {
return { return {
init: createFastMutation<void, string, void>({ init: createFastMutation<void, string, void>({
mutationKey: ["git", "init"], mutationKey: ["git", "init"],
mutationFn: () => invoke("cmd_git_initialize", { dir }), mutationFn: () => platform.rpc("cmd_git_initialize", { dir }),
onSuccess, onSuccess,
}), }),
add: createFastMutation<void, string, { relaPaths: string[] }>({ add: createFastMutation<void, string, { relaPaths: string[] }>({
mutationKey: ["git", "add", dir], mutationKey: ["git", "add", dir],
mutationFn: (args) => invoke("cmd_git_add", { dir, ...args }), mutationFn: (args) => platform.rpc("cmd_git_add", { dir, ...args }),
onSuccess, onSuccess,
}), }),
addRemote: createFastMutation<GitRemote, string, GitRemote>({ addRemote: createFastMutation<GitRemote, string, GitRemote>({
mutationKey: ["git", "add-remote"], mutationKey: ["git", "add-remote"],
mutationFn: (args) => invoke("cmd_git_add_remote", { dir, ...args }), mutationFn: (args) => platform.rpc("cmd_git_add_remote", { dir, ...args }),
onSuccess, onSuccess,
}), }),
rmRemote: createFastMutation<void, string, { name: string }>({ rmRemote: createFastMutation<void, string, { name: string }>({
mutationKey: ["git", "rm-remote", dir], mutationKey: ["git", "rm-remote", dir],
mutationFn: (args) => invoke("cmd_git_rm_remote", { dir, ...args }), mutationFn: (args) => platform.rpc("cmd_git_rm_remote", { dir, ...args }),
onSuccess, onSuccess,
}), }),
createBranch: createFastMutation<void, string, { branch: string; base?: string }>({ createBranch: createFastMutation<void, string, { branch: string; base?: string }>({
mutationKey: ["git", "branch", dir], mutationKey: ["git", "branch", dir],
mutationFn: (args) => invoke("cmd_git_branch", { dir, ...args }), mutationFn: (args) => platform.rpc("cmd_git_branch", { dir, ...args }),
onSuccess, onSuccess,
}), }),
mergeBranch: createFastMutation<void, string, { branch: string }>({ mergeBranch: createFastMutation<void, string, { branch: string }>({
mutationKey: ["git", "merge", dir], mutationKey: ["git", "merge", dir],
mutationFn: (args) => invoke("cmd_git_merge_branch", { dir, ...args }), mutationFn: (args) => platform.rpc("cmd_git_merge_branch", { dir, ...args }),
onSuccess, onSuccess,
}), }),
deleteBranch: createFastMutation< deleteBranch: createFastMutation<
@@ -232,33 +230,33 @@ export const gitMutations = (dir: string, callbacks: GitCallbacks) => {
{ branch: string; force?: boolean } { branch: string; force?: boolean }
>({ >({
mutationKey: ["git", "delete-branch", dir], mutationKey: ["git", "delete-branch", dir],
mutationFn: (args) => invoke("cmd_git_delete_branch", { dir, ...args }), mutationFn: (args) => platform.rpc("cmd_git_delete_branch", { dir, ...args }),
onSuccess, onSuccess,
}), }),
deleteRemoteBranch: createFastMutation<void, string, { branch: string }>({ deleteRemoteBranch: createFastMutation<void, string, { branch: string }>({
mutationKey: ["git", "delete-remote-branch", dir], mutationKey: ["git", "delete-remote-branch", dir],
mutationFn: (args) => invoke("cmd_git_delete_remote_branch", { dir, ...args }), mutationFn: (args) => platform.rpc("cmd_git_delete_remote_branch", { dir, ...args }),
onSuccess, onSuccess,
}), }),
renameBranch: createFastMutation<void, string, { oldName: string; newName: string }>({ renameBranch: createFastMutation<void, string, { oldName: string; newName: string }>({
mutationKey: ["git", "rename-branch", dir], mutationKey: ["git", "rename-branch", dir],
mutationFn: (args) => invoke("cmd_git_rename_branch", { dir, ...args }), mutationFn: (args) => platform.rpc("cmd_git_rename_branch", { dir, ...args }),
onSuccess, onSuccess,
}), }),
checkout: createFastMutation<string, string, { branch: string; force: boolean }>({ checkout: createFastMutation<string, string, { branch: string; force: boolean }>({
mutationKey: ["git", "checkout", dir], mutationKey: ["git", "checkout", dir],
mutationFn: (args) => invoke("cmd_git_checkout", { dir, ...args }), mutationFn: (args) => platform.rpc("cmd_git_checkout", { dir, ...args }),
onSuccess, onSuccess,
}), }),
commit: createFastMutation<void, string, { message: string }>({ commit: createFastMutation<void, string, { message: string }>({
mutationKey: ["git", "commit", dir], mutationKey: ["git", "commit", dir],
mutationFn: (args) => invoke("cmd_git_commit", { dir, ...args }), mutationFn: (args) => platform.rpc("cmd_git_commit", { dir, ...args }),
onSuccess, onSuccess,
}), }),
commitAndPush: createFastMutation<PushResult, string, { message: string }>({ commitAndPush: createFastMutation<PushResult, string, { message: string }>({
mutationKey: ["git", "commit_push", dir], mutationKey: ["git", "commit_push", dir],
mutationFn: async (args) => { mutationFn: async (args) => {
await invoke("cmd_git_commit", { dir, ...args }); await platform.rpc("cmd_git_commit", { dir, ...args });
return push(); return push();
}, },
onSuccess, onSuccess,
@@ -272,20 +270,20 @@ export const gitMutations = (dir: string, callbacks: GitCallbacks) => {
pull: createFastMutation<PullResult, string, void>({ pull: createFastMutation<PullResult, string, void>({
mutationKey: ["git", "pull", dir], mutationKey: ["git", "pull", dir],
async mutationFn() { async mutationFn() {
const result = await invoke<PullResult>("cmd_git_pull", { dir }); const result = await platform.rpc<PullResult>("cmd_git_pull", { dir });
if (result.type === "needs_credentials") { if (result.type === "needs_credentials") {
const creds = await callbacks.promptCredentials(result); const creds = await callbacks.promptCredentials(result);
if (creds == null) throw new Error("Canceled"); if (creds == null) throw new Error("Canceled");
await invoke("cmd_git_add_credential", { await platform.rpc("cmd_git_add_credential", {
remoteUrl: result.url, remoteUrl: result.url,
username: creds.username, username: creds.username,
password: creds.password, password: creds.password,
}); });
// Pull again after credentials // Pull again after credentials
return invoke<PullResult>("cmd_git_pull", { dir }); return platform.rpc<PullResult>("cmd_git_pull", { dir });
} }
if (result.type === "uncommitted_changes") { if (result.type === "uncommitted_changes") {
@@ -294,8 +292,8 @@ export const gitMutations = (dir: string, callbacks: GitCallbacks) => {
.then(async (strategy) => { .then(async (strategy) => {
if (strategy === "cancel") return; if (strategy === "cancel") return;
await invoke("cmd_git_reset_changes", { dir }); await platform.rpc("cmd_git_reset_changes", { dir });
return invoke<PullResult>("cmd_git_pull", { dir }); return platform.rpc<PullResult>("cmd_git_pull", { dir });
}) })
.then(async () => { .then(async () => {
await onSuccess(); await onSuccess();
@@ -310,14 +308,14 @@ export const gitMutations = (dir: string, callbacks: GitCallbacks) => {
if (strategy === "cancel") return; if (strategy === "cancel") return;
if (strategy === "force_reset") { if (strategy === "force_reset") {
return invoke<PullResult>("cmd_git_pull_force_reset", { return platform.rpc<PullResult>("cmd_git_pull_force_reset", {
dir, dir,
remote: result.remote, remote: result.remote,
branch: result.branch, branch: result.branch,
}); });
} }
return invoke<PullResult>("cmd_git_pull_merge", { return platform.rpc<PullResult>("cmd_git_pull_merge", {
dir, dir,
remote: result.remote, remote: result.remote,
branch: result.branch, branch: result.branch,
@@ -335,17 +333,17 @@ export const gitMutations = (dir: string, callbacks: GitCallbacks) => {
}), }),
unstage: createFastMutation<void, string, { relaPaths: string[] }>({ unstage: createFastMutation<void, string, { relaPaths: string[] }>({
mutationKey: ["git", "unstage", dir], mutationKey: ["git", "unstage", dir],
mutationFn: (args) => invoke("cmd_git_unstage", { dir, ...args }), mutationFn: (args) => platform.rpc("cmd_git_unstage", { dir, ...args }),
onSuccess, onSuccess,
}), }),
resetChanges: createFastMutation<void, string, void>({ resetChanges: createFastMutation<void, string, void>({
mutationKey: ["git", "reset-changes", dir], mutationKey: ["git", "reset-changes", dir],
mutationFn: () => invoke("cmd_git_reset_changes", { dir }), mutationFn: () => platform.rpc("cmd_git_reset_changes", { dir }),
onSuccess, onSuccess,
}), }),
restore: createFastMutation<void, string, { relaPaths: string[] }>({ restore: createFastMutation<void, string, { relaPaths: string[] }>({
mutationKey: ["git", "restore", dir], mutationKey: ["git", "restore", dir],
mutationFn: (args) => invoke("cmd_git_restore_files", { dir, ...args }), mutationFn: (args) => platform.rpc("cmd_git_restore_files", { dir, ...args }),
onSuccess, onSuccess,
}), }),
restoreFileFromCommit: createFastMutation< restoreFileFromCommit: createFastMutation<
@@ -354,18 +352,18 @@ export const gitMutations = (dir: string, callbacks: GitCallbacks) => {
{ commitOid: string; relaPath: string } { commitOid: string; relaPath: string }
>({ >({
mutationKey: ["git", "restore-file-from-commit", dir], mutationKey: ["git", "restore-file-from-commit", dir],
mutationFn: (args) => invoke("cmd_git_restore_file_from_commit", { dir, ...args }), mutationFn: (args) => platform.rpc("cmd_git_restore_file_from_commit", { dir, ...args }),
onSuccess, onSuccess,
}), }),
} as const; } as const;
}; };
async function getRemotes(dir: string) { async function getRemotes(dir: string) {
return invoke<GitRemote[]>("cmd_git_remotes", { dir }); return platform.rpc<GitRemote[]>("cmd_git_remotes", { dir });
} }
function unlistenGitWatcher(unlistenEvent: string) { function unlistenGitWatcher(unlistenEvent: string) {
void emit(unlistenEvent).then(() => { void platform.emit(unlistenEvent).then(() => {
removeGitWatchKey(unlistenEvent); removeGitWatchKey(unlistenEvent);
}); });
} }
@@ -404,7 +402,7 @@ export async function gitClone(
error: string | null; error: string | null;
}) => Promise<GitCredentials | null>, }) => Promise<GitCredentials | null>,
): Promise<CloneResult> { ): Promise<CloneResult> {
const result = await invoke<CloneResult>("cmd_git_clone", { url, dir }); const result = await platform.rpc<CloneResult>("cmd_git_clone", { url, dir });
if (result.type !== "needs_credentials") return result; if (result.type !== "needs_credentials") return result;
// Prompt for credentials // Prompt for credentials
@@ -415,11 +413,11 @@ export async function gitClone(
if (creds == null) return { type: "cancelled" }; if (creds == null) return { type: "cancelled" };
// Store credentials and retry // Store credentials and retry
await invoke("cmd_git_add_credential", { await platform.rpc("cmd_git_add_credential", {
remoteUrl: result.url, remoteUrl: result.url,
username: creds.username, username: creds.username,
password: creds.password, password: creds.password,
}); });
return invoke<CloneResult>("cmd_git_clone", { url, dir }); return platform.rpc<CloneResult>("cmd_git_clone", { url, dir });
} }
+32 -35
View File
@@ -1,5 +1,4 @@
import { invoke } from "@tauri-apps/api/core"; import { platform } from "@yaakapp-internal/platform";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import { debounce } from "@yaakapp-internal/lib"; import { debounce } from "@yaakapp-internal/lib";
import { AnyModel, ModelPayload } from "../bindings/gen_models"; import { AnyModel, ModelPayload } from "../bindings/gen_models";
import { modelStoreDataAtom } from "./atoms"; import { modelStoreDataAtom } from "./atoms";
@@ -16,37 +15,35 @@ export function initModelStore(store: JotaiStore) {
// Don't lose debounced patches if the window closes while one is pending // Don't lose debounced patches if the window closes while one is pending
window.addEventListener("beforeunload", flushAllPendingPatches); window.addEventListener("beforeunload", flushAllPendingPatches);
getCurrentWebviewWindow() platform.listen<ModelPayload[]>("model_writes", (payloads) => {
.listen<ModelPayload[]>("model_writes", ({ payload: payloads }) => { mustStore().set(modelStoreDataAtom, (prev: ModelStoreData) => {
mustStore().set(modelStoreDataAtom, (prev: ModelStoreData) => { // Apply the entire batch in one update, cloning each touched bucket only
// Apply the entire batch in one update, cloning each touched bucket only // once. Bulk writes (imports, sync, CLI) can carry hundreds of models.
// once. Bulk writes (imports, sync, CLI) can carry hundreds of models. const next = { ...prev };
const next = { ...prev }; const clonedBuckets = new Set<AnyModel["model"]>();
const clonedBuckets = new Set<AnyModel["model"]>(); let changed = false;
let changed = false;
for (const payload of payloads) { for (const payload of payloads) {
if (shouldIgnoreModel(payload)) continue; if (shouldIgnoreModel(payload)) continue;
if (isUnsafeObjectKey(payload.model.model)) continue; if (isUnsafeObjectKey(payload.model.model)) continue;
if (isUnsafeObjectKey(payload.model.id)) continue; if (isUnsafeObjectKey(payload.model.id)) continue;
if (payload.change.type === "upsert") { if (payload.change.type === "upsert") {
const modelType = payload.model.model; const modelType = payload.model.model;
if (!clonedBuckets.has(modelType)) { if (!clonedBuckets.has(modelType)) {
next[modelType] = { ...next[modelType] } as never; next[modelType] = { ...next[modelType] } as never;
clonedBuckets.add(modelType); clonedBuckets.add(modelType);
}
(next[modelType] as Record<string, AnyModel>)[payload.model.id] = payload.model;
changed = true;
} else {
changed = applyModelDelete(next, clonedBuckets, payload.model) || changed;
} }
(next[modelType] as Record<string, AnyModel>)[payload.model.id] = payload.model;
changed = true;
} else {
changed = applyModelDelete(next, clonedBuckets, payload.model) || changed;
} }
}
return changed ? next : prev; return changed ? next : prev;
}); });
}) });
.catch(console.error);
} }
/** /**
@@ -211,7 +208,7 @@ let _activeWorkspaceId: string | null = null;
export async function changeModelStoreWorkspace(workspaceId: string | null) { export async function changeModelStoreWorkspace(workspaceId: string | null) {
console.log("Syncing models with new workspace", workspaceId); console.log("Syncing models with new workspace", workspaceId);
const workspaceModelsStr = await invoke<string>("models_workspace_models", { const workspaceModelsStr = await platform.rpc<string>("models_workspace_models", {
workspaceId, // NOTE: if no workspace id provided, it will just fetch global models workspaceId, // NOTE: if no workspace id provided, it will just fetch global models
}); });
const workspaceModels = JSON.parse(workspaceModelsStr) as AnyModel[]; const workspaceModels = JSON.parse(workspaceModelsStr) as AnyModel[];
@@ -288,7 +285,7 @@ export async function patchModel<M extends AnyModel["model"], T extends ExtractM
export async function updateModel<M extends AnyModel["model"], T extends ExtractModel<AnyModel, M>>( export async function updateModel<M extends AnyModel["model"], T extends ExtractModel<AnyModel, M>>(
model: T, model: T,
): Promise<string> { ): Promise<string> {
return trackModelWrite(invoke<string>("models_upsert", { model })); return trackModelWrite(platform.rpc<string>("models_upsert", { model }));
} }
export async function deleteModelById< export async function deleteModelById<
@@ -305,7 +302,7 @@ export async function deleteModel<M extends AnyModel["model"], T extends Extract
if (model == null) { if (model == null) {
throw new Error("Failed to delete null model"); throw new Error("Failed to delete null model");
} }
await trackModelWrite(invoke<string>("models_delete", { model })); await trackModelWrite(platform.rpc<string>("models_delete", { model }));
// Apply the delete locally right away so callers can rely on the store once the // Apply the delete locally right away so callers can rely on the store once the
// promise resolves. The backend echo arrives async, so anything that reads the // promise resolves. The backend echo arrives async, so anything that reads the
@@ -331,20 +328,20 @@ export async function duplicateModel<
await flushAllModelWrites(); await flushAllModelWrites();
return trackModelWrite( return trackModelWrite(
invoke<string>("models_duplicate", { modelType: model.model, modelId: model.id }), platform.rpc<string>("models_duplicate", { modelType: model.model, modelId: model.id }),
); );
} }
export async function createGlobalModel<T extends Exclude<AnyModel, { workspaceId: string }>>( export async function createGlobalModel<T extends Exclude<AnyModel, { workspaceId: string }>>(
patch: Partial<T> & Pick<T, "model">, patch: Partial<T> & Pick<T, "model">,
): Promise<string> { ): Promise<string> {
return trackModelWrite(invoke<string>("models_upsert", { model: patch })); return trackModelWrite(platform.rpc<string>("models_upsert", { model: patch }));
} }
export async function createWorkspaceModel<T extends Extract<AnyModel, { workspaceId: string }>>( export async function createWorkspaceModel<T extends Extract<AnyModel, { workspaceId: string }>>(
patch: Partial<T> & Pick<T, "model" | "workspaceId">, patch: Partial<T> & Pick<T, "model" | "workspaceId">,
): Promise<string> { ): Promise<string> {
return trackModelWrite(invoke<string>("models_upsert", { model: patch })); return trackModelWrite(platform.rpc<string>("models_upsert", { model: patch }));
} }
export function replaceModelsInStore< export function replaceModelsInStore<
@@ -399,7 +396,7 @@ function shouldIgnoreModel({ model, updateSource }: ModelPayload) {
} }
// Never ignore same-window updates // Never ignore same-window updates
if (updateSource.label === getCurrentWebviewWindow().label) { if (updateSource.label === platform.window.label) {
return false; return false;
} }
+7 -7
View File
@@ -1,4 +1,4 @@
import { invoke } from "@tauri-apps/api/core"; import { platform } from "@yaakapp-internal/platform";
import { PluginNameVersion, PluginSearchResponse, PluginUpdatesResponse } from "./bindings/gen_api"; import { PluginNameVersion, PluginSearchResponse, PluginUpdatesResponse } from "./bindings/gen_api";
export * from "./bindings/gen_models"; export * from "./bindings/gen_models";
@@ -6,25 +6,25 @@ export * from "./bindings/gen_events";
export * from "./bindings/gen_search"; export * from "./bindings/gen_search";
export async function searchPlugins(query: string) { export async function searchPlugins(query: string) {
return invoke<PluginSearchResponse>("cmd_plugins_search", { query }); return platform.rpc<PluginSearchResponse>("cmd_plugins_search", { query });
} }
export async function installPlugin(name: string, version: string | null) { export async function installPlugin(name: string, version: string | null) {
return invoke<void>("cmd_plugins_install", { name, version }); return platform.rpc<void>("cmd_plugins_install", { name, version });
} }
export async function uninstallPlugin(pluginId: string) { export async function uninstallPlugin(pluginId: string) {
return invoke<void>("cmd_plugins_uninstall", { pluginId }); return platform.rpc<void>("cmd_plugins_uninstall", { pluginId });
} }
export async function checkPluginUpdates() { export async function checkPluginUpdates() {
return invoke<PluginUpdatesResponse>("cmd_plugins_updates", {}); return platform.rpc<PluginUpdatesResponse>("cmd_plugins_updates", {});
} }
export async function updateAllPlugins() { export async function updateAllPlugins() {
return invoke<PluginNameVersion[]>("cmd_plugins_update_all", {}); return platform.rpc<PluginNameVersion[]>("cmd_plugins_update_all", {});
} }
export async function installPluginFromDirectory(directory: string) { export async function installPluginFromDirectory(directory: string) {
return invoke<void>("cmd_plugins_install_from_directory", { directory }); return platform.rpc<void>("cmd_plugins_install_from_directory", { directory });
} }
+10 -13
View File
@@ -1,5 +1,4 @@
import { Channel, invoke } from "@tauri-apps/api/core"; import { platform } from "@yaakapp-internal/platform";
import { emit } from "@tauri-apps/api/event";
import type { WatchResult } from "@yaakapp-internal/tauri-client"; import type { WatchResult } from "@yaakapp-internal/tauri-client";
import { SyncOp } from "./bindings/gen_sync"; import { SyncOp } from "./bindings/gen_sync";
import { WatchEvent } from "./bindings/gen_watch"; import { WatchEvent } from "./bindings/gen_watch";
@@ -7,18 +6,18 @@ import { WatchEvent } from "./bindings/gen_watch";
export * from "./bindings/gen_models"; export * from "./bindings/gen_models";
export async function calculateSync(workspaceId: string, syncDir: string) { export async function calculateSync(workspaceId: string, syncDir: string) {
return invoke<SyncOp[]>("cmd_sync_calculate", { return platform.rpc<SyncOp[]>("cmd_sync_calculate", {
workspaceId, workspaceId,
syncDir, syncDir,
}); });
} }
export async function calculateSyncFsOnly(dir: string) { export async function calculateSyncFsOnly(dir: string) {
return invoke<SyncOp[]>("cmd_sync_calculate_fs", { dir }); return platform.rpc<SyncOp[]>("cmd_sync_calculate_fs", { dir });
} }
export async function applySync(workspaceId: string, syncDir: string, syncOps: SyncOp[]) { export async function applySync(workspaceId: string, syncDir: string, syncOps: SyncOp[]) {
return invoke<void>("cmd_sync_apply", { return platform.rpc<void>("cmd_sync_apply", {
workspaceId, workspaceId,
syncDir, syncDir,
syncOps: syncOps, syncOps: syncOps,
@@ -31,13 +30,11 @@ export function watchWorkspaceFiles(
callback: (e: WatchEvent) => void, callback: (e: WatchEvent) => void,
) { ) {
console.log("Watching workspace files", workspaceId, syncDir); console.log("Watching workspace files", workspaceId, syncDir);
const channel = new Channel<WatchEvent>(); const unlistenPromise = platform.rpcStream<WatchResult, WatchEvent>(
channel.onmessage = callback; "cmd_sync_watch",
const unlistenPromise = invoke<WatchResult>("cmd_sync_watch", { { workspaceId, syncDir },
workspaceId, callback,
syncDir, );
channel,
});
void unlistenPromise.then(({ unlistenEvent }) => { void unlistenPromise.then(({ unlistenEvent }) => {
addWatchKey(unlistenEvent); addWatchKey(unlistenEvent);
@@ -53,7 +50,7 @@ export function watchWorkspaceFiles(
} }
function unlistenToWatcher(unlistenEvent: string) { function unlistenToWatcher(unlistenEvent: string) {
void emit(unlistenEvent).then(() => { void platform.emit(unlistenEvent).then(() => {
removeWatchKey(unlistenEvent); removeWatchKey(unlistenEvent);
}); });
} }
+5 -5
View File
@@ -1,8 +1,8 @@
import { invoke } from "@tauri-apps/api/core"; import { platform } from "@yaakapp-internal/platform";
import { WebsocketConnection } from "@yaakapp-internal/models"; import { WebsocketConnection } from "@yaakapp-internal/models";
export function deleteWebsocketConnections(requestId: string) { export function deleteWebsocketConnections(requestId: string) {
return invoke("cmd_ws_delete_connections", { return platform.rpc("cmd_ws_delete_connections", {
requestId, requestId,
}); });
} }
@@ -16,7 +16,7 @@ export function connectWebsocket({
environmentId: string | null; environmentId: string | null;
cookieJarId: string | null; cookieJarId: string | null;
}) { }) {
return invoke("cmd_ws_connect", { return platform.rpc("cmd_ws_connect", {
requestId, requestId,
environmentId, environmentId,
cookieJarId, cookieJarId,
@@ -24,7 +24,7 @@ export function connectWebsocket({
} }
export function closeWebsocket({ connectionId }: { connectionId: string }) { export function closeWebsocket({ connectionId }: { connectionId: string }) {
return invoke("cmd_ws_close", { return platform.rpc("cmd_ws_close", {
connectionId, connectionId,
}); });
} }
@@ -36,7 +36,7 @@ export function sendWebsocket({
connectionId: string; connectionId: string;
environmentId: string | null; environmentId: string | null;
}) { }) {
return invoke("cmd_ws_send", { return platform.rpc("cmd_ws_send", {
connectionId, connectionId,
environmentId, environmentId,
}); });
+17
View File
@@ -13,6 +13,7 @@
"packages/tailwind-config", "packages/tailwind-config",
"packages/model-store", "packages/model-store",
"packages/common-lib", "packages/common-lib",
"packages/platform",
"packages/plugin-runtime", "packages/plugin-runtime",
"packages/plugin-runtime-types", "packages/plugin-runtime-types",
"plugins-external/mcp-server", "plugins-external/mcp-server",
@@ -5736,6 +5737,10 @@
"resolved": "crates/yaak-models", "resolved": "crates/yaak-models",
"link": true "link": true
}, },
"node_modules/@yaakapp-internal/platform": {
"resolved": "packages/platform",
"link": true
},
"node_modules/@yaakapp-internal/plugin-runtime": { "node_modules/@yaakapp-internal/plugin-runtime": {
"resolved": "packages/plugin-runtime", "resolved": "packages/plugin-runtime",
"link": true "link": true
@@ -17175,6 +17180,18 @@
"jotai": "^2.18.0" "jotai": "^2.18.0"
} }
}, },
"packages/platform": {
"name": "@yaakapp-internal/platform",
"version": "1.0.0",
"dependencies": {
"@tauri-apps/api": "^2.11.0",
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
"@tauri-apps/plugin-dialog": "^2.7.1",
"@tauri-apps/plugin-fs": "^2.5.1",
"@tauri-apps/plugin-opener": "^2.5.4",
"@tauri-apps/plugin-os": "^2.3.2"
}
},
"packages/plugin-runtime": { "packages/plugin-runtime": {
"name": "@yaakapp-internal/plugin-runtime", "name": "@yaakapp-internal/plugin-runtime",
"dependencies": { "dependencies": {
+1
View File
@@ -12,6 +12,7 @@
"packages/tailwind-config", "packages/tailwind-config",
"packages/model-store", "packages/model-store",
"packages/common-lib", "packages/common-lib",
"packages/platform",
"packages/plugin-runtime", "packages/plugin-runtime",
"packages/plugin-runtime-types", "packages/plugin-runtime-types",
"plugins-external/mcp-server", "plugins-external/mcp-server",
+19
View File
@@ -0,0 +1,19 @@
{
"name": "@yaakapp-internal/platform",
"version": "1.0.0",
"private": true,
"type": "module",
"main": "src/index.ts",
"types": "src/index.ts",
"scripts": {
"lint": "tsc --noEmit"
},
"dependencies": {
"@tauri-apps/api": "^2.11.0",
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
"@tauri-apps/plugin-dialog": "^2.7.1",
"@tauri-apps/plugin-fs": "^2.5.1",
"@tauri-apps/plugin-opener": "^2.5.4",
"@tauri-apps/plugin-os": "^2.3.2"
}
}
+14
View File
@@ -0,0 +1,14 @@
import { platform } from "./registry";
import type { CapabilityName } from "./types";
/**
* Whether the current host supports a feature.
*
* A hook so components can gate on it directly, and so this can become reactive
* later — a browser tab gains and loses capabilities when the local bridge comes
* and goes. Capabilities are fixed for the life of the desktop app, so today it
* is a plain read.
*/
export function useCapability(name: CapabilityName): boolean {
return platform.capabilities[name];
}
+15
View File
@@ -0,0 +1,15 @@
import { setPlatform } from "./registry";
import { createTauriPlatform } from "./tauri";
// Desktop is the only host today, so it is installed unconditionally and
// synchronously — several modules call commands while the module graph is still
// evaluating, so there is no later moment to do this in.
//
// This line is the swap point. A browser build selects its own host here, and
// because nothing else in the app imports a host directly, that is the whole
// change.
setPlatform(createTauriPlatform());
export * from "./capabilities";
export { platform, setPlatform } from "./registry";
export * from "./types";
+60
View File
@@ -0,0 +1,60 @@
import type { Platform } from "./types";
let installed: Platform | null = null;
/**
* Install the host implementation. Called once at import time by this package's
* entry point, before any consumer's module body runs.
*
* It stays swappable at runtime because a browser build has to choose between
* talking to a local bridge and running in-page, and it can only find out which
* after it has tried to reach the bridge.
*/
export function setPlatform(next: Platform): void {
installed = next;
}
function host(): Platform {
if (installed == null) {
throw new Error("No platform installed. Import @yaakapp-internal/platform before using it.");
}
return installed;
}
/**
* The host, for everyone else to use.
*
* This is a fixed object that forwards to whatever is installed, so modules can
* import it at any time — including at module scope, which several boot-time
* modules do — without capturing a stale implementation.
*
* A host that has to connect before it can work (a WebSocket to the bridge)
* buffers inside its own `rpc`; nothing here waits on a connection, because the
* app's boot sequence calls commands while the module graph is still evaluating
* and cannot be made to wait.
*/
export const platform: Platform = {
get capabilities() {
return host().capabilities;
},
get window() {
return host().window;
},
get clipboard() {
return host().clipboard;
},
get dialog() {
return host().dialog;
},
get files() {
return host().files;
},
rpc: (cmd, payload) => host().rpc(cmd, payload),
rpcStream: (cmd, payload, onMessage) => host().rpcStream(cmd, payload, onMessage),
listen: (event, callback) => host().listen(event, callback),
emit: (event, payload) => host().emit(event, payload),
openUrl: (url) => host().openUrl(url),
revealItemInDir: (path) => host().revealItemInDir(path),
osType: () => host().osType(),
appIdentifier: () => host().appIdentifier(),
};

Some files were not shown because too many files have changed in this diff Show More