mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 17:18:32 +02:00
Bulk editor (#45)
Bulk editor for all pair editors except multipart/form-data
This commit is contained in:
@@ -43,7 +43,7 @@ type Pair = string | boolean;
|
|||||||
|
|
||||||
type PairsByName = Record<string, Pair[]>;
|
type PairsByName = Record<string, Pair[]>;
|
||||||
|
|
||||||
export function pluginHookImport(ctx: any, rawData: string) {
|
export function pluginHookImport(_: any, rawData: string) {
|
||||||
if (!rawData.match(/^\s*curl /)) {
|
if (!rawData.match(/^\s*curl /)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import { describe, expect, test } from 'vitest';
|
|||||||
import { HttpRequest, Model, Workspace } from '../../../src-web/lib/models';
|
import { HttpRequest, Model, Workspace } from '../../../src-web/lib/models';
|
||||||
import { pluginHookImport } from '../src';
|
import { pluginHookImport } from '../src';
|
||||||
|
|
||||||
|
const ctx = {};
|
||||||
|
|
||||||
describe('importer-curl', () => {
|
describe('importer-curl', () => {
|
||||||
test('Imports basic GET', () => {
|
test('Imports basic GET', () => {
|
||||||
expect(pluginHookImport('curl https://yaak.app')).toEqual({
|
expect(pluginHookImport(ctx, 'curl https://yaak.app')).toEqual({
|
||||||
resources: {
|
resources: {
|
||||||
workspaces: [baseWorkspace()],
|
workspaces: [baseWorkspace()],
|
||||||
httpRequests: [
|
httpRequests: [
|
||||||
@@ -17,7 +19,7 @@ describe('importer-curl', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Explicit URL', () => {
|
test('Explicit URL', () => {
|
||||||
expect(pluginHookImport('curl --url https://yaak.app')).toEqual({
|
expect(pluginHookImport(ctx, 'curl --url https://yaak.app')).toEqual({
|
||||||
resources: {
|
resources: {
|
||||||
workspaces: [baseWorkspace()],
|
workspaces: [baseWorkspace()],
|
||||||
httpRequests: [
|
httpRequests: [
|
||||||
@@ -30,7 +32,7 @@ describe('importer-curl', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Missing URL', () => {
|
test('Missing URL', () => {
|
||||||
expect(pluginHookImport('curl -X POST')).toEqual({
|
expect(pluginHookImport(ctx, 'curl -X POST')).toEqual({
|
||||||
resources: {
|
resources: {
|
||||||
workspaces: [baseWorkspace()],
|
workspaces: [baseWorkspace()],
|
||||||
httpRequests: [
|
httpRequests: [
|
||||||
@@ -43,7 +45,7 @@ describe('importer-curl', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('URL between', () => {
|
test('URL between', () => {
|
||||||
expect(pluginHookImport('curl -v https://yaak.app -X POST')).toEqual({
|
expect(pluginHookImport(ctx, 'curl -v https://yaak.app -X POST')).toEqual({
|
||||||
resources: {
|
resources: {
|
||||||
workspaces: [baseWorkspace()],
|
workspaces: [baseWorkspace()],
|
||||||
httpRequests: [
|
httpRequests: [
|
||||||
@@ -57,7 +59,7 @@ describe('importer-curl', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Random flags', () => {
|
test('Random flags', () => {
|
||||||
expect(pluginHookImport('curl --random -Z -Y -S --foo https://yaak.app')).toEqual({
|
expect(pluginHookImport(ctx, 'curl --random -Z -Y -S --foo https://yaak.app')).toEqual({
|
||||||
resources: {
|
resources: {
|
||||||
workspaces: [baseWorkspace()],
|
workspaces: [baseWorkspace()],
|
||||||
httpRequests: [
|
httpRequests: [
|
||||||
@@ -70,7 +72,7 @@ describe('importer-curl', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Imports --request method', () => {
|
test('Imports --request method', () => {
|
||||||
expect(pluginHookImport('curl --request POST https://yaak.app')).toEqual({
|
expect(pluginHookImport(ctx, 'curl --request POST https://yaak.app')).toEqual({
|
||||||
resources: {
|
resources: {
|
||||||
workspaces: [baseWorkspace()],
|
workspaces: [baseWorkspace()],
|
||||||
httpRequests: [
|
httpRequests: [
|
||||||
@@ -84,7 +86,7 @@ describe('importer-curl', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Imports -XPOST method', () => {
|
test('Imports -XPOST method', () => {
|
||||||
expect(pluginHookImport('curl -XPOST --request POST https://yaak.app')).toEqual({
|
expect(pluginHookImport(ctx, 'curl -XPOST --request POST https://yaak.app')).toEqual({
|
||||||
resources: {
|
resources: {
|
||||||
workspaces: [baseWorkspace()],
|
workspaces: [baseWorkspace()],
|
||||||
httpRequests: [
|
httpRequests: [
|
||||||
@@ -99,7 +101,10 @@ describe('importer-curl', () => {
|
|||||||
|
|
||||||
test('Imports multiple requests', () => {
|
test('Imports multiple requests', () => {
|
||||||
expect(
|
expect(
|
||||||
pluginHookImport('curl \\\n https://yaak.app\necho "foo"\ncurl example.com;curl foo.com'),
|
pluginHookImport(
|
||||||
|
ctx,
|
||||||
|
'curl \\\n https://yaak.app\necho "foo"\ncurl example.com;curl foo.com',
|
||||||
|
),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
resources: {
|
resources: {
|
||||||
workspaces: [baseWorkspace()],
|
workspaces: [baseWorkspace()],
|
||||||
@@ -114,7 +119,7 @@ describe('importer-curl', () => {
|
|||||||
|
|
||||||
test('Imports form data', () => {
|
test('Imports form data', () => {
|
||||||
expect(
|
expect(
|
||||||
pluginHookImport('curl -X POST -F "a=aaa" -F b=bbb" -F f=@filepath https://yaak.app'),
|
pluginHookImport(ctx, 'curl -X POST -F "a=aaa" -F b=bbb" -F f=@filepath https://yaak.app'),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
resources: {
|
resources: {
|
||||||
workspaces: [baseWorkspace()],
|
workspaces: [baseWorkspace()],
|
||||||
@@ -144,7 +149,7 @@ describe('importer-curl', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Imports data params as form url-encoded', () => {
|
test('Imports data params as form url-encoded', () => {
|
||||||
expect(pluginHookImport('curl -d a -d b -d c=ccc https://yaak.app')).toEqual({
|
expect(pluginHookImport(ctx, 'curl -d a -d b -d c=ccc https://yaak.app')).toEqual({
|
||||||
resources: {
|
resources: {
|
||||||
workspaces: [baseWorkspace()],
|
workspaces: [baseWorkspace()],
|
||||||
httpRequests: [
|
httpRequests: [
|
||||||
@@ -174,7 +179,7 @@ describe('importer-curl', () => {
|
|||||||
|
|
||||||
test('Imports data params as text', () => {
|
test('Imports data params as text', () => {
|
||||||
expect(
|
expect(
|
||||||
pluginHookImport('curl -H Content-Type:text/plain -d a -d b -d c=ccc https://yaak.app'),
|
pluginHookImport(ctx, 'curl -H Content-Type:text/plain -d a -d b -d c=ccc https://yaak.app'),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
resources: {
|
resources: {
|
||||||
workspaces: [baseWorkspace()],
|
workspaces: [baseWorkspace()],
|
||||||
@@ -194,6 +199,7 @@ describe('importer-curl', () => {
|
|||||||
test('Imports multi-line JSON', () => {
|
test('Imports multi-line JSON', () => {
|
||||||
expect(
|
expect(
|
||||||
pluginHookImport(
|
pluginHookImport(
|
||||||
|
ctx,
|
||||||
`curl -H Content-Type:application/json -d $'{\n "foo":"bar"\n}' https://yaak.app`,
|
`curl -H Content-Type:application/json -d $'{\n "foo":"bar"\n}' https://yaak.app`,
|
||||||
),
|
),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
@@ -214,7 +220,7 @@ describe('importer-curl', () => {
|
|||||||
|
|
||||||
test('Imports multiple headers', () => {
|
test('Imports multiple headers', () => {
|
||||||
expect(
|
expect(
|
||||||
pluginHookImport('curl -H Foo:bar --header Name -H AAA:bbb -H :ccc https://yaak.app'),
|
pluginHookImport(ctx, 'curl -H Foo:bar --header Name -H AAA:bbb -H :ccc https://yaak.app'),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
resources: {
|
resources: {
|
||||||
workspaces: [baseWorkspace()],
|
workspaces: [baseWorkspace()],
|
||||||
@@ -234,7 +240,7 @@ describe('importer-curl', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Imports basic auth', () => {
|
test('Imports basic auth', () => {
|
||||||
expect(pluginHookImport('curl --user user:pass https://yaak.app')).toEqual({
|
expect(pluginHookImport(ctx, 'curl --user user:pass https://yaak.app')).toEqual({
|
||||||
resources: {
|
resources: {
|
||||||
workspaces: [baseWorkspace()],
|
workspaces: [baseWorkspace()],
|
||||||
httpRequests: [
|
httpRequests: [
|
||||||
@@ -252,7 +258,7 @@ describe('importer-curl', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Imports digest auth', () => {
|
test('Imports digest auth', () => {
|
||||||
expect(pluginHookImport('curl --digest --user user:pass https://yaak.app')).toEqual({
|
expect(pluginHookImport(ctx, 'curl --digest --user user:pass https://yaak.app')).toEqual({
|
||||||
resources: {
|
resources: {
|
||||||
workspaces: [baseWorkspace()],
|
workspaces: [baseWorkspace()],
|
||||||
httpRequests: [
|
httpRequests: [
|
||||||
@@ -270,7 +276,7 @@ describe('importer-curl', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Imports cookie as header', () => {
|
test('Imports cookie as header', () => {
|
||||||
expect(pluginHookImport('curl --cookie "foo=bar" https://yaak.app')).toEqual({
|
expect(pluginHookImport(ctx, 'curl --cookie "foo=bar" https://yaak.app')).toEqual({
|
||||||
resources: {
|
resources: {
|
||||||
workspaces: [baseWorkspace()],
|
workspaces: [baseWorkspace()],
|
||||||
httpRequests: [
|
httpRequests: [
|
||||||
@@ -284,7 +290,7 @@ describe('importer-curl', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Imports query params from the URL', () => {
|
test('Imports query params from the URL', () => {
|
||||||
expect(pluginHookImport('curl "https://yaak.app?foo=bar&baz=a%20a"')).toEqual({
|
expect(pluginHookImport(ctx, 'curl "https://yaak.app?foo=bar&baz=a%20a"')).toEqual({
|
||||||
resources: {
|
resources: {
|
||||||
workspaces: [baseWorkspace()],
|
workspaces: [baseWorkspace()],
|
||||||
httpRequests: [
|
httpRequests: [
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
import { describe, expect, test } from 'vitest';
|
import { describe, expect, test } from 'vitest';
|
||||||
import { pluginHookImport } from '../src';
|
import { pluginHookImport } from '../src';
|
||||||
|
|
||||||
|
const ctx = {};
|
||||||
|
|
||||||
describe('importer-yaak', () => {
|
describe('importer-yaak', () => {
|
||||||
test('Skips invalid imports', () => {
|
test('Skips invalid imports', () => {
|
||||||
expect(pluginHookImport('not JSON')).toBeUndefined();
|
expect(pluginHookImport(ctx, 'not JSON')).toBeUndefined();
|
||||||
expect(pluginHookImport('[]')).toBeUndefined();
|
expect(pluginHookImport(ctx, '[]')).toBeUndefined();
|
||||||
expect(pluginHookImport(JSON.stringify({ resources: {} }))).toBeUndefined();
|
expect(pluginHookImport(ctx, JSON.stringify({ resources: {} }))).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('converts schema 1 to 2', () => {
|
test('converts schema 1 to 2', () => {
|
||||||
const imported = pluginHookImport(
|
const imported = pluginHookImport(
|
||||||
|
ctx,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
yaakSchema: 1,
|
yaakSchema: 1,
|
||||||
resources: {
|
resources: {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import type { HttpRequest } from '../lib/models';
|
import type { HttpRequest } from '../lib/models';
|
||||||
import type { Pair, PairEditorProps } from './core/PairEditor';
|
import type { Pair, PairEditorProps } from './core/PairEditor';
|
||||||
import { PairEditor } from './core/PairEditor';
|
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
forceUpdateKey: string;
|
forceUpdateKey: string;
|
||||||
@@ -27,7 +27,8 @@ export function FormUrlencodedEditor({ body, forceUpdateKey, onChange }: Props)
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PairEditor
|
<PairOrBulkEditor
|
||||||
|
preferenceName="form_urlencoded"
|
||||||
valueAutocompleteVariables
|
valueAutocompleteVariables
|
||||||
nameAutocompleteVariables
|
nameAutocompleteVariables
|
||||||
namePlaceholder="entry_name"
|
namePlaceholder="entry_name"
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
|
|||||||
{...extraEditorProps}
|
{...extraEditorProps}
|
||||||
/>
|
/>
|
||||||
<div className="grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1 min-h-[5rem]">
|
<div className="grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1 min-h-[5rem]">
|
||||||
<Separator variant="primary" className="pb-1">
|
<Separator dashed className="pb-1">
|
||||||
Variables
|
Variables
|
||||||
</Separator>
|
</Separator>
|
||||||
<Editor
|
<Editor
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { mimeTypes } from '../lib/data/mimetypes';
|
|||||||
import type { HttpRequest } from '../lib/models';
|
import type { HttpRequest } from '../lib/models';
|
||||||
import type { GenericCompletionConfig } from './core/Editor/genericCompletion';
|
import type { GenericCompletionConfig } from './core/Editor/genericCompletion';
|
||||||
import type { PairEditorProps } from './core/PairEditor';
|
import type { PairEditorProps } from './core/PairEditor';
|
||||||
import { PairEditor } from './core/PairEditor';
|
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
forceUpdateKey: string;
|
forceUpdateKey: string;
|
||||||
@@ -16,7 +16,8 @@ type Props = {
|
|||||||
|
|
||||||
export function HeadersEditor({ headers, onChange, forceUpdateKey }: Props) {
|
export function HeadersEditor({ headers, onChange, forceUpdateKey }: Props) {
|
||||||
return (
|
return (
|
||||||
<PairEditor
|
<PairOrBulkEditor
|
||||||
|
preferenceName="headers"
|
||||||
valueAutocompleteVariables
|
valueAutocompleteVariables
|
||||||
nameAutocompleteVariables
|
nameAutocompleteVariables
|
||||||
pairs={headers}
|
pairs={headers}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { HttpRequest } from '../lib/models';
|
import type { HttpRequest } from '../lib/models';
|
||||||
import { PairEditor } from './core/PairEditor';
|
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
forceUpdateKey: string;
|
forceUpdateKey: string;
|
||||||
@@ -9,7 +9,8 @@ type Props = {
|
|||||||
|
|
||||||
export function UrlParametersEditor({ urlParameters, forceUpdateKey, onChange }: Props) {
|
export function UrlParametersEditor({ urlParameters, forceUpdateKey, onChange }: Props) {
|
||||||
return (
|
return (
|
||||||
<PairEditor
|
<PairOrBulkEditor
|
||||||
|
preferenceName="url_parameters"
|
||||||
valueAutocompleteVariables
|
valueAutocompleteVariables
|
||||||
nameAutocompleteVariables
|
nameAutocompleteVariables
|
||||||
namePlaceholder="param_name"
|
namePlaceholder="param_name"
|
||||||
|
|||||||
48
src-web/components/core/BulkPairEditor.tsx
Normal file
48
src-web/components/core/BulkPairEditor.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import { Editor } from './Editor';
|
||||||
|
import type { PairEditorProps } from './PairEditor';
|
||||||
|
|
||||||
|
type Props = Pick<
|
||||||
|
PairEditorProps,
|
||||||
|
'onChange' | 'pairs' | 'namePlaceholder' | 'valuePlaceholder'
|
||||||
|
> & {
|
||||||
|
foo?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function BulkPairEditor({ pairs, onChange, namePlaceholder, valuePlaceholder }: Props) {
|
||||||
|
const pairsText = useMemo(() => {
|
||||||
|
return pairs
|
||||||
|
.filter((p) => !(p.name.trim() === '' && p.value.trim() === ''))
|
||||||
|
.map((p) => `${p.name}: ${p.value}`)
|
||||||
|
.join('\n');
|
||||||
|
}, [pairs]);
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(text: string) => {
|
||||||
|
const pairs = text
|
||||||
|
.split('\n')
|
||||||
|
.filter((l: string) => l.trim())
|
||||||
|
.map(lineToPair);
|
||||||
|
onChange(pairs);
|
||||||
|
},
|
||||||
|
[onChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Editor
|
||||||
|
placeholder={`${namePlaceholder ?? 'name'}: ${valuePlaceholder ?? 'value'}`}
|
||||||
|
defaultValue={pairsText}
|
||||||
|
contentType="pairs"
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function lineToPair(l: string): PairEditorProps['pairs'][0] {
|
||||||
|
const [name, ...values] = l.split(':');
|
||||||
|
const pair: PairEditorProps['pairs'][0] = {
|
||||||
|
name: (name ?? '').trim(),
|
||||||
|
value: values.join(':').trim(),
|
||||||
|
};
|
||||||
|
return pair;
|
||||||
|
}
|
||||||
@@ -102,7 +102,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
|||||||
// Use ref so we can update the handler without re-initializing the editor
|
// Use ref so we can update the handler without re-initializing the editor
|
||||||
const handleChange = useRef<EditorProps['onChange']>(onChange);
|
const handleChange = useRef<EditorProps['onChange']>(onChange);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleChange.current = onChange;
|
handleChange.current = onChange ? onChange : onChange;
|
||||||
}, [onChange]);
|
}, [onChange]);
|
||||||
|
|
||||||
// Use ref so we can update the handler without re-initializing the editor
|
// Use ref so we can update the handler without re-initializing the editor
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import { graphql, graphqlLanguageSupport } from 'cm6-graphql';
|
|||||||
import { EditorView } from 'codemirror';
|
import { EditorView } from 'codemirror';
|
||||||
import type { Environment, Workspace } from '../../../lib/models';
|
import type { Environment, Workspace } from '../../../lib/models';
|
||||||
import type { EditorProps } from './index';
|
import type { EditorProps } from './index';
|
||||||
|
import { pairs } from './pairs/extension';
|
||||||
import { text } from './text/extension';
|
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';
|
||||||
@@ -71,6 +72,7 @@ const syntaxExtensions: Record<string, LanguageSupport> = {
|
|||||||
'application/xml': xml(),
|
'application/xml': xml(),
|
||||||
'text/xml': xml(),
|
'text/xml': xml(),
|
||||||
url: url(),
|
url: url(),
|
||||||
|
pairs: pairs(),
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getLanguageExtension({
|
export function getLanguageExtension({
|
||||||
|
|||||||
11
src-web/components/core/Editor/pairs/extension.ts
Normal file
11
src-web/components/core/Editor/pairs/extension.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { LanguageSupport, LRLanguage } from '@codemirror/language';
|
||||||
|
import { parser } from './pairs';
|
||||||
|
|
||||||
|
const urlLanguage = LRLanguage.define({
|
||||||
|
parser,
|
||||||
|
languageData: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export function pairs() {
|
||||||
|
return new LanguageSupport(urlLanguage, []);
|
||||||
|
}
|
||||||
7
src-web/components/core/Editor/pairs/highlight.ts
Normal file
7
src-web/components/core/Editor/pairs/highlight.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { styleTags, tags as t } from '@lezer/highlight';
|
||||||
|
|
||||||
|
export const highlight = styleTags({
|
||||||
|
Sep: t.bracket,
|
||||||
|
Key: t.attributeName,
|
||||||
|
Value: t.string,
|
||||||
|
});
|
||||||
9
src-web/components/core/Editor/pairs/pairs.grammar
Normal file
9
src-web/components/core/Editor/pairs/pairs.grammar
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
@top pairs { (Key? Sep Value)* }
|
||||||
|
|
||||||
|
@tokens {
|
||||||
|
Sep { ":" }
|
||||||
|
Key { ![:]+ }
|
||||||
|
Value { ![\n]+ }
|
||||||
|
}
|
||||||
|
|
||||||
|
@external propSource highlight from "./highlight"
|
||||||
19
src-web/components/core/Editor/pairs/pairs.ts
Normal file
19
src-web/components/core/Editor/pairs/pairs.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||||
|
import {LRParser} from "@lezer/lr"
|
||||||
|
import {highlight} from "./highlight"
|
||||||
|
export const parser = LRParser.deserialize({
|
||||||
|
version: 14,
|
||||||
|
states: "!QQQOPOOOYOQO'#CaO_OPO'#CaQQOPOOOOOO,58{,58{OdOQO,58{OOOO-E6_-E6_OOOO1G.g1G.g",
|
||||||
|
stateData: "i~OQQORPO~OSSO~ORTO~OSVO~O",
|
||||||
|
goto: "]UPPPPPVQRORUR",
|
||||||
|
nodeNames: "⚠ pairs Key Sep Value",
|
||||||
|
maxTerm: 6,
|
||||||
|
propSources: [highlight],
|
||||||
|
skippedNodes: [0],
|
||||||
|
repeatNodeCount: 1,
|
||||||
|
tokenData: "#oRRVOYhYZ!UZ![h![!]#[!];'Sh;'S;=`#U<%lOhRoVQPSQOYhYZ!UZ![h![!]!m!];'Sh;'S;=`#U<%lOhP!ZSQPO![!U!];'S!U;'S;=`!g<%lO!UP!jP;=`<%l!UQ!rSSQOY!mZ;'S!m;'S;=`#O<%lO!mQ#RP;=`<%l!mR#XP;=`<%lhR#cSRPSQOY!mZ;'S!m;'S;=`#O<%lO!m",
|
||||||
|
tokenizers: [0, 1],
|
||||||
|
topRules: {"pairs":[0,1]},
|
||||||
|
tokenPrec: 0
|
||||||
|
})
|
||||||
|
|
||||||
@@ -5,6 +5,9 @@ import { memo } from 'react';
|
|||||||
|
|
||||||
const icons = {
|
const icons = {
|
||||||
alert: lucide.AlertTriangleIcon,
|
alert: lucide.AlertTriangleIcon,
|
||||||
|
text: lucide.FileTextIcon,
|
||||||
|
table: lucide.TableIcon,
|
||||||
|
fileCode: lucide.FileCodeIcon,
|
||||||
archive: lucide.ArchiveIcon,
|
archive: lucide.ArchiveIcon,
|
||||||
arrowBigDownDash: lucide.ArrowBigDownDashIcon,
|
arrowBigDownDash: lucide.ArrowBigDownDashIcon,
|
||||||
arrowBigLeftDash: lucide.ArrowBigLeftDashIcon,
|
arrowBigLeftDash: lucide.ArrowBigLeftDashIcon,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { open } from '@tauri-apps/plugin-dialog';
|
import { open } from '@tauri-apps/plugin-dialog';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { EditorView } from 'codemirror';
|
import type { EditorView } from 'codemirror';
|
||||||
import { Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import type { XYCoord } from 'react-dnd';
|
import type { XYCoord } from 'react-dnd';
|
||||||
import { useDrag, useDrop } from 'react-dnd';
|
import { useDrag, useDrop } from 'react-dnd';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
@@ -49,7 +49,7 @@ type PairContainer = {
|
|||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PairEditor = memo(function PairEditor({
|
export function PairEditor({
|
||||||
className,
|
className,
|
||||||
forceUpdateKey,
|
forceUpdateKey,
|
||||||
nameAutocomplete,
|
nameAutocomplete,
|
||||||
@@ -163,8 +163,8 @@ export const PairEditor = memo(function PairEditor({
|
|||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'@container',
|
'@container relative',
|
||||||
'pb-2 mb-auto',
|
'pb-2 mb-auto h-full',
|
||||||
!noScroll && 'overflow-y-auto max-h-full',
|
!noScroll && 'overflow-y-auto max-h-full',
|
||||||
// Move over the width of the drag handle
|
// Move over the width of the drag handle
|
||||||
'-ml-3',
|
'-ml-3',
|
||||||
@@ -204,7 +204,7 @@ export const PairEditor = memo(function PairEditor({
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
enum ItemTypes {
|
enum ItemTypes {
|
||||||
ROW = 'pair-row',
|
ROW = 'pair-row',
|
||||||
|
|||||||
37
src-web/components/core/PairOrBulkEditor.tsx
Normal file
37
src-web/components/core/PairOrBulkEditor.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
import { useKeyValue } from '../../hooks/useKeyValue';
|
||||||
|
import { BulkPairEditor } from './BulkPairEditor';
|
||||||
|
import { IconButton } from './IconButton';
|
||||||
|
import type { PairEditorProps } from './PairEditor';
|
||||||
|
import { PairEditor } from './PairEditor';
|
||||||
|
|
||||||
|
interface Props extends PairEditorProps {
|
||||||
|
preferenceName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PairOrBulkEditor({ preferenceName, ...props }: Props) {
|
||||||
|
const { value: useBulk, set: setUseBulk } = useKeyValue<boolean>({
|
||||||
|
namespace: 'global',
|
||||||
|
key: ['bulk_edit', preferenceName],
|
||||||
|
fallback: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative h-full w-full group/wrapper">
|
||||||
|
{useBulk ? <BulkPairEditor {...props} /> : <PairEditor {...props} />}
|
||||||
|
<div className="absolute right-0 bottom-0">
|
||||||
|
<IconButton
|
||||||
|
size="sm"
|
||||||
|
variant="border"
|
||||||
|
title={useBulk ? 'Bulk edit' : 'Regular Edit'}
|
||||||
|
className={classNames(
|
||||||
|
'transition-opacity opacity-0 group-hover:opacity-80 hover:!opacity-100 shadow',
|
||||||
|
'bg-background text-fg-subtle hover:text-fg group-hover/wrapper:opacity-100',
|
||||||
|
)}
|
||||||
|
onClick={() => setUseBulk((b) => !b)}
|
||||||
|
icon={useBulk ? 'table' : 'fileCode'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,18 +3,19 @@ import type { ReactNode } from 'react';
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
orientation?: 'horizontal' | 'vertical';
|
orientation?: 'horizontal' | 'vertical';
|
||||||
variant?: 'primary' | 'secondary';
|
dashed?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Separator({ className, orientation = 'horizontal', children }: Props) {
|
export function Separator({ className, dashed, orientation = 'horizontal', children }: Props) {
|
||||||
return (
|
return (
|
||||||
<div role="separator" className={classNames(className, 'flex items-center')}>
|
<div role="separator" className={classNames(className, 'flex items-center')}>
|
||||||
{children && <div className="text-sm text-fg-subtler mr-2 whitespace-nowrap">{children}</div>}
|
{children && <div className="text-sm text-fg-subtler mr-2 whitespace-nowrap">{children}</div>}
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'bg-background-highlight',
|
'h-0 border-t border-t-background-highlight',
|
||||||
|
dashed && 'border-dashed',
|
||||||
orientation === 'horizontal' && 'w-full h-[1px]',
|
orientation === 'horizontal' && 'w-full h-[1px]',
|
||||||
orientation === 'vertical' && 'h-full w-[1px]',
|
orientation === 'vertical' && 'h-full w-[1px]',
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export function TextViewer({ response, pretty, className }: Props) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}, [canFilter, filterText, isJson, isSearching, setFilterText, toggleSearch]);
|
}, [canFilter, filterText, isJson, isSearching, response.id, setFilterText, toggleSearch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Editor
|
<Editor
|
||||||
|
|||||||
Reference in New Issue
Block a user