Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve inputs for headers, query params, to stretch when focused (like HTTPie) #234

Merged
merged 26 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1406ce3
Prototype stretchy inputs for keyvalue forms
brettimus Aug 30, 2024
cc65823
Mild cleanup
brettimus Aug 30, 2024
5e4f5e5
Add key value stretchy inputs
brettimus Sep 4, 2024
a68552b
Using CodeMirror and almost there but trickiness with truncating text…
brettimus Sep 4, 2024
88a26ab
Add an httpie-like input for key value tables
brettimus Sep 4, 2024
bd7ce48
Disable codemirror features on inputs
brettimus Sep 16, 2024
cdf7afc
Remove failed prototype for stretchy input
brettimus Sep 16, 2024
50cd280
Fix input styles and prevent jumping
brettimus Sep 16, 2024
b3c727a
Refactor CodeMirrorInput to its own file and use it for the value inp…
brettimus Sep 16, 2024
a765305
Remove some unnecessary styles
brettimus Sep 16, 2024
31c705a
Fix some minor jumping, but not all the jumping
brettimus Sep 16, 2024
f664060
Remove unused Input
brettimus Sep 17, 2024
2b65c6f
Resolve issue with content in request params panel pushing the tabs t…
brettimus Sep 17, 2024
c39c019
Fix issue with tab indentations in the codemirror input
brettimus Sep 17, 2024
2fc9efa
Make path parameter key inputs readonly
brettimus Sep 17, 2024
aa5a30b
Modify readonly styles and fix jumping issue
brettimus Sep 17, 2024
5ffbb7b
Remove dead code and fix a few nits
brettimus Sep 17, 2024
386ddfb
Move the codemirror editors to their own Component folder
brettimus Sep 17, 2024
14c2884
Split out all the separate codemirror editors
brettimus Sep 17, 2024
3f1e672
Update a few comments
brettimus Sep 17, 2024
925604f
Blur the editor when the user hits escape in a code mirror input
brettimus Sep 19, 2024
92d98b6
Prevent newlines in firefox and add an optional onSubmit handler that…
brettimus Sep 19, 2024
31fd31c
Move inline styles on CodeMirrorInput into tailwind duhhh
brettimus Sep 20, 2024
59e8796
Remove style object from CodeMirrorInput and set width via tailwind c…
brettimus Sep 20, 2024
8d39db7
Refactor tailwind styles for CodeMirrorInput to rely on focus-within:…
brettimus Sep 20, 2024
2e0b67b
Remove unused parameter
brettimus Sep 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 203 additions & 0 deletions studio/src/components/CodeMirrorEditor/CodeMirrorInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import "./CodeMirrorEditorCssOverrides.css";

import { cn } from "@/utils";
import CodeMirror, { EditorView, gutter, keymap } from "@uiw/react-codemirror";
import { useMemo, useState } from "react";

const inputTheme = EditorView.theme({
"&": {
background: "transparent !important",
// HACK - Using tailwind `text-sm` values
fontSize: "0.875rem",
lineHeight: "1.25rem",
},
// Hide the gutters
".cm-gutters": {
borderColor: "transparent",
backgroundColor: "transparent",
},
".cm-cursor": {
borderLeftColor: "white",
},
});

// Configuration to disable all basic setup features on code mirror instance, so it behaves more like a text input
const CODE_MIRROR_BASIC_SETUP_DISABLE_ALL = {
lineNumbers: false,
foldGutter: false,
dropCursor: false,
allowMultipleSelections: false,
indentOnInput: false,
bracketMatching: false,
closeBrackets: false,
autocompletion: false,
rectangularSelection: false,
highlightActiveLine: false,
highlightSelectionMatches: false,
closeBracketsKeymap: false,
defaultKeymap: false,
searchKeymap: false,
historyKeymap: false,
foldKeymap: false,
completionKeymap: false,
lintKeymap: false,
};

// An extension that does nothing to make conditional extensions easier,
// e.g., `isFocused ? noopExtension : inputTrucateExtension`
const noopExtension = EditorView.theme({});

// Apply styles for the editor to make it look like one of our inputs
const inputBaseStylesExtension = EditorView.theme({
".cm-placeholder": {
color: "hsl(var(--muted-foreground))",
// HACK - Using tailwind `text-sm` values
fontSize: "0.875rem",
lineHeight: "1.25rem",
},
".cm-scroller": {
overflow: "hidden !important",
maxWidth: "100% !important",
},
});

// Extension to truncate text that overflows with an ellipsis
// We only want to use this when the editor is *not* focused
const inputTrucateExtension = EditorView.theme({
".cm-content": {
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
width: "100%",
},
".cm-line": {
textOverflow: "ellipsis",
overflow: "hidden",
width: "100%",
},
});

// Extension for special styling in readonly mode, which makes the editor's cursor transparent,
// but uses a pointer cursor when the user hovers over the input,
// to indicate that you can click on it to expand it (in case the path parameter is truncated).
// As of writing, this is only used for the path parameter *key* input.
const readonlyExtension = EditorView.theme({
".cm-cursor": {
borderLeftColor: "transparent !important",
},
".cm-content": {
cursor: "pointer !important",
},
});

// Hide the gutter on the code mirror instance
const hiddenGutterExtension = gutter({ class: "hidden border-none" });

type CodeMirrorInputProps = {
readOnly?: boolean;
className?: string;
value?: string;
onChange: (value?: string) => void;
placeholder?: string;
onSubmit?: () => void;
};

const preventNewlineInFirefox = keymap.of([
{
key: "Enter",
run: () => {
// Do nothingggg and don't bubble up the keyboard event
// (Returning `true` means "we handled this keyboard event, don't bother delegating it to other extensions or the DOM")
return true;
},
},
]);

// Extension that blurs the editor when the user presses "Escape"
const escapeKeymap = keymap.of([
{
key: "Escape",
run: (view) => {
view.contentDOM.blur();
return true;
},
},
]);

const submitKeymap = (onSubmit: (() => void) | undefined) =>
keymap.of([
{
key: "Mod-Enter",
run: (_view) => {
if (onSubmit) {
onSubmit();
}
return true;
},
},
]);

export function CodeMirrorInput(props: CodeMirrorInputProps) {
const { value, onChange, placeholder, className, readOnly, onSubmit } = props;

const [isFocused, setIsFocused] = useState(false);

const extensions = useMemo(() => {
return [
preventNewlineInFirefox,
escapeKeymap,
hiddenGutterExtension,
inputBaseStylesExtension,
/**
* NOTE: This is the CSS added by lineWrapping:

".cm-lineWrapping": {
whiteSpace_fallback: "pre-wrap", // For IE
whiteSpace: "break-spaces",
wordBreak: "break-word", // For Safari, which doesn't support overflow-wrap: anywhere
overflowWrap: "anywhere",
flexShrink: 1
}

*/
EditorView.lineWrapping,
readOnly ? readonlyExtension : noopExtension,
isFocused ? noopExtension : inputTrucateExtension,
submitKeymap(onSubmit),
];
}, [isFocused, readOnly, onSubmit]);

return (
<div
className={cn(
"h-auto",
"rounded border border-transparent",
// Show a text cursor on hover
"cursor-text",
// Show truncated text when not focused
"overflow-hidden whitespace-nowrap text-ellipsis",
// Show expanded text when focused
"focus-within:overflow-visible focus-within:whitespace-normal focus-within:text-clip",
// Show colorful border when focused
{
"focus-within:border-blue-600": !readOnly,
"focus-within:border-gray-600/50": readOnly,
},
className,
)}
>
<CodeMirror
value={value}
onChange={onChange}
theme={[inputTheme]}
indentWithTab={false} // Allows us to skip to the next input when the user presses "tab"
readOnly={readOnly}
extensions={extensions}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
basicSetup={CODE_MIRROR_BASIC_SETUP_DISABLE_ALL}
placeholder={placeholder}
/>
</div>
);
}
47 changes: 47 additions & 0 deletions studio/src/components/CodeMirrorEditor/CodeMirrorJsonEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import "./CodeMirrorEditorCssOverrides.css";

import { json } from "@codemirror/lang-json";
import { duotoneDark } from "@uiw/codemirror-theme-duotone";

import CodeMirror, { EditorView } from "@uiw/react-codemirror";
import { customTheme } from "./themes";

type CodeMirrorEditorProps = {
height?: string;
minHeight?: string;
maxHeight?: string;
theme?: string;
readOnly?: boolean;
value?: string;
onChange: (value?: string) => void;
};

export function CodeMirrorJsonEditor(props: CodeMirrorEditorProps) {
const {
height,
value,
onChange,
readOnly,
minHeight = "200px",
maxHeight,
} = props;

return (
<CodeMirror
value={value}
height={height}
maxHeight={maxHeight}
minHeight={minHeight}
extensions={[EditorView.lineWrapping, json()]}
onChange={onChange}
theme={[duotoneDark, customTheme]}
readOnly={readOnly}
basicSetup={{
// Turn off searching the input via cmd+g and cmd+f
searchKeymap: false,
// Investigate: Remap the default keymap: https://codemirror.net/docs/ref/#commands.defaultKeymap
// This will allow us to do things like cmd+enter to submit a payload
}}
/>
);
}
26 changes: 26 additions & 0 deletions studio/src/components/CodeMirrorEditor/CodeMirrorSqlEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import "./CodeMirrorEditorCssOverrides.css";

import { sql } from "@codemirror/lang-sql";
import { duotoneDark } from "@uiw/codemirror-theme-duotone";

import CodeMirror, { EditorView } from "@uiw/react-codemirror";
import { customTheme } from "./themes";
import type { CodeMirrorEditorProps } from "./types";

type CodeMirrorSqlEditorProps = CodeMirrorEditorProps;

export function CodeMirrorSqlEditor(props: CodeMirrorSqlEditorProps) {
const { height, value, onChange, minHeight, maxHeight, readOnly } = props;
return (
<CodeMirror
value={value}
height={height}
minHeight={minHeight}
maxHeight={maxHeight}
readOnly={readOnly}
extensions={[EditorView.lineWrapping, sql()]}
onChange={onChange}
theme={[duotoneDark, customTheme]}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import "./CodeMirrorEditorCssOverrides.css";

import { javascript } from "@codemirror/lang-javascript";
import { duotoneDark } from "@uiw/codemirror-theme-duotone";

import CodeMirror, { EditorView } from "@uiw/react-codemirror";
import { customTheme } from "./themes";
import type { CodeMirrorEditorProps } from "./types";

type CodeMirrorTypescriptEditorProps = CodeMirrorEditorProps & {
jsx: boolean;
};

export function CodeMirrorTypescriptEditor(
props: CodeMirrorTypescriptEditorProps,
) {
const { height, value, onChange, jsx, minHeight, maxHeight, readOnly } =
props;
return (
<CodeMirror
value={value}
height={height}
minHeight={minHeight}
maxHeight={maxHeight}
readOnly={readOnly}
extensions={[
EditorView.lineWrapping,
javascript({ jsx, typescript: true }),
]}
onChange={onChange}
theme={[duotoneDark, customTheme]}
/>
);
}
4 changes: 4 additions & 0 deletions studio/src/components/CodeMirrorEditor/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { CodeMirrorSqlEditor } from "./CodeMirrorSqlEditor";
export { CodeMirrorTypescriptEditor } from "./CodeMirrorTypescriptEditor";
export { CodeMirrorJsonEditor } from "./CodeMirrorJsonEditor";
export { CodeMirrorInput } from "./CodeMirrorInput";
9 changes: 9 additions & 0 deletions studio/src/components/CodeMirrorEditor/themes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { EditorView } from "@uiw/react-codemirror";

export const customTheme = EditorView.theme({
"&": {
fontSize: "14px",
// HACK
background: "transparent !important",
},
});
9 changes: 9 additions & 0 deletions studio/src/components/CodeMirrorEditor/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export type CodeMirrorEditorProps = {
height?: string;
minHeight?: string;
maxHeight?: string;
theme?: string;
readOnly?: boolean;
value?: string;
onChange: (value?: string) => void;
};
Loading
Loading