diff --git a/src-web/components/GlobalHooks.tsx b/src-web/components/GlobalHooks.tsx index 3c2761d0..b32d8c54 100644 --- a/src-web/components/GlobalHooks.tsx +++ b/src-web/components/GlobalHooks.tsx @@ -29,8 +29,8 @@ import { useSyncWorkspaceChildModels } from '../hooks/useSyncWorkspaceChildModel import { useSyncWorkspaceRequestTitle } from '../hooks/useSyncWorkspaceRequestTitle'; import { useSyncZoomSetting } from '../hooks/useSyncZoomSetting'; import { useSubscribeTemplateFunctions } from '../hooks/useTemplateFunctions'; -import {useToast} from "../hooks/useToast"; import { useToggleCommandPalette } from '../hooks/useToggleCommandPalette'; +import { showToast } from '../lib/toast'; export function GlobalHooks() { useSyncModelStores(); @@ -58,9 +58,8 @@ export function GlobalHooks() { useEnsureActiveCookieJar(); // Listen for toasts - const toast = useToast(); useListenToTauriEvent('show_toast', (event) => { - toast.show({ ...event.payload }); + showToast({ ...event.payload }); }); // Trigger workspace sync operation when workspace files change diff --git a/src-web/components/MoveToWorkspaceDialog.tsx b/src-web/components/MoveToWorkspaceDialog.tsx index 7897ccb8..ca71d27c 100644 --- a/src-web/components/MoveToWorkspaceDialog.tsx +++ b/src-web/components/MoveToWorkspaceDialog.tsx @@ -1,11 +1,11 @@ import { useNavigate } from '@tanstack/react-router'; import type { GrpcRequest, HttpRequest } from '@yaakapp-internal/models'; import React, { useState } from 'react'; -import { useToast } from '../hooks/useToast'; import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest'; import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest'; import { useWorkspaces } from '../hooks/useWorkspaces'; import { fallbackRequestName } from '../lib/fallbackRequestName'; +import {showToast} from "../lib/toast"; import { Button } from './core/Button'; import { InlineCode } from './core/InlineCode'; import { Select } from './core/Select'; @@ -21,7 +21,6 @@ export function MoveToWorkspaceDialog({ onDone, request, activeWorkspaceId }: Pr const workspaces = useWorkspaces(); const updateHttpRequest = useUpdateAnyHttpRequest(); const updateGrpcRequest = useUpdateAnyGrpcRequest(); - const toast = useToast(); const navigate = useNavigate(); const [selectedWorkspaceId, setSelectedWorkspaceId] = useState(activeWorkspaceId); @@ -54,7 +53,7 @@ export function MoveToWorkspaceDialog({ onDone, request, activeWorkspaceId }: Pr // Hide after a moment, to give time for request to disappear setTimeout(onDone, 100); - toast.show({ + showToast({ id: 'workspace-moved', message: ( <> diff --git a/src-web/components/RequestPane.tsx b/src-web/components/RequestPane.tsx index 2a36a219..52d17b14 100644 --- a/src-web/components/RequestPane.tsx +++ b/src-web/components/RequestPane.tsx @@ -16,7 +16,6 @@ import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse'; import { useRequestEditor, useRequestEditorEvent } from '../hooks/useRequestEditor'; import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey'; import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest'; -import { useToast } from '../hooks/useToast'; import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest'; import { deepEqualAtom } from '../lib/atoms'; import { languageFromContentType } from '../lib/contentType'; @@ -35,6 +34,7 @@ import { BODY_TYPE_OTHER, BODY_TYPE_XML, } from '../lib/model_util'; +import { showToast } from '../lib/toast'; import { BasicAuth } from './BasicAuth'; import { BearerAuth } from './BearerAuth'; import { BinaryFileEditor } from './BinaryFileEditor'; @@ -123,8 +123,6 @@ export const RequestPane = memo(function RequestPane({ [activeRequest, updateRequestAsync], ); - const toast = useToast(); - const { urlParameterPairs, urlParametersKey } = useMemo(() => { const placeholderNames = Array.from(activeRequest.url.matchAll(/\/(:[^/]+)/g)).map( (m) => m[1] ?? '', @@ -182,7 +180,7 @@ export const RequestPane = memo(function RequestPane({ const showMethodToast = (newMethod: string) => { if (activeRequest.method.toLowerCase() === newMethod.toLowerCase()) return; - toast.show({ + showToast({ id: 'switched-method', message: ( <> @@ -276,7 +274,6 @@ export const RequestPane = memo(function RequestPane({ activeRequestId, handleContentTypeChange, numParams, - toast, updateRequest, updateRequestAsync, urlParameterPairs.length, diff --git a/src-web/components/Toasts.tsx b/src-web/components/Toasts.tsx index 05d70616..91db7767 100644 --- a/src-web/components/Toasts.tsx +++ b/src-web/components/Toasts.tsx @@ -1,7 +1,7 @@ import { AnimatePresence } from 'framer-motion'; import { useAtomValue } from 'jotai'; import React, { type ReactNode } from 'react'; -import { toastsAtom, useToast } from '../hooks/useToast'; +import { hideToast, toastsAtom } from '../lib/toast'; import { Toast, type ToastProps } from './core/Toast'; import { Portal } from './Portal'; @@ -18,7 +18,6 @@ export type PrivateToastEntry = ToastEntry & { }; function ToastInstance({ id, message, timeout, ...props }: PrivateToastEntry) { - const toast = useToast(); return ( toast.hide(id)} + onClose={() => hideToast(id)} > {message} diff --git a/src-web/hooks/useActiveWorkspaceChangedToast.tsx b/src-web/hooks/useActiveWorkspaceChangedToast.tsx index 5f290b7d..050590cd 100644 --- a/src-web/hooks/useActiveWorkspaceChangedToast.tsx +++ b/src-web/hooks/useActiveWorkspaceChangedToast.tsx @@ -1,10 +1,9 @@ import { useEffect, useState } from 'react'; import { InlineCode } from '../components/core/InlineCode'; import { useActiveWorkspace } from './useActiveWorkspace'; -import { useToast } from './useToast'; +import { showToast } from '../lib/toast'; export function useActiveWorkspaceChangedToast() { - const toast = useToast(); const activeWorkspace = useActiveWorkspace(); const [id, setId] = useState(activeWorkspace?.id ?? null); @@ -17,7 +16,7 @@ export function useActiveWorkspaceChangedToast() { // Don't notify on the first load if (id === null) return; - toast.show({ + showToast({ id: `workspace-changed-${activeWorkspace.id}`, timeout: 3000, message: ( @@ -27,5 +26,5 @@ export function useActiveWorkspaceChangedToast() { ), }); - }, [activeWorkspace, id, toast]); + }, [activeWorkspace, id]); } diff --git a/src-web/hooks/useCommands.ts b/src-web/hooks/useCommands.ts index b28d1bd8..d266fac3 100644 --- a/src-web/hooks/useCommands.ts +++ b/src-web/hooks/useCommands.ts @@ -6,7 +6,6 @@ import { invokeCmd } from '../lib/tauri'; import { getActiveWorkspaceId } from './useActiveWorkspace'; import { createFastMutation } from './useFastMutation'; import { usePrompt } from './usePrompt'; -import { useToast } from './useToast'; function makeCommands({ navigate, @@ -14,7 +13,6 @@ function makeCommands({ }: { navigate: ReturnType; prompt: ReturnType; - toast: ReturnType; }) { return { createWorkspace: createFastMutation>({ @@ -65,7 +63,6 @@ function makeCommands({ export function useCommands() { const navigate = useNavigate(); - const toast = useToast(); const prompt = usePrompt(); - return useMemo(() => makeCommands({ navigate, toast, prompt }), [navigate, prompt, toast]); + return useMemo(() => makeCommands({ navigate, prompt }), [navigate, prompt]); } diff --git a/src-web/hooks/useCopy.ts b/src-web/hooks/useCopy.ts index 4f6f500b..dc2aa8e1 100644 --- a/src-web/hooks/useCopy.ts +++ b/src-web/hooks/useCopy.ts @@ -1,10 +1,8 @@ import { clear, writeText } from '@tauri-apps/plugin-clipboard-manager'; import { useCallback } from 'react'; -import { useToast } from './useToast'; +import { showToast } from '../lib/toast'; export function useCopy({ disableToast }: { disableToast?: boolean } = {}) { - const toast = useToast(); - const copy = useCallback( (text: string | null) => { if (text == null) { @@ -13,7 +11,7 @@ export function useCopy({ disableToast }: { disableToast?: boolean } = {}) { writeText(text).catch(console.error); } if (text != '' && !disableToast) { - toast.show({ + showToast({ id: 'copied', color: 'secondary', icon: 'copy', @@ -21,7 +19,7 @@ export function useCopy({ disableToast }: { disableToast?: boolean } = {}) { }); } }, - [disableToast, toast], + [disableToast], ); return copy; diff --git a/src-web/hooks/useExportData.tsx b/src-web/hooks/useExportData.tsx index 43a3b93d..831eccea 100644 --- a/src-web/hooks/useExportData.tsx +++ b/src-web/hooks/useExportData.tsx @@ -4,13 +4,12 @@ import { getActiveWorkspace } from './useActiveWorkspace'; import { useAlert } from './useAlert'; import { useDialog } from './useDialog'; import { useFastMutation } from './useFastMutation'; -import { useToast } from './useToast'; +import { showToast } from '../lib/toast'; import { workspacesAtom } from './useWorkspaces'; export function useExportData() { const alert = useAlert(); const dialog = useDialog(); - const toast = useToast(); return useFastMutation({ mutationKey: ['export_data'], @@ -32,7 +31,7 @@ export function useExportData() { { - toast.show({ + showToast({ color: 'success', message: 'Data export successful', }); diff --git a/src-web/hooks/useImportCurl.ts b/src-web/hooks/useImportCurl.ts index a0df5331..f1205dce 100644 --- a/src-web/hooks/useImportCurl.ts +++ b/src-web/hooks/useImportCurl.ts @@ -4,14 +4,13 @@ import { getActiveWorkspaceId } from './useActiveWorkspace'; import { useCreateHttpRequest } from './useCreateHttpRequest'; import { useFastMutation } from './useFastMutation'; import { useRequestUpdateKey } from './useRequestUpdateKey'; -import { useToast } from './useToast'; +import { showToast } from '../lib/toast'; import { useUpdateAnyHttpRequest } from './useUpdateAnyHttpRequest'; export function useImportCurl() { const updateRequest = useUpdateAnyHttpRequest(); const createRequest = useCreateHttpRequest(); const { wasUpdatedExternally } = useRequestUpdateKey(null); - const toast = useToast(); return useFastMutation({ mutationKey: ['import_curl'], @@ -50,7 +49,7 @@ export function useImportCurl() { setTimeout(() => wasUpdatedExternally(overwriteRequestId), 100); } - toast.show({ + showToast({ color: 'success', message: `${verb} request from Curl`, }); diff --git a/src-web/hooks/useImportQuerystring.ts b/src-web/hooks/useImportQuerystring.ts index ee1d0e96..679ca4f8 100644 --- a/src-web/hooks/useImportQuerystring.ts +++ b/src-web/hooks/useImportQuerystring.ts @@ -1,15 +1,14 @@ -import {generateId} from "../lib/generateId"; -import { useFastMutation } from './useFastMutation'; import type { HttpUrlParameter } from '@yaakapp-internal/models'; -import { useToast } from './useToast'; +import { generateId } from '../lib/generateId'; import { pluralize } from '../lib/pluralize'; import { getHttpRequest } from '../lib/store'; +import { useFastMutation } from './useFastMutation'; import { useRequestEditor } from './useRequestEditor'; +import { showToast } from '../lib/toast'; import { useUpdateAnyHttpRequest } from './useUpdateAnyHttpRequest'; export function useImportQuerystring(requestId: string) { const updateRequest = useUpdateAnyHttpRequest(); - const toast = useToast(); const [, { focusParamsTab, forceParamsRefresh, forceUrlRefresh }] = useRequestEditor(); return useFastMutation({ @@ -40,7 +39,7 @@ export function useImportQuerystring(requestId: string) { }); if (urlParameters.length > 0) { - toast.show({ + showToast({ id: 'querystring-imported', color: 'info', message: `Extracted ${urlParameters.length} ${pluralize('parameter', urlParameters.length)} from URL`, diff --git a/src-web/hooks/useNotificationToast.tsx b/src-web/hooks/useNotificationToast.tsx index dfd10a4c..6c7b078a 100644 --- a/src-web/hooks/useNotificationToast.tsx +++ b/src-web/hooks/useNotificationToast.tsx @@ -2,11 +2,9 @@ import { openUrl } from '@tauri-apps/plugin-opener'; import { Button } from '../components/core/Button'; import { invokeCmd } from '../lib/tauri'; import { useListenToTauriEvent } from './useListenToTauriEvent'; -import { useToast } from './useToast'; +import { showToast } from '../lib/toast'; export function useNotificationToast() { - const toast = useToast(); - const markRead = (id: string) => { invokeCmd('cmd_dismiss_notification', { notificationId: id }).catch(console.error); }; @@ -23,7 +21,7 @@ export function useNotificationToast() { console.log('Got notification event', payload); const actionUrl = payload.action?.url; const actionLabel = payload.action?.label; - toast.show({ + showToast({ id: payload.id, timeout: null, message: payload.message, diff --git a/src-web/hooks/useSaveResponse.tsx b/src-web/hooks/useSaveResponse.tsx index 144382c8..b38b7331 100644 --- a/src-web/hooks/useSaveResponse.tsx +++ b/src-web/hooks/useSaveResponse.tsx @@ -1,17 +1,15 @@ -import { useFastMutation } from './useFastMutation'; import { save } from '@tauri-apps/plugin-dialog'; +import type { HttpResponse } from '@yaakapp-internal/models'; import mime from 'mime'; import slugify from 'slugify'; import { InlineCode } from '../components/core/InlineCode'; -import { useToast } from './useToast'; -import type { HttpResponse } from '@yaakapp-internal/models'; import { getContentTypeHeader } from '../lib/model_util'; import { getHttpRequest } from '../lib/store'; import { invokeCmd } from '../lib/tauri'; +import { useFastMutation } from './useFastMutation'; +import { showToast } from '../lib/toast'; export function useSaveResponse(response: HttpResponse) { - const toast = useToast(); - return useFastMutation({ mutationKey: ['save_response', response.id], mutationFn: async () => { @@ -26,7 +24,7 @@ export function useSaveResponse(response: HttpResponse) { title: 'Save Response', }); await invokeCmd('cmd_save_response', { responseId: response.id, filepath }); - toast.show({ + showToast({ message: ( <> Response saved to {filepath} diff --git a/src-web/hooks/useToast.ts b/src-web/hooks/useToast.ts deleted file mode 100644 index 715271bd..00000000 --- a/src-web/hooks/useToast.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { atom } from 'jotai/index'; -import { useMemo } from 'react'; -import type { PrivateToastEntry, ToastEntry } from '../components/Toasts'; -import { generateId } from '../lib/generateId'; -import { jotaiStore } from '../lib/jotai'; - -export const toastsAtom = atom([]); - -export function useToast() { - return useMemo( - () => ({ - show({ id, timeout = 5000, ...props }: ToastEntry) { - id = id ?? generateId(); - if (timeout != null) { - setTimeout(() => this.hide(id), timeout); - } - jotaiStore.set(toastsAtom, (a) => { - if (a.some((v) => v.id === id)) { - // It's already visible with this id - return a; - } - return [...a, { id, timeout, ...props }]; - }); - return id; - }, - hide: (id: string) => { - jotaiStore.set(toastsAtom, (all) => { - const t = all.find((t) => t.id === id); - t?.onClose?.(); - return all.filter((t) => t.id !== id); - }); - }, - }), - [], - ); -} diff --git a/src-web/lib/toast.ts b/src-web/lib/toast.ts new file mode 100644 index 00000000..4d1d161b --- /dev/null +++ b/src-web/lib/toast.ts @@ -0,0 +1,29 @@ +import { atom } from 'jotai/index'; +import type { PrivateToastEntry, ToastEntry } from '../components/Toasts'; +import { generateId } from './generateId'; +import { jotaiStore } from './jotai'; + +export const toastsAtom = atom([]); + +export function showToast({ id, timeout = 5000, ...props }: ToastEntry) { + id = id ?? generateId(); + if (timeout != null) { + setTimeout(() => hideToast(id), timeout); + } + jotaiStore.set(toastsAtom, (a) => { + if (a.some((v) => v.id === id)) { + // It's already visible with this id + return a; + } + return [...a, { id, timeout, ...props }]; + }); + return id; +} + +export function hideToast(id: string) { + jotaiStore.set(toastsAtom, (all) => { + const t = all.find((t) => t.id === id); + t?.onClose?.(); + return all.filter((t) => t.id !== id); + }); +}