diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 479ee0f7..fb1b9dc0 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1220,6 +1220,7 @@ async fn cmd_create_cookie_jar( #[tauri::command] async fn cmd_create_environment( workspace_id: &str, + environment_id: Option<&str>, name: &str, variables: Vec, w: WebviewWindow, @@ -1228,6 +1229,7 @@ async fn cmd_create_environment( &w, Environment { workspace_id: workspace_id.to_string(), + environment_id: environment_id.map(|s| s.to_string()), name: name.to_string(), variables, ..Default::default() diff --git a/src-tauri/vendored/plugins/importer-insomnia/build/index.js b/src-tauri/vendored/plugins/importer-insomnia/build/index.js index e89c90db..a09e9f14 100644 --- a/src-tauri/vendored/plugins/importer-insomnia/build/index.js +++ b/src-tauri/vendored/plugins/importer-insomnia/build/index.js @@ -7233,12 +7233,14 @@ function pluginHookImport(ctx, contents) { }; const workspacesToImport = parsed.resources.filter(isWorkspace); for (const workspaceToImport of workspacesToImport) { + console.log("IMPORT WORKSPACE", workspaceToImport); resources.workspaces.push({ id: convertId(workspaceToImport._id), createdAt: new Date(workspacesToImport.created ?? Date.now()).toISOString().replace("Z", ""), updatedAt: new Date(workspacesToImport.updated ?? Date.now()).toISOString().replace("Z", ""), model: "workspace", - name: workspaceToImport.name + name: workspaceToImport.name, + description: workspacesToImport.description }); const environmentsToImport = parsed.resources.filter( (r) => isEnvironment(r) @@ -7294,6 +7296,7 @@ function importFolder(f, workspaceId) { updatedAt: new Date(f.updated ?? Date.now()).toISOString().replace("Z", ""), folderId: f.parentId === workspaceId ? null : convertId(f.parentId), workspaceId: convertId(workspaceId), + description: f.description ?? null, model: "folder", name: f.name }; @@ -7311,6 +7314,7 @@ function importGrpcRequest(r, workspaceId, sortPriority = 0) { model: "grpc_request", sortPriority, name: r.name, + description: r.description ?? null, url: convertSyntax(r.url), service, method, @@ -7377,6 +7381,7 @@ function importHttpRequest(r, workspaceId, sortPriority = 0) { model: "http_request", sortPriority, name: r.name, + description: r.description ?? null, url: convertSyntax(r.url), body, bodyType, diff --git a/src-tauri/vendored/plugins/importer-openapi/build/index.js b/src-tauri/vendored/plugins/importer-openapi/build/index.js index aad8421e..c7f3ae2e 100644 --- a/src-tauri/vendored/plugins/importer-openapi/build/index.js +++ b/src-tauri/vendored/plugins/importer-openapi/build/index.js @@ -145931,6 +145931,7 @@ function pluginHookImport(_ctx, contents) { workspaceId: workspace.id, folderId, name: v.name, + description: v.description, method: r.method || "GET", url, urlParameters, diff --git a/src-tauri/vendored/plugins/importer-postman/build/index.js b/src-tauri/vendored/plugins/importer-postman/build/index.js index c3dd59a8..1325118d 100644 --- a/src-tauri/vendored/plugins/importer-postman/build/index.js +++ b/src-tauri/vendored/plugins/importer-postman/build/index.js @@ -98,6 +98,7 @@ function pluginHookImport(_ctx, contents) { workspaceId: workspace.id, folderId, name: v.name, + description: v.description, method: r.method || "GET", url, urlParameters, diff --git a/src-tauri/yaak_models/src/queries.rs b/src-tauri/yaak_models/src/queries.rs index 51acd3db..52af6b9a 100644 --- a/src-tauri/yaak_models/src/queries.rs +++ b/src-tauri/yaak_models/src/queries.rs @@ -152,6 +152,7 @@ pub async fn list_workspaces(mgr: &impl Manager) -> Result( .from(EnvironmentIden::Table) .cond_where(Expr::col(EnvironmentIden::WorkspaceId).eq(workspace_id)) .column(Asterisk) - .order_by(EnvironmentIden::CreatedAt, Order::Desc) + .order_by(EnvironmentIden::Name, Order::Asc) .build_rusqlite(SqliteQueryBuilder); let mut stmt = db.prepare(sql.as_str())?; let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; @@ -757,6 +758,7 @@ pub async fn list_environments( environments.iter().find(|e| e.environment_id == None && e.workspace_id == workspace_id); if let None = base_environment { + info!("Creating base environment for {workspace_id}"); let base_environment = upsert_environment( window, Environment { @@ -766,7 +768,6 @@ pub async fn list_environments( }, ) .await?; - info!("Created base environment for {workspace_id}"); environments.push(base_environment); } diff --git a/src-web/components/EnvironmentActionsDropdown.tsx b/src-web/components/EnvironmentActionsDropdown.tsx index f33e0700..5794bfc0 100644 --- a/src-web/components/EnvironmentActionsDropdown.tsx +++ b/src-web/components/EnvironmentActionsDropdown.tsx @@ -41,9 +41,9 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo leftSlot: e.id === activeEnvironment?.id ? : , onSelect: async () => { if (e.id !== activeEnvironment?.id) { - setActiveEnvironmentId(e.id); + await setActiveEnvironmentId(e.id); } else { - setActiveEnvironmentId(null); + await setActiveEnvironmentId(null); } }, }), diff --git a/src-web/components/EnvironmentEditDialog.tsx b/src-web/components/EnvironmentEditDialog.tsx index bb6fb428..2c3f2b0a 100644 --- a/src-web/components/EnvironmentEditDialog.tsx +++ b/src-web/components/EnvironmentEditDialog.tsx @@ -40,7 +40,8 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) { const selectedEnvironment = allEnvironments.find((e) => e.id === selectedEnvironmentId); const handleCreateEnvironment = async () => { - const e = await createEnvironment.mutateAsync(); + if (baseEnvironment == null) return; + const e = await createEnvironment.mutateAsync(baseEnvironment); if (e == null) return; setSelectedEnvironmentId(e.id); }; diff --git a/src-web/components/ExportDataDialog.tsx b/src-web/components/ExportDataDialog.tsx index e28408a0..1219de7d 100644 --- a/src-web/components/ExportDataDialog.tsx +++ b/src-web/components/ExportDataDialog.tsx @@ -1,7 +1,9 @@ import { save } from '@tauri-apps/plugin-dialog'; +import type { Workspace } from '@yaakapp-internal/models'; import { useCallback, useMemo, useState } from 'react'; import slugify from 'slugify'; -import type { Workspace } from '@yaakapp-internal/models'; +import { useActiveWorkspace } from '../hooks/useActiveWorkspace'; +import { useWorkspaces } from '../hooks/useWorkspaces'; import { count } from '../lib/pluralize'; import { invokeCmd } from '../lib/tauri'; import { Button } from './core/Button'; @@ -11,16 +13,32 @@ import { HStack, VStack } from './core/Stacks'; interface Props { onHide: () => void; onSuccess: (path: string) => void; - activeWorkspace: Workspace; - workspaces: Workspace[]; } -export function ExportDataDialog({ +export function ExportDataDialog({ onHide, onSuccess }: Props) { + const allWorkspaces = useWorkspaces(); + const activeWorkspace = useActiveWorkspace(); + if (activeWorkspace == null || allWorkspaces.length === 0) return null; + + return ( + + ); +} + +function ExportDataDialogContent({ onHide, onSuccess, activeWorkspace, - workspaces: allWorkspaces, -}: Props) { + allWorkspaces, +}: Props & { + allWorkspaces: Workspace[]; + activeWorkspace: Workspace; +}) { const [selectedWorkspaces, setSelectedWorkspaces] = useState>({ [activeWorkspace.id]: true, }); diff --git a/src-web/components/WorkpaceSettingsDialog.tsx b/src-web/components/WorkpaceSettingsDialog.tsx index 41745f91..47889aa1 100644 --- a/src-web/components/WorkpaceSettingsDialog.tsx +++ b/src-web/components/WorkpaceSettingsDialog.tsx @@ -17,6 +17,7 @@ export function WorkspaceSettingsDialog({ workspaceId }: Props) { return ( + {workspace.id} [...workspaces].sort((a, b) => (a.name.localeCompare(b.name) > 0 ? 1 : -1)), + [workspaces], + ); + const { workspaceItems, extraItems } = useMemo<{ workspaceItems: RadioDropdownItem[]; extraItems: DropdownItem[]; }>(() => { - const workspaceItems: RadioDropdownItem[] = workspaces.map((w) => ({ + const workspaceItems: RadioDropdownItem[] = orderedWorkspaces.map((w) => ({ key: w.id, label: w.name, value: w.id, @@ -84,13 +89,13 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({ return { workspaceItems, extraItems }; }, [ - activeWorkspace, + activeWorkspace?.id, activeWorkspaceId, createWorkspace.mutate, deleteSendHistory.mutate, deleteWorkspace.mutate, dialog, - workspaces, + orderedWorkspaces, ]); const handleChange = useCallback( diff --git a/src-web/hooks/useActiveCookieJar.ts b/src-web/hooks/useActiveCookieJar.ts index ee087589..f6ee9d2a 100644 --- a/src-web/hooks/useActiveCookieJar.ts +++ b/src-web/hooks/useActiveCookieJar.ts @@ -1,4 +1,4 @@ -import { getRouteApi, useSearch } from '@tanstack/react-router'; +import { useNavigate, useSearch } from '@tanstack/react-router'; import { useCallback, useEffect, useMemo } from 'react'; import { useCookieJars } from './useCookieJars'; @@ -38,17 +38,15 @@ export function useEnsureActiveCookieJar() { }, [activeCookieJarId, cookieJars, setActiveCookieJarId]); } -const routeApi = getRouteApi('/workspaces/$workspaceId/'); - function useActiveCookieJarId() { // NOTE: This query param is accessed from Rust side, so do not change - const { cookieJarId: id } = useSearch({ strict: false }); - const navigate = routeApi.useNavigate(); + const { cookie_jar_id: id } = useSearch({ strict: false }); + const navigate = useNavigate({ from: '/workspaces/$workspaceId' }); const setId = useCallback( (id: string) => navigate({ - search: (prev) => ({ ...prev, cookieJarId: id }), + search: (prev) => ({ ...prev, cookie_jar_id: id }), }), [navigate], ); diff --git a/src-web/hooks/useActiveEnvironment.ts b/src-web/hooks/useActiveEnvironment.ts index e3f0ef05..d6957a1c 100644 --- a/src-web/hooks/useActiveEnvironment.ts +++ b/src-web/hooks/useActiveEnvironment.ts @@ -1,4 +1,4 @@ -import { getRouteApi, useSearch } from '@tanstack/react-router'; +import { useNavigate, useSearch } from '@tanstack/react-router'; import { useCallback } from 'react'; import { useEnvironments } from './useEnvironments'; @@ -11,17 +11,15 @@ export function useActiveEnvironment() { export const QUERY_ENVIRONMENT_ID = 'environment_id'; -const routeApi = getRouteApi('/workspaces/$workspaceId/'); - function useActiveEnvironmentId() { // NOTE: This query param is accessed from Rust side, so do not change - const { environmentId: id } = useSearch({ strict: false }); - const navigate = routeApi.useNavigate(); + const { environment_id: id} = useSearch({ strict: false }); + const navigate = useNavigate({ from: '/workspaces/$workspaceId' }); const setId = useCallback( - (environment_id: string | null) => + (environmentId: string | null) => navigate({ - search: (prev) => ({ ...prev, environment_id: environment_id ?? undefined }), + search: (prev) => ({ ...prev, environment_id: environmentId ?? undefined }), }), [navigate], ); diff --git a/src-web/hooks/useCreateEnvironment.ts b/src-web/hooks/useCreateEnvironment.ts index 3b7e56f0..cd2bae52 100644 --- a/src-web/hooks/useCreateEnvironment.ts +++ b/src-web/hooks/useCreateEnvironment.ts @@ -1,13 +1,13 @@ -import { useFastMutation } from './useFastMutation'; import type { Environment } from '@yaakapp-internal/models'; -import {useSetAtom} from "jotai"; +import { useSetAtom } from 'jotai'; import { trackEvent } from '../lib/analytics'; import { invokeCmd } from '../lib/tauri'; import { useActiveEnvironment } from './useActiveEnvironment'; import { useActiveWorkspace } from './useActiveWorkspace'; -import {environmentsAtom} from "./useEnvironments"; +import { environmentsAtom } from './useEnvironments'; +import { useFastMutation } from './useFastMutation'; import { usePrompt } from './usePrompt'; -import {updateModelList} from "./useSyncModelStores"; +import { updateModelList } from './useSyncModelStores'; export function useCreateEnvironment() { const [, setActiveEnvironmentId] = useActiveEnvironment(); @@ -15,9 +15,9 @@ export function useCreateEnvironment() { const workspace = useActiveWorkspace(); const setEnvironments = useSetAtom(environmentsAtom); - return useFastMutation({ + return useFastMutation({ mutationKey: ['create_environment'], - mutationFn: async () => { + mutationFn: async (baseEnvironment) => { const name = await prompt({ id: 'new-environment', title: 'New Environment', @@ -33,6 +33,7 @@ export function useCreateEnvironment() { name, variables: [], workspaceId: workspace?.id, + environmentId: baseEnvironment.id, }); }, onSettled: () => trackEvent('environment', 'create'), diff --git a/src-web/hooks/useEnvironments.ts b/src-web/hooks/useEnvironments.ts index da5aa590..15d4ca5e 100644 --- a/src-web/hooks/useEnvironments.ts +++ b/src-web/hooks/useEnvironments.ts @@ -7,9 +7,8 @@ export const environmentsAtom = atom([]); export function useEnvironments() { const allEnvironments = useAtomValue(environmentsAtom); const baseEnvironment = allEnvironments.find((e) => e.environmentId == null); - const subEnvironments = allEnvironments.filter( - (e) => e.environmentId === (baseEnvironment?.id ?? 'n/a'), - ); + const subEnvironments = + allEnvironments.filter((e) => e.environmentId === (baseEnvironment?.id ?? 'n/a')) ?? []; return { baseEnvironment, subEnvironments, allEnvironments } as const; } diff --git a/src-web/hooks/useExportData.tsx b/src-web/hooks/useExportData.tsx index 22580116..7c28b84d 100644 --- a/src-web/hooks/useExportData.tsx +++ b/src-web/hooks/useExportData.tsx @@ -29,8 +29,6 @@ export function useExportData() { render: ({ hide }) => ( { toast.show({ color: 'success', diff --git a/src-web/hooks/useFastMutation.ts b/src-web/hooks/useFastMutation.ts index ab32cf6a..4e4af412 100644 --- a/src-web/hooks/useFastMutation.ts +++ b/src-web/hooks/useFastMutation.ts @@ -1,5 +1,6 @@ import type { MutationKey } from '@tanstack/react-query'; import { useCallback } from 'react'; +import { useToast } from './useToast'; export function useFastMutation({ mutationKey, @@ -7,13 +8,17 @@ export function useFastMutation Promise; onSettled?: () => void; onError?: (err: TError) => void; onSuccess?: (data: TData) => void; + toastyError?: boolean; }) { + const toast = useToast(); + const mutateAsync = useCallback( async (variables: TVariables) => { try { @@ -22,8 +27,14 @@ export function useFastMutation { + if (workspaceId == null) { + return; + } (async function () { console.log('Syncing model stores', { workspaceId }); // Set the things we need first, first diff --git a/src-web/hooks/useUpdateEnvironment.ts b/src-web/hooks/useUpdateEnvironment.ts index a1e38cb1..8a1d9d0a 100644 --- a/src-web/hooks/useUpdateEnvironment.ts +++ b/src-web/hooks/useUpdateEnvironment.ts @@ -13,6 +13,7 @@ export function useUpdateEnvironment(id: string | null) { unknown, Partial | ((r: Environment) => Environment) >({ + toastyError: true, mutationKey: ['update_environment', id], mutationFn: async (v) => { const environment = await getEnvironment(id); diff --git a/src-web/lib/tauri.ts b/src-web/lib/tauri.ts index 82f99c3d..0538d893 100644 --- a/src-web/lib/tauri.ts +++ b/src-web/lib/tauri.ts @@ -80,5 +80,10 @@ type TauriCmd = export async function invokeCmd(cmd: TauriCmd, args?: InvokeArgs): Promise { // console.log('RUN COMMAND', cmd, args); - return invoke(cmd, args); + try { + return await invoke(cmd, args); + } catch (err) { + console.warn('Tauri command error', cmd, err); + throw err; + } } diff --git a/src-web/routes/workspaces/$workspaceId/index.tsx b/src-web/routes/workspaces/$workspaceId/index.tsx index 9435105b..66f9c63a 100644 --- a/src-web/routes/workspaces/$workspaceId/index.tsx +++ b/src-web/routes/workspaces/$workspaceId/index.tsx @@ -2,15 +2,15 @@ import { createFileRoute } from '@tanstack/react-router'; import { Workspace } from '../../../components/Workspace'; interface WorkspaceSearchSchema { - cookieJarId?: string | null; - environmentId?: string | null; + cookie_jar_id?: string | null; + environment_id?: string | null; } export const Route = createFileRoute('/workspaces/$workspaceId/')({ component: RouteComponent, validateSearch: (search: Record): WorkspaceSearchSchema => ({ - environmentId: search.environment_id as string, - cookieJarId: search.cookie_jar_id as string, + environment_id: search.environment_id as string, + cookie_jar_id: search.cookie_jar_id as string, }), });