Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions packages/web/src/components/request-workspace/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
// Model exports
export {
DEFAULT_REQUEST_WORKSPACE_TAB,
isRequestWorkspaceTabId,
REQUEST_WORKSPACE_TABS,
type RequestWorkspaceTabId
} from './model';
export { BodyPanel } from './panels/body';
export { HeadersPanel } from './panels/headers';
// New panel component exports
export { ParamsPanel } from './panels/params';
export {
DraftHeader as SharedDraftHeader,
ErrorBanner as SharedErrorBanner,
KeyValueRow as SharedKeyValueRow,
KeyValueTable as SharedKeyValueTable
} from './panels/shared';

// Legacy panel component exports (re-exported from new locations)
export {
DraftHeader,
ErrorBanner,
KeyValueRow,
KeyValueTable,
RequestWorkspaceBodyPanel,
RequestWorkspaceHeadersPanel,
RequestWorkspaceParamsPanel
} from './request-workspace-tab-panels';
// Legacy exports (keep for backward compatibility)
export { RequestWorkspaceTabs } from './request-workspace-tabs';
export { useRequestBodyDraftController } from './use-request-body-draft-controller';
export { useRequestHeaderDraftController } from './use-request-header-draft-controller';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Show } from 'solid-js';
import type { FileBodyEditorProps } from './types';

export function FileBodyEditor(props: FileBodyEditorProps) {
return (
<div class="space-y-2">
<div class="flex flex-wrap items-center justify-between gap-2">
<button
type="button"
class="btn btn-ghost btn-xs font-mono"
onClick={props.onBodyCopy}
disabled={!props.hasRequest}
>
Copy
</button>

<div class="flex items-center gap-2">
<Show when={props.bodyDraftDirty && !props.bodyDraftSaving}>
<span class="badge badge-sm badge-warning font-mono">Unsaved</span>
</Show>
<button
type="button"
class="btn btn-ghost btn-xs font-mono"
onClick={props.onDiscardBody}
disabled={!props.hasRequest || !props.bodyDraftDirty || props.bodyDraftSaving}
>
Discard
</button>
<button
type="button"
class="btn btn-primary btn-xs font-mono"
onClick={props.onSaveBody}
disabled={!props.hasRequest || !props.bodyDraftDirty || props.bodyDraftSaving}
>
{props.bodyDraftSaving ? 'Saving…' : 'Save'}
</button>
</div>
</div>

<div class="rounded-box border border-base-300 bg-base-100/80 p-2">
<label
for="request-workspace-body-file-path"
class="mb-1 block font-mono text-[11px] uppercase tracking-[0.06em] text-base-content/70"
>
File Path
</label>
<input
id="request-workspace-body-file-path"
type="text"
class="input input-sm w-full border-base-300 bg-base-100 font-mono text-xs"
value={props.requestBodyFilePathDraft}
onInput={(event) => props.onBodyFilePathChange(event.currentTarget.value)}
disabled={!props.hasRequest || props.bodyDraftSaving}
placeholder="./payload.json"
/>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { Index, Show } from 'solid-js';
import type { FormDataEditorProps } from './types';

export function FormDataEditor(props: FormDataEditorProps) {
return (
<div class="space-y-2">
<div class="flex flex-wrap items-center justify-between gap-2">
<button
type="button"
class="btn btn-ghost btn-xs font-mono"
onClick={props.onBodyFormDataAddField}
disabled={!props.hasRequest || props.bodyDraftSaving}
>
Add Field
</button>

<div class="flex items-center gap-2">
<Show when={props.bodyDraftDirty && !props.bodyDraftSaving}>
<span class="badge badge-sm badge-warning font-mono">Unsaved</span>
</Show>
<button
type="button"
class="btn btn-ghost btn-xs font-mono"
onClick={props.onDiscardBody}
disabled={!props.hasRequest || !props.bodyDraftDirty || props.bodyDraftSaving}
>
Discard
</button>
<button
type="button"
class="btn btn-primary btn-xs font-mono"
onClick={props.onSaveBody}
disabled={!props.hasRequest || !props.bodyDraftDirty || props.bodyDraftSaving}
>
{props.bodyDraftSaving ? 'Saving…' : 'Save'}
</button>
</div>
</div>

<div class="overflow-auto rounded-box border border-base-300 bg-base-100/80">
<table class="table table-xs">
<thead>
<tr>
<th class="font-mono uppercase tracking-[0.06em] text-[11px]">Name</th>
<th class="font-mono uppercase tracking-[0.06em] text-[11px]">Type</th>
<th class="font-mono uppercase tracking-[0.06em] text-[11px]">Value</th>
<th class="font-mono uppercase tracking-[0.06em] text-[11px]">Filename</th>
<th class="font-mono uppercase tracking-[0.06em] text-[11px] text-right">Actions</th>
</tr>
</thead>
<tbody>
<Show
when={props.requestBodyFormDataDraft.length > 0}
fallback={
<tr>
<td colSpan={5} class="font-mono text-xs text-base-content/70 text-center py-3">
No form-data fields configured.
</td>
</tr>
}
>
<Index each={props.requestBodyFormDataDraft}>
{(field, index) => (
<tr>
<td>
<input
type="text"
class="input input-xs w-full border-base-300 bg-base-100 font-mono text-xs"
value={field().name}
onInput={(event) =>
props.onBodyFormDataNameChange(index, event.currentTarget.value)
}
disabled={!props.hasRequest || props.bodyDraftSaving}
/>
</td>
<td>
<select
class="select select-xs w-full border-base-300 bg-base-100 font-mono text-xs"
value={field().isFile ? 'file' : 'text'}
onChange={(event) =>
props.onBodyFormDataTypeChange(
index,
event.currentTarget.value === 'file'
)
}
disabled={!props.hasRequest || props.bodyDraftSaving}
>
<option value="text">text</option>
<option value="file">file</option>
</select>
</td>
<td>
<input
type="text"
class="input input-xs w-full border-base-300 bg-base-100 font-mono text-xs"
value={field().isFile ? (field().path ?? '') : field().value}
onInput={(event) =>
props.onBodyFormDataValueChange(index, event.currentTarget.value)
}
placeholder={field().isFile ? './path/to/file' : 'value'}
disabled={!props.hasRequest || props.bodyDraftSaving}
/>
</td>
<td>
<input
type="text"
class="input input-xs w-full border-base-300 bg-base-100 font-mono text-xs"
value={field().filename ?? ''}
onInput={(event) =>
props.onBodyFormDataFilenameChange(index, event.currentTarget.value)
}
placeholder="optional"
disabled={!props.hasRequest || props.bodyDraftSaving || !field().isFile}
/>
</td>
<td class="text-right">
<button
type="button"
class="btn btn-ghost btn-xs text-error"
onClick={() => props.onBodyFormDataRemoveField(index)}
disabled={!props.hasRequest || props.bodyDraftSaving}
>
Remove
</button>
</td>
</tr>
)}
</Index>
</Show>
</tbody>
</table>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Match, Show, Switch } from 'solid-js';
import { FileBodyEditor } from './file-editor';
import { FormDataEditor } from './form-data-editor';
import { InlineBodyEditor } from './inline-editor';
import type { BodyPanelProps } from './types';

export function BodyPanel(props: BodyPanelProps) {
const shouldShowDescription =
props.requestBodySummary.description !== 'Request includes an inline body payload.';

return (
<div class="space-y-2">
<Show when={props.bodyDraftSaveError}>
{(message) => (
<div class="rounded-box border border-error/35 bg-error/10 px-2 py-1.5 text-xs text-base-content">
{message()}
</div>
)}
</Show>

<Show when={shouldShowDescription}>
<p>{props.requestBodySummary.description}</p>
</Show>

<Switch>
<Match when={props.requestBodySummary.kind === 'inline'}>
<InlineBodyEditor
hasRequest={props.hasRequest}
requestBodyDraft={props.requestBodyDraft}
requestBodySummary={props.requestBodySummary}
bodyDraftDirty={props.bodyDraftDirty}
bodyDraftSaving={props.bodyDraftSaving}
bodyDraftValidationError={props.bodyDraftValidationError}
bodyDraftIsJsonEditable={props.bodyDraftIsJsonEditable}
bodyDraftTemplateWarnings={props.bodyDraftTemplateWarnings}
onBodyChange={props.onBodyChange}
onBodyPrettify={props.onBodyPrettify}
onBodyMinify={props.onBodyMinify}
onBodyCopy={props.onBodyCopy}
onSaveBody={props.onSaveBody}
onDiscardBody={props.onDiscardBody}
/>
</Match>

<Match when={props.requestBodySummary.kind === 'form-data'}>
<FormDataEditor
hasRequest={props.hasRequest}
requestBodyFormDataDraft={props.requestBodyFormDataDraft}
bodyDraftDirty={props.bodyDraftDirty}
bodyDraftSaving={props.bodyDraftSaving}
onBodyFormDataNameChange={props.onBodyFormDataNameChange}
onBodyFormDataTypeChange={props.onBodyFormDataTypeChange}
onBodyFormDataValueChange={props.onBodyFormDataValueChange}
onBodyFormDataFilenameChange={props.onBodyFormDataFilenameChange}
onBodyFormDataAddField={props.onBodyFormDataAddField}
onBodyFormDataRemoveField={props.onBodyFormDataRemoveField}
onSaveBody={props.onSaveBody}
onDiscardBody={props.onDiscardBody}
/>
</Match>

<Match when={props.requestBodySummary.kind === 'file'}>
<FileBodyEditor
hasRequest={props.hasRequest}
requestBodyFilePathDraft={props.requestBodyFilePathDraft}
bodyDraftDirty={props.bodyDraftDirty}
bodyDraftSaving={props.bodyDraftSaving}
onBodyFilePathChange={props.onBodyFilePathChange}
onBodyCopy={props.onBodyCopy}
onSaveBody={props.onSaveBody}
onDiscardBody={props.onDiscardBody}
/>
</Match>
</Switch>
</div>
);
}

// Re-export types for backward compatibility
export type {
BodyPanelProps,
FileBodyEditorProps,
FormDataEditorProps,
InlineBodyEditorProps
} from './types';
Loading