Skip to content

Commit 2966a45

Browse files
feat(web)/request-workspace-panels (#119)
* feat(web)/request-workspace-panels
1 parent 7aeed05 commit 2966a45

File tree

13 files changed

+730
-511
lines changed

13 files changed

+730
-511
lines changed

packages/web/src/components/request-workspace/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,30 @@
1+
// Model exports
12
export {
23
DEFAULT_REQUEST_WORKSPACE_TAB,
34
isRequestWorkspaceTabId,
45
REQUEST_WORKSPACE_TABS,
56
type RequestWorkspaceTabId
67
} from './model';
8+
9+
// Panel component exports
10+
export { BodyPanel } from './panels/body';
11+
export { HeadersPanel } from './panels/headers';
12+
export { ParamsPanel } from './panels/params';
13+
14+
// Shared panel component exports
15+
export {
16+
DraftHeader,
17+
ErrorBanner,
18+
KeyValueTable
19+
} from './panels/shared';
20+
21+
// Legacy panel component exports (re-exported from new locations)
22+
export {
23+
RequestWorkspaceBodyPanel,
24+
RequestWorkspaceHeadersPanel,
25+
RequestWorkspaceParamsPanel
26+
} from './request-workspace-tab-panels';
27+
// Legacy exports (keep for backward compatibility)
728
export { RequestWorkspaceTabs } from './request-workspace-tabs';
829
export { useRequestBodyDraftController } from './use-request-body-draft-controller';
930
export { useRequestHeaderDraftController } from './use-request-header-draft-controller';
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Show } from 'solid-js';
2+
import type { FileBodyEditorProps } from './types';
3+
4+
export function FileBodyEditor(props: FileBodyEditorProps) {
5+
return (
6+
<div class="space-y-2">
7+
<div class="flex flex-wrap items-center justify-between gap-2">
8+
<button
9+
type="button"
10+
class="btn btn-ghost btn-xs font-mono"
11+
onClick={props.onBodyCopy}
12+
disabled={!props.hasRequest}
13+
>
14+
Copy
15+
</button>
16+
17+
<div class="flex items-center gap-2">
18+
<Show when={props.bodyDraftDirty && !props.bodyDraftSaving}>
19+
<span class="badge badge-sm badge-warning font-mono">Unsaved</span>
20+
</Show>
21+
<button
22+
type="button"
23+
class="btn btn-ghost btn-xs font-mono"
24+
onClick={props.onDiscardBody}
25+
disabled={!props.hasRequest || !props.bodyDraftDirty || props.bodyDraftSaving}
26+
>
27+
Discard
28+
</button>
29+
<button
30+
type="button"
31+
class="btn btn-primary btn-xs font-mono"
32+
onClick={props.onSaveBody}
33+
disabled={!props.hasRequest || !props.bodyDraftDirty || props.bodyDraftSaving}
34+
>
35+
{props.bodyDraftSaving ? 'Saving…' : 'Save'}
36+
</button>
37+
</div>
38+
</div>
39+
40+
<div class="rounded-box border border-base-300 bg-base-100/80 p-2">
41+
<label
42+
for="request-workspace-body-file-path"
43+
class="mb-1 block font-mono text-[11px] uppercase tracking-[0.06em] text-base-content/70"
44+
>
45+
File Path
46+
</label>
47+
<input
48+
id="request-workspace-body-file-path"
49+
type="text"
50+
class="input input-sm w-full border-base-300 bg-base-100 font-mono text-xs"
51+
value={props.requestBodyFilePathDraft}
52+
onInput={(event) => props.onBodyFilePathChange(event.currentTarget.value)}
53+
disabled={!props.hasRequest || props.bodyDraftSaving}
54+
placeholder="./payload.json"
55+
/>
56+
</div>
57+
</div>
58+
);
59+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { Index, Show } from 'solid-js';
2+
import type { FormDataEditorProps } from './types';
3+
4+
export function FormDataEditor(props: FormDataEditorProps) {
5+
return (
6+
<div class="space-y-2">
7+
<div class="flex flex-wrap items-center justify-between gap-2">
8+
<button
9+
type="button"
10+
class="btn btn-ghost btn-xs font-mono"
11+
onClick={props.onBodyFormDataAddField}
12+
disabled={!props.hasRequest || props.bodyDraftSaving}
13+
>
14+
Add Field
15+
</button>
16+
17+
<div class="flex items-center gap-2">
18+
<Show when={props.bodyDraftDirty && !props.bodyDraftSaving}>
19+
<span class="badge badge-sm badge-warning font-mono">Unsaved</span>
20+
</Show>
21+
<button
22+
type="button"
23+
class="btn btn-ghost btn-xs font-mono"
24+
onClick={props.onDiscardBody}
25+
disabled={!props.hasRequest || !props.bodyDraftDirty || props.bodyDraftSaving}
26+
>
27+
Discard
28+
</button>
29+
<button
30+
type="button"
31+
class="btn btn-primary btn-xs font-mono"
32+
onClick={props.onSaveBody}
33+
disabled={!props.hasRequest || !props.bodyDraftDirty || props.bodyDraftSaving}
34+
>
35+
{props.bodyDraftSaving ? 'Saving…' : 'Save'}
36+
</button>
37+
</div>
38+
</div>
39+
40+
<div class="overflow-auto rounded-box border border-base-300 bg-base-100/80">
41+
<table class="table table-xs">
42+
<thead>
43+
<tr>
44+
<th class="font-mono uppercase tracking-[0.06em] text-[11px]">Name</th>
45+
<th class="font-mono uppercase tracking-[0.06em] text-[11px]">Type</th>
46+
<th class="font-mono uppercase tracking-[0.06em] text-[11px]">Value</th>
47+
<th class="font-mono uppercase tracking-[0.06em] text-[11px]">Filename</th>
48+
<th class="font-mono uppercase tracking-[0.06em] text-[11px] text-right">Actions</th>
49+
</tr>
50+
</thead>
51+
<tbody>
52+
<Show
53+
when={props.requestBodyFormDataDraft.length > 0}
54+
fallback={
55+
<tr>
56+
<td colSpan={5} class="font-mono text-xs text-base-content/70 text-center py-3">
57+
No form-data fields configured.
58+
</td>
59+
</tr>
60+
}
61+
>
62+
<Index each={props.requestBodyFormDataDraft}>
63+
{(field, index) => (
64+
<tr>
65+
<td>
66+
<input
67+
type="text"
68+
class="input input-xs w-full border-base-300 bg-base-100 font-mono text-xs"
69+
value={field().name}
70+
onInput={(event) =>
71+
props.onBodyFormDataNameChange(index, event.currentTarget.value)
72+
}
73+
disabled={!props.hasRequest || props.bodyDraftSaving}
74+
/>
75+
</td>
76+
<td>
77+
<select
78+
class="select select-xs w-full border-base-300 bg-base-100 font-mono text-xs"
79+
value={field().isFile ? 'file' : 'text'}
80+
onChange={(event) =>
81+
props.onBodyFormDataTypeChange(
82+
index,
83+
event.currentTarget.value === 'file'
84+
)
85+
}
86+
disabled={!props.hasRequest || props.bodyDraftSaving}
87+
>
88+
<option value="text">text</option>
89+
<option value="file">file</option>
90+
</select>
91+
</td>
92+
<td>
93+
<input
94+
type="text"
95+
class="input input-xs w-full border-base-300 bg-base-100 font-mono text-xs"
96+
value={field().isFile ? (field().path ?? '') : field().value}
97+
onInput={(event) =>
98+
props.onBodyFormDataValueChange(index, event.currentTarget.value)
99+
}
100+
placeholder={field().isFile ? './path/to/file' : 'value'}
101+
disabled={!props.hasRequest || props.bodyDraftSaving}
102+
/>
103+
</td>
104+
<td>
105+
<input
106+
type="text"
107+
class="input input-xs w-full border-base-300 bg-base-100 font-mono text-xs"
108+
value={field().filename ?? ''}
109+
onInput={(event) =>
110+
props.onBodyFormDataFilenameChange(index, event.currentTarget.value)
111+
}
112+
placeholder="optional"
113+
disabled={!props.hasRequest || props.bodyDraftSaving || !field().isFile}
114+
/>
115+
</td>
116+
<td class="text-right">
117+
<button
118+
type="button"
119+
class="btn btn-ghost btn-xs text-error"
120+
onClick={() => props.onBodyFormDataRemoveField(index)}
121+
disabled={!props.hasRequest || props.bodyDraftSaving}
122+
>
123+
Remove
124+
</button>
125+
</td>
126+
</tr>
127+
)}
128+
</Index>
129+
</Show>
130+
</tbody>
131+
</table>
132+
</div>
133+
</div>
134+
);
135+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { Match, Show, Switch } from 'solid-js';
2+
import { ErrorBanner } from '../shared';
3+
import { FileBodyEditor } from './file-editor';
4+
import { FormDataEditor } from './form-data-editor';
5+
import { InlineBodyEditor } from './inline-editor';
6+
import type { BodyPanelProps } from './types';
7+
8+
export function BodyPanel(props: BodyPanelProps) {
9+
const shouldShowDescription = () =>
10+
props.requestBodySummary.description !== 'Request includes an inline body payload.';
11+
12+
return (
13+
<div class="space-y-2">
14+
<ErrorBanner message={props.bodyDraftSaveError} />
15+
16+
<Show when={shouldShowDescription()}>
17+
<p>{props.requestBodySummary.description}</p>
18+
</Show>
19+
20+
<Switch
21+
fallback={
22+
<p class="font-mono text-xs text-base-content/70">
23+
Unsupported body kind: {props.requestBodySummary.kind}
24+
</p>
25+
}
26+
>
27+
<Match when={props.requestBodySummary.kind === 'inline'}>
28+
<InlineBodyEditor
29+
hasRequest={props.hasRequest}
30+
requestBodyDraft={props.requestBodyDraft}
31+
requestBodySummary={props.requestBodySummary}
32+
bodyDraftDirty={props.bodyDraftDirty}
33+
bodyDraftSaving={props.bodyDraftSaving}
34+
bodyDraftValidationError={props.bodyDraftValidationError}
35+
bodyDraftIsJsonEditable={props.bodyDraftIsJsonEditable}
36+
bodyDraftTemplateWarnings={props.bodyDraftTemplateWarnings}
37+
onBodyChange={props.onBodyChange}
38+
onBodyPrettify={props.onBodyPrettify}
39+
onBodyMinify={props.onBodyMinify}
40+
onBodyCopy={props.onBodyCopy}
41+
onSaveBody={props.onSaveBody}
42+
onDiscardBody={props.onDiscardBody}
43+
/>
44+
</Match>
45+
46+
<Match when={props.requestBodySummary.kind === 'form-data'}>
47+
<FormDataEditor
48+
hasRequest={props.hasRequest}
49+
requestBodyFormDataDraft={props.requestBodyFormDataDraft}
50+
bodyDraftDirty={props.bodyDraftDirty}
51+
bodyDraftSaving={props.bodyDraftSaving}
52+
onBodyFormDataNameChange={props.onBodyFormDataNameChange}
53+
onBodyFormDataTypeChange={props.onBodyFormDataTypeChange}
54+
onBodyFormDataValueChange={props.onBodyFormDataValueChange}
55+
onBodyFormDataFilenameChange={props.onBodyFormDataFilenameChange}
56+
onBodyFormDataAddField={props.onBodyFormDataAddField}
57+
onBodyFormDataRemoveField={props.onBodyFormDataRemoveField}
58+
onSaveBody={props.onSaveBody}
59+
onDiscardBody={props.onDiscardBody}
60+
/>
61+
</Match>
62+
63+
<Match when={props.requestBodySummary.kind === 'file'}>
64+
<FileBodyEditor
65+
hasRequest={props.hasRequest}
66+
requestBodyFilePathDraft={props.requestBodyFilePathDraft}
67+
bodyDraftDirty={props.bodyDraftDirty}
68+
bodyDraftSaving={props.bodyDraftSaving}
69+
onBodyFilePathChange={props.onBodyFilePathChange}
70+
onBodyCopy={props.onBodyCopy}
71+
onSaveBody={props.onSaveBody}
72+
onDiscardBody={props.onDiscardBody}
73+
/>
74+
</Match>
75+
</Switch>
76+
</div>
77+
);
78+
}
79+
80+
// Re-export types for backward compatibility
81+
export type {
82+
BodyPanelProps,
83+
FileBodyEditorProps,
84+
FormDataEditorProps,
85+
InlineBodyEditorProps
86+
} from './types';

0 commit comments

Comments
 (0)