mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-10 15:16:06 +01:00
123 lines
3.9 KiB
TypeScript
123 lines
3.9 KiB
TypeScript
import { linter } from '@codemirror/lint';
|
|
import type { HttpRequest } from '@yaakapp-internal/models';
|
|
import { patchModel } from '@yaakapp-internal/models';
|
|
import { useCallback, useMemo } from 'react';
|
|
import { useKeyValue } from '../hooks/useKeyValue';
|
|
import { textLikelyContainsJsonComments } from '../lib/jsonComments';
|
|
import { Banner } from './core/Banner';
|
|
import type { DropdownItem } from './core/Dropdown';
|
|
import { Dropdown } from './core/Dropdown';
|
|
import type { EditorProps } from './core/Editor/Editor';
|
|
import { jsonParseLinter } from './core/Editor/json-lint';
|
|
import { Editor } from './core/Editor/LazyEditor';
|
|
import { Icon } from './core/Icon';
|
|
import { IconButton } from './core/IconButton';
|
|
import { IconTooltip } from './core/IconTooltip';
|
|
|
|
interface Props {
|
|
forceUpdateKey: string;
|
|
heightMode: EditorProps['heightMode'];
|
|
request: HttpRequest;
|
|
}
|
|
|
|
export function JsonBodyEditor({ forceUpdateKey, heightMode, request }: Props) {
|
|
const handleChange = useCallback(
|
|
(text: string) => patchModel(request, { body: { ...request.body, text } }),
|
|
[request],
|
|
);
|
|
|
|
const autoFix = request.body?.sendJsonComments !== true;
|
|
|
|
const lintExtension = useMemo(
|
|
() =>
|
|
linter(
|
|
jsonParseLinter(
|
|
autoFix
|
|
? { allowComments: true, allowTrailingCommas: true }
|
|
: { allowComments: false, allowTrailingCommas: false },
|
|
),
|
|
),
|
|
[autoFix],
|
|
);
|
|
|
|
const hasComments = useMemo(
|
|
() => textLikelyContainsJsonComments(request.body?.text ?? ''),
|
|
[request.body?.text],
|
|
);
|
|
|
|
const { value: bannerDismissed, set: setBannerDismissed } = useKeyValue<boolean>({
|
|
namespace: 'no_sync',
|
|
key: ['json-fix-3', request.workspaceId],
|
|
fallback: false,
|
|
});
|
|
|
|
const handleToggleAutoFix = useCallback(() => {
|
|
const newBody = { ...request.body };
|
|
if (autoFix) {
|
|
newBody.sendJsonComments = true;
|
|
} else {
|
|
delete newBody.sendJsonComments;
|
|
}
|
|
patchModel(request, { body: newBody });
|
|
}, [request, autoFix]);
|
|
|
|
const handleDropdownOpen = useCallback(() => {
|
|
if (!bannerDismissed) {
|
|
setBannerDismissed(true);
|
|
}
|
|
}, [bannerDismissed, setBannerDismissed]);
|
|
|
|
const showBanner = hasComments && autoFix && !bannerDismissed;
|
|
|
|
const stripMessage = 'Automatically strip comments and trailing commas before sending';
|
|
const actions = useMemo<EditorProps['actions']>(
|
|
() => [
|
|
showBanner && (
|
|
<Banner color="notice" className="!opacity-100 h-sm !py-0 !px-2 flex items-center text-xs">
|
|
<p className="inline-flex items-center gap-1 min-w-0">
|
|
<span className="truncate">Auto-fix enabled</span>
|
|
<Icon icon="arrow_right" size="sm" className="opacity-disabled" />
|
|
</p>
|
|
</Banner>
|
|
),
|
|
<div key="settings" className="!opacity-100 !shadow">
|
|
<Dropdown
|
|
onOpen={handleDropdownOpen}
|
|
items={
|
|
[
|
|
{
|
|
label: 'Automatically Fix JSON',
|
|
keepOpenOnSelect: true,
|
|
onSelect: handleToggleAutoFix,
|
|
rightSlot: <IconTooltip content={stripMessage} />,
|
|
leftSlot: (
|
|
<Icon icon={autoFix ? 'check_square_checked' : 'check_square_unchecked'} />
|
|
),
|
|
},
|
|
] satisfies DropdownItem[]
|
|
}
|
|
>
|
|
<IconButton size="sm" variant="border" icon="settings" title="JSON Settings" />
|
|
</Dropdown>
|
|
</div>,
|
|
],
|
|
[handleDropdownOpen, handleToggleAutoFix, autoFix, showBanner],
|
|
);
|
|
|
|
return (
|
|
<Editor
|
|
forceUpdateKey={forceUpdateKey}
|
|
autocompleteFunctions
|
|
autocompleteVariables
|
|
placeholder="..."
|
|
heightMode={heightMode}
|
|
defaultValue={`${request.body?.text ?? ''}`}
|
|
language="json"
|
|
onChange={handleChange}
|
|
stateKey={`json.${request.id}`}
|
|
actions={actions}
|
|
lintExtension={lintExtension}
|
|
/>
|
|
);
|
|
}
|