Started on environment edit dialog

This commit is contained in:
Gregory Schier
2023-10-23 21:00:36 -07:00
parent 0d27c17e28
commit e74f9f33c0
9 changed files with 125 additions and 50 deletions

View File

@@ -343,6 +343,22 @@ async fn create_workspace(
emit_and_return(&window, "created_model", created_workspace) emit_and_return(&window, "created_model", created_workspace)
} }
#[tauri::command]
async fn create_environment(
workspace_id: &str,
name: &str,
window: Window<Wry>,
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<models::Environment, String> {
let pool = &*db_instance.lock().await;
let data: HashMap<String, JsonValue> = HashMap::new();
let created_environment = models::create_environment(workspace_id, name, data, pool)
.await
.expect("Failed to create environment");
emit_and_return(&window, "created_model", created_environment)
}
#[tauri::command] #[tauri::command]
async fn create_request( async fn create_request(
workspace_id: &str, workspace_id: &str,
@@ -639,6 +655,7 @@ fn main() {
}) })
}) })
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
create_environment,
create_request, create_request,
create_workspace, create_workspace,
delete_all_responses, delete_all_responses,

View File

@@ -5,7 +5,6 @@ import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend'; import { HTML5Backend } from 'react-dnd-html5-backend';
import { HelmetProvider } from 'react-helmet-async'; import { HelmetProvider } from 'react-helmet-async';
import { AppRouter } from './AppRouter'; import { AppRouter } from './AppRouter';
import { DialogProvider } from './DialogContext';
const queryClient = new QueryClient({ const queryClient = new QueryClient({
logger: undefined, logger: undefined,
@@ -24,12 +23,10 @@ export function App() {
<MotionConfig transition={{ duration: 0.1 }}> <MotionConfig transition={{ duration: 0.1 }}>
<HelmetProvider> <HelmetProvider>
<DndProvider backend={HTML5Backend}> <DndProvider backend={HTML5Backend}>
<DialogProvider>
<Suspense> <Suspense>
<AppRouter /> <AppRouter />
{/*<ReactQueryDevtools initialIsOpen={false} />*/} {/*<ReactQueryDevtools initialIsOpen={false} />*/}
</Suspense> </Suspense>
</DialogProvider>
</DndProvider> </DndProvider>
</HelmetProvider> </HelmetProvider>
</MotionConfig> </MotionConfig>

View File

@@ -6,6 +6,7 @@ import { GlobalHooks } from './GlobalHooks';
import RouteError from './RouteError'; import RouteError from './RouteError';
import Workspace from './Workspace'; import Workspace from './Workspace';
import Workspaces from './Workspaces'; import Workspaces from './Workspaces';
import { DialogProvider } from './DialogContext';
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
@@ -59,9 +60,9 @@ function WorkspaceOrRedirect() {
function Layout() { function Layout() {
return ( return (
<> <DialogProvider>
<Outlet /> <Outlet />
<GlobalHooks /> <GlobalHooks />
</> </DialogProvider>
); );
} }

View File

@@ -1,6 +1,8 @@
import React, { createContext, useContext, useMemo, useState } from 'react'; import React, { createContext, useContext, useMemo, useState } from 'react';
import type { DialogProps } from './core/Dialog'; import type { DialogProps } from './core/Dialog';
import { Dialog } from './core/Dialog'; import { Dialog } from './core/Dialog';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId';
type DialogEntry = { type DialogEntry = {
id: string; id: string;
@@ -54,9 +56,11 @@ export const DialogProvider = ({ children }: { children: React.ReactNode }) => {
function DialogInstance({ id, render, ...props }: DialogEntry) { function DialogInstance({ id, render, ...props }: DialogEntry) {
const { actions } = useContext(DialogContext); const { actions } = useContext(DialogContext);
const children = render({ hide: () => actions.hide(id) });
console.log("ACITEV WORKSPAXCE ID 2", useActiveWorkspaceId());
return ( return (
<Dialog open onClose={() => actions.hide(id)} {...props}> <Dialog open onClose={() => actions.hide(id)} {...props}>
{render({ hide: () => actions.hide(id) })} {children}
</Dialog> </Dialog>
); );
} }

View File

@@ -0,0 +1,69 @@
import { useState } from 'react';
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
import { useEnvironments } from '../hooks/useEnvironments';
import { usePrompt } from '../hooks/usePrompt';
import { useUpdateEnvironment } from '../hooks/useUpdateEnvironment';
import type { Environment } from '../lib/models';
import { Button } from './core/Button';
import { Editor } from './core/Editor';
import classnames from 'classnames';
export const EnvironmentEditDialog = function () {
const prompt = usePrompt();
const environments = useEnvironments();
const createEnvironment = useCreateEnvironment();
const [activeEnvironment, setActiveEnvironment] = useState<Environment | null>(null);
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">
{environments.map((e) => (
<Button
className={classnames('w-full', activeEnvironment?.id === e.id && 'bg-highlight')}
justify="start"
key={e.id}
onClick={() => {
setActiveEnvironment(e);
}}
>
{e.name}
</Button>
))}
<Button
color="gray"
onClick={async () => {
const name = await prompt({
title: 'Environment Name',
defaultValue: 'My Env',
label: 'Name',
name: 'environment',
});
createEnvironment.mutate({ name });
}}
>
Create Environment
</Button>
</aside>
{activeEnvironment != null && <EnvironmentEditor environment={activeEnvironment} />}
</div>
);
};
const EnvironmentEditor = function ({ environment }: { environment: Environment }) {
const updateEnvironment = useUpdateEnvironment(environment.id);
return (
<Editor
contentType="application/json"
className="w-full min-h-[40px] !bg-gray-50"
defaultValue={JSON.stringify(environment.data, null, 2)}
forceUpdateKey={environment.id}
onChange={(data) => {
try {
updateEnvironment.mutate({ data: JSON.parse(data) });
} catch (err) {
// That's okay
}
}}
/>
);
};

View File

@@ -40,7 +40,7 @@ export function Overlay({ zIndex = 30, open, onClose, portalName, children }: Pr
{children} {children}
</motion.div> </motion.div>
</FocusTrap> </FocusTrap>
)} )}
</Portal> </Portal>
); );
} }

View File

@@ -9,17 +9,13 @@ import { SidebarActions } from './SidebarActions';
import { WorkspaceActionsDropdown } from './WorkspaceActionsDropdown'; import { WorkspaceActionsDropdown } from './WorkspaceActionsDropdown';
import { Button } from './core/Button'; import { Button } from './core/Button';
import { useDialog } from './DialogContext'; import { useDialog } from './DialogContext';
import { useEnvironments } from '../hooks/useEnvironments'; import { EnvironmentEditDialog } from './EnvironmentEditDialog';
import type { Environment } from '../lib/models';
import { Editor } from './core/Editor';
import { useUpdateEnvironment } from '../hooks/useUpdateEnvironment';
interface Props { interface Props {
className?: string; className?: string;
} }
export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Props) { export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Props) {
const environments = useEnvironments();
const activeRequest = useActiveRequest(); const activeRequest = useActiveRequest();
const dialog = useDialog(); const dialog = useDialog();
@@ -35,14 +31,8 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
<Button onClick={() => { <Button onClick={() => {
dialog.show({ dialog.show({
title: 'Environments', title: 'Environments',
render: () => <div> size: 'full',
{environments.map(e => ( render: () => <EnvironmentEditDialog />,
<EnvironmentList
key={e.id}
environment={e}
/>
))}
</div>
}) })
}}> }}>
Environments Environments
@@ -66,28 +56,3 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
</HStack> </HStack>
); );
}); });
interface EnvironmentListProps {
environment: Environment;
}
const EnvironmentList = function({ environment }: EnvironmentListProps) {
const updateEnvironment = useUpdateEnvironment(environment.id)
return (
<div>
<h1>{environment.name}</h1>
<Editor
contentType="application/json"
className='w-full h-[400px] !bg-gray-50'
defaultValue={JSON.stringify(environment.data, null, 2)}
onChange={data => {
try {
updateEnvironment.mutate({ data: JSON.parse(data) });
} catch (err) {
// That's okay
}
}}
/>
</div>
);
};

View File

@@ -54,11 +54,11 @@ export function Dialog({
className={classnames( className={classnames(
className, className,
'relative bg-gray-50 pointer-events-auto', 'relative bg-gray-50 pointer-events-auto',
'max-h-[80vh] p-5 rounded-lg overflow-auto', 'p-5 rounded-lg overflow-auto',
'dark:border border-highlight shadow shadow-black/10', 'dark:border border-highlight shadow shadow-black/10',
size === 'sm' && 'w-[25rem]', size === 'sm' && 'w-[25rem] max-h-[80vh]',
size === 'md' && 'w-[45rem]', size === 'md' && 'w-[45rem] max-h-[80vh]',
size === 'full' && 'w-[80vw]', size === 'full' && 'w-[calc(100vw-8em)] h-[calc(100vh-8em)]',
size === 'dynamic' && 'min-w-[30vw] max-w-[80vw]', size === 'dynamic' && 'min-w-[30vw] max-w-[80vw]',
)} )}
> >
@@ -66,7 +66,7 @@ export function Dialog({
{title} {title}
</Heading> </Heading>
{description && <p id={descriptionId}>{description}</p>} {description && <p id={descriptionId}>{description}</p>}
<div className="mt-4">{children}</div> <div className="h-full w-full mt-4">{children}</div>
{/*Put close at the end so that it's the last thing to be tabbed to*/} {/*Put close at the end so that it's the last thing to be tabbed to*/}
{!hideX && ( {!hideX && (

View File

@@ -0,0 +1,22 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api';
import type { Environment } from '../lib/models';
import { environmentsQueryKey } from './useEnvironments';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
export function useCreateEnvironment() {
const workspaceId = useActiveWorkspaceId();
const queryClient = useQueryClient();
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,
]);
},
});
}