mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-26 19:31:12 +01:00
Ability to sync environments to folder (#207)
This commit is contained in:
@@ -11,6 +11,7 @@ export function BulkPairEditor({
|
||||
namePlaceholder,
|
||||
valuePlaceholder,
|
||||
forceUpdateKey,
|
||||
forcedEnvironmentId,
|
||||
stateKey,
|
||||
}: Props) {
|
||||
const pairsText = useMemo(() => {
|
||||
@@ -36,6 +37,7 @@ export function BulkPairEditor({
|
||||
autocompleteFunctions
|
||||
autocompleteVariables
|
||||
stateKey={`bulk_pair.${stateKey}`}
|
||||
forcedEnvironmentId={forcedEnvironmentId}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
placeholder={`${namePlaceholder ?? 'name'}: ${valuePlaceholder ?? 'value'}`}
|
||||
defaultValue={pairsText}
|
||||
|
||||
@@ -2,7 +2,6 @@ import classNames from 'classnames';
|
||||
import * as m from 'motion/react-m';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useKey } from 'react-use';
|
||||
import { Overlay } from '../Overlay';
|
||||
import { Heading } from './Heading';
|
||||
import { IconButton } from './IconButton';
|
||||
@@ -42,18 +41,9 @@ export function Dialog({
|
||||
[description],
|
||||
);
|
||||
|
||||
useKey(
|
||||
'Escape',
|
||||
() => {
|
||||
if (!open) return;
|
||||
onClose?.();
|
||||
},
|
||||
{},
|
||||
[open],
|
||||
);
|
||||
|
||||
return (
|
||||
<Overlay open={open} onClose={disableBackdropClose ? undefined : onClose} portalName="dialog">
|
||||
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */}
|
||||
<div
|
||||
role="dialog"
|
||||
className={classNames(
|
||||
@@ -64,6 +54,16 @@ export function Dialog({
|
||||
)}
|
||||
aria-labelledby={titleId}
|
||||
aria-describedby={descriptionId}
|
||||
tabIndex={-1}
|
||||
onKeyDown={(e) => {
|
||||
// NOTE: We handle Escape on the element itself so that it doesn't close multiple
|
||||
// dialogs and can be intercepted by children if needed.
|
||||
if (e.key === 'Escape') {
|
||||
onClose?.();
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<m.div
|
||||
initial={{ top: 5, scale: 0.97 }}
|
||||
|
||||
@@ -2,7 +2,7 @@ import classNames from 'classnames';
|
||||
import { useKeyValue } from '../../hooks/useKeyValue';
|
||||
import type { BannerProps } from './Banner';
|
||||
import { Banner } from './Banner';
|
||||
import { IconButton } from './IconButton';
|
||||
import { Button } from './Button';
|
||||
|
||||
export function DismissibleBanner({
|
||||
children,
|
||||
@@ -19,14 +19,17 @@ export function DismissibleBanner({
|
||||
if (dismissed) return null;
|
||||
|
||||
return (
|
||||
<Banner className={classNames(className, 'relative pr-8')} {...props}>
|
||||
<IconButton
|
||||
className="!absolute right-0 top-0"
|
||||
icon="x"
|
||||
<Banner className={classNames(className, 'relative grid grid-cols-[1fr_auto] gap-3')} {...props}>
|
||||
{children}
|
||||
<Button
|
||||
variant="border"
|
||||
color={props.color}
|
||||
size="xs"
|
||||
onClick={() => setDismissed((d) => !d)}
|
||||
title="Dismiss message"
|
||||
/>
|
||||
{children}
|
||||
>
|
||||
Dismiss
|
||||
</Button>
|
||||
</Banner>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import * as m from 'motion/react-m';
|
||||
import { atom } from 'jotai';
|
||||
import * as m from 'motion/react-m';
|
||||
import type {
|
||||
CSSProperties,
|
||||
FocusEvent as ReactFocusEvent,
|
||||
@@ -34,9 +34,9 @@ import { Overlay } from '../Overlay';
|
||||
import { Button } from './Button';
|
||||
import { HotKey } from './HotKey';
|
||||
import { Icon } from './Icon';
|
||||
import { LoadingIcon } from './LoadingIcon';
|
||||
import { Separator } from './Separator';
|
||||
import { HStack, VStack } from './Stacks';
|
||||
import { LoadingIcon } from './LoadingIcon';
|
||||
|
||||
export type DropdownItemSeparator = {
|
||||
type: 'separator';
|
||||
|
||||
@@ -26,7 +26,8 @@ import {
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { useActiveEnvironmentVariables } from '../../../hooks/useActiveEnvironmentVariables';
|
||||
import { activeEnvironmentIdAtom } from '../../../hooks/useActiveEnvironment';
|
||||
import { useEnvironmentVariables } from '../../../hooks/useEnvironmentVariables';
|
||||
import { useRequestEditor } from '../../../hooks/useRequestEditor';
|
||||
import { useTemplateFunctionCompletionOptions } from '../../../hooks/useTemplateFunctions';
|
||||
import { showDialog } from '../../../lib/dialog';
|
||||
@@ -69,6 +70,7 @@ export interface EditorProps {
|
||||
disableTabIndent?: boolean;
|
||||
disabled?: boolean;
|
||||
extraExtensions?: Extension[];
|
||||
forcedEnvironmentId?: string;
|
||||
forceUpdateKey?: string | number;
|
||||
format?: (v: string) => Promise<string>;
|
||||
heightMode?: 'auto' | 'full';
|
||||
@@ -108,6 +110,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
disableTabIndent,
|
||||
disabled,
|
||||
extraExtensions,
|
||||
forcedEnvironmentId,
|
||||
forceUpdateKey,
|
||||
format,
|
||||
heightMode,
|
||||
@@ -130,7 +133,9 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
) {
|
||||
const settings = useAtomValue(settingsAtom);
|
||||
|
||||
const allEnvironmentVariables = useActiveEnvironmentVariables();
|
||||
const activeEnvironmentId = useAtomValue(activeEnvironmentIdAtom);
|
||||
const environmentId = forcedEnvironmentId ?? activeEnvironmentId ?? null;
|
||||
const allEnvironmentVariables = useEnvironmentVariables(environmentId);
|
||||
const environmentVariables = autocompleteVariables ? allEnvironmentVariables : emptyVariables;
|
||||
const useTemplating = !!(autocompleteFunctions || autocompleteVariables || autocomplete);
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ const icons = {
|
||||
eye_closed: lucide.EyeOffIcon,
|
||||
file_code: lucide.FileCodeIcon,
|
||||
filter: lucide.FilterIcon,
|
||||
flame: lucide.FlameIcon,
|
||||
flask: lucide.FlaskConicalIcon,
|
||||
folder: lucide.FolderIcon,
|
||||
folder_git: lucide.FolderGitIcon,
|
||||
@@ -138,7 +139,7 @@ export const Icon = memo(function Icon({
|
||||
size === 'xs' && 'h-3 w-3',
|
||||
size === '2xs' && 'h-2.5 w-2.5',
|
||||
color === 'default' && 'inherit',
|
||||
color === 'danger' && 'text-danger!',
|
||||
color === 'danger' && 'text-danger',
|
||||
color === 'warning' && 'text-warning',
|
||||
color === 'notice' && 'text-notice',
|
||||
color === 'info' && 'text-info',
|
||||
|
||||
@@ -7,13 +7,25 @@ import { Tooltip } from './Tooltip';
|
||||
type Props = Omit<TooltipProps, 'children'> & {
|
||||
icon?: IconProps['icon'];
|
||||
iconSize?: IconProps['size'];
|
||||
iconColor?: IconProps['color'];
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function IconTooltip({ content, icon = 'info', iconSize, ...tooltipProps }: Props) {
|
||||
export function IconTooltip({
|
||||
content,
|
||||
icon = 'info',
|
||||
iconColor,
|
||||
iconSize,
|
||||
...tooltipProps
|
||||
}: Props) {
|
||||
return (
|
||||
<Tooltip content={content} {...tooltipProps}>
|
||||
<Icon className="opacity-60 hover:opacity-100" icon={icon} size={iconSize} />
|
||||
<Icon
|
||||
className="opacity-60 hover:opacity-100"
|
||||
icon={icon}
|
||||
size={iconSize}
|
||||
color={iconColor}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,11 +30,13 @@ import { Icon } from './Icon';
|
||||
import { IconButton } from './IconButton';
|
||||
import { Label } from './Label';
|
||||
import { HStack } from './Stacks';
|
||||
import { copyToClipboard } from '../../lib/copy';
|
||||
|
||||
export type InputProps = Pick<
|
||||
EditorProps,
|
||||
| 'language'
|
||||
| 'autocomplete'
|
||||
| 'forcedEnvironmentId'
|
||||
| 'forceUpdateKey'
|
||||
| 'disabled'
|
||||
| 'autoFocus'
|
||||
@@ -387,19 +389,32 @@ function EncryptionInput({
|
||||
const dropdownItems = useMemo<DropdownItem[]>(
|
||||
() => [
|
||||
{
|
||||
label: state.obscured ? 'Reveal value' : 'Conceal value',
|
||||
label: state.obscured ? 'Reveal' : 'Conceal',
|
||||
disabled: isEncryptionEnabled && state.fieldType === 'text',
|
||||
leftSlot: <Icon icon={state.obscured ? 'eye' : 'eye_closed'} />,
|
||||
onSelect: () => setState((s) => ({ ...s, obscured: !s.obscured })),
|
||||
},
|
||||
{
|
||||
label: 'Copy',
|
||||
leftSlot: <Icon icon="copy" />,
|
||||
hidden: !state.value,
|
||||
onSelect: () => copyToClipboard(state.value ?? ''),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: state.fieldType === 'text' ? 'Encrypt Value' : 'Decrypt Value',
|
||||
label: state.fieldType === 'text' ? 'Encrypt Field' : 'Decrypt Field',
|
||||
leftSlot: <Icon icon={state.fieldType === 'text' ? 'lock' : 'lock_open'} />,
|
||||
onSelect: () => handleFieldTypeChange(state.fieldType === 'text' ? 'encrypted' : 'text'),
|
||||
},
|
||||
],
|
||||
[handleFieldTypeChange, isEncryptionEnabled, setState, state.fieldType, state.obscured],
|
||||
[
|
||||
handleFieldTypeChange,
|
||||
isEncryptionEnabled,
|
||||
setState,
|
||||
state.fieldType,
|
||||
state.obscured,
|
||||
state.value,
|
||||
],
|
||||
);
|
||||
|
||||
let tint: InputProps['tint'];
|
||||
|
||||
@@ -41,6 +41,7 @@ export type PairEditorProps = {
|
||||
allowFileValues?: boolean;
|
||||
allowMultilineValues?: boolean;
|
||||
className?: string;
|
||||
forcedEnvironmentId?: string;
|
||||
forceUpdateKey?: string;
|
||||
nameAutocomplete?: GenericCompletionConfig;
|
||||
nameAutocompleteFunctions?: boolean;
|
||||
@@ -81,6 +82,7 @@ export const PairEditor = forwardRef<PairEditorRef, PairEditorProps>(function Pa
|
||||
allowFileValues,
|
||||
allowMultilineValues,
|
||||
className,
|
||||
forcedEnvironmentId,
|
||||
forceUpdateKey,
|
||||
nameAutocomplete,
|
||||
nameAutocompleteFunctions,
|
||||
@@ -235,6 +237,7 @@ export const PairEditor = forwardRef<PairEditorRef, PairEditorProps>(function Pa
|
||||
allowFileValues={allowFileValues}
|
||||
allowMultilineValues={allowMultilineValues}
|
||||
className="py-1"
|
||||
forcedEnvironmentId={forcedEnvironmentId}
|
||||
forceFocusNamePairId={forceFocusNamePairId}
|
||||
forceFocusValuePairId={forceFocusValuePairId}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
@@ -292,6 +295,7 @@ type PairEditorRowProps = {
|
||||
PairEditorProps,
|
||||
| 'allowFileValues'
|
||||
| 'allowMultilineValues'
|
||||
| 'forcedEnvironmentId'
|
||||
| 'forceUpdateKey'
|
||||
| 'nameAutocomplete'
|
||||
| 'nameAutocompleteVariables'
|
||||
@@ -311,6 +315,7 @@ function PairEditorRow({
|
||||
allowFileValues,
|
||||
allowMultilineValues,
|
||||
className,
|
||||
forcedEnvironmentId,
|
||||
forceFocusNamePairId,
|
||||
forceFocusValuePairId,
|
||||
forceUpdateKey,
|
||||
@@ -502,6 +507,7 @@ function PairEditorRow({
|
||||
size="sm"
|
||||
required={!isLast && !!pair.enabled && !!pair.value}
|
||||
validate={nameValidate}
|
||||
forcedEnvironmentId={forcedEnvironmentId}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
containerClassName={classNames(isLast && 'border-dashed')}
|
||||
defaultValue={pair.name}
|
||||
@@ -549,6 +555,7 @@ function PairEditorRow({
|
||||
size="sm"
|
||||
containerClassName={classNames(isLast && 'border-dashed')}
|
||||
validate={valueValidate}
|
||||
forcedEnvironmentId={forcedEnvironmentId}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
defaultValue={pair.value}
|
||||
label="Value"
|
||||
|
||||
@@ -8,6 +8,7 @@ import { PairEditor } from './PairEditor';
|
||||
|
||||
interface Props extends PairEditorProps {
|
||||
preferenceName: string;
|
||||
forcedEnvironmentId?: string;
|
||||
}
|
||||
|
||||
export const PairOrBulkEditor = forwardRef<PairEditorRef, Props>(function PairOrBulkEditor(
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Portal } from '../Portal';
|
||||
export interface TooltipProps {
|
||||
children: ReactNode;
|
||||
content: ReactNode;
|
||||
tabIndex?: number,
|
||||
size?: 'md' | 'lg';
|
||||
}
|
||||
|
||||
@@ -18,7 +19,7 @@ const hiddenStyles: CSSProperties = {
|
||||
opacity: 0,
|
||||
};
|
||||
|
||||
export function Tooltip({ children, content, size = 'md' }: TooltipProps) {
|
||||
export function Tooltip({ children, content, tabIndex, size = 'md' }: TooltipProps) {
|
||||
const [isOpen, setIsOpen] = useState<CSSProperties>();
|
||||
const triggerRef = useRef<HTMLButtonElement>(null);
|
||||
const tooltipRef = useRef<HTMLDivElement>(null);
|
||||
@@ -89,11 +90,12 @@ export function Tooltip({ children, content, size = 'md' }: TooltipProps) {
|
||||
<Triangle className="text-border mb-2" />
|
||||
</div>
|
||||
</Portal>
|
||||
<button
|
||||
<span
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
role="button"
|
||||
aria-describedby={isOpen ? id.current : undefined}
|
||||
className="flex-grow-0 inline-flex items-center"
|
||||
tabIndex={tabIndex ?? 0}
|
||||
className="flex-grow-0 flex items-center"
|
||||
onClick={handleToggleImmediate}
|
||||
onMouseEnter={handleOpen}
|
||||
onMouseLeave={handleClose}
|
||||
@@ -102,7 +104,7 @@ export function Tooltip({ children, content, size = 'md' }: TooltipProps) {
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user