4e35871861
* Improvements to the shortcut keys * Updated code mirror theme to the new dark mode style * Removed the old test page help panel copy * WIP new structure for the test page * Added some of the side panel options for examples and account ID * Conditionally hide the side panel components if they’re blank * Example with selected state working * Allow selecting the example again to overwrite any edits * Previous run payloads working * Select a recent payload if there’s no example * Created Icon and DetailCell components * The integrations now use the DetailCell * Use DetailCell on the integrations page * Added support for description to DetailCell. With proper variants now * DateTimeAccurate and formatDateTimeAccurate weren’t displaying correctly. fractionalSecondDigits isn’t in the types, but is supported for 92.5% of users * DetailCell now allows label and description to be React components * Using the DetailCell on the Test page * Styled the CodeMirror scrollbars to match elsewhere * Adding copy and clear to the JSONEditor, not working properly yet though * Removed the copy/clear buttons from the Test route * Delete the light color theme * The editor now supports copying/clearing etc * Made it clear when text is copied * Changed the learn link to a tertiary button * Fixed CMD shortcut keys * Run test button now works with ⌘ Enter * CodeMirror now allows ⌘Enter to escape the field * Added JSON linter to the test editor * Fix for buttons not being aligned correctly * Copy/clear buttons now aligned * Made the integration DetailCell text smaller * The test recent payloads now have colored text to help identify the status * DetailCell descriptions are dimmer * Added link to docs for BYOA * Fix for buttons going full width when they shouldn’t --------- Co-authored-by: James Ritchie <james@jamesritchie.co.uk>
153 lines
4.0 KiB
TypeScript
153 lines
4.0 KiB
TypeScript
import { json as jsonLang } from "@codemirror/lang-json";
|
|
import type { ViewUpdate } from "@codemirror/view";
|
|
import { CheckIcon, ClipboardIcon } from "@heroicons/react/20/solid";
|
|
import type { ReactCodeMirrorProps, UseCodeMirror } from "@uiw/react-codemirror";
|
|
import { useCodeMirror } from "@uiw/react-codemirror";
|
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
import { cn } from "~/utils/cn";
|
|
import { Button } from "../primitives/Buttons";
|
|
import { getEditorSetup } from "./codeMirrorSetup";
|
|
import { darkTheme } from "./codeMirrorTheme";
|
|
|
|
export interface JSONEditorProps extends Omit<ReactCodeMirrorProps, "onBlur"> {
|
|
defaultValue?: string;
|
|
language?: "json";
|
|
readOnly?: boolean;
|
|
onChange?: (value: string) => void;
|
|
onUpdate?: (update: ViewUpdate) => void;
|
|
onBlur?: (code: string) => void;
|
|
showCopyButton?: boolean;
|
|
showClearButton?: boolean;
|
|
}
|
|
|
|
const languages = {
|
|
json: jsonLang,
|
|
};
|
|
|
|
type JSONEditorDefaultProps = Partial<JSONEditorProps>;
|
|
|
|
const defaultProps: JSONEditorDefaultProps = {
|
|
language: "json",
|
|
readOnly: true,
|
|
basicSetup: false,
|
|
};
|
|
|
|
export function JSONEditor(opts: JSONEditorProps) {
|
|
const {
|
|
defaultValue = "",
|
|
language,
|
|
readOnly,
|
|
onChange,
|
|
onUpdate,
|
|
onBlur,
|
|
basicSetup,
|
|
autoFocus,
|
|
showCopyButton = true,
|
|
showClearButton = true,
|
|
} = {
|
|
...defaultProps,
|
|
...opts,
|
|
};
|
|
|
|
const extensions = getEditorSetup();
|
|
|
|
if (!language) throw new Error("language is required");
|
|
const languageExtension = languages[language];
|
|
|
|
extensions.push(languageExtension());
|
|
|
|
const editor = useRef<HTMLDivElement>(null);
|
|
const settings: Omit<UseCodeMirror, "onBlur"> = {
|
|
...opts,
|
|
container: editor.current,
|
|
extensions,
|
|
editable: !readOnly,
|
|
contentEditable: !readOnly,
|
|
value: defaultValue,
|
|
autoFocus,
|
|
theme: darkTheme(),
|
|
indentWithTab: false,
|
|
basicSetup,
|
|
onChange,
|
|
onUpdate,
|
|
};
|
|
const { setContainer, view } = useCodeMirror(settings);
|
|
const [copied, setCopied] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (editor.current) {
|
|
setContainer(editor.current);
|
|
}
|
|
}, [setContainer]);
|
|
|
|
//if the defaultValue changes update the editor
|
|
useEffect(() => {
|
|
if (view !== undefined) {
|
|
if (view.state.doc.toString() === defaultValue) return;
|
|
view.dispatch({
|
|
changes: { from: 0, to: view.state.doc.length, insert: defaultValue },
|
|
});
|
|
}
|
|
}, [defaultValue, view]);
|
|
|
|
const clear = useCallback(() => {
|
|
if (view === undefined) return;
|
|
view.dispatch({
|
|
changes: { from: 0, to: view.state.doc.length, insert: undefined },
|
|
});
|
|
onChange?.("");
|
|
}, [view]);
|
|
|
|
const copy = useCallback(() => {
|
|
if (view === undefined) return;
|
|
navigator.clipboard.writeText(view.state.doc.toString());
|
|
setCopied(true);
|
|
setTimeout(() => {
|
|
setCopied(false);
|
|
}, 1500);
|
|
}, [view]);
|
|
|
|
return (
|
|
<div className={cn(opts.className, "relative")}>
|
|
<div
|
|
className="h-full w-full"
|
|
ref={editor}
|
|
onBlur={() => {
|
|
if (!onBlur) return;
|
|
onBlur(editor.current?.textContent ?? "");
|
|
}}
|
|
/>
|
|
<div className="absolute right-3 top-3 flex items-center gap-2">
|
|
{showClearButton && (
|
|
<Button
|
|
type="button"
|
|
variant="secondary/small"
|
|
onClick={(event) => {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
clear();
|
|
}}
|
|
>
|
|
Clear
|
|
</Button>
|
|
)}
|
|
{showCopyButton && (
|
|
<Button
|
|
type="button"
|
|
variant="secondary/small"
|
|
LeadingIcon={copied ? CheckIcon : ClipboardIcon}
|
|
leadingIconClassName={copied ? "text-green-500 group-hover:text-green-500" : undefined}
|
|
onClick={(event) => {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
copy();
|
|
}}
|
|
>
|
|
Copy
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|