import type { Environment } from '@yaakapp-internal/models'; import type { GenericCompletionOption } from '@yaakapp-internal/plugins'; import classNames from 'classnames'; import type { ReactNode } from 'react'; import React, { useCallback, useMemo, useState } from 'react'; import { useCreateEnvironment } from '../hooks/useCreateEnvironment'; import { useDeleteEnvironment } from '../hooks/useDeleteEnvironment'; import { useEnvironments } from '../hooks/useEnvironments'; import { useKeyValue } from '../hooks/useKeyValue'; import { useUpdateEnvironment } from '../hooks/useUpdateEnvironment'; import { showPrompt } from '../lib/prompt'; import { Banner } from './core/Banner'; import { Button } from './core/Button'; import { ContextMenu } from './core/Dropdown'; import type { GenericCompletionConfig } from './core/Editor/genericCompletion'; import { Heading } from './core/Heading'; import { Icon } from './core/Icon'; import { IconButton } from './core/IconButton'; import { InlineCode } from './core/InlineCode'; import type { PairEditorProps } from './core/PairEditor'; import { PairOrBulkEditor } from './core/PairOrBulkEditor'; import { Separator } from './core/Separator'; import { SplitLayout } from './core/SplitLayout'; import { HStack, VStack } from './core/Stacks'; interface Props { initialEnvironment: Environment | null; } export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) { const createEnvironment = useCreateEnvironment(); const { baseEnvironment, subEnvironments, allEnvironments } = useEnvironments(); const [selectedEnvironmentId, setSelectedEnvironmentId] = useState( initialEnvironment?.id ?? null, ); const selectedEnvironment = selectedEnvironmentId != null ? allEnvironments.find((e) => e.id === selectedEnvironmentId) : baseEnvironment; const handleCreateEnvironment = async () => { if (baseEnvironment == null) return; const e = await createEnvironment.mutateAsync(baseEnvironment); if (e == null) return; setSelectedEnvironmentId(e.id); }; return ( ( )} secondSlot={() => selectedEnvironment == null ? (
Failed to find selected environment {selectedEnvironmentId}
) : ( ) } /> ); }; const EnvironmentEditor = function ({ environment: activeEnvironment, className, }: { environment: Environment; className?: string; }) { const valueVisibility = useKeyValue({ namespace: 'global', key: 'environmentValueVisibility', fallback: true, }); const { allEnvironments } = useEnvironments(); const updateEnvironment = useUpdateEnvironment(activeEnvironment?.id ?? null); const handleChange = useCallback( (variables) => updateEnvironment.mutate({ variables }), [updateEnvironment], ); // Gather a list of env names from other environments, to help the user get them aligned const nameAutocomplete = useMemo(() => { const options: GenericCompletionOption[] = []; const isBaseEnv = activeEnvironment.environmentId == null; if (isBaseEnv) { return { options }; } const allVariables = allEnvironments.flatMap((e) => e?.variables); const allVariableNames = new Set(allVariables.map((v) => v?.name)); for (const name of allVariableNames) { const containingEnvs = allEnvironments.filter((e) => e.variables.some((v) => v.name === name), ); const isAlreadyInActive = containingEnvs.find((e) => e.id === activeEnvironment.id); if (isAlreadyInActive) continue; options.push({ label: name, type: 'constant', detail: containingEnvs.map((e) => e.name).join(', '), }); } return { options }; }, [activeEnvironment.environmentId, activeEnvironment.id, allEnvironments]); 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 (
{activeEnvironment?.name}
{ return valueVisibility.set((v) => !v); }} />
); }; function SidebarButton({ children, className, active, onClick, onDelete, rightSlot, environment, }: { className?: string; children: ReactNode; active: boolean; onClick: () => void; onDelete?: () => void; rightSlot?: ReactNode; environment: Environment | null; }) { const updateEnvironment = useUpdateEnvironment(environment?.id ?? null); const deleteEnvironment = useDeleteEnvironment(environment); const [showContextMenu, setShowContextMenu] = useState<{ x: number; y: number; } | null>(null); const handleContextMenu = useCallback((e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); setShowContextMenu({ x: e.clientX, y: e.clientY }); }, []); return ( <>
{rightSlot}
{environment != null && ( setShowContextMenu(null)} items={[ { label: 'Rename', leftSlot: , onSelect: async () => { const name = await showPrompt({ id: 'rename-environment', title: 'Rename Environment', description: ( <> Enter a new name for {environment.name} ), label: 'Name', confirmText: 'Save', placeholder: 'New Name', defaultValue: environment.name, }); if (name == null) return; updateEnvironment.mutate({ name }); }, }, { color: 'danger', label: 'Delete', leftSlot: , onSelect: () => { deleteEnvironment.mutate(undefined, { onSuccess: onDelete, }); }, }, ]} /> )} ); }