diff --git a/src-web/commands/createEnvironment.ts b/src-web/commands/createEnvironment.tsx similarity index 56% rename from src-web/commands/createEnvironment.ts rename to src-web/commands/createEnvironment.tsx index 98867dac..9b2a78aa 100644 --- a/src-web/commands/createEnvironment.ts +++ b/src-web/commands/createEnvironment.tsx @@ -1,8 +1,9 @@ -import { createWorkspaceModel, type Environment } from '@yaakapp-internal/models'; +import { type Environment } from '@yaakapp-internal/models'; +import { CreateEnvironmentDialog } from '../components/CreateEnvironmentDialog'; import { activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace'; import { createFastMutation } from '../hooks/useFastMutation'; +import { showDialog } from '../lib/dialog'; import { jotaiStore } from '../lib/jotai'; -import { showPrompt } from '../lib/prompt'; import { setWorkspaceSearchParams } from '../lib/setWorkspaceSearchParams'; export const createSubEnvironmentAndActivate = createFastMutation< @@ -21,24 +22,23 @@ export const createSubEnvironmentAndActivate = createFastMutation< throw new Error('Cannot create environment when no active workspace'); } - const name = await showPrompt({ - id: 'new-environment', - title: 'New Environment', - description: 'Create multiple environments with different sets of variables', - label: 'Name', - placeholder: 'My Environment', - defaultValue: 'My Environment', - confirmText: 'Create', - }); - if (name == null) return null; - - return createWorkspaceModel({ - model: 'environment', - name, - variables: [], - workspaceId, - parentId: baseEnvironment.id, - parentModel: 'environment', + return new Promise((resolve) => { + showDialog({ + id: 'new-environment', + title: 'New Environment', + description: 'Create multiple environments with different sets of variables', + size: 'sm', + onClose: () => resolve(null), + render: ({ hide }) => ( + { + resolve(id); + }} + /> + ), + }); }); }, onSuccess: async (environmentId) => { diff --git a/src-web/components/ColorIndicator.tsx b/src-web/components/ColorIndicator.tsx new file mode 100644 index 00000000..e4bad672 --- /dev/null +++ b/src-web/components/ColorIndicator.tsx @@ -0,0 +1,25 @@ +import classNames from 'classnames'; +import type { CSSProperties } from 'react'; + +interface Props { + color: string | null; + onClick?: () => void; +} + +export function ColorIndicator({ color, onClick }: Props) { + const style: CSSProperties = { backgroundColor: color ?? undefined }; + const className = + 'inline-block w-[0.75em] h-[0.75em] rounded-full mr-1.5 border border-transparent'; + + if (onClick) { + return ( + + + ); +} diff --git a/src-web/components/EnvironmentColorIndicator.tsx b/src-web/components/EnvironmentColorIndicator.tsx index 768e02ce..dab22e86 100644 --- a/src-web/components/EnvironmentColorIndicator.tsx +++ b/src-web/components/EnvironmentColorIndicator.tsx @@ -1,6 +1,6 @@ import type { Environment } from '@yaakapp-internal/models'; -import classNames from 'classnames'; import { showColorPicker } from '../lib/showColorPicker'; +import { ColorIndicator } from './ColorIndicator'; export function EnvironmentColorIndicator({ environment, @@ -11,19 +11,10 @@ export function EnvironmentColorIndicator({ }) { if (environment?.color == null) return null; - const style = { backgroundColor: environment.color }; - const className = - 'inline-block w-[0.75em] h-[0.75em] rounded-full mr-1.5 border border-transparent'; - - if (clickToEdit) { - return ( - - - + + This color will be used to color the interface when this environment is active + + + ); } diff --git a/src-web/components/EnvironmentEditDialog.tsx b/src-web/components/EnvironmentEditDialog.tsx index e9a5379e..5362d53f 100644 --- a/src-web/components/EnvironmentEditDialog.tsx +++ b/src-web/components/EnvironmentEditDialog.tsx @@ -232,17 +232,14 @@ function EnvironmentDialogSidebarButton({ await patchModel(environment, { name }); }, }, - ...((duplicateEnvironment - ? [ - { - label: 'Duplicate', - leftSlot: , - onSelect: () => { - duplicateEnvironment?.(environment); - }, - }, - ] - : []) as DropdownItem[]), + { + label: 'Duplicate', + leftSlot: , + hidden: isBaseEnvironment(environment), + onSelect: () => { + duplicateEnvironment?.(environment); + }, + }, { label: environment.color ? 'Change Color' : 'Assign Color', leftSlot: , diff --git a/src-web/components/core/ColorPicker.tsx b/src-web/components/core/ColorPicker.tsx index 441b4316..960640d3 100644 --- a/src-web/components/core/ColorPicker.tsx +++ b/src-web/components/core/ColorPicker.tsx @@ -1,16 +1,20 @@ +import classNames from 'classnames'; +import { useState } from 'react'; import { HexColorPicker } from 'react-colorful'; import { useRandomKey } from '../../hooks/useRandomKey'; +import { Icon } from './Icon'; import { PlainInput } from './PlainInput'; interface Props { onChange: (value: string | null) => void; color: string | null; + className?: string; } -export function ColorPicker({ onChange, color }: Props) { +export function ColorPicker({ onChange, color, className }: Props) { const [updateKey, regenerateKey] = useRandomKey(); return ( -
+
); } + +const colors = [ + null, + 'danger', + 'warning', + 'notice', + 'success', + 'primary', + 'info', + 'secondary', + 'custom', +] as const; + +export function ColorPickerWithThemeColors({ onChange, color, className }: Props) { + const [updateKey, regenerateKey] = useRandomKey(); + const [selectedColor, setSelectedColor] = useState(() => { + if (color == null) return null; + const c = color?.match(/var\(--([a-z]+)\)/)?.[1]; + return c ?? 'custom'; + }); + return ( +
+
+ {colors.map((color) => ( + + ))} +
+ {selectedColor === 'custom' && ( + <> + { + onChange(color); + regenerateKey(); // To force input to change + }} + /> + color.match(/#[0-9a-fA-F]{6}$/) !== null} + /> + + )} +
+ ); +} diff --git a/src-web/components/core/Icon.tsx b/src-web/components/core/Icon.tsx index c3d0c506..61761075 100644 --- a/src-web/components/core/Icon.tsx +++ b/src-web/components/core/Icon.tsx @@ -30,6 +30,7 @@ import { CircleDollarSignIcon, CircleFadingArrowUpIcon, CircleHelpIcon, + CircleOffIcon, ClipboardPasteIcon, ClockIcon, CodeIcon, @@ -191,6 +192,7 @@ const icons = { git_fork: GitForkIcon, git_pull_request: GitPullRequestIcon, grip_vertical: GripVerticalIcon, + circle_off: CircleOffIcon, hand: HandIcon, help: CircleHelpIcon, history: HistoryIcon, diff --git a/src-web/lib/model_util.ts b/src-web/lib/model_util.ts index 5d842009..56749bb6 100644 --- a/src-web/lib/model_util.ts +++ b/src-web/lib/model_util.ts @@ -49,7 +49,7 @@ export function getCharsetFromContentType(headers: HttpResponseHeader[]): string } export function isBaseEnvironment(environment: Environment): boolean { - return environment.parentId == null; + return environment.parentModel == 'workspace'; } export function isSubEnvironment(environment: Environment): boolean { diff --git a/src-web/lib/showColorPicker.tsx b/src-web/lib/showColorPicker.tsx index eac79b69..52c7a306 100644 --- a/src-web/lib/showColorPicker.tsx +++ b/src-web/lib/showColorPicker.tsx @@ -1,17 +1,17 @@ import type { Environment } from '@yaakapp-internal/models'; import { patchModel } from '@yaakapp-internal/models'; -import { showDialog } from './dialog'; import { EnvironmentColorPicker } from '../components/EnvironmentColorPicker'; +import { showDialog } from './dialog'; export function showColorPicker(environment: Environment) { showDialog({ title: 'Environment Color', id: 'color-picker', - size: 'dynamic', + size: 'sm', render: ({ hide }) => { return ( { await patchModel(environment, { color }); hide();