diff --git a/client/src/app/components/SimpleDocumentViewer.tsx b/client/src/app/components/SimpleDocumentViewer.tsx deleted file mode 100644 index 91dc6c4a32..0000000000 --- a/client/src/app/components/SimpleDocumentViewer.tsx +++ /dev/null @@ -1,262 +0,0 @@ -import * as React from "react"; -import { - CodeEditor, - CodeEditorControl, - Language, -} from "@patternfly/react-code-editor"; -import { - Button, - Checkbox, - EmptyState, - EmptyStateIcon, - EmptyStateVariant, - Modal, - ModalProps, - Spinner, - Title, - ToggleGroup, - ToggleGroupItem, -} from "@patternfly/react-core"; -import { css } from "@patternfly/react-styles"; -import editorStyles from "@patternfly/react-styles/css/components/CodeEditor/code-editor"; -import CodeIcon from "@patternfly/react-icons/dist/esm/icons/code-icon"; -import UndoIcon from "@patternfly/react-icons/dist/esm/icons/undo-icon"; - -import "./SimpleDocumentViewer.css"; -import { useFetchTaskByID } from "@app/queries/tasks"; - -export { Language } from "@patternfly/react-code-editor"; - -/** The subset of MonacoEditor component functions we want to use. */ -type ControlledEditor = { - focus: () => void; - setPosition: (position: object) => void; -}; - -export interface ISimpleDocumentViewerProps { - /** The id of the document to display, or `undefined` to display the empty state. */ - documentId: number | undefined; - - /** Filename, without extension, to use with the download file action. */ - downloadFilename?: string; - - /** - * Initial language of the document. Also used for the file extensions with - * the download file action. Defaults to `Language.yaml`. - */ - language?: Language.yaml | Language.json; - - /** - * Height of the document viewer, or `"full"` to take up all of the available - * vertical space. Defaults to "450px". - */ - height?: string | "full"; -} - -/** - * Fetch and then use the `@patternfly/react-code-editor` to display a document in - * read-only mode with language highlighting applied. - */ -export const SimpleDocumentViewer = ({ - documentId, - downloadFilename, - language = Language.yaml, - height = "450px", -}: ISimpleDocumentViewerProps) => { - const editorRef = React.useRef(); - const [currentLanguage, setCurrentLanguage] = React.useState(language); - const [code, setCode] = React.useState(); - const [merged, setMerged] = React.useState(false); - - const { task, isFetching, fetchError, refetch } = useFetchTaskByID( - documentId, - currentLanguage === Language.yaml ? "yaml" : "json", - merged - ); - - const onMergedChange = (checked: boolean) => { - setMerged(checked); - refetch(); - }; - - React.useEffect(() => { - if (task) { - const formattedCode = - currentLanguage === Language.yaml - ? task.toString() - : JSON.stringify(task, undefined, 2); - - setCode(formattedCode); - focusAndHomePosition(); - } - }, [task, currentLanguage]); - - const focusAndHomePosition = () => { - if (editorRef.current) { - editorRef.current.focus(); - editorRef.current.setPosition({ column: 0, lineNumber: 1 }); - } - }; - - const refreshControl = ( - } - aria-label="refresh-task" - tooltipProps={{ content: "Refresh" }} - onClick={() => { - refetch(); - }} - isVisible={code !== ""} - /> - ); - - return ( - { - editorRef.current = editor as ControlledEditor; - }} - showEditor={code !== undefined} - emptyState={ -
- - - - Loading {currentLanguage} - - -
- } - customControls={[ - refreshControl, - onMergedChange(checked)} - aria-label="Merged Checkbox" - />, -
- - - - - - JSON - - } - buttonId="code-language-select-json" - isSelected={currentLanguage === "json"} - isDisabled={!code && currentLanguage !== "json"} - onChange={() => setCurrentLanguage(Language.json)} - /> - - - - - YAML - - } - buttonId="code-language-select-yaml" - isSelected={currentLanguage === "yaml"} - isDisabled={!code && currentLanguage !== "yaml"} - onChange={() => setCurrentLanguage(Language.yaml)} - /> - -
, - ]} - /> - ); -}; - -export interface ISimpleDocumentViewerModalProps - extends ISimpleDocumentViewerProps { - /** Simple text content of the modal header. */ - title?: string; - - /** A callback for when the close button is clicked. */ - onClose?: ModalProps["onClose"]; - - /** - * Position of the modal, `"top"` aligned or `"normal"`/centered on the view. - * Defaults to `top`. - */ - position?: "top" | "normal"; - - /** - * Flag indicating if the modal should be displayed as tall as possible. - * Defaults to `true`. - */ - isFullHeight?: boolean; -} - -/** - * Inside of a Modal window, fetch and then use the `SimpleDocumentViewer` to display - * a document in read-only mode with language highlighting applied. The modal will be - * displayed if the `documentId` is set. If `documentId` is `undefined`, the modal is - * closed. - */ -export const SimpleDocumentViewerModal = ({ - title, - documentId, - onClose, - position = "top", - isFullHeight = true, - ...rest -}: ISimpleDocumentViewerModalProps) => { - const isOpen = documentId !== undefined; - - return ( - - Close - , - ]} - > - - - ); -}; diff --git a/client/src/app/components/simple-document-viewer/LanguageToggle.tsx b/client/src/app/components/simple-document-viewer/LanguageToggle.tsx new file mode 100644 index 0000000000..f558149c97 --- /dev/null +++ b/client/src/app/components/simple-document-viewer/LanguageToggle.tsx @@ -0,0 +1,55 @@ +import * as React from "react"; +import { Language } from "@patternfly/react-code-editor"; +import { ToggleGroup, ToggleGroupItem } from "@patternfly/react-core"; +import { css } from "@patternfly/react-styles"; +import editorStyles from "@patternfly/react-styles/css/components/CodeEditor/code-editor"; +import CodeIcon from "@patternfly/react-icons/dist/esm/icons/code-icon"; +import "./SimpleDocumentViewer.css"; + +export const LanguageToggle: React.FC<{ + currentLanguage: Language.yaml | Language.json; + code?: string; + setCurrentLanguage: (lang: Language.yaml | Language.json) => void; +}> = ({ currentLanguage, code, setCurrentLanguage }) => ( +
+ + + + + + JSON + + } + buttonId="code-language-select-json" + isSelected={currentLanguage === "json"} + isDisabled={!code && currentLanguage !== "json"} + onChange={() => setCurrentLanguage(Language.json)} + /> + + + + + YAML + + } + buttonId="code-language-select-yaml" + isSelected={currentLanguage === "yaml"} + isDisabled={!code && currentLanguage !== "yaml"} + onChange={() => setCurrentLanguage(Language.yaml)} + /> + +
+); diff --git a/client/src/app/components/simple-document-viewer/RefreshControl.tsx b/client/src/app/components/simple-document-viewer/RefreshControl.tsx new file mode 100644 index 0000000000..913b51ac05 --- /dev/null +++ b/client/src/app/components/simple-document-viewer/RefreshControl.tsx @@ -0,0 +1,17 @@ +import * as React from "react"; +import { CodeEditorControl } from "@patternfly/react-code-editor"; +import UndoIcon from "@patternfly/react-icons/dist/esm/icons/undo-icon"; +import "./SimpleDocumentViewer.css"; + +export const RefreshControl: React.FC<{ + refetch: () => void; + isVisible: boolean; +}> = ({ refetch, isVisible }) => ( + } + aria-label="refresh-task" + tooltipProps={{ content: "Refresh" }} + onClick={refetch} + isVisible={isVisible} + /> +); diff --git a/client/src/app/components/SimpleDocumentViewer.css b/client/src/app/components/simple-document-viewer/SimpleDocumentViewer.css similarity index 100% rename from client/src/app/components/SimpleDocumentViewer.css rename to client/src/app/components/simple-document-viewer/SimpleDocumentViewer.css diff --git a/client/src/app/components/simple-document-viewer/SimpleDocumentViewer.tsx b/client/src/app/components/simple-document-viewer/SimpleDocumentViewer.tsx new file mode 100644 index 0000000000..8307a28a7b --- /dev/null +++ b/client/src/app/components/simple-document-viewer/SimpleDocumentViewer.tsx @@ -0,0 +1,144 @@ +import * as React from "react"; +import { CodeEditor, Language } from "@patternfly/react-code-editor"; +import { + Checkbox, + EmptyState, + EmptyStateIcon, + EmptyStateVariant, + Spinner, + Title, +} from "@patternfly/react-core"; + +import "./SimpleDocumentViewer.css"; +import { useFetchTaskByID } from "@app/queries/tasks"; +import { RefreshControl } from "./RefreshControl"; +import { LanguageToggle } from "./LanguageToggle"; + +export { Language } from "@patternfly/react-code-editor"; + +/** The subset of MonacoEditor component functions we want to use. */ +type ControlledEditor = { + focus: () => void; + setPosition: (position: object) => void; +}; + +export interface ISimpleDocumentViewerProps { + /** The id of the document to display, or `undefined` to display the empty state. */ + documentId: number | undefined; + + /** Filename, without extension, to use with the download file action. */ + downloadFilename?: string; + + /** + * Initial language of the document. Also used for the file extensions with + * the download file action. Defaults to `Language.yaml`. + */ + language?: Language.yaml | Language.json; + + /** + * Height of the document viewer, or `"full"` to take up all of the available + * vertical space. Defaults to "450px". + */ + height?: string | "full"; +} + +/** + * Fetch and then use the `@patternfly/react-code-editor` to display a document in + * read-only mode with language highlighting applied. + */ +export const SimpleDocumentViewer = ({ + documentId, + downloadFilename, + language = Language.yaml, + height = "450px", +}: ISimpleDocumentViewerProps) => { + const editorRef = React.useRef(); + const [currentLanguage, setCurrentLanguage] = React.useState(language); + const [code, setCode] = React.useState(); + const [merged, setMerged] = React.useState(false); + + const { task, isFetching, fetchError, refetch } = useFetchTaskByID( + documentId, + currentLanguage === Language.yaml ? "yaml" : "json", + merged + ); + + const onMergedChange = (checked: boolean) => { + setMerged(checked); + refetch(); + }; + + React.useEffect(() => { + if (task) { + const formattedCode = + currentLanguage === Language.yaml + ? task.toString() + : JSON.stringify(task, undefined, 2); + + setCode(formattedCode); + focusAndHomePosition(); + } + }, [task, currentLanguage]); + + const focusAndHomePosition = () => { + if (editorRef.current) { + editorRef.current.focus(); + editorRef.current.setPosition({ column: 0, lineNumber: 1 }); + } + }; + + return ( + { + editorRef.current = editor as ControlledEditor; + }} + showEditor={code !== undefined} + emptyState={ +
+ + + + Loading {currentLanguage} + + +
+ } + customControls={[ + , + onMergedChange(checked)} + aria-label="Merged Checkbox" + />, + , + ]} + /> + ); +}; diff --git a/client/src/app/components/simple-document-viewer/SimpleDocumentViewerModal.tsx b/client/src/app/components/simple-document-viewer/SimpleDocumentViewerModal.tsx new file mode 100644 index 0000000000..4a179ce6e6 --- /dev/null +++ b/client/src/app/components/simple-document-viewer/SimpleDocumentViewerModal.tsx @@ -0,0 +1,73 @@ +import * as React from "react"; +import { Button, Modal, ModalProps } from "@patternfly/react-core"; +import { css } from "@patternfly/react-styles"; +import { + ISimpleDocumentViewerProps, + SimpleDocumentViewer, +} from "./SimpleDocumentViewer"; +import "./SimpleDocumentViewer.css"; + +export interface ISimpleDocumentViewerModalProps + extends ISimpleDocumentViewerProps { + /** Simple text content of the modal header. */ + title?: string; + + /** A callback for when the close button is clicked. */ + onClose?: ModalProps["onClose"]; + + /** + * Position of the modal, `"top"` aligned or `"normal"`/centered on the view. + * Defaults to `top`. + */ + position?: "top" | "normal"; + + /** + * Flag indicating if the modal should be displayed as tall as possible. + * Defaults to `true`. + */ + isFullHeight?: boolean; +} + +/** + * Inside of a Modal window, fetch and then use the `SimpleDocumentViewer` to display + * a document in read-only mode with language highlighting applied. The modal will be + * displayed if the `documentId` is set. If `documentId` is `undefined`, the modal is + * closed. + */ + +export const SimpleDocumentViewerModal = ({ + title, + documentId, + onClose, + position = "top", + isFullHeight = true, + ...rest +}: ISimpleDocumentViewerModalProps) => { + const isOpen = documentId !== undefined; + + return ( + + Close + , + ]} + > + + + ); +}; diff --git a/client/src/app/components/simple-document-viewer/index.ts b/client/src/app/components/simple-document-viewer/index.ts new file mode 100644 index 0000000000..11ebc6f77b --- /dev/null +++ b/client/src/app/components/simple-document-viewer/index.ts @@ -0,0 +1,2 @@ +export * from "./SimpleDocumentViewer"; +export * from "./SimpleDocumentViewerModal"; diff --git a/client/src/app/pages/applications/analysis-details/AnalysisDetails.tsx b/client/src/app/pages/applications/analysis-details/AnalysisDetails.tsx index 1c2ebf8982..82e8c7ebca 100644 --- a/client/src/app/pages/applications/analysis-details/AnalysisDetails.tsx +++ b/client/src/app/pages/applications/analysis-details/AnalysisDetails.tsx @@ -7,7 +7,7 @@ import { PageSection } from "@patternfly/react-core"; import { AnalysisDetailsRoute, Paths } from "@app/Paths"; import { PageHeader } from "@app/components/PageHeader"; import { formatPath } from "@app/utils/utils"; -import { SimpleDocumentViewer } from "@app/components/SimpleDocumentViewer"; +import { SimpleDocumentViewer } from "@app/components/simple-document-viewer"; import { useFetchApplicationById } from "@app/queries/applications"; import { useFetchTaskByID } from "@app/queries/tasks";