From 49a76f46993917fb312aad24168a303e75d5ef28 Mon Sep 17 00:00:00 2001 From: Riccardo Forina Date: Wed, 20 Nov 2024 11:06:35 +0100 Subject: [PATCH] Changes to support the new view modes --- packages/ui/src/OpenApiEditor.tsx | 182 +++++++++--------- packages/ui/src/OpenApiEditorMachine.ts | 31 ++- packages/ui/src/components/EditorSidebar.tsx | 13 -- packages/ui/src/components/EditorToolbar.tsx | 26 ++- packages/ui/src/components/Markdown.tsx | 10 +- packages/ui/src/components/OmniSearch.tsx | 21 +- .../ui/src/components/SearchableTable.tsx | 122 ++++++++++++ packages/ui/src/components/ServerRow.tsx | 64 +++--- packages/ui/src/components/ServersTable.tsx | 124 ------------ packages/ui/src/components/SidebarSection.tsx | 30 +-- .../DataTypeDesignerMachine.ts | 2 + packages/ui/src/dataTypeDesigner/Info.tsx | 4 +- .../documentDesigner/SecurityRequirements.tsx | 69 +++---- .../src/documentDesigner/SecurityScheme.tsx | 181 +++++------------ packages/ui/src/documentDesigner/Servers.tsx | 32 ++- .../src/documentDesigner/TagDefinitions.tsx | 173 ++++++----------- packages/ui/src/pathDesigner/Info.tsx | 17 +- .../src/pathDesigner/PathDesignerMachine.ts | 2 + packages/ui/src/pathDesigner/Servers.tsx | 6 +- packages/ui/src/responseDesigner/Info.tsx | 4 +- .../ResponseDesignerMachine.ts | 2 + 21 files changed, 507 insertions(+), 608 deletions(-) create mode 100644 packages/ui/src/components/SearchableTable.tsx delete mode 100644 packages/ui/src/components/ServersTable.tsx diff --git a/packages/ui/src/OpenApiEditor.tsx b/packages/ui/src/OpenApiEditor.tsx index ab14d06..47a3e5b 100644 --- a/packages/ui/src/OpenApiEditor.tsx +++ b/packages/ui/src/OpenApiEditor.tsx @@ -237,14 +237,21 @@ export function OpenApiEditor({ } function Editor() { - const { isSavingSlowly, documentTitle, selectedNode, view, actorRef } = - OpenApiEditorMachineContext.useSelector(({ context, value }) => ({ - isSavingSlowly: value === "slowSaving", - documentTitle: context.documentTitle, - selectedNode: context.selectedNode, - view: context.view, - actorRef: context.actorRef, - })); + const { + isSavingSlowly, + showNavigation, + documentTitle, + selectedNode, + view, + spawnedMachineRef, + } = OpenApiEditorMachineContext.useSelector(({ context, value }) => ({ + isSavingSlowly: value === "slowSaving", + documentTitle: context.documentTitle, + selectedNode: context.selectedNode, + showNavigation: context.showNavigation, + view: context.view, + spawnedMachineRef: context.spawnedMachineRef, + })); const title = (() => { switch (selectedNode.type) { @@ -281,7 +288,7 @@ function Editor() { canGoBack={selectedNode.type !== "root"} /> - {(() => { - switch (true) { - case selectedNode.type === "validation": - return ; - case view === "design": - case view === "visualize": - switch (selectedNode.type) { - case "root": - return actorRef ? ( - ["value"] - } - > - - - ) : ( - - ); - case "path": - return actorRef ? ( - ["value"] - } - > - - - ) : ( - - ); - case "datatype": - return actorRef ? ( - ["value"] - } - > - - - ) : ( - - ); - case "response": - return actorRef ? ( - ["value"] - } - > - - - ) : ( - - ); - } - break; - case view === "code": - return ( - + + } + > + {(() => { + switch (true) { + case selectedNode.type === "validation": + return ; + case view === "design": + case view === "visualize": + switch (selectedNode.type) { + case "root": + return spawnedMachineRef ? ( + ["value"] + } + > + + + ) : ( + + ); + case "path": + return spawnedMachineRef ? ( + ["value"] } > - - + + + ) : ( + + ); + case "datatype": + return spawnedMachineRef ? ( + ["value"] + } + > + + + ) : ( + + ); + case "response": + return spawnedMachineRef ? ( + ["value"] + } + > + + + ) : ( + ); } - })()} - - } - > - + break; + case view === "code": + return ( + ["value"] + } + > + + + ); + } + })()} ; - actorRef?: + showNavigation: boolean; + spawnedMachineRef?: | ActorRefFrom | ActorRefFrom | ActorRefFrom @@ -35,6 +36,12 @@ type Events = | { readonly type: "xstate.init"; } + | { + readonly type: "SHOW_NAVIGATION"; + } + | { + readonly type: "HIDE_NAVIGATION"; + } | { readonly type: "FILTER"; readonly filter: string; @@ -185,6 +192,7 @@ export const OpenApiEditorMachine = setup({ type: "root", }, view: "visualize", + showNavigation: false, }, initial: "viewChanged", states: { @@ -198,9 +206,9 @@ export const OpenApiEditorMachine = setup({ viewChanged: { always: "updateEditorState", entry: assign({ - actorRef: ({ context, spawn, self }) => { - if (context.actorRef) { - stopChild(context.actorRef); + spawnedMachineRef: ({ context, spawn, self }) => { + if (context.spawnedMachineRef) { + stopChild(context.spawnedMachineRef); } if (context.selectedNode.type === "validation") { return undefined; @@ -222,6 +230,7 @@ export const OpenApiEditorMachine = setup({ input: { parentRef: self, path: context.selectedNode, + editable: context.view === "design", }, }); case "datatype": @@ -229,6 +238,7 @@ export const OpenApiEditorMachine = setup({ input: { parentRef: self, dataType: context.selectedNode, + editable: context.view === "design", }, }); case "response": @@ -236,6 +246,7 @@ export const OpenApiEditorMachine = setup({ input: { parentRef: self, response: context.selectedNode, + editable: context.view === "design", }, }); } @@ -284,7 +295,7 @@ export const OpenApiEditorMachine = setup({ actions: [ "onDocumentChange", assign(({ event }) => event.output), - sendTo(({ context }) => context.actorRef!, { + sendTo(({ context }) => context.spawnedMachineRef!, { type: "DOCUMENT_CHANGED", }), ], @@ -500,5 +511,15 @@ export const OpenApiEditorMachine = setup({ REDO: ".redoing", START_SAVING: ".saving", END_SAVING: ".idle", + SHOW_NAVIGATION: { + actions: assign({ + showNavigation: true, + }), + }, + HIDE_NAVIGATION: { + actions: assign({ + showNavigation: false, + }), + }, }, }); diff --git a/packages/ui/src/components/EditorSidebar.tsx b/packages/ui/src/components/EditorSidebar.tsx index eb707ca..e6f3cc1 100644 --- a/packages/ui/src/components/EditorSidebar.tsx +++ b/packages/ui/src/components/EditorSidebar.tsx @@ -2,7 +2,6 @@ import { ReactNode } from "react"; import { OpenApiEditorMachineContext } from "../OpenApiEditor"; import { EditorSidebarSkeleton } from "./EditorSidebarSkeleton"; import { SidebarSection } from "./SidebarSection.tsx"; -import { OmniSearch } from "./OmniSearch.tsx"; import { Label, PageSection } from "@patternfly/react-core"; import { NavigationPaths } from "./NavigationPaths.tsx"; import { NavigationResponses } from "./NavigationResponses.tsx"; @@ -32,12 +31,6 @@ export function EditorSidebar() { return ( <> - - - {(() => { switch (isFiltering) { @@ -155,10 +148,8 @@ function PathsSection({ return ( Paths} - addTooltip={"Add a path"} count={count} idx={0} - onAdd={() => {}} > {children} @@ -175,10 +166,8 @@ function ResponsesSection({ return ( Responses} - addTooltip={"Add a response"} count={count} idx={1} - onAdd={() => {}} > {children} @@ -195,10 +184,8 @@ function DataTypesSection({ return ( Data types} - addTooltip={"Add a data type"} count={count} idx={2} - onAdd={() => {}} > {children} diff --git a/packages/ui/src/components/EditorToolbar.tsx b/packages/ui/src/components/EditorToolbar.tsx index 117f991..e424ec7 100644 --- a/packages/ui/src/components/EditorToolbar.tsx +++ b/packages/ui/src/components/EditorToolbar.tsx @@ -21,6 +21,7 @@ import { WarningTriangleIcon, } from "@patternfly/react-icons"; import { ReactNode } from "react"; +import { OmniSearch } from "./OmniSearch.tsx"; export type EditorToolbarView = "design" | "code" | "visualize" | "hidden"; export type EditorToolbarProps = { @@ -57,17 +58,22 @@ export function EditorToolbar({ - {canGoBack && ( - - + + + + + + + )} + + + + )} + + {filteredData.length > 0 && ( + + {filteredData.map((t, idx) => { + return {onRenderRow(t, idx)}; + })} + + )} + {filteredData.length === 0 && filter.length > 0 && ( + + + + No {label}s were found that meet the search criteria. + + + + + + + )} + {filteredData.length === 0 && filter.length === 0 && ( + + + No {label}s have been defined. + {editing && ( + + + + )} + + + )} + + + ); +} diff --git a/packages/ui/src/components/ServerRow.tsx b/packages/ui/src/components/ServerRow.tsx index dc9b22c..c6f8c8a 100644 --- a/packages/ui/src/components/ServerRow.tsx +++ b/packages/ui/src/components/ServerRow.tsx @@ -17,12 +17,14 @@ export function ServerRow({ id, url, description, + editing, onRename, onRemove, }: { id: string; url: string; description: string; + editing: boolean; onRename: () => void; onRemove: () => void; }) { @@ -41,37 +43,39 @@ export function ServerRow({ , ]} /> - - ( - - - )} - isOpen={isMenuOpen} - onOpenChange={(isOpen: boolean) => setIsMenuOpen(isOpen)} + {editing && ( + - - - Rename - - - Delete - - - - + ( + + + )} + isOpen={isMenuOpen} + onOpenChange={(isOpen: boolean) => setIsMenuOpen(isOpen)} + > + + + Rename + + + Delete + + + + + )} ); diff --git a/packages/ui/src/components/ServersTable.tsx b/packages/ui/src/components/ServersTable.tsx deleted file mode 100644 index 826a230..0000000 --- a/packages/ui/src/components/ServersTable.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import { - Button, - DataList, - EmptyState, - EmptyStateActions, - EmptyStateBody, - Panel, - PanelHeader, - PanelMain, - PanelMainBody, - SearchInput, - Toolbar, - ToolbarContent, - ToolbarItem, -} from "@patternfly/react-core"; -import { AddCircleOIcon, SearchIcon, TrashIcon } from "@patternfly/react-icons"; -import { useState } from "react"; -import { ServerRow } from "./ServerRow.tsx"; -import { Server } from "../OpenApiEditorModels.ts"; - -export function ServersTable({ - servers, - onAdd, - onRemove, - onRename, - onRemoveAll, -}: { - servers: Server[]; - onAdd: () => void; - onRename: (id: string) => void; - onRemove: (id: string) => void; - onRemoveAll: () => void; -}) { - const [filter, setFilter] = useState(""); - const filteredTags = servers.filter( - (server) => - server.url.toLowerCase().includes(filter.toLowerCase()) || - server.description.toLowerCase().includes(filter.toLowerCase()) - ); - return ( - - {servers.length > 10 && ( - - - - - setFilter(v)} - /> - - - - - - - - - - - - )} - - {filteredTags.length > 0 && ( - - {filteredTags.map((t, idx) => { - const id = `server-${idx}`; - return ( - onRename(id)} - onRemove={() => onRemove(id)} - /> - ); - })} - - )} - {filteredTags.length === 0 && filter.length > 0 && ( - - - - No servers were found that meet the search criteria. - - - - - - - )} - {filteredTags.length === 0 && filter.length === 0 && ( - - - No servers have been defined. - - - - - - )} - - - ); -} diff --git a/packages/ui/src/components/SidebarSection.tsx b/packages/ui/src/components/SidebarSection.tsx index 80060f6..06d8370 100644 --- a/packages/ui/src/components/SidebarSection.tsx +++ b/packages/ui/src/components/SidebarSection.tsx @@ -4,24 +4,17 @@ import { Button, Toolbar, ToolbarContent, - ToolbarGroup, ToolbarItem, - Tooltip, } from "@patternfly/react-core"; -import { PlusCircleIcon } from "@patternfly/react-icons"; export function SidebarSection({ title, - addTooltip, count, - onAdd, idx, children, }: { title: ReactNode; - addTooltip: string; count?: number; - onAdd: () => void; idx: number; children: ReactNode; }) { @@ -30,7 +23,7 @@ export function SidebarSection({ <> @@ -38,24 +31,11 @@ export function SidebarSection({ {title} - - {count !== undefined && ( - - {count} - - )} - - - - - - - )} - - + {}} + onFilter={(sr, filter) => + sr.schemes.reduce( + (_, scheme) => scheme.toLowerCase().includes(filter.toLowerCase()), + false + ) + } + onRenderRow={(s, idx) => ( + + )} + onRemoveAll={() => {}} + /> ); } diff --git a/packages/ui/src/documentDesigner/SecurityScheme.tsx b/packages/ui/src/documentDesigner/SecurityScheme.tsx index 5a5c344..559b347 100644 --- a/packages/ui/src/documentDesigner/SecurityScheme.tsx +++ b/packages/ui/src/documentDesigner/SecurityScheme.tsx @@ -1,6 +1,4 @@ import { - Button, - DataList, DataListAction, DataListCell, DataListItem, @@ -9,118 +7,41 @@ import { Dropdown, DropdownItem, DropdownList, - EmptyState, - EmptyStateActions, - EmptyStateBody, MenuToggle, - Panel, - PanelHeader, - PanelMain, - PanelMainBody, - SearchInput, - Toolbar, - ToolbarContent, - ToolbarItem, } from "@patternfly/react-core"; -import { - AddCircleOIcon, - EllipsisVIcon, - SearchIcon, - TrashIcon, -} from "@patternfly/react-icons"; +import { EllipsisVIcon } from "@patternfly/react-icons"; import { useState } from "react"; import { Markdown } from "../components/Markdown.tsx"; -import { - useMachineActorRef, - useMachineSelector, -} from "./DocumentDesignerMachineContext.ts"; +import { useMachineSelector } from "./DocumentDesignerMachineContext.ts"; +import { SearchableTable } from "../components/SearchableTable.tsx"; export function SecurityScheme() { - const { securityScheme } = useMachineSelector(({ context }) => { + const { securityScheme, editable } = useMachineSelector(({ context }) => { return { securityScheme: context.securityScheme, + editable: context.editable, }; }); - const actorRef = useMachineActorRef(); - const [filter, setFilter] = useState(""); - const filteredTags = securityScheme.filter( - (securityScheme) => - securityScheme.name.toLowerCase().includes(filter.toLowerCase()) || - securityScheme.description.toLowerCase().includes(filter.toLowerCase()) - ); return ( - - {securityScheme.length > 10 && ( - - - - - setFilter(v)} - /> - - - - - - - - - - - + {}} + onFilter={(securityScheme, filter) => + securityScheme.name.toLowerCase().includes(filter.toLowerCase()) || + securityScheme.description.toLowerCase().includes(filter.toLowerCase()) + } + onRenderRow={(s, idx) => ( + )} - - {filteredTags.length > 0 && ( - - {filteredTags.map((t, idx) => { - const id = `securityScheme-${idx}`; - return ( - - ); - })} - - )} - {filteredTags.length === 0 && filter.length > 0 && ( - - - - No security scheme were found that meet the search criteria. - - - - - - - )} - {securityScheme.length === 0 && filter.length === 0 && ( - - - - No security scheme have been configured. - - - - - - - )} - - + onRemoveAll={() => {}} + /> ); } @@ -128,10 +49,12 @@ function SecuritySchemeRow({ id, name, description, + editable, }: { id: string; name: string; description: string; + editable: boolean; }) { const [isMenuOpen, setIsMenuOpen] = useState(false); const toggleMenu = () => setIsMenuOpen((v) => !v); @@ -148,33 +71,35 @@ function SecuritySchemeRow({ , ]} /> - - ( - - - )} - isOpen={isMenuOpen} - onOpenChange={(isOpen: boolean) => setIsMenuOpen(isOpen)} + {editable && ( + - - Rename - Delete - - - + ( + + + )} + isOpen={isMenuOpen} + onOpenChange={(isOpen: boolean) => setIsMenuOpen(isOpen)} + > + + Rename + Delete + + + + )} ); diff --git a/packages/ui/src/documentDesigner/Servers.tsx b/packages/ui/src/documentDesigner/Servers.tsx index 58833cc..75de797 100644 --- a/packages/ui/src/documentDesigner/Servers.tsx +++ b/packages/ui/src/documentDesigner/Servers.tsx @@ -1,28 +1,40 @@ -import { ServersTable } from "../components/ServersTable.tsx"; +import { SearchableTable } from "../components/SearchableTable.tsx"; import { useMachineActorRef, useMachineSelector, } from "./DocumentDesignerMachineContext.ts"; +import { ServerRow } from "../components/ServerRow.tsx"; export function Servers() { - const { servers } = useMachineSelector(({ context }) => { + const { servers, editable } = useMachineSelector(({ context }) => { return { servers: context.servers, + editable: context.editable, }; }); const actorRef = useMachineActorRef(); return ( - + server.url.toLowerCase().includes(filter.toLowerCase()) || + server.description.toLowerCase().includes(filter.toLowerCase()) + } + onRenderRow={(server, idx) => ( + {}} + onRemove={() => {}} + /> + )} onAdd={function (): void { throw new Error("Function not implemented."); }} - onRename={function (id: string): void { - throw new Error("Function not implemented."); - }} - onRemove={function (id: string): void { - throw new Error("Function not implemented."); - }} onRemoveAll={function (): void { throw new Error("Function not implemented."); }} diff --git a/packages/ui/src/documentDesigner/TagDefinitions.tsx b/packages/ui/src/documentDesigner/TagDefinitions.tsx index b733e04..4c231b7 100644 --- a/packages/ui/src/documentDesigner/TagDefinitions.tsx +++ b/packages/ui/src/documentDesigner/TagDefinitions.tsx @@ -1,6 +1,4 @@ import { - Button, - DataList, DataListAction, DataListCell, DataListItem, @@ -9,107 +7,46 @@ import { Dropdown, DropdownItem, DropdownList, - EmptyState, - EmptyStateActions, - EmptyStateBody, - Label, MenuToggle, - Panel, - PanelHeader, - PanelMain, - PanelMainBody, - SearchInput, - Toolbar, - ToolbarContent, - ToolbarItem, } from "@patternfly/react-core"; -import { - AddCircleOIcon, - EllipsisVIcon, - TagIcon, - TrashIcon, -} from "@patternfly/react-icons"; +import { EllipsisVIcon } from "@patternfly/react-icons"; import { useState } from "react"; import { Markdown } from "../components/Markdown.tsx"; import { useMachineActorRef, useMachineSelector, } from "./DocumentDesignerMachineContext.ts"; +import { SearchableTable } from "../components/SearchableTable.tsx"; +import { InlineEdit } from "../components/InlineEdit.tsx"; export function TagDefinitions() { - const { tags } = useMachineSelector(({ context }) => { + const { tags, editable } = useMachineSelector(({ context }) => { return { tags: context.tags, + editable: context.editable, }; }); const actorRef = useMachineActorRef(); - const [filter, setFilter] = useState(""); - const filteredTags = tags.filter( - (tag) => - tag.name.toLowerCase().includes(filter.toLowerCase()) || - tag.description.toLowerCase().includes(filter.toLowerCase()) - ); return ( - - {tags.length > 10 && ( - - - - - setFilter(v)} - /> - - - - - - - - - - - + {}} + onFilter={(tag, filter) => + tag.name.toLowerCase().includes(filter.toLowerCase()) || + tag.description.toLowerCase().includes(filter.toLowerCase()) + } + onRenderRow={(tag, idx) => ( + )} - - {filteredTags.length > 0 && ( - - {filteredTags.map((t, idx) => { - const id = `tag-${idx}`; - return ( - - ); - })} - - )} - {filteredTags.length === 0 && filter.length > 0 && ( - - - - No tags were found that meet the search criteria. - - - - - - - )} - - + onRemoveAll={() => {}} + /> ); } @@ -117,10 +54,12 @@ function Tag({ id, name, description, + editing, }: { id: string; name: string; description: string; + editing: boolean; }) { const [isMenuOpen, setIsMenuOpen] = useState(false); const toggleMenu = () => setIsMenuOpen((v) => !v); @@ -130,42 +69,44 @@ function Tag({ - + + + , - {description} + {description} , ]} /> - - ( - - - )} - isOpen={isMenuOpen} - onOpenChange={(isOpen: boolean) => setIsMenuOpen(isOpen)} + {editing && ( + - - Rename - Delete - - - + ( + + + )} + isOpen={isMenuOpen} + onOpenChange={(isOpen: boolean) => setIsMenuOpen(isOpen)} + > + + Rename + Delete + + + + )} ); diff --git a/packages/ui/src/pathDesigner/Info.tsx b/packages/ui/src/pathDesigner/Info.tsx index 230c5e1..d96ab33 100644 --- a/packages/ui/src/pathDesigner/Info.tsx +++ b/packages/ui/src/pathDesigner/Info.tsx @@ -11,12 +11,15 @@ import { } from "./PathDesignerMachineContext.ts"; export function Info() { - const { summary, description } = useMachineSelector(({ context }) => { - return { - summary: context.summary, - description: context.description, - }; - }); + const { summary, description, editable } = useMachineSelector( + ({ context }) => { + return { + summary: context.summary, + description: context.description, + editable: context.editable, + }; + } + ); const actorRef = useMachineActorRef(); return ( @@ -28,6 +31,7 @@ export function Info() { actorRef.send({ type: "CHANGE_SUMMARY", summary }); }} value={summary} + editing={editable} /> @@ -39,6 +43,7 @@ export function Info() { actorRef.send({ type: "CHANGE_DESCRIPTION", description }); }} value={description} + editing={editable} /> diff --git a/packages/ui/src/pathDesigner/PathDesignerMachine.ts b/packages/ui/src/pathDesigner/PathDesignerMachine.ts index d4982fa..823a1bd 100644 --- a/packages/ui/src/pathDesigner/PathDesignerMachine.ts +++ b/packages/ui/src/pathDesigner/PathDesignerMachine.ts @@ -3,6 +3,7 @@ import { DocumentPath, NodePath } from "../OpenApiEditorModels"; type Context = DocumentPath & { path: NodePath; + editable: boolean; parentRef: ParentActor; }; @@ -30,6 +31,7 @@ export const PathDesignerMachine = setup({ events: {} as Events, input: {} as { path: NodePath; + editable: boolean; parentRef: ParentActor; }, }, diff --git a/packages/ui/src/pathDesigner/Servers.tsx b/packages/ui/src/pathDesigner/Servers.tsx index cc8cf7c..cd13d2b 100644 --- a/packages/ui/src/pathDesigner/Servers.tsx +++ b/packages/ui/src/pathDesigner/Servers.tsx @@ -1,4 +1,4 @@ -import { ServersTable } from "../components/ServersTable.tsx"; +import { SearchableTable } from "../components/SearchableTable.tsx"; import { useMachineActorRef, useMachineSelector, @@ -12,8 +12,8 @@ export function Servers() { }); const actorRef = useMachineActorRef(); return ( - { + const { description, editable } = useMachineSelector(({ context }) => { return { description: context.description, + editable: context.editable, }; }); const actorRef = useMachineActorRef(); @@ -27,6 +28,7 @@ export function Info() { actorRef.send({ type: "CHANGE_DESCRIPTION", description }); }} value={description} + editing={editable} /> diff --git a/packages/ui/src/responseDesigner/ResponseDesignerMachine.ts b/packages/ui/src/responseDesigner/ResponseDesignerMachine.ts index 219e134..3ad1c8f 100644 --- a/packages/ui/src/responseDesigner/ResponseDesignerMachine.ts +++ b/packages/ui/src/responseDesigner/ResponseDesignerMachine.ts @@ -7,6 +7,7 @@ import { type Context = DocumentPath & { response: NodeResponse; + editable: boolean; parentRef: ParentActor; }; @@ -30,6 +31,7 @@ export const ResponseDesignerMachine = setup({ events: {} as Events, input: {} as { response: NodeResponse; + editable: boolean; parentRef: ParentActor; }, },