Skip to content

Commit

Permalink
Shuffle things around (#17)
Browse files Browse the repository at this point in the history
* Move todo to #16

* TODO has been done

* Move searchreplace to utils/

* Make selected step its own hook

* Move array manipulation to utils/

* Renamed step 2 node + Generalize movestep to moveitem

Where nodes from the catalog and workflow conflicted renamed to catalog.nodes

* Move workflow upload/download to their own components

* Make linter happy + fail ci when linter finds problems

* Make store dummer

* Move toast to component + use errorboundary when catalog fails to load

* Add ErrorBoundary component

* Use toast during archive creation

* todo is done

* TODOs moved to #20 and #21

* mimetype is already being added to data url

* Forget files that are not mentioned in parameters.

* Describe catalog format better

* Moved dropUnusedFiles()
  • Loading branch information
sverhoeven authored Feb 1, 2022
1 parent f1f2a1a commit f3069ac
Show file tree
Hide file tree
Showing 40 changed files with 633 additions and 412 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
with:
node-version: 16.x
cache: 'yarn'
- run: yarn
- run: yarn
- run: yarn test
- run: yarn lint || true # TODO dont ignore lint errors
- run: yarn lint
- run: yarn build
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,24 +76,27 @@ The configuration file contains paths to input files which are included in the z
The workflow configuration file consists out of 2 parts:

1. Global parameters, which are available to engine and each node.
2. Table with parameters for each node the workflow should run.
2. Tables with parameters for each node the workflow should run.

TOML does not allow for tables with same name. So any node that needs be run multiple times should have a index appened to the table name.
TOML does not allow for tables with same name. So any node that occurs multiple times should have a index appened to the table name.

### Catalog

The catalog is a YAML formatted file which tells the app what nodes are available. In has the following info:

1. Description of global parameters
1. global: Description of global parameters
* schema: What parameters are valid. Formatted as JSON schema draft 7.
* uiSchema: How the form for filling the parameters should be rendered.
2. Description of available nodes.
2. nodes: Description of available nodes.
* id: Identifier of node, for computers
* label: Label of node, for humans
* category: Category to which node belongs
* description: Text describing what node needs, does and produces.
* schema: What parameters are valid. Formatted as JSON schema draft 7.
* uiSchema: How the form for filling the parameters should be rendered.
3. Descriptions of node categories
4. Title and link to example workflows
5. Title of the catalog
3. catagories: Descriptions of node categories
* name: Name of category
* description: Description of category
4. examples: Title and link to example workflows
* map with title as key and link as value
5. title: Title of the catalog
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,10 @@
},
"engines": {
"node": "~16"
},
"ts-standard": {
"ignore": [
"vite.config.ts"
]
}
}
23 changes: 13 additions & 10 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,25 @@ import 'react-toastify/dist/ReactToastify.css'

import './App.css'
import { CatalogPanel } from './CatalogPanel'
import { StepPanel } from './StepPanel'
import { NodePanel } from './NodePanel'
import { WorkflowPanel } from './WorkflowPanel'
import { Header } from './Header'
import { ErrorBoundary } from './ErrorBoundary'

function App (): JSX.Element {
return (
<RecoilRoot>
<React.Suspense fallback={<div>Loading...</div>}>
<ToastContainer position='top-center' autoClose={1000} closeOnClick pauseOnFocusLoss />
<Header />
<div style={{ display: 'grid', gridTemplateColumns: '400px 0.6fr 1fr', gridAutoRows: '90vh', columnGap: '0.5em' }}>
<CatalogPanel />
<WorkflowPanel />
<StepPanel />
</div>
</React.Suspense>
<ErrorBoundary>
<React.Suspense fallback={<div>Loading...</div>}>
<ToastContainer position='top-center' autoClose={1000} closeOnClick pauseOnFocusLoss />
<Header />
<div style={{ display: 'grid', gridTemplateColumns: '400px 0.6fr 1fr', gridAutoRows: '90vh', columnGap: '0.5em' }}>
<CatalogPanel />
<WorkflowPanel />
<NodePanel />
</div>
</React.Suspense>
</ErrorBoundary>
</RecoilRoot>
)
}
Expand Down
2 changes: 1 addition & 1 deletion src/CatalogCategory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useCatalog } from './store'
import { ICategory } from './types'
import { CatalogNode } from './CatalogNode'

export const CatalogCategory = ({ name }: ICategory) => {
export const CatalogCategory = ({ name }: ICategory): JSX.Element => {
const catalog = useCatalog()
return (
<li>
Expand Down
4 changes: 2 additions & 2 deletions src/CatalogNode.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useWorkflow } from './store'
import { INode } from './types'
import { ICatalogNode } from './types'

export const CatalogNode = ({ id, label }: INode) => {
export const CatalogNode = ({ id, label }: ICatalogNode): JSX.Element => {
const { addNodeToWorkflow } = useWorkflow()
return (
<li>
Expand Down
9 changes: 5 additions & 4 deletions src/CatalogPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import React from 'react'

import { CatalogPicker } from './CatalogPicker'
import { CatalogCategory } from './CatalogCategory'
import { useCatalog, useWorkflow } from './store'
import { Example } from './Example'

export const CatalogPanel = () => {
export const CatalogPanel = (): JSX.Element => {
const catalog = useCatalog()
const { toggleGlobalEdit } = useWorkflow()
return (
<fieldset>
<legend>Step catalog</legend>
<legend>Catalog</legend>
<CatalogPicker />
<React.Suspense fallback={<span>Loading catalog...</span>}>
<button className='btn btn-light' onClick={toggleGlobalEdit}>Configure global parameters</button>
<h3>Nodes</h3>
<h4>Nodes</h4>
<ul>
{catalog?.categories.map((category) => <CatalogCategory key={category.name} {...category} />)}
</ul>
<h3>Examples</h3>
<h4>Examples</h4>
Workflow examples that can be loaded as a starting point.
<ul>
{Object.entries(catalog?.examples).map(([name, workflow]) => <Example key={name} name={name} workflow={workflow} />)}
Expand Down
4 changes: 2 additions & 2 deletions src/CatalogPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { useRecoilState } from 'recoil'
import { catalogURLchoices } from './constants'
import { catalogURLState } from './store'

export const CatalogPicker = () => {
export const CatalogPicker = (): JSX.Element => {
const [url, setURL] = useRecoilState(catalogURLState)
// TODO clear workflow when switching catalogs
// TODO clear? workflow when switching catalogs
return (
<select value={url} onChange={(e) => setURL(e.target.value)}>
{catalogURLchoices.map(([k, v]) => <option key={v} value={v}>{k}</option>)}
Expand Down
32 changes: 32 additions & 0 deletions src/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react'
import { ValidationError } from './validate'

interface State {
error: Error | null
}

export class ErrorBoundary extends React.Component<{}, State> {
static getDerivedStateFromError (error: Error): State {
return { error }
}

state: State = {
error: null
}

render (): React.ReactNode {
if (this.state.error !== null) {
if (this.state.error instanceof ValidationError) {
console.error(this.state.error.errors)
}
return (
<div>
<h1>Something went terribly wrong.</h1>
<span>{this.state.error.message}</span>
</div>
)
}

return this.props.children
}
}
32 changes: 30 additions & 2 deletions src/Example.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,39 @@
import { toast } from 'react-toastify'
import { useWorkflow } from './store'

interface IProps {
name: string
workflow: string
}

export const Example = ({ name, workflow }: IProps) => {
export const Example = ({ name, workflow }: IProps): JSX.Element => {
const { loadWorkflowArchive } = useWorkflow()
return <li><button className='btn btn-light' onClick={async () => await loadWorkflowArchive(workflow)} title={workflow}>{name}</button></li>

async function onClick (): Promise<void> {
await toast.promise(
loadWorkflowArchive(workflow),
{
pending: 'Loading workfow ...',
success: 'Workflow loaded',
error: {
render ({ data }) {
console.error(data)
return 'Workflow archive failed to load. See DevTools (F12) console for errors.'
}
}
}
)
}

return (
<li>
<button
className='btn btn-light'
onClick={onClick}
title={workflow}
>
{name}
</button>
</li>
)
}
6 changes: 3 additions & 3 deletions src/FilesList.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { saveAs } from 'file-saver'
import { useFiles } from './store'

export const FilesList = () => {
const { files } = useFiles()
export const FilesList = (): JSX.Element => {
const files = useFiles()

function downloadFile (filename: string) {
function downloadFile (filename: string): void {
saveAs(files[filename], filename)
}

Expand Down
5 changes: 3 additions & 2 deletions src/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { Theme } from '@rjsf/bootstrap-4'
const registry = utils.getDefaultRegistry()
const DefaultFileWidget = registry.widgets.FileWidget;
(Theme as any).widgets.FileWidget = (props: WidgetProps) => {
const label = props.schema.title ?? props.label
return (
<div>
<label className='form-label'>{props.schema.title || props.label}
{(props.label || props.schema.title) && props.required ? '*' : null}
<label className='form-label'>{label}
{props.required ? '*' : null}
</label>
<DefaultFileWidget {...props} />
</div>
Expand Down
10 changes: 5 additions & 5 deletions src/GlobalForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { useCatalog, useFiles, useWorkflow } from './store'
import { internalizeDataUrls } from './dataurls'
import { Form } from './Form'

export const GlobalForm = () => {
export const GlobalForm = (): JSX.Element => {
const { global: globalSchemas } = useCatalog()
const { setParameters, global: parameters } = useWorkflow()
const { files } = useFiles()
const parametersWithDataUrls = internalizeDataUrls(parameters, files)
const { setGlobalParameters, global: parameters } = useWorkflow()
const files = useFiles()
const parametersWithDataUrls = internalizeDataUrls(parameters, files) // TODO move to hook, each time component is re-rendered this method will be called
const uiSchema = (globalSchemas.uiSchema != null) ? globalSchemas.uiSchema : {}
return (
<Form schema={globalSchemas.schema} uiSchema={uiSchema} formData={parametersWithDataUrls} onSubmit={({ formData }) => setParameters(formData)} />
<Form schema={globalSchemas.schema} uiSchema={uiSchema} formData={parametersWithDataUrls} onSubmit={({ formData }) => setGlobalParameters(formData)} />
)
}
2 changes: 1 addition & 1 deletion src/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useCatalog } from './store'

export const Header = () => {
export const Header = (): JSX.Element => {
const { title } = useCatalog()
return (
<h1 style={{ height: '1em' }}>i-VRESSE workflow builder: {title}</h1>
Expand Down
30 changes: 30 additions & 0 deletions src/NodeForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useFiles, useSelectedCatalogNode, useSelectedNode, useWorkflow } from './store'
import { internalizeDataUrls } from './dataurls'
import { Form } from './Form'

export const NodeForm = (): JSX.Element => {
// TODO move setParameters to useSelectedNode
const { setNodeParameters } = useWorkflow()
const files = useFiles()
const node = useSelectedNode()
const catalogNode = useSelectedCatalogNode()

if (node === undefined) {
return <div>No node selected</div>
}
if (catalogNode === undefined) {
return <div>Unable to find schema belonging to node</div>
}
const parametersWithDataUrls = internalizeDataUrls(node.parameters, files)

const uiSchema = (catalogNode?.uiSchema != null) ? catalogNode.uiSchema : {}
return (
<>
<h4>{catalogNode.label} ({node.id})</h4>
<div>
{catalogNode.description}
</div>
<Form schema={catalogNode.schema} uiSchema={uiSchema} formData={parametersWithDataUrls} onSubmit={({ formData }) => setNodeParameters(formData)} />
</>
)
}
23 changes: 23 additions & 0 deletions src/NodePanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { GlobalForm } from './GlobalForm'
import { NodeForm } from './NodeForm'
import { useSelectNodeIndex, useWorkflow } from './store'

export const NodePanel = (): JSX.Element => {
const selectedNodeIndex = useSelectNodeIndex()
const { editingGlobal } = useWorkflow()
let form = <div>No node or global parameters selected for configuration.</div>
let legend = 'Node'
if (editingGlobal) {
form = <GlobalForm />
legend = 'Global parameters'
}
if (selectedNodeIndex !== -1) {
form = <NodeForm />
}
return (
<fieldset>
<legend>{legend}</legend>
{form}
</fieldset>
)
}
29 changes: 0 additions & 29 deletions src/StepForm.tsx

This file was deleted.

22 changes: 0 additions & 22 deletions src/StepPanel.tsx

This file was deleted.

2 changes: 0 additions & 2 deletions src/TextPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ export const TextPanel = (): JSX.Element => {
await navigator.clipboard.writeText(code)
}

// TODO would be nice if text was editable and showed parameter description on hover and inline validation errors.
// TODO would be nice to be able to click in text to select step to edit.
return (
<div>
<HighlightedCode code={code} />
Expand Down
Loading

0 comments on commit f3069ac

Please sign in to comment.