diff --git a/package-lock.json b/package-lock.json index 3146df0c..d6920d42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index f62db3b8..f1aa06af 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns index 6c84ab9d..25afb057 100644 Binary files a/src-tauri/icons/icon.icns and b/src-tauri/icons/icon.icns differ diff --git a/src-tauri/src/models.rs b/src-tauri/src/models.rs index 13bc9afe..147dde8d 100644 --- a/src-tauri/src/models.rs +++ b/src-tauri/src/models.rs @@ -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, } diff --git a/src-web/components/RequestPane.tsx b/src-web/components/RequestPane.tsx index b5c494c9..52fa876b 100644 --- a/src-web/components/RequestPane.tsx +++ b/src-web/components/RequestPane.tsx @@ -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({ 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 (
- - - - - - - null} /> - - - {activeRequest.bodyType === 'json' ? ( - tryFormatJson(v) : undefined} - /> - ) : activeRequest.bodyType === 'graphql' ? ( - - ) : ( -
No Body
- )} -
-
+ {activeRequest && ( + <> + + + + + + + null} /> + + + {activeRequest.bodyType === 'json' ? ( + tryFormatJson(v)} + /> + ) : activeRequest.bodyType === 'xml' ? ( + + ) : activeRequest.bodyType === 'graphql' ? ( + + ) : ( +
No Body
+ )} +
+
+ + )}
); } diff --git a/src-web/components/Sidebar.tsx b/src-web/components/Sidebar.tsx index efc56767..0dc8f9ff 100644 --- a/src-web/components/Sidebar.tsx +++ b/src-web/components/Sidebar.tsx @@ -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', diff --git a/src-web/components/WorkspaceDropdown.tsx b/src-web/components/WorkspaceDropdown.tsx index 36442f3b..a2e04cf3 100644 --- a/src-web/components/WorkspaceDropdown.tsx +++ b/src-web/components/WorkspaceDropdown.tsx @@ -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 ? : , - onSelect: () => navigate(`/workspaces/${w.id}`), + onSelect: () => { + if (w.id === activeWorkspace?.id) return; + navigate(`/workspaces/${w.id}`); + }, })); return [ diff --git a/src-web/components/core/Checkbox.tsx b/src-web/components/core/Checkbox.tsx new file mode 100644 index 00000000..77062c8d --- /dev/null +++ b/src-web/components/core/Checkbox.tsx @@ -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 ( + + + {checked === 'indeterminate' && } + {checked === true && } + + + ); +} diff --git a/src-web/components/core/Dropdown.tsx b/src-web/components/core/Dropdown.tsx index e997d197..2f1b4a07 100644 --- a/src-web/components/core/Dropdown.tsx +++ b/src-web/components/core/Dropdown.tsx @@ -182,7 +182,7 @@ const DropdownMenuItem = memo(function DropdownMenuItem({ diff --git a/src-web/components/core/Editor/extensions.ts b/src-web/components/core/Editor/extensions.ts index f10272e0..ec812482 100644 --- a/src-web/components/core/Editor/extensions.ts +++ b/src-web/components/core/Editor/extensions.ts @@ -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'; diff --git a/src-web/components/core/Editor/text/extension.ts b/src-web/components/core/Editor/text/extension.ts index d3f0950a..902a4f2b 100644 --- a/src-web/components/core/Editor/text/extension.ts +++ b/src-web/components/core/Editor/text/extension.ts @@ -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: {}, }); diff --git a/src-web/components/core/Editor/twig/extension.ts b/src-web/components/core/Editor/twig/extension.ts index 757bad8e..a1a78794 100644 --- a/src-web/components/core/Editor/twig/extension.ts +++ b/src-web/components/core/Editor/twig/extension.ts @@ -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', }; }), }); diff --git a/src-web/components/core/Editor/twig/highlight.ts b/src-web/components/core/Editor/twig/highlight.ts index 3da5e480..dd4e341f 100644 --- a/src-web/components/core/Editor/twig/highlight.ts +++ b/src-web/components/core/Editor/twig/highlight.ts @@ -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, }); diff --git a/src-web/components/core/Editor/twig/twig.grammar b/src-web/components/core/Editor/twig/twig.grammar index 5f4c40ba..11b9003b 100644 --- a/src-web/components/core/Editor/twig/twig.grammar +++ b/src-web/components/core/Editor/twig/twig.grammar @@ -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" diff --git a/src-web/components/core/Editor/twig/twig.ts b/src-web/components/core/Editor/twig/twig.ts index e6ea8e39..58f4897a 100644 --- a/src-web/components/core/Editor/twig/twig.ts +++ b/src-web/components/core/Editor/twig/twig.ts @@ -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 diff --git a/src-web/components/core/Icon.tsx b/src-web/components/core/Icon.tsx index dc465e1c..e3e15a4c 100644 --- a/src-web/components/core/Icon.tsx +++ b/src-web/components/core/Icon.tsx @@ -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, diff --git a/src-web/components/core/Input.tsx b/src-web/components/core/Input.tsx index 8cc5d687..d6d2a331 100644 --- a/src-web/components/core/Input.tsx +++ b/src-web/components/core/Input.tsx @@ -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', )} diff --git a/src-web/components/core/PairEditor.tsx b/src-web/components/core/PairEditor.tsx index bc91998d..37686cae 100644 --- a/src-web/components/core/PairEditor.tsx +++ b/src-web/components/core/PairEditor.tsx @@ -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 (
(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 (
{!isLast ? (
)} + { - return { pair: pair ?? { name: '', value: '' }, id: uuid() }; + return { pair: pair ?? { name: '', value: '', enabled: true }, id: uuid() }; }; diff --git a/src-web/components/core/Tabs/Tabs.tsx b/src-web/components/core/Tabs/Tabs.tsx index 8489d6b3..00ed8728 100644 --- a/src-web/components/core/Tabs/Tabs.tsx +++ b/src-web/components/core/Tabs/Tabs.tsx @@ -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; diff --git a/src-web/hooks/useKeyValue.ts b/src-web/hooks/useKeyValue.ts index 9ebf5885..51bf68d9 100644 --- a/src-web/hooks/useKeyValue.ts +++ b/src-web/hooks/useKeyValue.ts @@ -16,16 +16,15 @@ export function keyValueQueryKey({ export function useKeyValue({ namespace = DEFAULT_NAMESPACE, key, - initialValue, + defaultValue, }: { namespace?: string; key: string | string[]; - initialValue: T; + defaultValue: T; }) { const query = useQuery({ - initialData: initialValue, queryKey: keyValueQueryKey({ namespace, key }), - queryFn: async () => getKeyValue({ namespace, key, fallback: initialValue }), + queryFn: async () => getKeyValue({ namespace, key, fallback: defaultValue }), }); const mutate = useMutation({ @@ -34,6 +33,7 @@ export function useKeyValue({ return { value: query.data, + isLoading: query.isLoading, set: (value: T) => mutate.mutate(value), }; } diff --git a/src-web/hooks/useResponseViewMode.ts b/src-web/hooks/useResponseViewMode.ts index 6a03369f..ff405cdb 100644 --- a/src-web/hooks/useResponseViewMode.ts +++ b/src-web/hooks/useResponseViewMode.ts @@ -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({ namespace: 'app', key: ['response_view_mode', requestId ?? 'n/a'], - initialValue: 'pretty', + defaultValue: 'pretty', }); const toggle = () => { diff --git a/src-web/lib/models.ts b/src-web/lib/models.ts index e8404fee..f86ee707 100644 --- a/src-web/lib/models.ts +++ b/src-web/lib/models.ts @@ -14,6 +14,7 @@ export interface Workspace extends BaseModel { export interface HttpHeader { name: string; value: string; + enabled?: boolean; } export interface HttpRequest extends BaseModel { diff --git a/tailwind.config.cjs b/tailwind.config.cjs index 34be4811..d64ef10c 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -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% / )",