mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-01-11 20:00:29 +01:00
Environment dropdown and actions
This commit is contained in:
@@ -378,15 +378,15 @@
|
||||
},
|
||||
"query": "\n SELECT\n id,\n model,\n workspace_id,\n created_at,\n updated_at,\n name,\n url,\n method,\n body,\n body_type,\n authentication AS \"authentication!: Json<HashMap<String, JsonValue>>\",\n authentication_type,\n sort_priority,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpRequestHeader>>\"\n FROM http_requests\n WHERE workspace_id = ?\n "
|
||||
},
|
||||
"80bc37d283b67a70919c7b03a106fe563829741fb2c2fbd34ae4d8f581ecb697": {
|
||||
"6f12b56113b09966b472431b6cb95c354bea51b4dfb22a96517655c0fca0ab05": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
"Right": 3
|
||||
}
|
||||
},
|
||||
"query": "\n UPDATE environments\n SET (data, updated_at) = (?, CURRENT_TIMESTAMP)\n WHERE id = ?;\n "
|
||||
"query": "\n UPDATE environments\n SET (name, data, updated_at) = (?, ?, CURRENT_TIMESTAMP)\n WHERE id = ?;\n "
|
||||
},
|
||||
"84be2b954870ab181738656ecd4d03fca2ff21012947014c79626abfce8e999b": {
|
||||
"describe": {
|
||||
|
||||
@@ -425,10 +425,14 @@ async fn update_environment(
|
||||
) -> Result<models::Environment, String> {
|
||||
let pool = &*db_instance.lock().await;
|
||||
|
||||
let updated_environment =
|
||||
models::update_environment(environment.id.as_str(), environment.data.0, pool)
|
||||
.await
|
||||
.expect("Failed to update request");
|
||||
let updated_environment = models::update_environment(
|
||||
environment.id.as_str(),
|
||||
environment.name.as_str(),
|
||||
environment.data.0,
|
||||
pool,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to update request");
|
||||
|
||||
emit_and_return(&window, "updated_model", updated_environment)
|
||||
}
|
||||
|
||||
@@ -254,6 +254,7 @@ pub async fn create_environment(
|
||||
|
||||
pub async fn update_environment(
|
||||
id: &str,
|
||||
name: &str,
|
||||
data: HashMap<String, JsonValue>,
|
||||
pool: &Pool<Sqlite>,
|
||||
) -> Result<Environment, sqlx::Error> {
|
||||
@@ -262,9 +263,10 @@ pub async fn update_environment(
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE environments
|
||||
SET (data, updated_at) = (?, CURRENT_TIMESTAMP)
|
||||
SET (name, data, updated_at) = (?, ?, CURRENT_TIMESTAMP)
|
||||
WHERE id = ?;
|
||||
"#,
|
||||
name,
|
||||
json_data,
|
||||
id,
|
||||
)
|
||||
|
||||
129
src-web/components/EnvironmentActionsDropdown.tsx
Normal file
129
src-web/components/EnvironmentActionsDropdown.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import classnames from 'classnames';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { Button } from './core/Button';
|
||||
import type { DropdownItem } from './core/Dropdown';
|
||||
import { Dropdown } from './core/Dropdown';
|
||||
import { Icon } from './core/Icon';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
import { useEnvironments } from '../hooks/useEnvironments';
|
||||
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
||||
import { useUpdateEnvironment } from '../hooks/useUpdateEnvironment';
|
||||
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
||||
import { usePrompt } from '../hooks/usePrompt';
|
||||
import { useDialog } from './DialogContext';
|
||||
import { EnvironmentEditDialog } from './EnvironmentEditDialog';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdown({
|
||||
className,
|
||||
}: Props) {
|
||||
const environments = useEnvironments();
|
||||
const [activeEnvironment, setActiveEnvironment] = useActiveEnvironment();
|
||||
const updateEnvironment = useUpdateEnvironment(activeEnvironment?.id ?? null);
|
||||
const createEnvironment = useCreateEnvironment();
|
||||
const prompt = usePrompt();
|
||||
const dialog = useDialog();
|
||||
|
||||
const items: DropdownItem[] = useMemo(() => {
|
||||
const environmentItems = environments.map(
|
||||
(e) => ({
|
||||
key: e.id,
|
||||
label: e.name,
|
||||
onSelect: async () => {
|
||||
setActiveEnvironment(e);
|
||||
},
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const activeEnvironmentItems: DropdownItem[] =
|
||||
environments.length <= 1
|
||||
? []
|
||||
: [
|
||||
...environmentItems,
|
||||
{
|
||||
type: 'separator',
|
||||
label: activeEnvironment?.name,
|
||||
},
|
||||
];
|
||||
|
||||
return [
|
||||
...activeEnvironmentItems,
|
||||
{
|
||||
key: 'edit',
|
||||
label: 'Edit',
|
||||
leftSlot: <Icon icon="sun" />,
|
||||
onSelect: async () => {
|
||||
dialog.show({
|
||||
title: 'Environments',
|
||||
render: () => <EnvironmentEditDialog />,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'rename',
|
||||
label: 'Rename',
|
||||
leftSlot: <Icon icon="pencil" />,
|
||||
onSelect: async () => {
|
||||
const name = await prompt({
|
||||
title: 'Rename Environment',
|
||||
description: (
|
||||
<>
|
||||
Enter a new name for <InlineCode>{activeEnvironment?.name}</InlineCode>
|
||||
</>
|
||||
),
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
defaultValue: activeEnvironment?.name,
|
||||
});
|
||||
updateEnvironment.mutate({ name });
|
||||
},
|
||||
},
|
||||
// {
|
||||
// key: 'delete',
|
||||
// label: 'Delete',
|
||||
// leftSlot: <Icon icon="trash" />,
|
||||
// onSelect: deleteEnv.mutate,
|
||||
// variant: 'danger',
|
||||
// },
|
||||
{ type: 'separator' },
|
||||
{
|
||||
key: 'create-environment',
|
||||
label: 'Create Environment',
|
||||
leftSlot: <Icon icon="plus" />,
|
||||
onSelect: async () => {
|
||||
const name = await prompt({
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
defaultValue: '',
|
||||
description: 'Enter a name for the new environment',
|
||||
title: 'Create Environment',
|
||||
});
|
||||
createEnvironment.mutate({ name });
|
||||
},
|
||||
},
|
||||
];
|
||||
}, [
|
||||
environments,
|
||||
activeEnvironment?.name,
|
||||
// deleteEnvironment.mutate,
|
||||
dialog,
|
||||
prompt,
|
||||
updateEnvironment,
|
||||
createEnvironment,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Dropdown items={items}>
|
||||
<Button
|
||||
size="sm"
|
||||
className={classnames(className, 'text-gray-800 !px-2 truncate')}
|
||||
forDropdown
|
||||
>
|
||||
{activeEnvironment?.name ?? 'No Env'}
|
||||
</Button>
|
||||
</Dropdown>
|
||||
);
|
||||
});
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
||||
import { useEnvironments } from '../hooks/useEnvironments';
|
||||
import { usePrompt } from '../hooks/usePrompt';
|
||||
@@ -7,19 +6,24 @@ import type { Environment } from '../lib/models';
|
||||
import { Button } from './core/Button';
|
||||
import { Editor } from './core/Editor';
|
||||
import classnames from 'classnames';
|
||||
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
||||
|
||||
export const EnvironmentEditDialog = function () {
|
||||
export const EnvironmentEditDialog = function() {
|
||||
const prompt = usePrompt();
|
||||
const environments = useEnvironments();
|
||||
const createEnvironment = useCreateEnvironment();
|
||||
const [activeEnvironment, setActiveEnvironment] = useState<Environment | null>(null);
|
||||
const [activeEnvironment, setActiveEnvironment] = useActiveEnvironment();
|
||||
|
||||
return (
|
||||
<div className="h-full grid gap-3 grid-cols-[auto_minmax(0,1fr)]">
|
||||
<aside className="h-full min-w-[120px] pr-3 border-r border-gray-200">
|
||||
<aside className="relative h-full min-w-[200px] pr-3 border-r border-gray-200">
|
||||
{environments.map((e) => (
|
||||
<Button
|
||||
className={classnames('w-full', activeEnvironment?.id === e.id && 'bg-highlight')}
|
||||
size="sm"
|
||||
className={classnames(
|
||||
'w-full',
|
||||
activeEnvironment?.id === e.id && 'bg-gray-100 text-gray-1000',
|
||||
)}
|
||||
justify="start"
|
||||
key={e.id}
|
||||
onClick={() => {
|
||||
@@ -30,6 +34,8 @@ export const EnvironmentEditDialog = function () {
|
||||
</Button>
|
||||
))}
|
||||
<Button
|
||||
size="sm"
|
||||
className="mr-5 absolute bottom-0 left-0 right-0"
|
||||
color="gray"
|
||||
onClick={async () => {
|
||||
const name = await prompt({
|
||||
@@ -41,7 +47,7 @@ export const EnvironmentEditDialog = function () {
|
||||
createEnvironment.mutate({ name });
|
||||
}}
|
||||
>
|
||||
Create Environment
|
||||
New Environment
|
||||
</Button>
|
||||
</aside>
|
||||
{activeEnvironment != null && <EnvironmentEditor environment={activeEnvironment} />}
|
||||
@@ -49,7 +55,7 @@ export const EnvironmentEditDialog = function () {
|
||||
);
|
||||
};
|
||||
|
||||
const EnvironmentEditor = function ({ environment }: { environment: Environment }) {
|
||||
const EnvironmentEditor = function({ environment }: { environment: Environment }) {
|
||||
const updateEnvironment = useUpdateEnvironment(environment.id);
|
||||
return (
|
||||
<Editor
|
||||
|
||||
@@ -20,7 +20,7 @@ type Props = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const WorkspaceActionsDropdown = memo(function WorkspaceDropdown({ className }: Props) {
|
||||
export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({ className }: Props) {
|
||||
const workspaces = useWorkspaces();
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const activeWorkspaceId = activeWorkspace?.id ?? null;
|
||||
|
||||
@@ -7,9 +7,7 @@ import { RecentRequestsDropdown } from './RecentRequestsDropdown';
|
||||
import { RequestActionsDropdown } from './RequestActionsDropdown';
|
||||
import { SidebarActions } from './SidebarActions';
|
||||
import { WorkspaceActionsDropdown } from './WorkspaceActionsDropdown';
|
||||
import { Button } from './core/Button';
|
||||
import { useDialog } from './DialogContext';
|
||||
import { EnvironmentEditDialog } from './EnvironmentEditDialog';
|
||||
import { EnvironmentActionsDropdown } from './EnvironmentActionsDropdown';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
@@ -17,7 +15,6 @@ interface Props {
|
||||
|
||||
export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Props) {
|
||||
const activeRequest = useActiveRequest();
|
||||
const dialog = useDialog();
|
||||
|
||||
return (
|
||||
<HStack
|
||||
@@ -28,15 +25,7 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
|
||||
<HStack space={0.5} className="flex-1 pointer-events-none" alignItems="center">
|
||||
<SidebarActions />
|
||||
<WorkspaceActionsDropdown className="pointer-events-auto" />
|
||||
<Button onClick={() => {
|
||||
dialog.show({
|
||||
title: 'Environments',
|
||||
size: 'full',
|
||||
render: () => <EnvironmentEditDialog />,
|
||||
})
|
||||
}}>
|
||||
Environments
|
||||
</Button>
|
||||
<EnvironmentActionsDropdown className="pointer-events-auto" />
|
||||
</HStack>
|
||||
<div className="pointer-events-none">
|
||||
<RecentRequestsDropdown />
|
||||
|
||||
@@ -53,6 +53,7 @@ export function Dialog({
|
||||
animate={{ top: 0, scale: 1 }}
|
||||
className={classnames(
|
||||
className,
|
||||
'h-full gap-2 grid grid-rows-[auto_minmax(0,1fr)]',
|
||||
'relative bg-gray-50 pointer-events-auto',
|
||||
'p-5 rounded-lg overflow-auto',
|
||||
'dark:border border-highlight shadow shadow-black/10',
|
||||
@@ -66,8 +67,7 @@ export function Dialog({
|
||||
{title}
|
||||
</Heading>
|
||||
{description && <p id={descriptionId}>{description}</p>}
|
||||
<div className="h-full w-full mt-4">{children}</div>
|
||||
|
||||
<div className="h-full w-full">{children}</div>
|
||||
{/*Put close at the end so that it's the last thing to be tabbed to*/}
|
||||
{!hideX && (
|
||||
<IconButton
|
||||
|
||||
19
src-web/hooks/useActiveEnvironment.ts
Normal file
19
src-web/hooks/useActiveEnvironment.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import type { Environment } from '../lib/models';
|
||||
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
|
||||
import { useEnvironments } from './useEnvironments';
|
||||
|
||||
export function useActiveEnvironment(): [Environment | null, (environment: Environment) => void] {
|
||||
const [id, setId] = useActiveEnvironmentId();
|
||||
const environments = useEnvironments();
|
||||
const environment = useMemo(
|
||||
() => environments.find((w) => w.id === id) ?? null,
|
||||
[environments, id],
|
||||
);
|
||||
|
||||
const setActiveEnvironment = useCallback((e: Environment) => {
|
||||
setId(e.id)
|
||||
}, [setId]);
|
||||
|
||||
return [environment, setActiveEnvironment];
|
||||
}
|
||||
14
src-web/hooks/useActiveEnvironmentId.ts
Normal file
14
src-web/hooks/useActiveEnvironmentId.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
export function useActiveEnvironmentId(): [string | null, (id: string) => void] {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const id = searchParams.get('environmentId') ?? null;
|
||||
|
||||
const setId = useCallback((id: string) => {
|
||||
searchParams.set('environmentId', id)
|
||||
setSearchParams(searchParams);
|
||||
}, [searchParams, setSearchParams])
|
||||
|
||||
return [id, setId];
|
||||
}
|
||||
@@ -3,20 +3,23 @@ import { invoke } from '@tauri-apps/api';
|
||||
import type { Environment } from '../lib/models';
|
||||
import { environmentsQueryKey } from './useEnvironments';
|
||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
|
||||
|
||||
export function useCreateEnvironment() {
|
||||
const workspaceId = useActiveWorkspaceId();
|
||||
const queryClient = useQueryClient();
|
||||
const [, setActiveEnvironmentId ] = useActiveEnvironmentId();
|
||||
return useMutation<Environment, unknown, Pick<Environment, 'name'>>({
|
||||
mutationFn: (patch) => {
|
||||
return invoke('create_environment', { ...patch, workspaceId });
|
||||
},
|
||||
onSuccess: async (environment) => {
|
||||
if (workspaceId == null) return;
|
||||
queryClient.setQueryData<Environment[]>(environmentsQueryKey({ workspaceId }), (environments) => [
|
||||
...(environments ?? []),
|
||||
environment,
|
||||
]);
|
||||
setActiveEnvironmentId(environment.id);
|
||||
queryClient.setQueryData<Environment[]>(
|
||||
environmentsQueryKey({ workspaceId }),
|
||||
(environments) => [...(environments ?? []), environment],
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user