mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-27 03:41:11 +01:00
Extract base environment (#149)
This commit is contained in:
@@ -59,7 +59,7 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
const [activeEnvironment, setActiveEnvironmentId] = useActiveEnvironment();
|
||||
const httpRequestActions = useHttpRequestActions();
|
||||
const workspaces = useWorkspaces();
|
||||
const environments = useEnvironments();
|
||||
const { subEnvironments } = useEnvironments();
|
||||
const recentEnvironments = useRecentEnvironments();
|
||||
const recentWorkspaces = useRecentWorkspaces();
|
||||
const requests = useRequests();
|
||||
@@ -211,7 +211,7 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
}, [recentRequests, requests]);
|
||||
|
||||
const sortedEnvironments = useMemo(() => {
|
||||
return [...environments].sort((a, b) => {
|
||||
return [...subEnvironments].sort((a, b) => {
|
||||
const aRecentIndex = recentEnvironments.indexOf(a.id);
|
||||
const bRecentIndex = recentEnvironments.indexOf(b.id);
|
||||
|
||||
@@ -225,7 +225,7 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
return a.createdAt.localeCompare(b.createdAt);
|
||||
}
|
||||
});
|
||||
}, [environments, recentEnvironments]);
|
||||
}, [subEnvironments, recentEnvironments]);
|
||||
|
||||
const sortedWorkspaces = useMemo(() => {
|
||||
return [...workspaces].sort((a, b) => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||
import { useEnvironments } from '../hooks/useEnvironments';
|
||||
import type { ButtonProps } from './core/Button';
|
||||
import { Button } from './core/Button';
|
||||
@@ -19,8 +18,7 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
|
||||
className,
|
||||
...buttonProps
|
||||
}: Props) {
|
||||
const environments = useEnvironments();
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const { subEnvironments, baseEnvironment } = useEnvironments();
|
||||
const [activeEnvironment, setActiveEnvironmentId] = useActiveEnvironment();
|
||||
const dialog = useDialog();
|
||||
|
||||
@@ -36,7 +34,7 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
|
||||
|
||||
const items: DropdownItem[] = useMemo(
|
||||
() => [
|
||||
...environments.map(
|
||||
...subEnvironments.map(
|
||||
(e) => ({
|
||||
key: e.id,
|
||||
label: e.name,
|
||||
@@ -51,7 +49,7 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
|
||||
}),
|
||||
[activeEnvironment?.id],
|
||||
),
|
||||
...((environments.length > 0
|
||||
...((subEnvironments.length > 0
|
||||
? [{ type: 'separator', label: 'Environments' }]
|
||||
: []) as DropdownItem[]),
|
||||
{
|
||||
@@ -62,11 +60,11 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
|
||||
onSelect: showEnvironmentDialog,
|
||||
},
|
||||
],
|
||||
[activeEnvironment?.id, environments, setActiveEnvironmentId, showEnvironmentDialog],
|
||||
[activeEnvironment?.id, subEnvironments, setActiveEnvironmentId, showEnvironmentDialog],
|
||||
);
|
||||
|
||||
const hasWorkspaceVars =
|
||||
(activeWorkspace?.variables ?? []).filter((v) => v.enabled && (v.name || v.value)).length > 0;
|
||||
const hasBaseVars =
|
||||
(baseEnvironment?.variables ?? []).filter((v) => v.enabled && (v.name || v.value)).length > 0;
|
||||
|
||||
return (
|
||||
<Dropdown items={items}>
|
||||
@@ -75,14 +73,14 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
|
||||
className={classNames(
|
||||
className,
|
||||
'text !px-2 truncate',
|
||||
!activeEnvironment && !hasWorkspaceVars && 'text-text-subtlest italic',
|
||||
!activeEnvironment && !hasBaseVars && 'text-text-subtlest italic',
|
||||
)}
|
||||
// If no environments, the button simply opens the dialog.
|
||||
// NOTE: We don't create a new button because we want to reuse the hotkey from the menu items
|
||||
onClick={environments.length === 0 ? showEnvironmentDialog : undefined}
|
||||
onClick={subEnvironments.length === 0 ? showEnvironmentDialog : undefined}
|
||||
{...buttonProps}
|
||||
>
|
||||
{activeEnvironment?.name ?? (hasWorkspaceVars ? 'Environment' : 'No Environment')}
|
||||
{activeEnvironment?.name ?? (hasBaseVars ? 'Environment' : 'No Environment')}
|
||||
</Button>
|
||||
</Dropdown>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import type { Environment } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
||||
import { useDeleteEnvironment } from '../hooks/useDeleteEnvironment';
|
||||
import { useEnvironments } from '../hooks/useEnvironments';
|
||||
import { useKeyValue } from '../hooks/useKeyValue';
|
||||
import { usePrompt } from '../hooks/usePrompt';
|
||||
import { useUpdateEnvironment } from '../hooks/useUpdateEnvironment';
|
||||
import { useUpdateWorkspace } from '../hooks/useUpdateWorkspace';
|
||||
import type { Environment, Workspace } from '@yaakapp-internal/models';
|
||||
import { Banner } from './core/Banner';
|
||||
import { Button } from './core/Button';
|
||||
import { ContextMenu } from './core/Dropdown';
|
||||
import type {
|
||||
@@ -31,18 +30,15 @@ interface Props {
|
||||
}
|
||||
|
||||
export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
|
||||
const [selectedEnvironmentId, setSelectedEnvironmentId] = useState<string | null>(
|
||||
initialEnvironment?.id ?? null,
|
||||
);
|
||||
const environments = useEnvironments();
|
||||
const createEnvironment = useCreateEnvironment();
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const { baseEnvironment, subEnvironments, allEnvironments } = useEnvironments();
|
||||
|
||||
const selectedEnvironment = useMemo(
|
||||
() => environments.find((e) => e.id === selectedEnvironmentId) ?? null,
|
||||
[environments, selectedEnvironmentId],
|
||||
const [selectedEnvironmentId, setSelectedEnvironmentId] = useState<string | null>(
|
||||
initialEnvironment?.id ?? baseEnvironment?.id ?? null,
|
||||
);
|
||||
|
||||
const selectedEnvironment = allEnvironments.find((e) => e.id === selectedEnvironmentId);
|
||||
|
||||
const handleCreateEnvironment = async () => {
|
||||
const e = await createEnvironment.mutateAsync();
|
||||
if (e == null) return;
|
||||
@@ -59,8 +55,8 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
|
||||
<aside className="w-full min-w-0 pt-2">
|
||||
<div className="min-w-0 h-full overflow-y-auto pt-1">
|
||||
<SidebarButton
|
||||
active={selectedEnvironment?.id == null}
|
||||
onClick={() => setSelectedEnvironmentId(null)}
|
||||
active={selectedEnvironment?.id == baseEnvironment?.id}
|
||||
onClick={() => setSelectedEnvironmentId(baseEnvironment?.id ?? null)}
|
||||
environment={null}
|
||||
rightSlot={
|
||||
<IconButton
|
||||
@@ -75,14 +71,14 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
|
||||
/>
|
||||
}
|
||||
>
|
||||
Global Variables
|
||||
{baseEnvironment?.name}
|
||||
</SidebarButton>
|
||||
{environments.length > 0 && (
|
||||
{subEnvironments.length > 0 && (
|
||||
<div className="px-2">
|
||||
<Separator className="my-3"></Separator>
|
||||
</div>
|
||||
)}
|
||||
{environments.map((e) => (
|
||||
{subEnvironments.map((e) => (
|
||||
<SidebarButton
|
||||
key={e.id}
|
||||
active={selectedEnvironment?.id === e.id}
|
||||
@@ -96,11 +92,20 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
|
||||
</aside>
|
||||
)}
|
||||
secondSlot={() =>
|
||||
activeWorkspace != null && (
|
||||
selectedEnvironmentId == null ? (
|
||||
<div className="p-3 mt-10">
|
||||
<Banner color="danger">No selected environment</Banner>
|
||||
</div>
|
||||
) : selectedEnvironment == null ? (
|
||||
<div className="p-3 mt-10">
|
||||
<Banner color="danger">
|
||||
Failed to find selected environment <InlineCode>{selectedEnvironmentId}</InlineCode>
|
||||
</Banner>
|
||||
</div>
|
||||
) : (
|
||||
<EnvironmentEditor
|
||||
className="pt-2 border-l border-border-subtle"
|
||||
environment={selectedEnvironment}
|
||||
workspace={activeWorkspace}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -110,11 +115,9 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
|
||||
|
||||
const EnvironmentEditor = function ({
|
||||
environment,
|
||||
workspace,
|
||||
className,
|
||||
}: {
|
||||
environment: Environment | null;
|
||||
workspace: Workspace;
|
||||
environment: Environment;
|
||||
className?: string;
|
||||
}) {
|
||||
const valueVisibility = useKeyValue<boolean>({
|
||||
@@ -122,37 +125,25 @@ const EnvironmentEditor = function ({
|
||||
key: 'environmentValueVisibility',
|
||||
fallback: true,
|
||||
});
|
||||
const environments = useEnvironments();
|
||||
const { subEnvironments } = useEnvironments();
|
||||
const updateEnvironment = useUpdateEnvironment(environment?.id ?? null);
|
||||
const updateWorkspace = useUpdateWorkspace(workspace.id);
|
||||
const variables = environment == null ? workspace.variables : environment.variables;
|
||||
const handleChange = useCallback<PairEditorProps['onChange']>(
|
||||
(variables) => {
|
||||
if (environment != null) {
|
||||
updateEnvironment.mutate({ variables });
|
||||
} else {
|
||||
updateWorkspace.mutate({ variables });
|
||||
}
|
||||
},
|
||||
[updateWorkspace, updateEnvironment, environment],
|
||||
(variables) => updateEnvironment.mutate({ variables }),
|
||||
[updateEnvironment],
|
||||
);
|
||||
|
||||
// Gather a list of env names from other environments, to help the user get them aligned
|
||||
const nameAutocomplete = useMemo<GenericCompletionConfig>(() => {
|
||||
const otherEnvironments = environments.filter((e) => e.id !== environment?.id);
|
||||
const allVariableNames =
|
||||
environment == null
|
||||
? [
|
||||
// Nothing to autocomplete if we're in the base environment
|
||||
]
|
||||
: [
|
||||
...workspace.variables.map((v) => v.name),
|
||||
...otherEnvironments.flatMap((e) => e.variables.map((v) => v.name)),
|
||||
];
|
||||
? [] // Nothing to autocomplete if we're in the base environment
|
||||
: subEnvironments
|
||||
.filter((e) => e.environmentId != null)
|
||||
.flatMap((e) => e.variables.map((v) => v.name));
|
||||
|
||||
// Filter out empty strings and variables that already exist
|
||||
const variableNames = allVariableNames.filter(
|
||||
(name) => name != '' && !variables.find((v) => v.name === name),
|
||||
(name) => name != '' && !environment.variables.find((v) => v.name === name),
|
||||
);
|
||||
const uniqueVariableNames = [...new Set(variableNames)];
|
||||
const options = uniqueVariableNames.map(
|
||||
@@ -162,7 +153,7 @@ const EnvironmentEditor = function ({
|
||||
}),
|
||||
);
|
||||
return { options };
|
||||
}, [environments, variables, workspace, environment]);
|
||||
}, [subEnvironments, environment]);
|
||||
|
||||
const validateName = useCallback((name: string) => {
|
||||
// Empty just means the variable doesn't have a name yet, and is unusable
|
||||
@@ -174,7 +165,7 @@ const EnvironmentEditor = function ({
|
||||
<VStack space={4} className={classNames(className, 'pl-4')}>
|
||||
<HStack space={2} className="justify-between">
|
||||
<Heading className="w-full flex items-center gap-1">
|
||||
<div>{environment?.name ?? 'Global Variables'}</div>
|
||||
<div>{environment?.name}</div>
|
||||
<IconButton
|
||||
iconClassName="text-text-subtlest"
|
||||
size="sm"
|
||||
@@ -195,8 +186,8 @@ const EnvironmentEditor = function ({
|
||||
nameValidate={validateName}
|
||||
valueType={valueVisibility.value ? 'text' : 'password'}
|
||||
valueAutocompleteVariables={true}
|
||||
forceUpdateKey={environment?.id ?? workspace?.id ?? 'n/a'}
|
||||
pairs={variables}
|
||||
forceUpdateKey={environment.id}
|
||||
pairs={environment.variables}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -28,7 +28,7 @@ export function FolderSettingsDialog({ folderId }: Props) {
|
||||
|
||||
<MarkdownEditor
|
||||
name="folder-description"
|
||||
placeholder="A Markdown description of this folder."
|
||||
placeholder="Folder description"
|
||||
className="min-h-[10rem] border border-border px-2"
|
||||
defaultValue={folder.description}
|
||||
onChange={(description) => {
|
||||
|
||||
@@ -344,7 +344,7 @@ export function GrpcConnectionSetupPane({
|
||||
<TabContent value={TAB_DESCRIPTION}>
|
||||
<MarkdownEditor
|
||||
name="request-description"
|
||||
placeholder="A Markdown description of this request."
|
||||
placeholder="Request description"
|
||||
defaultValue={activeRequest.description}
|
||||
onChange={handleDescriptionChange}
|
||||
/>
|
||||
|
||||
@@ -461,7 +461,7 @@ export const RequestPane = memo(function RequestPane({
|
||||
/>
|
||||
<MarkdownEditor
|
||||
name="request-description"
|
||||
placeholder="A Markdown description of this request."
|
||||
placeholder="Request description"
|
||||
defaultValue={activeRequest.description}
|
||||
onChange={(description) =>
|
||||
updateRequest.mutate({ id: activeRequestId, update: { description } })
|
||||
|
||||
@@ -25,7 +25,7 @@ export function WorkspaceSettingsDialog({ workspaceId }: Props) {
|
||||
|
||||
<MarkdownEditor
|
||||
name="workspace-description"
|
||||
placeholder="A Markdown description of this workspace."
|
||||
placeholder="Workspace description"
|
||||
className="min-h-[10rem] border border-border px-2"
|
||||
defaultValue={workspace.description}
|
||||
onChange={(description) => updateWorkspace.mutate({ description })}
|
||||
|
||||
Reference in New Issue
Block a user