From a99c676eed97845393266fc405590dbc249e3f90 Mon Sep 17 00:00:00 2001 From: Panthevm Date: Fri, 22 May 2026 17:17:35 +0300 Subject: [PATCH] REST/ResourceEditor: address issues from #7442 Collections sidebar (issue #7442): - Add a top-level "Create" button to create a new collection (copying the active tab). - Save now resolves the snippet by tab.historyId ?? tab.id, then binds the editor tab to that snippet so subsequent edits update the same sidebar entry instead of creating a new one. - Rename via the row menu: defer startRenaming via setTimeout(0) so the dropdown finishes closing before focus moves into the input; stopPropagation on the menu item click; force-expand folders before rename so the children stay visible during editing. - Rename input: onBlur commits non-empty values via tree.completeRenaming and reverts empty input via tree.abortRenaming instead of always aborting. - Restore TreeView remount on data update so saved edits (e.g. method change) are reflected immediately; carry collapsed folder ids over to the new title on folder rename so closed folders stay closed. ResourceEditor: - Propagate resourceType down to HSComp.CodeEditor as resourceTypeHint through EditTabContent and EditorTab so the FHIR validator can resolve the StructureDefinition and stops marking valid properties (e.g. nested extension) as "Unknown property". REST response pane: - Add a Clear response button (X) that deletes the stored response for the current tab; pane falls back to the empty "No response yet" state. --- .../ResourceEditor/edit-tab-content.tsx | 1 + src/components/ResourceEditor/editor-tab.tsx | 3 + src/components/rest/collections.tsx | 80 +++++++++++++++++-- src/routes/rest.tsx | 21 +++++ 4 files changed, 97 insertions(+), 8 deletions(-) diff --git a/src/components/ResourceEditor/edit-tab-content.tsx b/src/components/ResourceEditor/edit-tab-content.tsx index 9ac0844..06be4c2 100644 --- a/src/components/ResourceEditor/edit-tab-content.tsx +++ b/src/components/ResourceEditor/edit-tab-content.tsx @@ -99,6 +99,7 @@ export function EditTabContent({ issueLineNumbers={issueLineNumbers} getStructureDefinitions={getStructureDefinitions} expandValueSet={expandValueSet} + resourceTypeHint={resourceType} trailingActions={ !isProfileOpen && ( { const vimMode = useVimMode(); return ( @@ -67,6 +69,7 @@ export const EditorTab = ({ getStructureDefinitions={getStructureDefinitions} expandValueSet={expandValueSet} vimMode={vimMode} + resourceTypeHint={resourceTypeHint} /> diff --git a/src/components/rest/collections.tsx b/src/components/rest/collections.tsx index 7bb5ea2..dfac6f2 100644 --- a/src/components/rest/collections.tsx +++ b/src/components/rest/collections.tsx @@ -51,7 +51,10 @@ export async function SaveRequest( saveToRootCollection?: boolean, setLeftMenuOpen?: (open: boolean) => void, ) { - const currentSnippet = collectionEntries.find((entry) => entry.id === tab.id); + const targetId = tab.historyId ?? tab.id; + const currentSnippet = collectionEntries.find( + (entry) => entry.id === targetId, + ); let collection: string | undefined; let snippetId: string; @@ -74,10 +77,17 @@ export async function SaveRequest( snippetId = generateId(); setTabs([ ...tabs.map((t) => ({ ...t, selected: false })), - { ...tab, id: snippetId, selected: true }, + { ...tab, id: snippetId, historyId: snippetId, selected: true }, ]); } else { - snippetId = tab.id; + snippetId = targetId; + // Bind the editor tab to the underlying snippet so subsequent edits + // update the same sidebar entry instead of spawning a new one. + if (tab.historyId !== snippetId) { + setTabs( + tabs.map((t) => (t.id === tab.id ? { ...t, historyId: snippetId } : t)), + ); + } } const result = await client.update({ @@ -399,7 +409,19 @@ function CollectionMoreButton({ tree.getItemInstance(itemId).startRenaming()} + onClick={(e) => { + e.stopPropagation(); + // Defer so the dropdown close completes before input is focused; + // otherwise the immediate blur aborts the rename. Also keep the + // folder expanded — closing it on rename hides children mid-edit. + setTimeout(() => { + const instance = tree.getItemInstance(itemId); + if (instance.isFolder() && !instance.isExpanded()) { + instance.expand(); + } + instance.startRenaming(); + }, 0); + }} > Rename @@ -480,10 +502,13 @@ function SnippetMoreButton({ { - if (itemData.meta?.id !== undefined) { - tree.getItemInstance(itemData.meta.id).startRenaming(); - } + onClick={(e) => { + e.stopPropagation(); + const id = itemData.meta?.id; + if (id === undefined) return; + // Defer so the dropdown close completes before input is focused; + // otherwise the immediate blur aborts the rename. + setTimeout(() => tree.getItemInstance(id).startRenaming(), 0); }} > Rename @@ -639,6 +664,14 @@ function customItemView( className="h-5 border-none p-0" autoFocus {...item.getRenameInputProps()} + onBlur={(e) => { + const tree = item.getTree(); + if (e.target.value.trim()) { + tree.completeRenaming(); + } else { + tree.abortRenaming(); + } + }} /> ) : itemData?.meta?.title ? (
{itemData.meta.title}
@@ -881,6 +914,27 @@ export const CollectionsView = ({ /> ) : (
+ {selectedTab && ( + + )} { + if (item.isFolder()) { + const oldId = item.getId(); + // Carry collapsed state over to the new folder id so the + // rename does not visually expand a previously-closed folder. + if (collapsedItems.includes(oldId)) { + setCollapsedItems( + collapsedItems.map((id) => (id === oldId ? newTitle : id)), + ); + } + } handleRenameSnippet(client, item, newTitle, queryClient); }} onItemLabelClick={(item) => { diff --git a/src/routes/rest.tsx b/src/routes/rest.tsx index 277b9b5..14512bb 100644 --- a/src/routes/rest.tsx +++ b/src/routes/rest.tsx @@ -33,6 +33,7 @@ import { Play, SquareTerminalIcon, Timer, + X, } from "lucide-react"; import type React from "react"; import { @@ -749,6 +750,7 @@ type ResponsePaneProps = { sendVersion: number; onExplainSubTabChange: (subTab: "query" | "statement" | "plan") => void; onFollowContentLocation?: (path: string) => void; + onClearResponse?: () => void; }; function ResponseInfo({ response }: { response: ResponseData }) { @@ -1178,6 +1180,7 @@ function ResponsePane({ sendVersion, onExplainSubTabChange, onFollowContentLocation, + onClearResponse, }: ResponsePaneProps) { // Use response mode from the response itself (set at request time) const responseMode = response?.mode || "json"; @@ -1214,6 +1217,21 @@ function ResponsePane({ onToggle={(state) => onFullScreenToggle(state)} state={fullScreenState} /> + {response && onClearResponse && ( + + + + + Clear response + + )}
@@ -2341,6 +2359,9 @@ function RouteComponent() { aidboxClient={client} sendVersion={sendVersion} onExplainSubTabChange={handleExplainSubTabChange} + onClearResponse={() => + responseStorage.delete(selectedTab.id) + } onFollowContentLocation={(contentPath) => { const updatedTab = { ...selectedTab,