mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-11 17:46:41 -05:00
Pair checkboxes and fix twig indent
This commit is contained in:
80
package-lock.json
generated
80
package-lock.json
generated
@@ -18,6 +18,7 @@
|
||||
"@lezer/generator": "^1.2.2",
|
||||
"@lezer/highlight": "^1.1.3",
|
||||
"@lezer/lr": "^1.3.3",
|
||||
"@radix-ui/react-checkbox": "^1.0.3",
|
||||
"@radix-ui/react-dialog": "^1.0.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.2",
|
||||
"@radix-ui/react-icons": "^1.2.0",
|
||||
@@ -1276,6 +1277,39 @@
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-checkbox": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.0.3.tgz",
|
||||
"integrity": "sha512-55B8/vKzTuzxllH5sGJO4zaBf9gYpJuJRRzaOKm+0oAefRnMvbf+Kgww7IOANVN0w3z7agFJgtnXaZl8Uj95AA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-context": "1.0.0",
|
||||
"@radix-ui/react-presence": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.2",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.0",
|
||||
"@radix-ui/react-use-previous": "1.0.0",
|
||||
"@radix-ui/react-use-size": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz",
|
||||
"integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collection": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.1.tgz",
|
||||
@@ -1735,6 +1769,17 @@
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-previous": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.0.0.tgz",
|
||||
"integrity": "sha512-RG2K8z/K7InnOKpq6YLDmT49HGjNmrK+fr82UCVKT2sW0GYfVnYp4wZWBooT/EYfQ5faA9uIjvsuMMhH61rheg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-rect": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.0.tgz",
|
||||
@@ -8930,6 +8975,33 @@
|
||||
"@radix-ui/react-primitive": "1.0.1"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-checkbox": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.0.3.tgz",
|
||||
"integrity": "sha512-55B8/vKzTuzxllH5sGJO4zaBf9gYpJuJRRzaOKm+0oAefRnMvbf+Kgww7IOANVN0w3z7agFJgtnXaZl8Uj95AA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-context": "1.0.0",
|
||||
"@radix-ui/react-presence": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.2",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.0",
|
||||
"@radix-ui/react-use-previous": "1.0.0",
|
||||
"@radix-ui/react-use-size": "1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-primitive": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz",
|
||||
"integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-collection": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.1.tgz",
|
||||
@@ -9287,6 +9359,14 @@
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-use-previous": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.0.0.tgz",
|
||||
"integrity": "sha512-RG2K8z/K7InnOKpq6YLDmT49HGjNmrK+fr82UCVKT2sW0GYfVnYp4wZWBooT/EYfQ5faA9uIjvsuMMhH61rheg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-use-rect": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.0.tgz",
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"@lezer/generator": "^1.2.2",
|
||||
"@lezer/highlight": "^1.1.3",
|
||||
"@lezer/lr": "^1.3.3",
|
||||
"@radix-ui/react-checkbox": "^1.0.3",
|
||||
"@radix-ui/react-dialog": "^1.0.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.2",
|
||||
"@radix-ui/react-icons": "^1.2.0",
|
||||
|
||||
Binary file not shown.
@@ -18,6 +18,8 @@ pub struct Workspace {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct HttpRequestHeader {
|
||||
#[serde(default)]
|
||||
pub enabled: bool,
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import classnames from 'classnames';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useKeyValue } from '../hooks/useKeyValue';
|
||||
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
||||
import { tryFormatJson } from '../lib/formatters';
|
||||
import type { HttpHeader } from '../lib/models';
|
||||
import { Editor } from './core/Editor';
|
||||
import { PairEditor } from './core/PairEditor';
|
||||
import type { TabItem } from './core/Tabs/Tabs';
|
||||
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
||||
import { GraphQLEditor } from './GraphQLEditor';
|
||||
@@ -26,7 +24,7 @@ export function RequestPane({ fullHeight, className }: Props) {
|
||||
const updateRequest = useUpdateRequest(activeRequestId);
|
||||
const activeTab = useKeyValue<string>({
|
||||
key: ['active_request_body_tab', activeRequestId ?? 'n/a'],
|
||||
initialValue: 'body',
|
||||
defaultValue: 'body',
|
||||
});
|
||||
|
||||
const tabs: TabItem[] = useMemo(
|
||||
@@ -40,6 +38,7 @@ export function RequestPane({ fullHeight, className }: Props) {
|
||||
items: [
|
||||
{ label: 'No Body', value: 'nobody' },
|
||||
{ label: 'JSON', value: 'json' },
|
||||
{ label: 'XML', value: 'xml' },
|
||||
{ label: 'GraphQL', value: 'graphql' },
|
||||
],
|
||||
},
|
||||
@@ -57,53 +56,65 @@ export function RequestPane({ fullHeight, className }: Props) {
|
||||
[],
|
||||
);
|
||||
|
||||
if (activeRequest === null) return null;
|
||||
|
||||
return (
|
||||
<div className={classnames(className, 'py-3 grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1')}>
|
||||
<UrlBar className="pl-3" request={activeRequest} />
|
||||
<Tabs
|
||||
value={activeTab.value}
|
||||
onChangeValue={activeTab.set}
|
||||
tabs={tabs}
|
||||
className="mt-2"
|
||||
tabListClassName="pl-3"
|
||||
label="Request body"
|
||||
>
|
||||
<TabContent value="headers">
|
||||
<HeaderEditor
|
||||
key={activeRequestId}
|
||||
headers={activeRequest.headers}
|
||||
onChange={handleHeadersChange}
|
||||
/>
|
||||
</TabContent>
|
||||
<TabContent value="params">
|
||||
<ParametersEditor key={activeRequestId} parameters={[]} onChange={() => null} />
|
||||
</TabContent>
|
||||
<TabContent value="body" className="pl-3 mt-1">
|
||||
{activeRequest.bodyType === 'json' ? (
|
||||
<Editor
|
||||
key={activeRequest.id}
|
||||
useTemplating
|
||||
className="!bg-gray-50"
|
||||
heightMode={fullHeight ? 'full' : 'auto'}
|
||||
defaultValue={activeRequest.body ?? ''}
|
||||
contentType="application/json"
|
||||
onChange={handleBodyChange}
|
||||
format={activeRequest.bodyType === 'json' ? (v) => tryFormatJson(v) : undefined}
|
||||
/>
|
||||
) : activeRequest.bodyType === 'graphql' ? (
|
||||
<GraphQLEditor
|
||||
key={activeRequest.id}
|
||||
className="!bg-gray-50"
|
||||
defaultValue={activeRequest?.body ?? ''}
|
||||
onChange={handleBodyChange}
|
||||
/>
|
||||
) : (
|
||||
<div className="h-full text-gray-400 flex items-center justify-center">No Body</div>
|
||||
)}
|
||||
</TabContent>
|
||||
</Tabs>
|
||||
{activeRequest && (
|
||||
<>
|
||||
<UrlBar className="pl-3" request={activeRequest} />
|
||||
<Tabs
|
||||
value={activeTab.value}
|
||||
onChangeValue={activeTab.set}
|
||||
tabs={tabs}
|
||||
className="mt-2"
|
||||
tabListClassName="pl-3"
|
||||
label="Request body"
|
||||
>
|
||||
<TabContent value="headers">
|
||||
<HeaderEditor
|
||||
key={activeRequestId}
|
||||
headers={activeRequest.headers}
|
||||
onChange={handleHeadersChange}
|
||||
/>
|
||||
</TabContent>
|
||||
<TabContent value="params">
|
||||
<ParametersEditor key={activeRequestId} parameters={[]} onChange={() => null} />
|
||||
</TabContent>
|
||||
<TabContent value="body" className="pl-3 mt-1">
|
||||
{activeRequest.bodyType === 'json' ? (
|
||||
<Editor
|
||||
key={activeRequest.id}
|
||||
useTemplating
|
||||
className="!bg-gray-50"
|
||||
heightMode={fullHeight ? 'full' : 'auto'}
|
||||
defaultValue={activeRequest.body ?? ''}
|
||||
contentType="application/json"
|
||||
onChange={handleBodyChange}
|
||||
format={(v) => tryFormatJson(v)}
|
||||
/>
|
||||
) : activeRequest.bodyType === 'xml' ? (
|
||||
<Editor
|
||||
key={activeRequest.id}
|
||||
useTemplating
|
||||
className="!bg-gray-50"
|
||||
heightMode={fullHeight ? 'full' : 'auto'}
|
||||
defaultValue={activeRequest.body ?? ''}
|
||||
contentType="text/xml"
|
||||
onChange={handleBodyChange}
|
||||
/>
|
||||
) : activeRequest.bodyType === 'graphql' ? (
|
||||
<GraphQLEditor
|
||||
key={activeRequest.id}
|
||||
className="!bg-gray-50"
|
||||
defaultValue={activeRequest?.body ?? ''}
|
||||
onChange={handleBodyChange}
|
||||
/>
|
||||
) : (
|
||||
<div className="h-full text-gray-400 flex items-center justify-center">No Body</div>
|
||||
)}
|
||||
</TabContent>
|
||||
</Tabs>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -309,7 +309,7 @@ const _SidebarItem = forwardRef(function SidebarItem(
|
||||
className={classnames(
|
||||
buttonClassName,
|
||||
'w-full',
|
||||
editing && 'focus-within:border-blue-400/40',
|
||||
editing && 'focus-within:border-focus',
|
||||
active
|
||||
? 'bg-gray-200/70 text-gray-900'
|
||||
: 'text-gray-600 group-hover/item:text-gray-800 active:bg-gray-200/30',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import classnames from 'classnames';
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
|
||||
@@ -24,7 +24,10 @@ export function WorkspaceDropdown({ className }: Props) {
|
||||
label: w.name,
|
||||
value: w.id,
|
||||
leftSlot: activeWorkspace?.id === w.id ? <Icon icon="check" /> : <Icon icon="empty" />,
|
||||
onSelect: () => navigate(`/workspaces/${w.id}`),
|
||||
onSelect: () => {
|
||||
if (w.id === activeWorkspace?.id) return;
|
||||
navigate(`/workspaces/${w.id}`);
|
||||
},
|
||||
}));
|
||||
|
||||
return [
|
||||
|
||||
35
src-web/components/core/Checkbox.tsx
Normal file
35
src-web/components/core/Checkbox.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { CheckedState } from '@radix-ui/react-checkbox';
|
||||
import * as CB from '@radix-ui/react-checkbox';
|
||||
import classnames from 'classnames';
|
||||
import { Icon } from './Icon';
|
||||
|
||||
interface Props {
|
||||
checked: CheckedState;
|
||||
onChange: (checked: CheckedState) => void;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Checkbox({ checked, onChange, className, disabled }: Props) {
|
||||
return (
|
||||
<CB.Root
|
||||
disabled={disabled}
|
||||
checked={checked}
|
||||
onCheckedChange={onChange}
|
||||
className={classnames(
|
||||
className,
|
||||
'w-5 h-5 border border-gray-200 rounded',
|
||||
'focus:border-focus',
|
||||
'disabled:opacity-disabled',
|
||||
'outline-none',
|
||||
checked && 'bg-gray-200/10',
|
||||
// Remove focus style
|
||||
)}
|
||||
>
|
||||
<CB.Indicator className="flex items-center justify-center">
|
||||
{checked === 'indeterminate' && <Icon icon="dividerH" />}
|
||||
{checked === true && <Icon icon="check" />}
|
||||
</CB.Indicator>
|
||||
</CB.Root>
|
||||
);
|
||||
}
|
||||
@@ -182,7 +182,7 @@ const DropdownMenuItem = memo(function DropdownMenuItem({
|
||||
<D.Item
|
||||
asChild
|
||||
disabled={disabled}
|
||||
className={classnames(className, disabled && 'opacity-30')}
|
||||
className={classnames(className, disabled && 'opacity-disabled')}
|
||||
{...props}
|
||||
>
|
||||
<ItemInner leftSlot={leftSlot} rightSlot={rightSlot}>
|
||||
|
||||
@@ -33,7 +33,6 @@ import {
|
||||
} from '@codemirror/view';
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { graphqlLanguageSupport } from 'cm6-graphql';
|
||||
import type { GenericCompletionOption } from './genericCompletion';
|
||||
import type { EditorProps } from './index';
|
||||
import { text } from './text/extension';
|
||||
import { twig } from './twig/extension';
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { LanguageSupport, LRLanguage } from '@codemirror/language';
|
||||
import { parser } from './text';
|
||||
|
||||
export const textLanguageName = 'text';
|
||||
|
||||
const textLanguage = LRLanguage.define({
|
||||
name: 'text',
|
||||
name: textLanguageName,
|
||||
parser,
|
||||
languageData: {},
|
||||
});
|
||||
|
||||
@@ -1,47 +1,53 @@
|
||||
import { LanguageSupport, LRLanguage } from '@codemirror/language';
|
||||
import type { LanguageSupport } from '@codemirror/language';
|
||||
import { LRLanguage } from '@codemirror/language';
|
||||
import { parseMixed } from '@lezer/common';
|
||||
import type { GenericCompletionConfig } from '../genericCompletion';
|
||||
import { genericCompletion } from '../genericCompletion';
|
||||
import { textLanguageName } from '../text/extension';
|
||||
import { placeholders } from '../widgets';
|
||||
import { completions } from './completion';
|
||||
import { parser as twigParser } from './twig';
|
||||
|
||||
export function twig(base?: LanguageSupport, autocomplete?: GenericCompletionConfig) {
|
||||
const language = mixedOrPlainLanguage(base);
|
||||
const additionalCompletion =
|
||||
autocomplete && base
|
||||
? [language.data.of({ autocomplete: genericCompletion(autocomplete) })]
|
||||
: [];
|
||||
export function twig(base: LanguageSupport, autocomplete?: GenericCompletionConfig) {
|
||||
const language = mixLanguage(base);
|
||||
const additionalCompletion = autocomplete
|
||||
? [language.data.of({ autocomplete: genericCompletion(autocomplete) })]
|
||||
: [];
|
||||
const completion = language.data.of({
|
||||
autocomplete: completions,
|
||||
});
|
||||
const languageSupport = new LanguageSupport(language, [completion, ...additionalCompletion]);
|
||||
|
||||
if (base) {
|
||||
const completion2 = base.language.data.of({ autocomplete: completions });
|
||||
const languageSupport2 = new LanguageSupport(base.language, [completion2]);
|
||||
return [languageSupport, languageSupport2, base.support];
|
||||
const completionBase = base.language.data.of({
|
||||
autocomplete: completions,
|
||||
});
|
||||
return [
|
||||
language,
|
||||
completion,
|
||||
completionBase,
|
||||
base.support,
|
||||
// placeholders,
|
||||
...additionalCompletion,
|
||||
];
|
||||
} else {
|
||||
return [languageSupport];
|
||||
return [language, completion, placeholders];
|
||||
}
|
||||
}
|
||||
|
||||
function mixedOrPlainLanguage(base?: LanguageSupport): LRLanguage {
|
||||
function mixLanguage(base: LanguageSupport): LRLanguage {
|
||||
const name = 'twig';
|
||||
|
||||
if (!base) {
|
||||
return LRLanguage.define({ name, parser: twigParser });
|
||||
}
|
||||
|
||||
const parser = twigParser.configure({
|
||||
wrap: parseMixed((node) => {
|
||||
console.log('HELLO', node.type.name, node.type.isTop);
|
||||
// If the base language is text, we can overwrite at the top
|
||||
if (base.language.name !== 'text' && !node.type.isTop) {
|
||||
if (base.language.name !== textLanguageName && !node.type.isTop) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
parser: base.language.parser,
|
||||
overlay: (node) => node.type.name === 'Text' || node.type.name === 'Template',
|
||||
overlay: (node) => node.type.name === 'Text',
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { styleTags, tags as t } from '@lezer/highlight';
|
||||
|
||||
export const highlight = styleTags({
|
||||
Open: t.meta,
|
||||
Close: t.meta,
|
||||
Content: t.comment,
|
||||
Template: t.comment,
|
||||
Open: t.tagName,
|
||||
Close: t.tagName,
|
||||
Content: t.keyword,
|
||||
});
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
@top Template { Tag | Text }
|
||||
@top Template { (Tag | Text)* }
|
||||
|
||||
@local tokens {
|
||||
Close { "]}" }
|
||||
@else Content
|
||||
}
|
||||
|
||||
@skip {} {
|
||||
@skip { } {
|
||||
Open { "${[" }
|
||||
Tag { Open (Content)+ Close }
|
||||
}
|
||||
|
||||
@tokens {
|
||||
Text { _ }
|
||||
Text { ![$] Text? }
|
||||
}
|
||||
|
||||
@external propSource highlight from "./highlight"
|
||||
|
||||
@@ -3,15 +3,15 @@ import {LRParser, LocalTokenGroup} from "@lezer/lr"
|
||||
import {highlight} from "./highlight"
|
||||
export const parser = LRParser.deserialize({
|
||||
version: 14,
|
||||
states: "!QOQOPOOOOOO'#C_'#C_OYOQO'#C^QOOOOOOOOO'#Cc'#CcO_OQO,58xOOOO-E6a-E6aOOOO1G.d1G.d",
|
||||
stateData: "g~OUROXPO~OSSO~OSSOTVO~O",
|
||||
goto: "eWPPX[PPP_RRORQOQTQRUT",
|
||||
states: "!^QQOPOOOOOO'#C_'#C_OYOQO'#C^OOOO'#Cc'#CcQQOPOOOOOO'#Cd'#CdO_OQO,58xOOOO-E6a-E6aOOOO-E6b-E6bOOOO1G.d1G.d",
|
||||
stateData: "g~OUROYPO~OSTO~OSTOTXO~O",
|
||||
goto: "nXPPY^PPPbhTROSTQOSQSORVSQUQRWU",
|
||||
nodeNames: "⚠ Template Tag Open Content Close Text",
|
||||
maxTerm: 9,
|
||||
maxTerm: 10,
|
||||
propSources: [highlight],
|
||||
skippedNodes: [0],
|
||||
repeatNodeCount: 1,
|
||||
tokenData: "!S~RTOtbtugu;'Sb;'S;=`z;=`Ob~gOU~~lPU~#o#po~rP!}#Ou~zOX~~!PPU~;=`<%lb",
|
||||
repeatNodeCount: 2,
|
||||
tokenData: "![~RTOtbtuyu;'Sb;'S;=`s<%lOb~gSU~Otbu;'Sb;'S;=`s<%lOb~vP;=`<%lb~|P#o#p!P~!SP!}#O!V~![OY~",
|
||||
tokenizers: [1, new LocalTokenGroup("b~RP#P#QU~XP#q#r[~aOT~~", 17, 4)],
|
||||
topRules: {"Template":[0,1]},
|
||||
tokenPrec: 0
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import {
|
||||
ArchiveIcon,
|
||||
CameraIcon,
|
||||
CheckboxIcon,
|
||||
CheckIcon,
|
||||
ClockIcon,
|
||||
CodeIcon,
|
||||
ColorWheelIcon,
|
||||
Cross2Icon,
|
||||
DividerHorizontalIcon,
|
||||
DotsHorizontalIcon,
|
||||
DotsVerticalIcon,
|
||||
DragHandleDots2Icon,
|
||||
@@ -35,6 +37,7 @@ const icons = {
|
||||
archive: ArchiveIcon,
|
||||
camera: CameraIcon,
|
||||
check: CheckIcon,
|
||||
checkbox: CheckboxIcon,
|
||||
clock: ClockIcon,
|
||||
code: CodeIcon,
|
||||
colorWheel: ColorWheelIcon,
|
||||
@@ -50,6 +53,7 @@ const icons = {
|
||||
moon: MoonIcon,
|
||||
paperPlane: PaperPlaneIcon,
|
||||
plus: PlusIcon,
|
||||
dividerH: DividerHorizontalIcon,
|
||||
plusCircle: PlusCircledIcon,
|
||||
question: QuestionMarkIcon,
|
||||
rows: RowsIcon,
|
||||
|
||||
@@ -63,7 +63,7 @@ export function Input({
|
||||
className={classnames(
|
||||
containerClassName,
|
||||
'relative w-full rounded-md text-gray-900',
|
||||
'border border-gray-200 focus-within:border-blue-400/40',
|
||||
'border border-gray-200 focus-within:border-focus',
|
||||
size === 'md' && 'h-9',
|
||||
size === 'sm' && 'h-7',
|
||||
)}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { CheckedState } from '@radix-ui/react-checkbox';
|
||||
import classnames from 'classnames';
|
||||
import React, { Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { XYCoord } from 'react-dnd';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { DropMarker } from '../DropMarker';
|
||||
import { Checkbox } from './Checkbox';
|
||||
import type { GenericCompletionConfig } from './Editor/genericCompletion';
|
||||
import { Icon } from './Icon';
|
||||
import { IconButton } from './IconButton';
|
||||
@@ -20,6 +22,7 @@ export type PairEditorProps = {
|
||||
};
|
||||
|
||||
type Pair = {
|
||||
enabled?: boolean;
|
||||
name: string;
|
||||
value: string;
|
||||
};
|
||||
@@ -84,20 +87,17 @@ export const PairEditor = memo(function PairEditor({
|
||||
[hoveredIndex],
|
||||
);
|
||||
|
||||
const handleChangeHeader = useCallback((pair: PairContainer) => {
|
||||
setPairsAndSave((pairs) => pairs.map((p) => (pair.id !== p.id ? p : pair)));
|
||||
}, []);
|
||||
const handleChange = useCallback(
|
||||
(pair: PairContainer) =>
|
||||
setPairsAndSave((pairs) => pairs.map((p) => (pair.id !== p.id ? p : pair))),
|
||||
[],
|
||||
);
|
||||
|
||||
// Ensure there's always at least one pair
|
||||
useEffect(() => {
|
||||
if (pairs.length === 0) {
|
||||
setPairs((pairs) => [...pairs, newPairContainer()]);
|
||||
}
|
||||
}, [pairs]);
|
||||
|
||||
const handleDelete = useCallback((pair: PairContainer) => {
|
||||
setPairsAndSave((oldPairs) => oldPairs.filter((p) => p.id !== pair.id));
|
||||
}, []);
|
||||
const handleDelete = useCallback(
|
||||
(pair: PairContainer) =>
|
||||
setPairsAndSave((oldPairs) => oldPairs.filter((p) => p.id !== pair.id)),
|
||||
[],
|
||||
);
|
||||
|
||||
const handleFocus = useCallback(
|
||||
(pair: PairContainer) => {
|
||||
@@ -109,6 +109,13 @@ export const PairEditor = memo(function PairEditor({
|
||||
[pairs],
|
||||
);
|
||||
|
||||
// Ensure there's always at least one pair
|
||||
useEffect(() => {
|
||||
if (pairs.length === 0) {
|
||||
setPairs((pairs) => [...pairs, newPairContainer()]);
|
||||
}
|
||||
}, [pairs]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(
|
||||
@@ -126,11 +133,11 @@ export const PairEditor = memo(function PairEditor({
|
||||
<FormRow
|
||||
pairContainer={p}
|
||||
isLast={isLast}
|
||||
onChange={handleChangeHeader}
|
||||
nameAutocomplete={nameAutocomplete}
|
||||
valueAutocomplete={valueAutocomplete}
|
||||
namePlaceholder={namePlaceholder}
|
||||
valuePlaceholder={valuePlaceholder}
|
||||
onChange={handleChange}
|
||||
onFocus={handleFocus}
|
||||
onDelete={isLast ? undefined : handleDelete}
|
||||
onEnd={handleEnd}
|
||||
@@ -177,14 +184,20 @@ const FormRow = memo(function FormRow({
|
||||
const { id } = pairContainer;
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleChangeEnabled = useMemo(
|
||||
() => (enabled: CheckedState) =>
|
||||
onChange({ id, pair: { ...pairContainer.pair, enabled: !!enabled } }),
|
||||
[onChange, pairContainer.pair.name, pairContainer.pair.value],
|
||||
);
|
||||
|
||||
const handleChangeName = useMemo(
|
||||
() => (name: string) => onChange({ id, pair: { name, value: pairContainer.pair.value } }),
|
||||
[onChange, pairContainer.pair.value],
|
||||
() => (name: string) => onChange({ id, pair: { ...pairContainer.pair, name } }),
|
||||
[onChange, pairContainer.pair.value, pairContainer.pair.enabled],
|
||||
);
|
||||
|
||||
const handleChangeValue = useMemo(
|
||||
() => (value: string) => onChange({ id, pair: { value, name: pairContainer.pair.name } }),
|
||||
[onChange, pairContainer.pair.name],
|
||||
() => (value: string) => onChange({ id, pair: { ...pairContainer.pair, value } }),
|
||||
[onChange, pairContainer.pair.name, pairContainer.pair.enabled],
|
||||
);
|
||||
|
||||
const nameEditorConfig = useMemo(
|
||||
@@ -231,7 +244,11 @@ const FormRow = memo(function FormRow({
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="pb-2 group grid grid-cols-[auto_minmax(0,1fr)_minmax(0,1fr)_auto] grid-rows-1 gap-2 items-center"
|
||||
className={classnames(
|
||||
'pb-2 group grid grid-cols-[auto_auto_minmax(0,1fr)_minmax(0,1fr)_auto]',
|
||||
'grid-rows-1 gap-2 items-center',
|
||||
!pairContainer.pair.enabled && 'opacity-60',
|
||||
)}
|
||||
>
|
||||
{!isLast ? (
|
||||
<div
|
||||
@@ -245,6 +262,12 @@ const FormRow = memo(function FormRow({
|
||||
) : (
|
||||
<span className="w-1" />
|
||||
)}
|
||||
<Checkbox
|
||||
disabled={isLast}
|
||||
checked={!!pairContainer.pair.enabled}
|
||||
onChange={handleChangeEnabled}
|
||||
className={isLast ? '!opacity-disabled' : undefined}
|
||||
/>
|
||||
<Input
|
||||
hideLabel
|
||||
containerClassName={classnames(isLast && 'border-dashed')}
|
||||
@@ -283,5 +306,5 @@ const FormRow = memo(function FormRow({
|
||||
});
|
||||
|
||||
const newPairContainer = (pair?: Pair): PairContainer => {
|
||||
return { pair: pair ?? { name: '', value: '' }, id: uuid() };
|
||||
return { pair: pair ?? { name: '', value: '', enabled: true }, id: uuid() };
|
||||
};
|
||||
|
||||
@@ -22,8 +22,8 @@ export type TabItem = {
|
||||
|
||||
interface Props {
|
||||
label: string;
|
||||
value?: string;
|
||||
onChangeValue: (value: string) => void;
|
||||
value: string;
|
||||
tabs: TabItem[];
|
||||
tabListClassName?: string;
|
||||
className?: string;
|
||||
|
||||
@@ -16,16 +16,15 @@ export function keyValueQueryKey({
|
||||
export function useKeyValue<T extends string | number | boolean>({
|
||||
namespace = DEFAULT_NAMESPACE,
|
||||
key,
|
||||
initialValue,
|
||||
defaultValue,
|
||||
}: {
|
||||
namespace?: string;
|
||||
key: string | string[];
|
||||
initialValue: T;
|
||||
defaultValue: T;
|
||||
}) {
|
||||
const query = useQuery<T>({
|
||||
initialData: initialValue,
|
||||
queryKey: keyValueQueryKey({ namespace, key }),
|
||||
queryFn: async () => getKeyValue({ namespace, key, fallback: initialValue }),
|
||||
queryFn: async () => getKeyValue({ namespace, key, fallback: defaultValue }),
|
||||
});
|
||||
|
||||
const mutate = useMutation<T, unknown, T>({
|
||||
@@ -34,6 +33,7 @@ export function useKeyValue<T extends string | number | boolean>({
|
||||
|
||||
return {
|
||||
value: query.data,
|
||||
isLoading: query.isLoading,
|
||||
set: (value: T) => mutate.mutate(value),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useKeyValue } from './useKeyValue';
|
||||
|
||||
export function useResponseViewMode(requestId?: string): [string, () => void] {
|
||||
export function useResponseViewMode(requestId?: string): [string | undefined, () => void] {
|
||||
const v = useKeyValue<string>({
|
||||
namespace: 'app',
|
||||
key: ['response_view_mode', requestId ?? 'n/a'],
|
||||
initialValue: 'pretty',
|
||||
defaultValue: 'pretty',
|
||||
});
|
||||
|
||||
const toggle = () => {
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface Workspace extends BaseModel {
|
||||
export interface HttpHeader {
|
||||
name: string;
|
||||
value: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface HttpRequest extends BaseModel {
|
||||
|
||||
@@ -6,7 +6,11 @@ module.exports = {
|
||||
"./src-web/**/*.{html,js,jsx,ts,tsx}"
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
extend: {
|
||||
opacity: {
|
||||
'disabled': '0.3',
|
||||
}
|
||||
},
|
||||
fontFamily: {
|
||||
"mono": ["JetBrains Mono", "Menlo", "monospace"],
|
||||
"sans": ["Inter", "sans-serif"],
|
||||
@@ -21,6 +25,7 @@ module.exports = {
|
||||
"5xl": "3.052rem"
|
||||
},
|
||||
colors: {
|
||||
focus: "hsl(var(--color-blue-500) / 0.6)",
|
||||
highlight: "hsl(var(--color-gray-200) / 0.3)",
|
||||
transparent: "transparent",
|
||||
white: "hsl(0 100% 100% / <alpha-value>)",
|
||||
|
||||
Reference in New Issue
Block a user