From d2bc885dc10f298007f728c6882d5b5f5f740164 Mon Sep 17 00:00:00 2001 From: Eric Wittmann Date: Mon, 11 Dec 2023 13:03:45 -0500 Subject: [PATCH] Created an embedded editor page, useful for integrations with other services --- ui/ui-app/src/app/App.tsx | 3 +- .../src/app/pages/EmbeddedEditorPage.tsx | 200 ++++++++++++++++++ ui/ui-app/src/app/pages/index.ts | 1 + 3 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 ui/ui-app/src/app/pages/EmbeddedEditorPage.tsx diff --git a/ui/ui-app/src/app/App.tsx b/ui/ui-app/src/app/App.tsx index 2a8959c..e8f9885 100644 --- a/ui/ui-app/src/app/App.tsx +++ b/ui/ui-app/src/app/App.tsx @@ -8,7 +8,7 @@ import { useOidcAuth } from "@app/auth"; import { ApiDesignerConfigContext, ApiDesignerConfigType } from "@app/contexts/config"; import { AuthConfig } from "@services/ServiceConfigContext.tsx"; -import { EditorPage, HomePage, PageConfig, PageContextProvider } from "@app/pages"; +import { EditorPage, EmbeddedEditorPage, HomePage, PageConfig, PageContextProvider } from "@app/pages"; import { AppHeader, ApplicationAuth } from "@app/components"; import { Page } from "@patternfly/react-core"; @@ -50,6 +50,7 @@ export const App: React.FunctionComponent = () => { }/> }/> + }/> diff --git a/ui/ui-app/src/app/pages/EmbeddedEditorPage.tsx b/ui/ui-app/src/app/pages/EmbeddedEditorPage.tsx new file mode 100644 index 0000000..f9ae3c1 --- /dev/null +++ b/ui/ui-app/src/app/pages/EmbeddedEditorPage.tsx @@ -0,0 +1,200 @@ +import React, { CSSProperties, FunctionComponent, useEffect, useState } from "react"; +import { Button, PageSection, PageSectionVariants, Toolbar, ToolbarContent, ToolbarItem } from "@patternfly/react-core"; +import { ArtifactTypes, ContentTypes, DesignContent } from "@models/designs"; +import { TextEditor } from "@editors/TextEditor.tsx"; +import { ProtoEditor } from "@editors/ProtoEditor.tsx"; +import { OpenApiEditor } from "@editors/OpenApiEditor.tsx"; +import { AsyncApiEditor } from "@editors/AsyncApiEditor.tsx"; +import { CompareModal } from "@app/pages/components"; +import { IfNotLoading } from "@apicurio/common-ui-components"; +import { formatContent, toJsonString, toYamlString } from "@utils/content.utils.ts"; + +const sectionContextStyle: CSSProperties = { + borderBottom: "1px solid #ccc", + marginBottom: "1px", + padding: "0" +}; +const sectionEditorStyle: CSSProperties = { + padding: 0, + display: "flex", + flexFlow: "column", + height: "auto", + width: "100%" +}; +const editorParentStyle: CSSProperties = { + flexGrow: 1 +}; + + +export type EmbeddedEditorPageProps = { + // No props +}; + +export const EmbeddedEditorPage: FunctionComponent = () => { + const [isLoading, setLoading] = useState(true); + const [correlationId, setCorrelationId] = useState("12345"); + const [type, setType] = useState(ArtifactTypes.AVRO); + const [content, setContent] = useState("{}"); + const [contentType, setContentType] = useState("application/json"); + const [editorDesignContent, setEditorDesignContent] = useState({ + id: "embedded", + contentType: "application/json", + data: "{}" + }); + + const [originalContent, setOriginalContent] = useState(); + const [currentContent, setCurrentContent] = useState(); + const [isDirty, setDirty] = useState(false); + const [isCompareModalOpen, setCompareModalOpen] = useState(false); + + // Called when the user makes an edit in the editor. + const onEditorChange = (value: any): void => { + setCurrentContent(value); + }; + + useEffect(() => { + setOriginalContent(content); + setCurrentContent(content); + setEditorDesignContent({ + id: "embedded", + data: content, + contentType: contentType + }); + setDirty(false); + }, [content]); + + useEffect(() => { + setDirty(originalContent != currentContent); + }, [currentContent]); + + useEffect(() => { + const eventListener: any = (event: any) => { + if (event.data && event.data.type === "apicurio_onLoad") { + const newType: string = event.data.artifactType; + const newCorrelationId: string = event.data.correlationId; + let newContent: any = event.data.data.content; + const newContentType: string = event.data.data.contentType; + + if (typeof newContent === "object") { + if (newContentType === ContentTypes.APPLICATION_YAML) { + console.info("[EmbeddedEditorPage] New content is 'object', converting to YAML string"); + newContent = toYamlString(newContent); + } else { + console.info("[EmbeddedEditorPage] New content is 'object', converting to JSON string"); + newContent = toJsonString(newContent); + } + } + + setType(newType); + setCorrelationId(newCorrelationId); + setContentType(newContentType); + setContent(newContent); + setLoading(false); + } + }; + + console.info("[EmbeddedEditorPage] Adding window event listener."); + window.addEventListener("message", eventListener, false); + return () => { + window.removeEventListener("message", eventListener, false); + }; + }); + + // Called when the user makes an edit in the editor. + const onSave = (): void => { + console.info("[EmbeddedEditorPage] Detected a document change"); + const message: any = { + type: "apicurio_onSave", + correlationId: correlationId, + data: { + content: currentContent + } + }; + if (parent) { + parent.postMessage(message, "*"); + } else if (window.top) { + window.top.postMessage(message, "*"); + } + setContent(currentContent); + }; + + const onFormat = (): void => { + console.info("[EmbeddedEditorPage] Formatting content."); + const formattedContent: string = formatContent(currentContent, editorDesignContent.contentType); + console.info("[EditorPage] New content is: ", formattedContent); + setEditorDesignContent({ + id: "embedded", + contentType: editorDesignContent.contentType, + data: formattedContent + }); + setCurrentContent(formattedContent); + }; + + const textEditor: React.ReactElement = ( + + ); + + const protoEditor: React.ReactElement = ( + + ); + + const openapiEditor: React.ReactElement = ( + + ); + + const asyncapiEditor: React.ReactElement = ( + + ); + + const editor = (): React.ReactElement => { + if (type === ArtifactTypes.OPENAPI) { + return openapiEditor; + } else if (type === ArtifactTypes.ASYNCAPI) { + return asyncapiEditor; + } else if (type === ArtifactTypes.PROTOBUF) { + return protoEditor; + } + + // TODO create different text editors depending on the content type? Or assume + // that the text editor can configure itself appropriately? + return textEditor; + }; + + const onCompareContent = () => { + setCompareModalOpen(true); + }; + + const closeCompareEditor = () => { + setCompareModalOpen(false); + }; + + return ( + + + + + + + + + + + + + + + + + + +
+ + + + ); +}; diff --git a/ui/ui-app/src/app/pages/index.ts b/ui/ui-app/src/app/pages/index.ts index fad7e0b..e86c31d 100644 --- a/ui/ui-app/src/app/pages/index.ts +++ b/ui/ui-app/src/app/pages/index.ts @@ -1,4 +1,5 @@ export * from "./HomePage"; export * from "./EditorPage"; +export * from "./EmbeddedEditorPage"; export * from "./components"; export * from "./context";