mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-21 00:01:22 +02:00
Beginnings of autocomplete for headers
This commit is contained in:
@@ -21,11 +21,12 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const MIN_WIDTH = 110;
|
const MIN_WIDTH = 110;
|
||||||
|
const INITIAL_WIDTH = 200;
|
||||||
const MAX_WIDTH = 500;
|
const MAX_WIDTH = 500;
|
||||||
|
|
||||||
export function Sidebar({ className }: Props) {
|
export function Sidebar({ className }: Props) {
|
||||||
const [isDragging, setIsDragging] = useState<boolean>(false);
|
const [isDragging, setIsDragging] = useState<boolean>(false);
|
||||||
const width = useKeyValue<number>({ key: 'sidebar_width', initialValue: 200 });
|
const width = useKeyValue<number>({ key: 'sidebar_width', initialValue: INITIAL_WIDTH });
|
||||||
const requests = useRequests();
|
const requests = useRequests();
|
||||||
const activeRequest = useActiveRequest();
|
const activeRequest = useActiveRequest();
|
||||||
const createRequest = useCreateRequest({ navigateAfter: true });
|
const createRequest = useCreateRequest({ navigateAfter: true });
|
||||||
@@ -39,6 +40,10 @@ export function Sidebar({ className }: Props) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleResizeReset = () => {
|
||||||
|
width.set(INITIAL_WIDTH);
|
||||||
|
};
|
||||||
|
|
||||||
const handleResizeStart = (e: React.MouseEvent<HTMLDivElement>) => {
|
const handleResizeStart = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
unsub();
|
unsub();
|
||||||
const mouseStartX = e.clientX;
|
const mouseStartX = e.clientX;
|
||||||
@@ -72,6 +77,7 @@ export function Sidebar({ className }: Props) {
|
|||||||
aria-hidden
|
aria-hidden
|
||||||
className="group absolute -right-2 top-0 bottom-0 w-4 cursor-ew-resize flex justify-center"
|
className="group absolute -right-2 top-0 bottom-0 w-4 cursor-ew-resize flex justify-center"
|
||||||
onMouseDown={handleResizeStart}
|
onMouseDown={handleResizeStart}
|
||||||
|
onDoubleClick={handleResizeReset}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classnames(
|
||||||
|
|||||||
@@ -24,7 +24,11 @@ export function UrlBar({ sendRequest, loading, onMethodChange, method, onUrlChan
|
|||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
hideLabel
|
hideLabel
|
||||||
useEditor={{ useTemplating: true, contentType: 'url' }}
|
useEditor={{
|
||||||
|
useTemplating: true,
|
||||||
|
contentType: 'url',
|
||||||
|
autocompleteOptions: [{ label: 'FOO', type: 'constant' }],
|
||||||
|
}}
|
||||||
className="px-0"
|
className="px-0"
|
||||||
name="url"
|
name="url"
|
||||||
label="Enter URL"
|
label="Enter URL"
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import { useWindowSize } from 'react-use';
|
import { useWindowSize } from 'react-use';
|
||||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||||
|
import { useDeleteRequest } from '../hooks/useDeleteRequest';
|
||||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import { DropdownMenuRadio, DropdownMenuTrigger } from './core/Dropdown';
|
import { Dropdown, DropdownMenuRadio, DropdownMenuTrigger } from './core/Dropdown';
|
||||||
|
import { Icon } from './core/Icon';
|
||||||
import { IconButton } from './core/IconButton';
|
import { IconButton } from './core/IconButton';
|
||||||
import { HStack } from './core/Stacks';
|
import { HStack } from './core/Stacks';
|
||||||
import { WindowDragRegion } from './core/WindowDragRegion';
|
import { WindowDragRegion } from './core/WindowDragRegion';
|
||||||
@@ -17,6 +19,7 @@ export default function Workspace() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const activeRequest = useActiveRequest();
|
const activeRequest = useActiveRequest();
|
||||||
const activeWorkspace = useActiveWorkspace();
|
const activeWorkspace = useActiveWorkspace();
|
||||||
|
const deleteRequest = useDeleteRequest(activeRequest);
|
||||||
const workspaces = useWorkspaces();
|
const workspaces = useWorkspaces();
|
||||||
const { width } = useWindowSize();
|
const { width } = useWindowSize();
|
||||||
const isSideBySide = width > 900;
|
const isSideBySide = width > 900;
|
||||||
@@ -54,7 +57,25 @@ export default function Workspace() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex-1 flex justify-end -mr-2">
|
<div className="flex-1 flex justify-end -mr-2">
|
||||||
<IconButton size="sm" title="" icon="magnifyingGlass" />
|
<IconButton size="sm" title="" icon="magnifyingGlass" />
|
||||||
<IconButton size="sm" title="" icon="gear" />
|
<Dropdown
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
label: 'Something Else',
|
||||||
|
onSelect: () => null,
|
||||||
|
leftSlot: <Icon icon="camera" />,
|
||||||
|
},
|
||||||
|
'-----',
|
||||||
|
{
|
||||||
|
label: 'Delete Request',
|
||||||
|
onSelect: deleteRequest.mutate,
|
||||||
|
leftSlot: <Icon icon="trash" />,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<DropdownMenuTrigger>
|
||||||
|
<IconButton size="sm" title="Request Options" icon="gear" />
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
</HStack>
|
</HStack>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
@apply bg-transparent;
|
@apply bg-transparent;
|
||||||
}
|
}
|
||||||
&.cm-focused .cm-selectionBackground {
|
&.cm-focused .cm-selectionBackground {
|
||||||
@apply bg-gray-400;
|
@apply bg-violet-500/20;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style gutters */
|
/* Style gutters */
|
||||||
@@ -155,7 +155,7 @@
|
|||||||
|
|
||||||
&.cm-tooltip-autocomplete {
|
&.cm-tooltip-autocomplete {
|
||||||
& > ul {
|
& > ul {
|
||||||
@apply p-1 max-h-[40vh];
|
@apply p-1 max-h-[20rem];
|
||||||
}
|
}
|
||||||
|
|
||||||
& > ul > li {
|
& > ul > li {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { useUnmount } from 'react-use';
|
|||||||
import { IconButton } from '../IconButton';
|
import { IconButton } from '../IconButton';
|
||||||
import './Editor.css';
|
import './Editor.css';
|
||||||
import { baseExtensions, getLanguageExtension, multiLineExtensions } from './extensions';
|
import { baseExtensions, getLanguageExtension, multiLineExtensions } from './extensions';
|
||||||
|
import type { GenericCompletionOption } from './genericCompletion';
|
||||||
import { singleLineExt } from './singleLine';
|
import { singleLineExt } from './singleLine';
|
||||||
|
|
||||||
export interface _EditorProps {
|
export interface _EditorProps {
|
||||||
@@ -26,6 +27,7 @@ export interface _EditorProps {
|
|||||||
onFocus?: () => void;
|
onFocus?: () => void;
|
||||||
singleLine?: boolean;
|
singleLine?: boolean;
|
||||||
format?: (v: string) => string;
|
format?: (v: string) => string;
|
||||||
|
autocompleteOptions?: GenericCompletionOption[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function _Editor({
|
export function _Editor({
|
||||||
@@ -41,6 +43,7 @@ export function _Editor({
|
|||||||
className,
|
className,
|
||||||
singleLine,
|
singleLine,
|
||||||
format,
|
format,
|
||||||
|
autocompleteOptions,
|
||||||
}: _EditorProps) {
|
}: _EditorProps) {
|
||||||
const cm = useRef<{ view: EditorView; languageCompartment: Compartment } | null>(null);
|
const cm = useRef<{ view: EditorView; languageCompartment: Compartment } | null>(null);
|
||||||
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
||||||
@@ -77,16 +80,16 @@ export function _Editor({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (cm.current === null) return;
|
if (cm.current === null) return;
|
||||||
const { view, languageCompartment } = cm.current;
|
const { view, languageCompartment } = cm.current;
|
||||||
const ext = getLanguageExtension({ contentType, useTemplating });
|
const ext = getLanguageExtension({ contentType, useTemplating, autocompleteOptions });
|
||||||
view.dispatch({ effects: languageCompartment.reconfigure(ext) });
|
view.dispatch({ effects: languageCompartment.reconfigure(ext) });
|
||||||
}, [contentType]);
|
}, [contentType, JSON.stringify(autocompleteOptions)]);
|
||||||
|
|
||||||
// Initialize the editor when ref mounts
|
// Initialize the editor when ref mounts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (wrapperRef.current === null || cm.current !== null) return;
|
if (wrapperRef.current === null || cm.current !== null) return;
|
||||||
try {
|
try {
|
||||||
const languageCompartment = new Compartment();
|
const languageCompartment = new Compartment();
|
||||||
const langExt = getLanguageExtension({ contentType, useTemplating });
|
const langExt = getLanguageExtension({ contentType, useTemplating, autocompleteOptions });
|
||||||
const state = EditorState.create({
|
const state = EditorState.create({
|
||||||
doc: `${defaultValue ?? ''}`,
|
doc: `${defaultValue ?? ''}`,
|
||||||
extensions: [
|
extensions: [
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ import {
|
|||||||
} from '@codemirror/view';
|
} from '@codemirror/view';
|
||||||
import { tags as t } from '@lezer/highlight';
|
import { tags as t } from '@lezer/highlight';
|
||||||
import { graphqlLanguageSupport } from 'cm6-graphql';
|
import { graphqlLanguageSupport } from 'cm6-graphql';
|
||||||
|
import type { GenericCompletionOption } from './genericCompletion';
|
||||||
|
import { text } from './text/extension';
|
||||||
import { twig } from './twig/extension';
|
import { twig } from './twig/extension';
|
||||||
import { url } from './url/extension';
|
import { url } from './url/extension';
|
||||||
|
|
||||||
@@ -93,17 +95,19 @@ const syntaxExtensions: Record<string, LanguageSupport> = {
|
|||||||
export function getLanguageExtension({
|
export function getLanguageExtension({
|
||||||
contentType,
|
contentType,
|
||||||
useTemplating = false,
|
useTemplating = false,
|
||||||
|
autocompleteOptions,
|
||||||
}: {
|
}: {
|
||||||
contentType?: string;
|
contentType?: string;
|
||||||
useTemplating?: boolean;
|
useTemplating?: boolean;
|
||||||
|
autocompleteOptions?: GenericCompletionOption[];
|
||||||
}) {
|
}) {
|
||||||
const justContentType = contentType?.split(';')[0] ?? contentType ?? '';
|
const justContentType = contentType?.split(';')[0] ?? contentType ?? '';
|
||||||
const base = syntaxExtensions[justContentType] ?? json();
|
const base = syntaxExtensions[justContentType] ?? text();
|
||||||
if (!useTemplating) {
|
if (!useTemplating) {
|
||||||
return [base];
|
return base ? base : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return twig(base);
|
return twig(base, autocompleteOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const baseExtensions = [
|
export const baseExtensions = [
|
||||||
@@ -115,7 +119,7 @@ export const baseExtensions = [
|
|||||||
// TODO: Figure out how to debounce showing of autocomplete in a good way
|
// TODO: Figure out how to debounce showing of autocomplete in a good way
|
||||||
// debouncedAutocompletionDisplay({ millis: 1000 }),
|
// debouncedAutocompletionDisplay({ millis: 1000 }),
|
||||||
// autocompletion({ closeOnBlur: true, interactionDelay: 200, activateOnTyping: false }),
|
// autocompletion({ closeOnBlur: true, interactionDelay: 200, activateOnTyping: false }),
|
||||||
autocompletion({ closeOnBlur: true, interactionDelay: 300 }),
|
autocompletion({ closeOnBlur: true, interactionDelay: 200 }),
|
||||||
syntaxHighlighting(myHighlightStyle),
|
syntaxHighlighting(myHighlightStyle),
|
||||||
EditorState.allowMultipleSelections.of(true),
|
EditorState.allowMultipleSelections.of(true),
|
||||||
];
|
];
|
||||||
|
|||||||
25
src-web/components/core/Editor/genericCompletion.ts
Normal file
25
src-web/components/core/Editor/genericCompletion.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import type { CompletionContext } from '@codemirror/autocomplete';
|
||||||
|
|
||||||
|
export interface GenericCompletionOption {
|
||||||
|
label: string;
|
||||||
|
type: 'constant' | 'variable';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function genericCompletion({
|
||||||
|
options,
|
||||||
|
minMatch = 1,
|
||||||
|
}: {
|
||||||
|
options: GenericCompletionOption[];
|
||||||
|
minMatch?: number;
|
||||||
|
}) {
|
||||||
|
return function completions(context: CompletionContext) {
|
||||||
|
const toMatch = context.matchBefore(/^[\w:/]*/);
|
||||||
|
if (toMatch === null) return null;
|
||||||
|
|
||||||
|
const matchedMinimumLength = toMatch.to - toMatch.from >= minMatch;
|
||||||
|
if (!matchedMinimumLength && !context.explicit) return null;
|
||||||
|
|
||||||
|
const optionsWithoutExactMatches = options.filter((o) => o.label !== toMatch.text);
|
||||||
|
return { from: toMatch.from, options: optionsWithoutExactMatches };
|
||||||
|
};
|
||||||
|
}
|
||||||
11
src-web/components/core/Editor/text/extension.ts
Normal file
11
src-web/components/core/Editor/text/extension.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { LanguageSupport, LRLanguage } from '@codemirror/language';
|
||||||
|
import { parser } from './text';
|
||||||
|
|
||||||
|
const textLanguage = LRLanguage.define({
|
||||||
|
parser,
|
||||||
|
languageData: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export function text() {
|
||||||
|
return new LanguageSupport(textLanguage);
|
||||||
|
}
|
||||||
5
src-web/components/core/Editor/text/text.grammar
Normal file
5
src-web/components/core/Editor/text/text.grammar
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
@top Template { Text }
|
||||||
|
|
||||||
|
@tokens {
|
||||||
|
Text { ![]+ }
|
||||||
|
}
|
||||||
4
src-web/components/core/Editor/text/text.terms.ts
Normal file
4
src-web/components/core/Editor/text/text.terms.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||||
|
export const
|
||||||
|
Template = 1,
|
||||||
|
Text = 2
|
||||||
16
src-web/components/core/Editor/text/text.ts
Normal file
16
src-web/components/core/Editor/text/text.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||||
|
import {LRParser} from "@lezer/lr"
|
||||||
|
export const parser = LRParser.deserialize({
|
||||||
|
version: 14,
|
||||||
|
states: "[OQOPOOQOOOOO",
|
||||||
|
stateData: "V~OQPO~O",
|
||||||
|
goto: "QPP",
|
||||||
|
nodeNames: "⚠ Template Text",
|
||||||
|
maxTerm: 3,
|
||||||
|
skippedNodes: [0],
|
||||||
|
repeatNodeCount: 0,
|
||||||
|
tokenData: "p~RRO;'S[;'S;=`j<%lO[~aRQ~O;'S[;'S;=`j<%lO[~mP;=`<%l[",
|
||||||
|
tokenizers: [0],
|
||||||
|
topRules: {"Template":[0,1]},
|
||||||
|
tokenPrec: 0
|
||||||
|
})
|
||||||
@@ -6,6 +6,7 @@ const closeTag = ' ]}';
|
|||||||
const variables = [
|
const variables = [
|
||||||
{ name: 'DOMAIN' },
|
{ name: 'DOMAIN' },
|
||||||
{ name: 'BASE_URL' },
|
{ name: 'BASE_URL' },
|
||||||
|
{ name: 'CONTENT_THINGY' },
|
||||||
{ name: 'TOKEN' },
|
{ name: 'TOKEN' },
|
||||||
{ name: 'PROJECT_ID' },
|
{ name: 'PROJECT_ID' },
|
||||||
{ name: 'DUMMY' },
|
{ name: 'DUMMY' },
|
||||||
@@ -17,7 +18,7 @@ const variables = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const MIN_MATCH_VAR = 2;
|
const MIN_MATCH_VAR = 2;
|
||||||
const MIN_MATCH_NAME = 4;
|
const MIN_MATCH_NAME = 3;
|
||||||
|
|
||||||
export function completions(context: CompletionContext) {
|
export function completions(context: CompletionContext) {
|
||||||
const toStartOfName = context.matchBefore(/\w*/);
|
const toStartOfName = context.matchBefore(/\w*/);
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
import { LanguageSupport, LRLanguage } from '@codemirror/language';
|
import { LanguageSupport, LRLanguage } from '@codemirror/language';
|
||||||
import { parseMixed } from '@lezer/common';
|
import { parseMixed } from '@lezer/common';
|
||||||
import { completions } from './completion';
|
import type { GenericCompletionOption } from '../genericCompletion';
|
||||||
|
import { genericCompletion } from '../genericCompletion';
|
||||||
import { placeholders } from '../widgets';
|
import { placeholders } from '../widgets';
|
||||||
|
import { completions } from './completion';
|
||||||
import { parser as twigParser } from './twig';
|
import { parser as twigParser } from './twig';
|
||||||
|
|
||||||
export function twig(base?: LanguageSupport) {
|
export function twig(base?: LanguageSupport, autocompleteOptions?: GenericCompletionOption[]) {
|
||||||
const language = mixedOrPlainLanguage(base);
|
const language = mixedOrPlainLanguage(base);
|
||||||
|
const additionalCompletion =
|
||||||
|
autocompleteOptions && base
|
||||||
|
? [language.data.of({ autocomplete: genericCompletion({ options: autocompleteOptions }) })]
|
||||||
|
: [];
|
||||||
const completion = language.data.of({
|
const completion = language.data.of({
|
||||||
autocomplete: completions,
|
autocomplete: completions,
|
||||||
});
|
});
|
||||||
const languageSupport = new LanguageSupport(language, [completion]);
|
const languageSupport = new LanguageSupport(language, [completion, ...additionalCompletion]);
|
||||||
|
|
||||||
if (base) {
|
if (base) {
|
||||||
const completion2 = base.language.data.of({ autocomplete: completions });
|
const completion2 = base.language.data.of({ autocomplete: completions });
|
||||||
@@ -23,18 +29,15 @@ export function twig(base?: LanguageSupport) {
|
|||||||
function mixedOrPlainLanguage(base?: LanguageSupport): LRLanguage {
|
function mixedOrPlainLanguage(base?: LanguageSupport): LRLanguage {
|
||||||
const name = 'twig';
|
const name = 'twig';
|
||||||
|
|
||||||
if (base == null) {
|
if (!base) {
|
||||||
return LRLanguage.define({ name, parser: twigParser });
|
return LRLanguage.define({ name, parser: twigParser });
|
||||||
}
|
}
|
||||||
|
|
||||||
const parser = twigParser.configure({
|
const parser = twigParser.configure({
|
||||||
wrap: parseMixed((node) => {
|
wrap: parseMixed(() => ({
|
||||||
if (!node.type.isTop) return null;
|
parser: base.language.parser,
|
||||||
return {
|
overlay: (node) => node.type.name === 'Text' || node.type.name === 'Template',
|
||||||
parser: base.language.parser,
|
})),
|
||||||
overlay: (node) => node.type.name === 'Text',
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return LRLanguage.define({ name, parser });
|
return LRLanguage.define({ name, parser });
|
||||||
|
|||||||
@@ -1,19 +1,9 @@
|
|||||||
import type { CompletionContext } from '@codemirror/autocomplete';
|
import { genericCompletion } from '../genericCompletion';
|
||||||
|
|
||||||
const options = [
|
export const completions = genericCompletion({
|
||||||
{ label: 'http://', type: 'constant' },
|
options: [
|
||||||
{ label: 'https://', type: 'constant' },
|
{ label: 'http://', type: 'constant' },
|
||||||
];
|
{ label: 'https://', type: 'constant' },
|
||||||
|
],
|
||||||
const MIN_MATCH = 1;
|
minMatch: 1,
|
||||||
|
});
|
||||||
export function completions(context: CompletionContext) {
|
|
||||||
const toMatch = context.matchBefore(/^[\w:/]*/);
|
|
||||||
if (toMatch === null) return null;
|
|
||||||
|
|
||||||
const matchedMinimumLength = toMatch.to - toMatch.from >= MIN_MATCH;
|
|
||||||
if (!matchedMinimumLength && !context.explicit) return null;
|
|
||||||
|
|
||||||
const optionsWithoutExactMatches = options.filter((o) => o.label !== toMatch.text);
|
|
||||||
return { from: toMatch.from, options: optionsWithoutExactMatches };
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ type Props = Omit<HTMLAttributes<HTMLInputElement>, 'onChange' | 'onFocus'> & {
|
|||||||
containerClassName?: string;
|
containerClassName?: string;
|
||||||
onChange?: (value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
onFocus?: () => void;
|
onFocus?: () => void;
|
||||||
useEditor?: Pick<EditorProps, 'contentType' | 'useTemplating'>;
|
useEditor?: Pick<EditorProps, 'contentType' | 'useTemplating' | 'autocompleteOptions'>;
|
||||||
defaultValue?: string;
|
defaultValue?: string;
|
||||||
leftSlot?: ReactNode;
|
leftSlot?: ReactNode;
|
||||||
rightSlot?: ReactNode;
|
rightSlot?: ReactNode;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
import type { GenericCompletionOption } from './Editor/genericCompletion';
|
||||||
import { IconButton } from './IconButton';
|
import { IconButton } from './IconButton';
|
||||||
import { Input } from './Input';
|
import { Input } from './Input';
|
||||||
import { VStack } from './Stacks';
|
import { VStack } from './Stacks';
|
||||||
@@ -94,6 +95,17 @@ function FormRow({
|
|||||||
isLast?: boolean;
|
isLast?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { id } = pairContainer;
|
const { id } = pairContainer;
|
||||||
|
const valueOptions = useMemo<GenericCompletionOption[] | undefined>(() => {
|
||||||
|
if (pairContainer.pair.name.toLowerCase() === 'content-type') {
|
||||||
|
return [
|
||||||
|
{ label: 'application/json', type: 'constant' },
|
||||||
|
{ label: 'text/xml', type: 'constant' },
|
||||||
|
{ label: 'text/html', type: 'constant' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}, [pairContainer.pair.value]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group grid grid-cols-[1fr_1fr_auto] grid-rows-1 gap-2 items-center">
|
<div className="group grid grid-cols-[1fr_1fr_auto] grid-rows-1 gap-2 items-center">
|
||||||
<Input
|
<Input
|
||||||
@@ -105,7 +117,10 @@ function FormRow({
|
|||||||
onChange={(name) => onChange({ id, pair: { name, value: pairContainer.pair.value } })}
|
onChange={(name) => onChange({ id, pair: { name, value: pairContainer.pair.value } })}
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
placeholder={isLast ? 'new name' : 'name'}
|
placeholder={isLast ? 'new name' : 'name'}
|
||||||
useEditor={{ useTemplating: true, contentType: 'text/plain' }}
|
useEditor={{
|
||||||
|
useTemplating: true,
|
||||||
|
autocompleteOptions: [{ label: 'Content-Type', type: 'constant' }],
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
hideLabel
|
hideLabel
|
||||||
@@ -116,7 +131,7 @@ function FormRow({
|
|||||||
onChange={(value) => onChange({ id, pair: { name: pairContainer.pair.name, value } })}
|
onChange={(value) => onChange({ id, pair: { name: pairContainer.pair.name, value } })}
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
placeholder={isLast ? 'new value' : 'value'}
|
placeholder={isLast ? 'new value' : 'value'}
|
||||||
useEditor={{ useTemplating: true, contentType: 'text/plain' }}
|
useEditor={{ useTemplating: true, autocompleteOptions: valueOptions }}
|
||||||
/>
|
/>
|
||||||
{onDelete ? (
|
{onDelete ? (
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
export function useWorkspaces() {
|
export function useWorkspaces() {
|
||||||
return (
|
return (
|
||||||
useQuery(['workspaces'], async () => {
|
useQuery(['workspaces'], async () => {
|
||||||
console.log('INVOKING WORKSPACES');
|
|
||||||
const workspaces = (await invoke('workspaces')) as Workspace[];
|
const workspaces = (await invoke('workspaces')) as Workspace[];
|
||||||
return workspaces.map(convertDates);
|
return workspaces.map(convertDates);
|
||||||
}).data ?? []
|
}).data ?? []
|
||||||
|
|||||||
Reference in New Issue
Block a user