Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
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
9 changes: 5 additions & 4 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,18 @@ Enhancements or new features can also be proposed by opening an issue. Please de
git checkout -b feature/your-feature-name
```
1. Make your changes.
1. Commit your changes, including a descriptive commit message:
1. Commit your changes, including a descriptive commit message following the [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/) style. Example:

```shell
git commit -m "Short description of the changes (Issue # if present)"
git commit -m "feat(ui): description of a new ui feature (Issue # if present)"
```

See the project's commit log for more examples.

1. Push to your fork and submit a pull request:

```shell
git push origin feature/your-feature-name
```

1. Ensure the pull request description clearly describes the problem and solution. Include the issue number if
applicable.
1. Ensure the pull request description clearly describes the problem and solution. Include the issue number if applicable.
7 changes: 6 additions & 1 deletion ui/src/components/canvas/FlowCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,12 @@ const ErrorHandler = ({ message, callback }: ErrorHandlerProps) => {
const acceptDroppedFile = (file: File, importFlow: (json: string) => void) => {
const reader = new FileReader()
reader.onload = (e) => {
e.target && importFlow(e.target.result as string)
try {
e.target && importFlow(e.target.result as string)
} catch (e) {
// TODO: Display an error pop-up on failed import
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this be covered under a different issue?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I created #7 to cover this task and added a link to the TODO comment.

console.error((e as Error).message)
}
}
reader.readAsText(file)
}
Expand Down
44 changes: 30 additions & 14 deletions ui/src/components/options-menu/OptionsMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,39 @@
import { OverflowMenu } from "@carbon/react"
import { OverflowMenu, OverflowMenuItem } from "@carbon/react"
import { Menu } from "@carbon/react/icons"
import { useState } from "react"
import ExportPng from "./ExportPng"
import SaveDiagram from "./SaveDiagram"
import { ImportFlowModal } from "./modals/ImportFlowModal"

const OptionsMenu = () => {
const [importFlowModalOpen, setImportFlowModalOpen] = useState(false)

return (
<OverflowMenu
className="options-menu"
aria-label="options-menu"
iconDescription="options"
align="bottom-end"
renderIcon={() => <Menu size={24} />}
size="lg"
flipped
>
{/* OverflowMenu does not play nice when OverflowMenuItems are not direct children. Custom components will need to use forwardRef to avoid errors. */}
<SaveDiagram />
<ExportPng />
</OverflowMenu>
<>
<OverflowMenu
className="options-menu"
aria-label="options-menu"
iconDescription="options"
align="bottom-end"
renderIcon={() => <Menu size={24} />}
size="lg"
flipped
>
{/* OverflowMenu does not play nice when OverflowMenuItems are not direct children. Custom components will need to use forwardRef to avoid errors. */}
<SaveDiagram />
<ExportPng />
<OverflowMenuItem
itemText="Import Flow JSON"
onClick={() => setImportFlowModalOpen(true)}
/>
</OverflowMenu>

{/* Modals */}
<ImportFlowModal
open={importFlowModalOpen}
setOpen={setImportFlowModalOpen}
/>
</>
)
}

Expand Down
91 changes: 91 additions & 0 deletions ui/src/components/options-menu/modals/ImportFlowModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Modal } from "@carbon/react"
import { InlineLoadingStatus } from "carbon-components-react"
import hljs from "highlight.js/lib/core"
import json from "highlight.js/lib/languages/json"
import { useState } from "react"
import { createPortal } from "react-dom"
import Editor from "react-simple-code-editor"
import { importFlowFromJson } from "../../../singletons/store/appActions"

hljs.registerLanguage("json", json)

interface ImportFlowModalProps {
open: boolean
setOpen: (open: boolean) => void
}

interface JsonEditorProps {
content: string
setContent: (content: string) => void
}

const getLoadingDescription = (status: InlineLoadingStatus) => {
switch (status) {
case "active":
return "importing JSON"
case "error":
return "Invalid Flow JSON"
case "inactive":
case "finished":
return ""
}
}

const FlowJsonEditor = ({ content, setContent }: JsonEditorProps) => {
return (
<div className="options-modal__editor">
<Editor
value={content}
onValueChange={(code) => setContent(code)}
highlight={(code) => hljs.highlight(code, { language: "json" }).value}
padding={16}
textareaClassName="options-modal__editor-textarea"
/>
</div>
)
}

export const ImportFlowModal = ({ open, setOpen }: ImportFlowModalProps) => {
const [loadingStatus, setLoadingStatus] =
useState<InlineLoadingStatus>("inactive")
const [content, setContent] = useState("")

const resetAndCloseModal = () => {
setOpen(false)
setLoadingStatus("inactive")
setContent("")
}

const doImport = () => {
setLoadingStatus("active")
try {
importFlowFromJson(content)
resetAndCloseModal()
} catch {
setLoadingStatus("error")
return
}
}

const updateContent = (content: string) => {
loadingStatus !== "inactive" && setLoadingStatus("inactive")
setContent(content)
}

return createPortal(
<Modal
open={open}
onRequestClose={resetAndCloseModal}
size="md"
modalHeading="Import a Flow JSON"
primaryButtonText="Import"
secondaryButtonText="Cancel"
loadingStatus={loadingStatus}
loadingDescription={getLoadingDescription(loadingStatus)}
onRequestSubmit={doImport}
>
<FlowJsonEditor content={content} setContent={updateContent} />
</Modal>,
document.body
)
}
3 changes: 1 addition & 2 deletions ui/src/singletons/store/appActions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -603,8 +603,7 @@ describe("import flow from an exported JSON file", () => {
const flow = JSON.parse(validExportedFlow) as Record<string, object>
delete flow[key]

act(() => importFlowFromJson(JSON.stringify(flow)))

expect(() => importFlowFromJson(JSON.stringify(flow))).toThrowError()
expect(getNodesView()).toEqual(initialNodes)
expect(getEdgesView()).toEqual(initialEdges)
}
Expand Down
4 changes: 1 addition & 3 deletions ui/src/singletons/store/appActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,10 @@ export const importFlowFromJson = (json: string) => {
importFlowFromObject(flow)
}

// TODO: Should a failed import throw an error on failure instead (for an error pop-up)?
export const importFlowFromObject = (flow: SerializedFlow) => {
useAppStore.setState(() => {
if (!isStoreType(flow)) {
console.error("Failed to import an EIP flow JSON. Malformed input")
return {}
throw new Error("Failed to import an EIP flow JSON. Malformed input")
}

// Maintain backwards compatibility with older exported formats
Expand Down
22 changes: 22 additions & 0 deletions ui/src/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,28 @@
}
}

$options-modal-editor-height: 30vh;

.options-modal__editor {
background-color: themes.$layer-02;
overflow-y: auto;
height: $options-modal-editor-height;

@include type.type-style("code-02");
}

.options-modal__editor > div {
min-height: $options-modal-editor-height;
}

.options-modal__editor:focus-within {
outline: 2px solid themes.$border-interactive;
}

.options-modal__editor-textarea:focus {
outline: none;
}

.canvas {
width: 100%;
height: 100%;
Expand Down