mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-01-11 20:00:29 +01:00
Move request to another workspace (#52)
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO http_requests (\n id, workspace_id, folder_id, name, url, url_parameters, method, body, body_type,\n authentication, authentication_type, headers, sort_priority\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n folder_id = excluded.folder_id,\n method = excluded.method,\n headers = excluded.headers,\n body = excluded.body,\n body_type = excluded.body_type,\n authentication = excluded.authentication,\n authentication_type = excluded.authentication_type,\n url = excluded.url,\n url_parameters = excluded.url_parameters,\n sort_priority = excluded.sort_priority\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 13
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "11394af12419cca3be3a26dff9275514ea2a44504e3c7a568a9578c64b5713d1"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO grpc_requests (\n id, name, workspace_id, folder_id, sort_priority, url, service, method, message,\n authentication_type, authentication, metadata\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n folder_id = excluded.folder_id,\n sort_priority = excluded.sort_priority,\n url = excluded.url,\n service = excluded.service,\n method = excluded.method,\n message = excluded.message,\n authentication_type = excluded.authentication_type,\n authentication = excluded.authentication,\n metadata = excluded.metadata\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 12
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "467b87ad1209a4653b1dc8462d79236a655240c5b402fa9fd75c12ebd9bb6b86"
|
||||
}
|
||||
12
src-tauri/.sqlx/query-5af82cd333895d3d7d67a92f37b0feb338f615b88aea2bd09cb5809008c645a3.json
generated
Normal file
12
src-tauri/.sqlx/query-5af82cd333895d3d7d67a92f37b0feb338f615b88aea2bd09cb5809008c645a3.json
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO grpc_requests (\n id, name, workspace_id, folder_id, sort_priority, url, service, method, message,\n authentication_type, authentication, metadata\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n workspace_id = excluded.workspace_id,\n name = excluded.name,\n folder_id = excluded.folder_id,\n sort_priority = excluded.sort_priority,\n url = excluded.url,\n service = excluded.service,\n method = excluded.method,\n message = excluded.message,\n authentication_type = excluded.authentication_type,\n authentication = excluded.authentication,\n metadata = excluded.metadata\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 12
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5af82cd333895d3d7d67a92f37b0feb338f615b88aea2bd09cb5809008c645a3"
|
||||
}
|
||||
12
src-tauri/.sqlx/query-5f2f40062abbe93e23b38876319cf16d4d2b3f8d0be32ffe7848528c725e1429.json
generated
Normal file
12
src-tauri/.sqlx/query-5f2f40062abbe93e23b38876319cf16d4d2b3f8d0be32ffe7848528c725e1429.json
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO http_requests (\n id, workspace_id, folder_id, name, url, url_parameters, method, body, body_type,\n authentication, authentication_type, headers, sort_priority\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n workspace_id = excluded.workspace_id,\n name = excluded.name,\n folder_id = excluded.folder_id,\n method = excluded.method,\n headers = excluded.headers,\n body = excluded.body,\n body_type = excluded.body_type,\n authentication = excluded.authentication,\n authentication_type = excluded.authentication_type,\n url = excluded.url,\n url_parameters = excluded.url_parameters,\n sort_priority = excluded.sort_priority\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 13
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5f2f40062abbe93e23b38876319cf16d4d2b3f8d0be32ffe7848528c725e1429"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n id, model, created_at, updated_at, theme, appearance,\n theme_dark, theme_light, update_channel,\n interface_font_size, interface_scale, editor_font_size, editor_soft_wrap, \n open_workspace_new_window\n FROM settings\n WHERE id = 'default'\n ",
|
||||
"query": "\n SELECT\n id, model, created_at, updated_at, theme, appearance,\n theme_dark, theme_light, update_channel,\n interface_font_size, interface_scale, editor_font_size, editor_soft_wrap,\n open_workspace_new_window\n FROM settings\n WHERE id = 'default'\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -94,5 +94,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "05dca7fe15ab1bf03952e94498ef3130e16f752da72782783696eb2cca4736d5"
|
||||
"hash": "daa61066517df649e7c80a8ce407839ad502e8e5e43aa8c02e049865acbbae75"
|
||||
}
|
||||
@@ -562,6 +562,7 @@ pub async fn upsert_grpc_request(
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
updated_at = CURRENT_TIMESTAMP,
|
||||
workspace_id = excluded.workspace_id,
|
||||
name = excluded.name,
|
||||
folder_id = excluded.folder_id,
|
||||
sort_priority = excluded.sort_priority,
|
||||
@@ -892,7 +893,7 @@ async fn get_settings(mgr: &impl Manager<Wry>) -> Result<Settings, sqlx::Error>
|
||||
SELECT
|
||||
id, model, created_at, updated_at, theme, appearance,
|
||||
theme_dark, theme_light, update_channel,
|
||||
interface_font_size, interface_scale, editor_font_size, editor_soft_wrap,
|
||||
interface_font_size, interface_scale, editor_font_size, editor_soft_wrap,
|
||||
open_workspace_new_window
|
||||
FROM settings
|
||||
WHERE id = 'default'
|
||||
@@ -1125,6 +1126,7 @@ pub async fn upsert_http_request(
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
updated_at = CURRENT_TIMESTAMP,
|
||||
workspace_id = excluded.workspace_id,
|
||||
name = excluded.name,
|
||||
folder_id = excluded.folder_id,
|
||||
method = excluded.method,
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { DialogProvider } from './DialogContext';
|
||||
import { GlobalHooks } from './GlobalHooks';
|
||||
import { ToastProvider } from './ToastContext';
|
||||
import classNames from 'classnames';
|
||||
import { useOsInfo } from '../hooks/useOsInfo';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { useOsInfo } from '../hooks/useOsInfo';
|
||||
import { DialogProvider, Dialogs } from './DialogContext';
|
||||
import { GlobalHooks } from './GlobalHooks';
|
||||
import { ToastProvider, Toasts } from './ToastContext';
|
||||
|
||||
export function DefaultLayout() {
|
||||
const osInfo = useOsInfo();
|
||||
return (
|
||||
<DialogProvider>
|
||||
<ToastProvider>
|
||||
<>
|
||||
{/* Must be inside all the providers, so they have access to them */}
|
||||
<Toasts />
|
||||
<Dialogs />
|
||||
</>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
|
||||
@@ -46,14 +46,7 @@ export const DialogProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
actions,
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogContext.Provider value={state}>
|
||||
{children}
|
||||
{dialogs.map((props: DialogEntry) => (
|
||||
<DialogInstance key={props.id} {...props} />
|
||||
))}
|
||||
</DialogContext.Provider>
|
||||
);
|
||||
return <DialogContext.Provider value={state}>{children}</DialogContext.Provider>;
|
||||
};
|
||||
|
||||
function DialogInstance({ id, render, ...props }: DialogEntry) {
|
||||
@@ -67,3 +60,14 @@ function DialogInstance({ id, render, ...props }: DialogEntry) {
|
||||
}
|
||||
|
||||
export const useDialog = () => useContext(DialogContext).actions;
|
||||
|
||||
export function Dialogs() {
|
||||
const { dialogs } = useContext(DialogContext);
|
||||
return (
|
||||
<>
|
||||
{dialogs.map((props: DialogEntry) => (
|
||||
<DialogInstance key={props.id} {...props} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { getCurrent } from '@tauri-apps/api/webviewWindow';
|
||||
import { useEffect } from 'react';
|
||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||
import { useClipboardText } from '../hooks/useClipboardText';
|
||||
import { useCommandPalette } from '../hooks/useCommandPalette';
|
||||
import { cookieJarsQueryKey } from '../hooks/useCookieJars';
|
||||
@@ -32,8 +33,12 @@ import { monokaiProDefault } from '../lib/theme/themes/monokai-pro';
|
||||
import { rosePineDefault } from '../lib/theme/themes/rose-pine';
|
||||
import { yaakDark } from '../lib/theme/themes/yaak';
|
||||
import { getThemeCSS } from '../lib/theme/window';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
import { useToast } from './ToastContext';
|
||||
|
||||
export function GlobalHooks() {
|
||||
const toast = useToast();
|
||||
|
||||
// Include here so they always update, even if no component references them
|
||||
useRecentWorkspaces();
|
||||
useRecentEnvironments();
|
||||
@@ -44,6 +49,21 @@ export function GlobalHooks() {
|
||||
useCommandPalette();
|
||||
useNotificationToast();
|
||||
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
|
||||
useEffect(() => {
|
||||
if (activeWorkspace == null) return;
|
||||
toast.show({
|
||||
id: 'workspace-changed',
|
||||
timeout: 3000,
|
||||
message: (
|
||||
<>
|
||||
Switched workspace to <InlineCode>{activeWorkspace.name}</InlineCode>
|
||||
</>
|
||||
),
|
||||
});
|
||||
}, [activeWorkspace, toast]);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const { wasUpdatedExternally } = useRequestUpdateKey(null);
|
||||
|
||||
|
||||
@@ -63,9 +63,9 @@ export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }:
|
||||
firstSlot={() =>
|
||||
activeConnection && (
|
||||
<div className="w-full grid grid-rows-[auto_minmax(0,1fr)] items-center">
|
||||
<HStack className="pl-3 mb-1 font-mono">
|
||||
<HStack className="pl-3 mb-1 font-mono text-sm">
|
||||
<HStack space={2}>
|
||||
<span>{events.length} messages</span>
|
||||
<span>{events.length} Messages</span>
|
||||
{isResponseLoading(activeConnection) && (
|
||||
<Icon icon="refresh" size="sm" spin className="text-fg-subtler" />
|
||||
)}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { BearerAuth } from './BearerAuth';
|
||||
import { Button } from './core/Button';
|
||||
import { Icon } from './core/Icon';
|
||||
import { IconButton } from './core/IconButton';
|
||||
import { PairEditor } from './core/PairEditor';
|
||||
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
|
||||
import { RadioDropdown } from './core/RadioDropdown';
|
||||
import { HStack, VStack } from './core/Stacks';
|
||||
import type { TabItem } from './core/Tabs/Tabs';
|
||||
@@ -209,7 +209,7 @@ export function GrpcConnectionSetupPane({
|
||||
rightSlot={<Icon className="text-fg-subtler" size="sm" icon="chevronDown" />}
|
||||
disabled={isStreaming || services == null}
|
||||
className={classNames(
|
||||
'font-mono text-sm min-w-[5rem] !ring-0',
|
||||
'font-mono text-editor min-w-[5rem] !ring-0',
|
||||
paneSize < 400 && 'flex-1',
|
||||
)}
|
||||
>
|
||||
@@ -291,7 +291,8 @@ export function GrpcConnectionSetupPane({
|
||||
)}
|
||||
</TabContent>
|
||||
<TabContent value="metadata">
|
||||
<PairEditor
|
||||
<PairOrBulkEditor
|
||||
preferenceName="grpc_metadata"
|
||||
valueAutocompleteVariables
|
||||
nameAutocompleteVariables
|
||||
pairs={activeRequest.metadata}
|
||||
|
||||
97
src-web/components/MoveToWorkspaceDialog.tsx
Normal file
97
src-web/components/MoveToWorkspaceDialog.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import React, { useState } from 'react';
|
||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||
import { grpcRequestsQueryKey } from '../hooks/useGrpcRequests';
|
||||
import { httpRequestsQueryKey } from '../hooks/useHttpRequests';
|
||||
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
|
||||
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||
import type { GrpcRequest, HttpRequest } from '../lib/models';
|
||||
import { Button } from './core/Button';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
import { Select } from './core/Select';
|
||||
import { VStack } from './core/Stacks';
|
||||
import { useToast } from './ToastContext';
|
||||
|
||||
interface Props {
|
||||
activeWorkspaceId: string;
|
||||
request: HttpRequest | GrpcRequest;
|
||||
onDone: () => void;
|
||||
}
|
||||
|
||||
export function MoveToWorkspaceDialog({ onDone, request, activeWorkspaceId }: Props) {
|
||||
const workspaces = useWorkspaces();
|
||||
const queryClient = useQueryClient();
|
||||
const updateHttpRequest = useUpdateAnyHttpRequest();
|
||||
const updateGrpcRequest = useUpdateAnyGrpcRequest();
|
||||
const toast = useToast();
|
||||
const routes = useAppRoutes();
|
||||
const [selectedWorkspaceId, setSelectedWorkspaceId] = useState<string>(activeWorkspaceId);
|
||||
|
||||
return (
|
||||
<VStack space={4} className="mb-4">
|
||||
<Select
|
||||
label="Workspace"
|
||||
name="workspace"
|
||||
value={selectedWorkspaceId}
|
||||
onChange={setSelectedWorkspaceId}
|
||||
options={workspaces.map((w) => ({
|
||||
label: w.id === activeWorkspaceId ? `${w.name} (current)` : w.name,
|
||||
value: w.id,
|
||||
}))}
|
||||
/>
|
||||
<Button
|
||||
color="primary"
|
||||
disabled={selectedWorkspaceId === activeWorkspaceId}
|
||||
onClick={async () => {
|
||||
const args = {
|
||||
id: request.id,
|
||||
update: { workspaceId: selectedWorkspaceId, folderId: null },
|
||||
};
|
||||
|
||||
if (request.model === 'http_request') {
|
||||
await updateHttpRequest.mutateAsync(args);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: httpRequestsQueryKey({ workspaceId: activeWorkspaceId }),
|
||||
});
|
||||
} else if (request.model === 'grpc_request') {
|
||||
await updateGrpcRequest.mutateAsync(args);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: grpcRequestsQueryKey({ workspaceId: activeWorkspaceId }),
|
||||
});
|
||||
}
|
||||
|
||||
// Hide after a moment, to give time for request to disappear
|
||||
setTimeout(onDone, 100);
|
||||
toast.show({
|
||||
id: 'workspace-moved',
|
||||
message: (
|
||||
<>
|
||||
<InlineCode>{fallbackRequestName(request)}</InlineCode> moved to{' '}
|
||||
<InlineCode>
|
||||
{workspaces.find((w) => w.id === selectedWorkspaceId)?.name ?? 'unknown'}
|
||||
</InlineCode>
|
||||
</>
|
||||
),
|
||||
action: (
|
||||
<Button
|
||||
size="xs"
|
||||
color="secondary"
|
||||
className="mr-auto min-w-[5rem]"
|
||||
onClick={() => {
|
||||
toast.hide('workspace-moved');
|
||||
routes.navigate('workspace', { workspaceId: selectedWorkspaceId });
|
||||
}}
|
||||
>
|
||||
Switch to Workspace
|
||||
</Button>
|
||||
),
|
||||
});
|
||||
}}
|
||||
>
|
||||
Change Workspace
|
||||
</Button>
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import { useHotKey } from '../hooks/useHotKey';
|
||||
import { useKeyValue } from '../hooks/useKeyValue';
|
||||
import { useLatestGrpcConnection } from '../hooks/useLatestGrpcConnection';
|
||||
import { useLatestHttpResponse } from '../hooks/useLatestHttpResponse';
|
||||
import { useMoveToWorkspace } from '../hooks/useMoveToWorkspace';
|
||||
import { usePrompt } from '../hooks/usePrompt';
|
||||
import { useRequests } from '../hooks/useRequests';
|
||||
import { useSendManyRequests } from '../hooks/useSendFolder';
|
||||
@@ -30,6 +31,7 @@ import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
|
||||
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
|
||||
import { useUpdateGrpcRequest } from '../hooks/useUpdateGrpcRequest';
|
||||
import { useUpdateHttpRequest } from '../hooks/useUpdateHttpRequest';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||
import type { Folder, GrpcRequest, HttpRequest, Workspace } from '../lib/models';
|
||||
import { isResponseLoading } from '../lib/models';
|
||||
@@ -608,10 +610,12 @@ const SidebarItem = forwardRef(function SidebarItem(
|
||||
const duplicateGrpcRequest = useDuplicateGrpcRequest({ id: itemId, navigateAfter: true });
|
||||
const copyAsCurl = useCopyAsCurl(itemId);
|
||||
const sendRequest = useSendRequest(itemId);
|
||||
const moveToWorkspace = useMoveToWorkspace(itemId);
|
||||
const sendManyRequests = useSendManyRequests();
|
||||
const latestHttpResponse = useLatestHttpResponse(itemId);
|
||||
const latestGrpcConnection = useLatestGrpcConnection(itemId);
|
||||
const updateHttpRequest = useUpdateHttpRequest(itemId);
|
||||
const workspaces = useWorkspaces();
|
||||
const updateGrpcRequest = useUpdateGrpcRequest(itemId);
|
||||
const updateAnyFolder = useUpdateAnyFolder();
|
||||
const prompt = usePrompt();
|
||||
@@ -732,7 +736,7 @@ const SidebarItem = forwardRef(function SidebarItem(
|
||||
hotKeyAction: 'http_request.send',
|
||||
hotKeyLabelOnly: true, // Already bound in URL bar
|
||||
leftSlot: <Icon icon="sendHorizontal" />,
|
||||
onSelect: () => sendRequest.mutate(),
|
||||
onSelect: sendRequest.mutate,
|
||||
},
|
||||
{
|
||||
key: 'copyCurl',
|
||||
@@ -783,6 +787,13 @@ const SidebarItem = forwardRef(function SidebarItem(
|
||||
: duplicateGrpcRequest.mutate();
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'moveWorkspace',
|
||||
label: 'Change Workspace',
|
||||
leftSlot: <Icon icon="house" />,
|
||||
hidden: workspaces.length <= 1,
|
||||
onSelect: moveToWorkspace.mutate,
|
||||
},
|
||||
{
|
||||
key: 'deleteRequest',
|
||||
variant: 'danger',
|
||||
|
||||
@@ -9,7 +9,7 @@ import { AnimatePresence } from 'framer-motion';
|
||||
type ToastEntry = {
|
||||
id?: string;
|
||||
message: ReactNode;
|
||||
timeout?: number | null;
|
||||
timeout?: 3000 | 5000 | 8000 | null;
|
||||
onClose?: ToastProps['onClose'];
|
||||
} & Omit<ToastProps, 'onClose' | 'open' | 'children' | 'timeout'>;
|
||||
|
||||
@@ -29,14 +29,14 @@ interface Actions {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const ToastContext = createContext<State>({} as State);
|
||||
export const ToastContext = createContext<State>({} as State);
|
||||
|
||||
export const ToastProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [toasts, setToasts] = useState<State['toasts']>([]);
|
||||
const timeoutRef = useRef<NodeJS.Timeout>();
|
||||
const actions = useMemo<Actions>(
|
||||
() => ({
|
||||
show({ id, timeout = 4000, ...props }: ToastEntry) {
|
||||
show({ id, timeout = 5000, ...props }: ToastEntry) {
|
||||
id = id ?? generateId();
|
||||
if (timeout != null) {
|
||||
timeoutRef.current = setTimeout(() => this.hide(id), timeout);
|
||||
@@ -62,21 +62,7 @@ export const ToastProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
);
|
||||
|
||||
const state: State = { toasts, actions };
|
||||
|
||||
return (
|
||||
<ToastContext.Provider value={state}>
|
||||
{children}
|
||||
<Portal name="toasts">
|
||||
<div className="absolute right-0 bottom-0 z-10">
|
||||
<AnimatePresence>
|
||||
{toasts.map((props: PrivateToastEntry) => (
|
||||
<ToastInstance key={props.id} {...props} />
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</Portal>
|
||||
</ToastContext.Provider>
|
||||
);
|
||||
return <ToastContext.Provider value={state}>{children}</ToastContext.Provider>;
|
||||
};
|
||||
|
||||
function ToastInstance({ id, message, timeout, ...props }: PrivateToastEntry) {
|
||||
@@ -96,3 +82,18 @@ function ToastInstance({ id, message, timeout, ...props }: PrivateToastEntry) {
|
||||
}
|
||||
|
||||
export const useToast = () => useContext(ToastContext).actions;
|
||||
|
||||
export const Toasts = () => {
|
||||
const { toasts } = useContext(ToastContext);
|
||||
return (
|
||||
<Portal name="toasts">
|
||||
<div className="absolute right-0 bottom-0 z-10">
|
||||
<AnimatePresence>
|
||||
{toasts.map((props: PrivateToastEntry) => (
|
||||
<ToastInstance key={props.id} {...props} />
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,9 +5,6 @@ import { memo } from 'react';
|
||||
|
||||
const icons = {
|
||||
alert: lucide.AlertTriangleIcon,
|
||||
text: lucide.FileTextIcon,
|
||||
table: lucide.TableIcon,
|
||||
fileCode: lucide.FileCodeIcon,
|
||||
archive: lucide.ArchiveIcon,
|
||||
arrowBigDownDash: lucide.ArrowBigDownDashIcon,
|
||||
arrowBigLeftDash: lucide.ArrowBigLeftDashIcon,
|
||||
@@ -33,19 +30,22 @@ const icons = {
|
||||
externalLink: lucide.ExternalLinkIcon,
|
||||
eye: lucide.EyeIcon,
|
||||
eyeClosed: lucide.EyeOffIcon,
|
||||
fileCode: lucide.FileCodeIcon,
|
||||
filter: lucide.FilterIcon,
|
||||
flask: lucide.FlaskConicalIcon,
|
||||
folderInput: lucide.FolderInputIcon,
|
||||
folderOutput: lucide.FolderOutputIcon,
|
||||
gripVertical: lucide.GripVerticalIcon,
|
||||
hand: lucide.HandIcon,
|
||||
house: lucide.HomeIcon,
|
||||
info: lucide.InfoIcon,
|
||||
keyboard: lucide.KeyboardIcon,
|
||||
leftPanelHidden: lucide.PanelLeftOpenIcon,
|
||||
leftPanelVisible: lucide.PanelLeftCloseIcon,
|
||||
magicWand: lucide.Wand2Icon,
|
||||
minus: lucide.MinusIcon,
|
||||
moreVertical: lucide.MoreVerticalIcon,
|
||||
moon: lucide.MoonIcon,
|
||||
moreVertical: lucide.MoreVerticalIcon,
|
||||
paste: lucide.ClipboardPasteIcon,
|
||||
pencil: lucide.PencilIcon,
|
||||
pin: lucide.PinIcon,
|
||||
@@ -61,6 +61,8 @@ const icons = {
|
||||
settings: lucide.SettingsIcon,
|
||||
sparkles: lucide.SparklesIcon,
|
||||
sun: lucide.SunIcon,
|
||||
table: lucide.TableIcon,
|
||||
text: lucide.FileTextIcon,
|
||||
trash: lucide.Trash2Icon,
|
||||
unpin: lucide.PinOffIcon,
|
||||
update: lucide.RefreshCcwIcon,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useKey } from 'react-use';
|
||||
import type { IconProps } from './Icon';
|
||||
import { Icon } from './Icon';
|
||||
import { IconButton } from './IconButton';
|
||||
import { VStack } from './Stacks';
|
||||
|
||||
export interface ToastProps {
|
||||
children: ReactNode;
|
||||
@@ -63,7 +64,7 @@ export function Toast({
|
||||
'text-fg',
|
||||
)}
|
||||
>
|
||||
<div className="px-3 py-2 flex items-center gap-2">
|
||||
<div className="px-3 py-3 flex items-center gap-2">
|
||||
{variant != null && (
|
||||
<Icon
|
||||
icon={ICONS[variant]}
|
||||
@@ -75,10 +76,10 @@ export function Toast({
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<div className="flex flex-col gap-1 w-full">
|
||||
<VStack space={2}>
|
||||
<div>{children}</div>
|
||||
{action}
|
||||
</div>
|
||||
</VStack>
|
||||
</div>
|
||||
|
||||
<IconButton
|
||||
|
||||
@@ -49,7 +49,7 @@ export const routePaths = {
|
||||
};
|
||||
|
||||
export function useAppRoutes() {
|
||||
const workspaceId = useActiveWorkspaceId();
|
||||
const activeWorkspaceId = useActiveWorkspaceId();
|
||||
const requestId = useActiveRequestId();
|
||||
const nav = useNavigate();
|
||||
|
||||
@@ -66,22 +66,22 @@ export function useAppRoutes() {
|
||||
|
||||
const setEnvironment = useCallback(
|
||||
(environment: Environment | null) => {
|
||||
if (workspaceId == null) {
|
||||
if (activeWorkspaceId == null) {
|
||||
navigate('workspaces');
|
||||
} else if (requestId == null) {
|
||||
navigate('workspace', {
|
||||
workspaceId: workspaceId,
|
||||
workspaceId: activeWorkspaceId,
|
||||
environmentId: environment == null ? undefined : environment.id,
|
||||
});
|
||||
} else {
|
||||
navigate('request', {
|
||||
workspaceId,
|
||||
workspaceId: activeWorkspaceId,
|
||||
environmentId: environment == null ? undefined : environment.id,
|
||||
requestId,
|
||||
});
|
||||
}
|
||||
},
|
||||
[navigate, workspaceId, requestId],
|
||||
[navigate, activeWorkspaceId, requestId],
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -57,7 +57,6 @@ export function useGrpc(
|
||||
)) as ReflectResponseService[],
|
||||
});
|
||||
|
||||
console.log('CONN', conn);
|
||||
return {
|
||||
go,
|
||||
reflect,
|
||||
|
||||
33
src-web/hooks/useMoveToWorkspace.tsx
Normal file
33
src-web/hooks/useMoveToWorkspace.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import React from 'react';
|
||||
import { useDialog } from '../components/DialogContext';
|
||||
import { MoveToWorkspaceDialog } from '../components/MoveToWorkspaceDialog';
|
||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||
import { useRequests } from './useRequests';
|
||||
|
||||
export function useMoveToWorkspace(id: string) {
|
||||
const dialog = useDialog();
|
||||
const requests = useRequests();
|
||||
const request = requests.find((r) => r.id === id);
|
||||
const activeWorkspaceId = useActiveWorkspaceId();
|
||||
|
||||
return useMutation<void, unknown>({
|
||||
mutationKey: ['moveWorkspace'],
|
||||
mutationFn: async () => {
|
||||
if (request == null || activeWorkspaceId == null) return;
|
||||
|
||||
dialog.show({
|
||||
id: 'change-workspace',
|
||||
title: 'Change Workspace',
|
||||
size: 'sm',
|
||||
render: ({ hide }) => (
|
||||
<MoveToWorkspaceDialog
|
||||
onDone={hide}
|
||||
request={request}
|
||||
activeWorkspaceId={activeWorkspaceId}
|
||||
/>
|
||||
),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user