mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-19 15:21:23 +02:00
Better environment edit dialog
This commit is contained in:
@@ -6,7 +6,7 @@ import { Dialog } from './core/Dialog';
|
|||||||
type DialogEntry = {
|
type DialogEntry = {
|
||||||
id: string;
|
id: string;
|
||||||
render: ({ hide }: { hide: () => void }) => React.ReactNode;
|
render: ({ hide }: { hide: () => void }) => React.ReactNode;
|
||||||
} & Pick<DialogProps, 'title' | 'description' | 'hideX' | 'className' | 'size'>;
|
} & Pick<DialogProps, 'title' | 'description' | 'hideX' | 'className' | 'size' | 'noPadding'>;
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
dialogs: DialogEntry[];
|
dialogs: DialogEntry[];
|
||||||
|
|||||||
@@ -27,7 +27,10 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
|
|||||||
const showEnvironmentDialog = useCallback(() => {
|
const showEnvironmentDialog = useCallback(() => {
|
||||||
dialog.toggle({
|
dialog.toggle({
|
||||||
id: 'environment-editor',
|
id: 'environment-editor',
|
||||||
title: 'Manage Environments',
|
hideX: true,
|
||||||
|
noPadding: true,
|
||||||
|
size: 'lg',
|
||||||
|
className: 'h-[80vh]',
|
||||||
render: () => <EnvironmentEditDialog initialEnvironment={activeEnvironment} />,
|
render: () => <EnvironmentEditDialog initialEnvironment={activeEnvironment} />,
|
||||||
});
|
});
|
||||||
}, [dialog, activeEnvironment]);
|
}, [dialog, activeEnvironment]);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
import { useWindowSize } from 'react-use';
|
|
||||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||||
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
||||||
import { useDeleteEnvironment } from '../hooks/useDeleteEnvironment';
|
import { useDeleteEnvironment } from '../hooks/useDeleteEnvironment';
|
||||||
@@ -11,17 +10,19 @@ import { useUpdateEnvironment } from '../hooks/useUpdateEnvironment';
|
|||||||
import { useUpdateWorkspace } from '../hooks/useUpdateWorkspace';
|
import { useUpdateWorkspace } from '../hooks/useUpdateWorkspace';
|
||||||
import type { Environment, Workspace } from '../lib/models';
|
import type { Environment, Workspace } from '../lib/models';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import type { DropdownItem } from './core/Dropdown';
|
import { ContextMenu } from './core/Dropdown';
|
||||||
import { Dropdown } from './core/Dropdown';
|
|
||||||
import type {
|
import type {
|
||||||
GenericCompletionConfig,
|
GenericCompletionConfig,
|
||||||
GenericCompletionOption,
|
GenericCompletionOption,
|
||||||
} from './core/Editor/genericCompletion';
|
} from './core/Editor/genericCompletion';
|
||||||
|
import { Heading } from './core/Heading';
|
||||||
import { Icon } from './core/Icon';
|
import { Icon } from './core/Icon';
|
||||||
import { IconButton } from './core/IconButton';
|
import { IconButton } from './core/IconButton';
|
||||||
import { InlineCode } from './core/InlineCode';
|
import { InlineCode } from './core/InlineCode';
|
||||||
import type { PairEditorProps } from './core/PairEditor';
|
import type { PairEditorProps } from './core/PairEditor';
|
||||||
import { PairEditor } from './core/PairEditor';
|
import { PairEditor } from './core/PairEditor';
|
||||||
|
import { Separator } from './core/Separator';
|
||||||
|
import { SplitLayout } from './core/SplitLayout';
|
||||||
import { HStack, VStack } from './core/Stacks';
|
import { HStack, VStack } from './core/Stacks';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -36,9 +37,6 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
|
|||||||
const createEnvironment = useCreateEnvironment();
|
const createEnvironment = useCreateEnvironment();
|
||||||
const activeWorkspace = useActiveWorkspace();
|
const activeWorkspace = useActiveWorkspace();
|
||||||
|
|
||||||
const windowSize = useWindowSize();
|
|
||||||
const showSidebar = windowSize.width > 500;
|
|
||||||
|
|
||||||
const selectedEnvironment = useMemo(
|
const selectedEnvironment = useMemo(
|
||||||
() => environments.find((e) => e.id === selectedEnvironmentId) ?? null,
|
() => environments.find((e) => e.id === selectedEnvironmentId) ?? null,
|
||||||
[environments, selectedEnvironmentId],
|
[environments, selectedEnvironmentId],
|
||||||
@@ -50,60 +48,72 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<SplitLayout
|
||||||
className={classNames(
|
name="env_editor"
|
||||||
'h-full pt-1 grid gap-x-8 grid-rows-[minmax(0,1fr)]',
|
defaultRatio={0.75}
|
||||||
showSidebar ? 'grid-cols-[auto_minmax(0,1fr)]' : 'grid-cols-[minmax(0,1fr)]',
|
layout="horizontal"
|
||||||
)}
|
className="gap-0"
|
||||||
>
|
firstSlot={() => (
|
||||||
{showSidebar && (
|
<aside className="w-full min-w-0 pt-2">
|
||||||
<aside className="grid grid-rows-[minmax(0,1fr)_auto] gap-y-0.5 h-full max-w-[250px] pr-3 border-r border-gray-100 -ml-2 pb-4">
|
<div className="min-w-0 h-full overflow-y-auto pt-1">
|
||||||
<div className="min-w-0 h-full w-full overflow-y-scroll">
|
|
||||||
<SidebarButton
|
<SidebarButton
|
||||||
active={selectedEnvironment?.id == null}
|
active={selectedEnvironment?.id == null}
|
||||||
onClick={() => setSelectedEnvironmentId(null)}
|
onClick={() => setSelectedEnvironmentId(null)}
|
||||||
|
className="group"
|
||||||
|
environment={null}
|
||||||
|
rightSlot={
|
||||||
|
<IconButton
|
||||||
|
size="sm"
|
||||||
|
title="Add sub environment"
|
||||||
|
icon="plusCircle"
|
||||||
|
iconClassName="text-gray-500 group-hover:text-gray-700"
|
||||||
|
onClick={handleCreateEnvironment}
|
||||||
|
/>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Global
|
Global Variables
|
||||||
</SidebarButton>
|
</SidebarButton>
|
||||||
|
<div className="px-2">
|
||||||
|
<Separator className="my-3"></Separator>
|
||||||
|
</div>
|
||||||
{environments.map((e) => (
|
{environments.map((e) => (
|
||||||
<SidebarButton
|
<SidebarButton
|
||||||
key={e.id}
|
key={e.id}
|
||||||
active={selectedEnvironment?.id === e.id}
|
active={selectedEnvironment?.id === e.id}
|
||||||
|
environment={e}
|
||||||
onClick={() => setSelectedEnvironmentId(e.id)}
|
onClick={() => setSelectedEnvironmentId(e.id)}
|
||||||
>
|
>
|
||||||
→ {e.name}
|
{e.name}
|
||||||
</SidebarButton>
|
</SidebarButton>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
className="w-full text-center"
|
|
||||||
color="gray"
|
|
||||||
justify="center"
|
|
||||||
onClick={handleCreateEnvironment}
|
|
||||||
>
|
|
||||||
New Environment
|
|
||||||
</Button>
|
|
||||||
</aside>
|
</aside>
|
||||||
)}
|
)}
|
||||||
{activeWorkspace != null && (
|
secondSlot={() =>
|
||||||
<EnvironmentEditor environment={selectedEnvironment} workspace={activeWorkspace} />
|
activeWorkspace != null && (
|
||||||
)}
|
<EnvironmentEditor
|
||||||
</div>
|
className="pt-2 border-l border-highlight"
|
||||||
|
environment={selectedEnvironment}
|
||||||
|
workspace={activeWorkspace}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const EnvironmentEditor = function ({
|
const EnvironmentEditor = function ({
|
||||||
environment,
|
environment,
|
||||||
workspace,
|
workspace,
|
||||||
|
className,
|
||||||
}: {
|
}: {
|
||||||
environment: Environment | null;
|
environment: Environment | null;
|
||||||
workspace: Workspace;
|
workspace: Workspace;
|
||||||
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
const environments = useEnvironments();
|
const environments = useEnvironments();
|
||||||
const updateEnvironment = useUpdateEnvironment(environment?.id ?? 'n/a');
|
const updateEnvironment = useUpdateEnvironment(environment?.id ?? null);
|
||||||
const updateWorkspace = useUpdateWorkspace(workspace.id);
|
const updateWorkspace = useUpdateWorkspace(workspace.id);
|
||||||
const deleteEnvironment = useDeleteEnvironment(environment);
|
|
||||||
const variables = environment == null ? workspace.variables : environment.variables;
|
const variables = environment == null ? workspace.variables : environment.variables;
|
||||||
const handleChange = useCallback<PairEditorProps['onChange']>(
|
const handleChange = useCallback<PairEditorProps['onChange']>(
|
||||||
(variables) => {
|
(variables) => {
|
||||||
@@ -143,12 +153,96 @@ const EnvironmentEditor = function ({
|
|||||||
return { options };
|
return { options };
|
||||||
}, [environments, variables, workspace, environment]);
|
}, [environments, variables, workspace, environment]);
|
||||||
|
|
||||||
|
const validateName = useCallback((name: string) => {
|
||||||
|
// Empty just means the variable doesn't have a name yet, and is unusable
|
||||||
|
if (name === '') return true;
|
||||||
|
return name.match(/^[a-z_][a-z0-9_]*$/i) != null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VStack space={4} className={classNames(className, 'pl-4')}>
|
||||||
|
<HStack space={2} className="justify-between">
|
||||||
|
<Heading className="w-full flex items-center">
|
||||||
|
<div>{environment?.name ?? 'Global Variables'}</div>
|
||||||
|
{environment == null && (
|
||||||
|
<span className="pr-3 text-sm text-gray-600 pl-2 font-normal italic ml-auto">
|
||||||
|
Always available no matter which environment is active
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Heading>
|
||||||
|
</HStack>
|
||||||
|
<PairEditor
|
||||||
|
nameAutocomplete={nameAutocomplete}
|
||||||
|
nameAutocompleteVariables={false}
|
||||||
|
namePlaceholder="VAR_NAME"
|
||||||
|
nameValidate={validateName}
|
||||||
|
valueAutocompleteVariables={false}
|
||||||
|
forceUpdateKey={environment?.id ?? workspace?.id ?? 'n/a'}
|
||||||
|
pairs={variables}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</VStack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function SidebarButton({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
active,
|
||||||
|
onClick,
|
||||||
|
rightSlot,
|
||||||
|
environment,
|
||||||
|
}: {
|
||||||
|
className?: string;
|
||||||
|
children: ReactNode;
|
||||||
|
active: boolean;
|
||||||
|
onClick: () => void;
|
||||||
|
rightSlot?: ReactNode;
|
||||||
|
environment: Environment | null;
|
||||||
|
}) {
|
||||||
const prompt = usePrompt();
|
const prompt = usePrompt();
|
||||||
const items = useMemo<DropdownItem[] | null>(
|
const updateEnvironment = useUpdateEnvironment(environment?.id ?? null);
|
||||||
() =>
|
const deleteEnvironment = useDeleteEnvironment(environment);
|
||||||
environment == null
|
const [showContextMenu, setShowContextMenu] = useState<{
|
||||||
? null
|
x: number;
|
||||||
: [
|
y: number;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const handleContextMenu = useCallback((e: React.MouseEvent<HTMLElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setShowContextMenu({ x: e.clientX, y: e.clientY });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
className,
|
||||||
|
'w-full grid grid-cols-[minmax(0,1fr)_auto] items-center',
|
||||||
|
'px-1', // Padding to show focus border
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
color="custom"
|
||||||
|
size="xs"
|
||||||
|
className={classNames(
|
||||||
|
'w-full',
|
||||||
|
active ? 'text-gray-800' : 'text-gray-600 hover:text-gray-700',
|
||||||
|
)}
|
||||||
|
justify="start"
|
||||||
|
onClick={onClick}
|
||||||
|
onContextMenu={handleContextMenu}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Button>
|
||||||
|
{rightSlot}
|
||||||
|
</div>
|
||||||
|
{environment != null && (
|
||||||
|
<ContextMenu
|
||||||
|
show={showContextMenu}
|
||||||
|
onClose={() => setShowContextMenu(null)}
|
||||||
|
items={[
|
||||||
{
|
{
|
||||||
key: 'rename',
|
key: 'rename',
|
||||||
label: 'Rename',
|
label: 'Rename',
|
||||||
@@ -177,68 +271,9 @@ const EnvironmentEditor = function ({
|
|||||||
leftSlot: <Icon icon="trash" size="sm" />,
|
leftSlot: <Icon icon="trash" size="sm" />,
|
||||||
onSelect: () => deleteEnvironment.mutate(),
|
onSelect: () => deleteEnvironment.mutate(),
|
||||||
},
|
},
|
||||||
],
|
]}
|
||||||
[deleteEnvironment, updateEnvironment, prompt, environment],
|
/>
|
||||||
);
|
|
||||||
|
|
||||||
const validateName = useCallback((name: string) => {
|
|
||||||
// Empty just means the variable doesn't have a name yet, and is unusable
|
|
||||||
if (name === '') return true;
|
|
||||||
return name.match(/^[a-z_][a-z0-9_]*$/i) != null;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<VStack space={2}>
|
|
||||||
<HStack space={2} className="justify-between">
|
|
||||||
<h1 className="text-xl">{environment?.name ?? 'Global Environment'}</h1>
|
|
||||||
{items != null && (
|
|
||||||
<Dropdown items={items}>
|
|
||||||
<IconButton
|
|
||||||
icon="moreVertical"
|
|
||||||
title="Environment Actions"
|
|
||||||
size="sm"
|
|
||||||
className="!h-auto w-8"
|
|
||||||
/>
|
|
||||||
</Dropdown>
|
|
||||||
)}
|
|
||||||
</HStack>
|
|
||||||
<PairEditor
|
|
||||||
nameAutocomplete={nameAutocomplete}
|
|
||||||
nameAutocompleteVariables={false}
|
|
||||||
namePlaceholder="VAR_NAME"
|
|
||||||
nameValidate={validateName}
|
|
||||||
valueAutocompleteVariables={false}
|
|
||||||
forceUpdateKey={environment?.id ?? workspace?.id ?? 'n/a'}
|
|
||||||
pairs={variables}
|
|
||||||
onChange={handleChange}
|
|
||||||
/>
|
|
||||||
</VStack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function SidebarButton({
|
|
||||||
children,
|
|
||||||
className,
|
|
||||||
active,
|
|
||||||
onClick,
|
|
||||||
}: {
|
|
||||||
className?: string;
|
|
||||||
children: ReactNode;
|
|
||||||
active: boolean;
|
|
||||||
onClick: () => void;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
tabIndex={active ? 0 : -1}
|
|
||||||
onClick={onClick}
|
|
||||||
className={classNames(
|
|
||||||
className,
|
|
||||||
'flex items-center text-sm text-left w-full mb-1 h-xs rounded px-2',
|
|
||||||
'text-gray-600 hocus:text-gray-800 focus:bg-highlightSecondary outline-none',
|
|
||||||
active && '!text-gray-900',
|
|
||||||
)}
|
)}
|
||||||
>
|
</>
|
||||||
{children}
|
|
||||||
</button>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }:
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SplitLayout
|
<SplitLayout
|
||||||
forceVertical
|
layout="vertical"
|
||||||
style={style}
|
style={style}
|
||||||
name={methodType === 'unary' ? 'grpc_messages_unary' : 'grpc_messages_streaming'}
|
name={methodType === 'unary' ? 'grpc_messages_unary' : 'grpc_messages_streaming'}
|
||||||
defaultRatio={methodType === 'unary' ? 0.75 : 0.3}
|
defaultRatio={methodType === 'unary' ? 0.75 : 0.3}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export function ResizeHandle({
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'group z-10 flex',
|
'group z-10 flex',
|
||||||
|
// 'bg-blue-100/10', // For debugging
|
||||||
vertical ? 'w-full h-3 cursor-row-resize' : 'h-full w-3 cursor-col-resize',
|
vertical ? 'w-full h-3 cursor-row-resize' : 'h-full w-3 cursor-col-resize',
|
||||||
justify === 'center' && 'justify-center',
|
justify === 'center' && 'justify-center',
|
||||||
justify === 'end' && 'justify-end',
|
justify === 'end' && 'justify-end',
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
|
|||||||
classNames(
|
classNames(
|
||||||
className,
|
className,
|
||||||
'max-w-full min-w-0', // Help with truncation
|
'max-w-full min-w-0', // Help with truncation
|
||||||
|
'hocus:opacity-100', // Force opacity for certain hover effects
|
||||||
'whitespace-nowrap outline-none',
|
'whitespace-nowrap outline-none',
|
||||||
'flex-shrink-0 flex items-center',
|
'flex-shrink-0 flex items-center',
|
||||||
'focus-visible-or-class:ring rounded-md',
|
'focus-visible-or-class:ring rounded-md',
|
||||||
|
|||||||
@@ -14,8 +14,9 @@ export interface DialogProps {
|
|||||||
title?: ReactNode;
|
title?: ReactNode;
|
||||||
description?: ReactNode;
|
description?: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
size?: 'sm' | 'md' | 'full' | 'dynamic';
|
size?: 'sm' | 'md' | 'lg' | 'full' | 'dynamic';
|
||||||
hideX?: boolean;
|
hideX?: boolean;
|
||||||
|
noPadding?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Dialog({
|
export function Dialog({
|
||||||
@@ -27,6 +28,7 @@ export function Dialog({
|
|||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
hideX,
|
hideX,
|
||||||
|
noPadding,
|
||||||
}: DialogProps) {
|
}: DialogProps) {
|
||||||
const titleId = useMemo(() => Math.random().toString(36).slice(2), []);
|
const titleId = useMemo(() => Math.random().toString(36).slice(2), []);
|
||||||
const descriptionId = useMemo(
|
const descriptionId = useMemo(
|
||||||
@@ -50,30 +52,36 @@ export function Dialog({
|
|||||||
animate={{ top: 0, scale: 1 }}
|
animate={{ top: 0, scale: 1 }}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'gap-2 grid grid-rows-[auto_minmax(0,1fr)]',
|
'grid grid-rows-[auto_minmax(0,1fr)]',
|
||||||
'pt-4 relative bg-gray-50 pointer-events-auto',
|
'relative bg-gray-50 pointer-events-auto',
|
||||||
'rounded-lg',
|
'rounded-lg',
|
||||||
'dark:border border-highlight shadow shadow-black/10',
|
'dark:border border-highlight shadow shadow-black/10',
|
||||||
'max-w-[calc(100vw-5rem)] max-h-[calc(100vh-6rem)]',
|
'max-w-[calc(100vw-5rem)] max-h-[calc(100vh-6rem)]',
|
||||||
size === 'sm' && 'w-[25rem] max-h-[80vh]',
|
size === 'sm' && 'w-[25rem] max-h-[80vh]',
|
||||||
size === 'md' && 'w-[45rem] max-h-[80vh]',
|
size === 'md' && 'w-[45rem] max-h-[80vh]',
|
||||||
|
size === 'lg' && 'w-[65rem] max-h-[80vh]',
|
||||||
size === 'full' && 'w-[100vw] h-[100vh]',
|
size === 'full' && 'w-[100vw] h-[100vh]',
|
||||||
size === 'dynamic' && 'min-w-[30vw] max-w-[80vw]',
|
size === 'dynamic' && 'min-w-[30vw] max-w-[80vw]',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{title ? (
|
{title ? (
|
||||||
<Heading className="px-6 pt-4" size={1} id={titleId}>
|
<Heading className="px-6 mt-4 mb-2" size={1} id={titleId}>
|
||||||
{title}
|
{title}
|
||||||
</Heading>
|
</Heading>
|
||||||
) : (
|
) : (
|
||||||
<span />
|
<span />
|
||||||
)}
|
)}
|
||||||
{description && (
|
{description && (
|
||||||
<p className="px-6" id={descriptionId}>
|
<p className="px-6 text-gray-700" id={descriptionId}>
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<div className="h-full w-full grid grid-cols-[minmax(0,1fr)] overflow-y-auto px-6 py-2">
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'h-full w-full grid grid-cols-[minmax(0,1fr)] overflow-y-auto',
|
||||||
|
!noPadding && 'px-6 py-2',
|
||||||
|
)}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -329,7 +329,7 @@ const FormRow = memo(function FormRow({
|
|||||||
'justify-center opacity-0 group-hover:opacity-70',
|
'justify-center opacity-0 group-hover:opacity-70',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Icon icon="gripVertical" className="pointer-events-none" />
|
<Icon size="sm" icon="gripVertical" className="pointer-events-none" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<span className="w-3" />
|
<span className="w-3" />
|
||||||
@@ -425,6 +425,7 @@ const FormRow = memo(function FormRow({
|
|||||||
color="custom"
|
color="custom"
|
||||||
icon={!isLast ? 'trash' : 'empty'}
|
icon={!isLast ? 'trash' : 'empty'}
|
||||||
size="sm"
|
size="sm"
|
||||||
|
iconSize="sm"
|
||||||
title="Delete header"
|
title="Delete header"
|
||||||
onClick={!isLast ? handleDelete : undefined}
|
onClick={!isLast ? handleDelete : undefined}
|
||||||
className="ml-0.5 opacity-0 group-hover:!opacity-100 focus-visible:!opacity-100"
|
className="ml-0.5 opacity-0 group-hover:!opacity-100 focus-visible:!opacity-100"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ interface Props {
|
|||||||
defaultRatio?: number;
|
defaultRatio?: number;
|
||||||
minHeightPx?: number;
|
minHeightPx?: number;
|
||||||
minWidthPx?: number;
|
minWidthPx?: number;
|
||||||
forceVertical?: boolean;
|
layout?: 'responsive' | 'vertical' | 'horizontal';
|
||||||
}
|
}
|
||||||
|
|
||||||
const areaL = { gridArea: 'left' };
|
const areaL = { gridArea: 'left' };
|
||||||
@@ -38,13 +38,13 @@ export function SplitLayout({
|
|||||||
secondSlot,
|
secondSlot,
|
||||||
className,
|
className,
|
||||||
name,
|
name,
|
||||||
forceVertical,
|
layout = 'responsive',
|
||||||
defaultRatio = 0.5,
|
defaultRatio = 0.5,
|
||||||
minHeightPx = 10,
|
minHeightPx = 10,
|
||||||
minWidthPx = 10,
|
minWidthPx = 10,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const [vertical, setVertical] = useState<boolean>(false);
|
const [verticalBasedOnSize, setVerticalBasedOnSize] = useState<boolean>(false);
|
||||||
const [widthRaw, setWidth] = useLocalStorage<number>(`${name}_width::${useActiveWorkspaceId()}`);
|
const [widthRaw, setWidth] = useLocalStorage<number>(`${name}_width::${useActiveWorkspaceId()}`);
|
||||||
const [heightRaw, setHeight] = useLocalStorage<number>(
|
const [heightRaw, setHeight] = useLocalStorage<number>(
|
||||||
`${name}_height::${useActiveWorkspaceId()}`,
|
`${name}_height::${useActiveWorkspaceId()}`,
|
||||||
@@ -62,26 +62,27 @@ export function SplitLayout({
|
|||||||
}
|
}
|
||||||
|
|
||||||
useResizeObserver(containerRef.current, ({ contentRect }) => {
|
useResizeObserver(containerRef.current, ({ contentRect }) => {
|
||||||
setVertical(contentRect.width < STACK_VERTICAL_WIDTH);
|
setVerticalBasedOnSize(contentRect.width < STACK_VERTICAL_WIDTH);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const vertical = layout !== 'horizontal' && (layout === 'vertical' || verticalBasedOnSize);
|
||||||
|
|
||||||
const styles = useMemo<CSSProperties>(() => {
|
const styles = useMemo<CSSProperties>(() => {
|
||||||
return {
|
return {
|
||||||
...style,
|
...style,
|
||||||
gridTemplate:
|
gridTemplate: vertical
|
||||||
forceVertical || vertical
|
? `
|
||||||
? `
|
|
||||||
' ${areaL.gridArea}' minmax(0,${1 - height}fr)
|
' ${areaL.gridArea}' minmax(0,${1 - height}fr)
|
||||||
' ${areaD.gridArea}' 0
|
' ${areaD.gridArea}' 0
|
||||||
' ${areaR.gridArea}' minmax(${minHeightPx}px,${height}fr)
|
' ${areaR.gridArea}' minmax(${minHeightPx}px,${height}fr)
|
||||||
/ 1fr
|
/ 1fr
|
||||||
`
|
`
|
||||||
: `
|
: `
|
||||||
' ${areaL.gridArea} ${areaD.gridArea} ${areaR.gridArea}' minmax(0,1fr)
|
' ${areaL.gridArea} ${areaD.gridArea} ${areaR.gridArea}' minmax(0,1fr)
|
||||||
/ ${1 - width}fr 0 ${width}fr
|
/ ${1 - width}fr 0 ${width}fr
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
}, [forceVertical, style, vertical, height, minHeightPx, width]);
|
}, [style, vertical, height, minHeightPx, width]);
|
||||||
|
|
||||||
const unsub = () => {
|
const unsub = () => {
|
||||||
if (moveState.current !== null) {
|
if (moveState.current !== null) {
|
||||||
@@ -154,7 +155,7 @@ export function SplitLayout({
|
|||||||
<ResizeHandle
|
<ResizeHandle
|
||||||
style={areaD}
|
style={areaD}
|
||||||
isResizing={isResizing}
|
isResizing={isResizing}
|
||||||
className={classNames(vertical ? 'translate-y-0.5' : 'translate-x-0.5')}
|
className={classNames(vertical ? '-translate-y-1.5' : '-translate-x-1.5')}
|
||||||
onResizeStart={handleResizeStart}
|
onResizeStart={handleResizeStart}
|
||||||
onReset={handleReset}
|
onReset={handleReset}
|
||||||
side={vertical ? 'top' : 'left'}
|
side={vertical ? 'top' : 'left'}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export function useCreateEnvironment() {
|
|||||||
id: 'new-environment',
|
id: 'new-environment',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
title: 'New Environment',
|
title: 'New Environment',
|
||||||
|
description: 'Create multiple environments with different sets of variables',
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
placeholder: 'My Environment',
|
placeholder: 'My Environment',
|
||||||
defaultValue: 'My Environment',
|
defaultValue: 'My Environment',
|
||||||
|
|||||||
Reference in New Issue
Block a user