diff --git a/package.json b/package.json index 5568442..8a9eb07 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "homepage": "https://circleci-public.github.io/visual-config-editor/", "dependencies": { - "@circleci/circleci-config-sdk": "0.5.0-alpha.10", + "@circleci/circleci-config-sdk": "0.5.0-alpha.11", "@craco/craco": "^6.3.0", "@monaco-editor/react": "^4.3.1", "@testing-library/jest-dom": "^5.11.4", @@ -33,6 +33,9 @@ "uuid": "^8.3.2", "web-vitals": "^2.1.2" }, + "resolutions": { + "@types/react": "^17.0.38" + }, "scripts": { "start": "craco start", "build": "craco build", diff --git a/src/App.tsx b/src/App.tsx index b441ec3..097017c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,8 @@ import NavigationPane from './components/panes/NavigationPane'; import WorkflowsPane from './components/panes/WorkflowsPane'; import Store from './state/Store'; +// Workaround for https://github.com/ctrlplusb/easy-peasy/issues/741 +// const StoreProviderOverride = StoreProvider as any; const App = () => { return ( diff --git a/src/components/atoms/Card.tsx b/src/components/atoms/Card.tsx new file mode 100644 index 0000000..ad13a25 --- /dev/null +++ b/src/components/atoms/Card.tsx @@ -0,0 +1,26 @@ +export interface CardProps { + description?: string; + title: string; + pinned?: React.ReactElement; + onClick: React.MouseEventHandler; +} + +const Card = (props: CardProps) => { + return ( + + ); +}; + +export default Card; diff --git a/src/components/atoms/ComponentInfo.tsx b/src/components/atoms/ComponentInfo.tsx index 9848aee..e27afb7 100644 --- a/src/components/atoms/ComponentInfo.tsx +++ b/src/components/atoms/ComponentInfo.tsx @@ -2,10 +2,18 @@ import ComponentMapping from '../../mappings/ComponentMapping'; const ComponentInfo = (props: { type: ComponentMapping }) => { return ( - <> -

{props.type.docsInfo.description}

- Learn More

- +
+

+ {props.type.docsInfo.description} +

+ + Learn More + +
); }; diff --git a/src/components/atoms/Definition.tsx b/src/components/atoms/Definition.tsx index 2ff1420..3bd5770 100644 --- a/src/components/atoms/Definition.tsx +++ b/src/components/atoms/Definition.tsx @@ -1,6 +1,6 @@ import ComponentMapping from '../../mappings/ComponentMapping'; import { useStoreActions } from '../../state/Hooks'; -import EditDefinitionMenu from '../menus/definitions/EditDefinitionMenu'; +import { InspectorDefinitionMenuNav } from '../menus/definitions/InspectorDefinitionMenu'; const Definition = (props: { data: any; type: ComponentMapping }) => { const Summary = props.type.components.summary; @@ -21,8 +21,8 @@ const Definition = (props: { data: any; type: ComponentMapping }) => { }} onClick={(e) => { navigateTo({ - component: EditDefinitionMenu, - props: { data: props.data, dataType: props.type }, + component: InspectorDefinitionMenuNav, + props: { editing: true, values: props.data, dataType: props.type }, }); }} > diff --git a/src/components/atoms/Tooltip.tsx b/src/components/atoms/Tooltip.tsx new file mode 100644 index 0000000..26e1494 --- /dev/null +++ b/src/components/atoms/Tooltip.tsx @@ -0,0 +1,40 @@ +import { MutableRefObject, ReactElement, useEffect, useState } from 'react'; +import ToolTipPointerIcon from '../../icons/ui/ToolTipPointerIcon'; + +export interface ToolTipProps { + target: MutableRefObject; + pointerColor?: string; + pointerClass?: string; + offsetX?: number; + offsetY?: number; + centered?: boolean; + facing?: 'top' | 'bottom' | 'left' | 'right'; + children: ReactElement; +} + +const ToolTip = (props: ToolTipProps) => { + const [pos, setPos] = useState({ left: 0, top: 0 }); + + const updatePos = () => { + const rect = props.target.current.getBoundingClientRect(); + + setPos({ left: rect.x - rect.width - 32, top: rect.y + 6 }); + }; + + useEffect(() => { + updatePos(); + }); + + return ( +
+ {props.children} + +
+ ); +}; + +export default ToolTip; diff --git a/src/components/atoms/nodes/JobNode.tsx b/src/components/atoms/nodes/JobNode.tsx index d0c5761..a135cc3 100644 --- a/src/components/atoms/nodes/JobNode.tsx +++ b/src/components/atoms/nodes/JobNode.tsx @@ -20,6 +20,8 @@ const JobNode: React.FunctionComponent = ( // ); const updateJob = useStoreActions((actions) => actions.updateJob); const setConnecting = useStoreActions((actions) => actions.setConnecting); + const removeWorkflowElement = useStoreActions((actions) => actions.removeWorkflowElement); + const connecting = useStoreState((state) => state.connecting); const updateConnecting = useStoreActions( (actions) => actions.updateConnecting, @@ -72,27 +74,6 @@ const JobNode: React.FunctionComponent = ( const nodeRef = useRef(null); - // const onConnect = (params: Connection) => { - // const targetJob = elements.find( - // (element) => element.id === params.target, - // )?.data; - - // setWorkflowElements( - // addEdge( - // { - // ...params, - // animated: false, - // style: { stroke: '#A3A3A3', strokeWidth: '2px' }, - // }, - // updateWorkflowJob(targetJob, { - // parameters: { - // requires: [props.data.job.name], - // }, - // }), - // ), - // ); - // }; - return (
= ( {props.data.parameters?.name || props.data.job.name}
-
{ + removeWorkflowElement(props.id); + }} > -
+ + + + + + + ); +}; + +export default GuideContainer; diff --git a/src/components/containers/ParametersContainer.tsx b/src/components/containers/ParametersContainer.tsx index 8027c8b..3d39481 100644 --- a/src/components/containers/ParametersContainer.tsx +++ b/src/components/containers/ParametersContainer.tsx @@ -2,7 +2,7 @@ import { FieldArray, useField } from 'formik'; import ComponentMapping from '../../mappings/ComponentMapping'; import ParameterMapping from '../../mappings/ParameterMapping'; import { useStoreActions } from '../../state/Hooks'; -import { CreateDefinitionMenu } from '../menus/definitions/CreateDefinitionMenu'; +import { InspectorDefinitionMenu } from '../menus/definitions/InspectorDefinitionMenu'; import SubTypeMenuNav from '../menus/SubTypeMenu'; const ParameterContainer = (props: { @@ -26,7 +26,7 @@ const ParameterContainer = (props: { props: { typePage: ParameterMapping.subtypes?.component, typeProps: { component: props.dataMapping }, - menuPage: CreateDefinitionMenu, + menuPage: InspectorDefinitionMenu, menuProps: { dataType: ParameterMapping, passBackKey: 'parameters', diff --git a/src/components/containers/WorkflowContainer.tsx b/src/components/containers/WorkflowContainer.tsx index a3bd381..58ebead 100644 --- a/src/components/containers/WorkflowContainer.tsx +++ b/src/components/containers/WorkflowContainer.tsx @@ -63,10 +63,6 @@ const WorkflowPane = (props: ElementProps) => { applyToData: (parameters: WorkflowJobParameters) => WorkflowJobParameters, ) => elements.map((element) => { - console.log( - (element.data.parameters?.name || element.data.job.name) === targetJob, - ); - return isNode(element) && JobMapping.node?.transform && (element.data.parameters?.name || element.data.job.name) === targetJob diff --git a/src/components/containers/inspector/subtypes/ExecutorSubtypes.tsx b/src/components/containers/inspector/subtypes/ExecutorSubtypes.tsx index f2fad1d..c7b201e 100644 --- a/src/components/containers/inspector/subtypes/ExecutorSubtypes.tsx +++ b/src/components/containers/inspector/subtypes/ExecutorSubtypes.tsx @@ -1,3 +1,5 @@ +import { executor } from '@circleci/circleci-config-sdk'; +import { DockerExecutor, MachineExecutor, MacOSExecutor, WindowsExecutor } from '@circleci/circleci-config-sdk/dist/src/lib/Components/Executor'; import { SubTypeMapping } from '../../../../mappings/ComponentMapping'; import InspectorProperty from '../../../atoms/form/InspectorProperty'; @@ -10,6 +12,7 @@ export interface ExecutorSubTypes { const executorSubtypes: ExecutorSubTypes = { docker: { text: 'Docker', + component: executor.DockerExecutor, resourceClasses: [ 'small', 'medium', @@ -27,6 +30,7 @@ const executorSubtypes: ExecutorSubTypes = { }, machine: { text: 'Machine', + component: executor.MachineExecutor, resourceClasses: ['medium', 'large', 'xlarge', '2xlarge'], fields: , docsLink: 'https://circleci.com/docs/2.0/executor-types/#using-machine', @@ -34,6 +38,7 @@ const executorSubtypes: ExecutorSubTypes = { }, macos: { text: 'MacOS', + component: executor.MacOSExecutor, resourceClasses: ['medium', 'large'], fields: , docsLink: 'https://circleci.com/docs/2.0/executor-types/#using-macos', @@ -42,6 +47,7 @@ const executorSubtypes: ExecutorSubTypes = { }, windows: { text: 'Windows', + component: executor.WindowsExecutor, resourceClasses: ['medium', 'large', 'xlarge', '2xlarge'], fields: , docsLink: 'https://circleci.com/docs/2.0/executor-types/#using-the-windows-executor', diff --git a/src/components/menus/definitions/EditDefinitionMenu.tsx b/src/components/menus/definitions/EditDefinitionMenu.tsx deleted file mode 100644 index ffe59c9..0000000 --- a/src/components/menus/definitions/EditDefinitionMenu.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { Form, Formik } from 'formik'; -import BreadCrumbArrowIcon from '../../../icons/ui/BreadCrumbArrowIcon'; -import { useStoreActions, useStoreState } from '../../../state/Hooks'; -import { DataModel, NavigationComponent } from '../../../state/Store'; -import TabbedMenu from '../TabbedMenu'; - -export type EditDefinitionProps = DataModel & { values: any }; - -const EditDefinitionMenu = (props: EditDefinitionProps) => { - const dataMapping = props.dataType; - const update = useStoreActions( - (actions) => dataMapping?.store.update(actions) || actions.error, - ); - const navigateBack = useStoreActions((actions) => actions.navigateBack); - const definitions = useStoreState((state) => state.definitions); - - const getIcon = (className: string) => { - let iconComponent = dataMapping?.components.icon; - - if (iconComponent) { - let Icon = iconComponent; - - return ; - } - }; - - return ( -
-
-
- - - {getIcon('w-6 h-8 py-2')} -

- Edit {dataMapping?.name.singular} -

-
-
- {getIcon('w-8 h-8 p-1 pl-0 mr-1')} -

{props.data.name}

-
-
- {dataMapping && ( - { - update({ - old: props.data, - new: dataMapping.transform(values, definitions), - }); - navigateBack(); - }} - > - {(formikProps) => ( -
- -
- {dataMapping.components.inspector({ - ...formikProps, - definitions, - })} -
-
parameters will go here!
-
- - - - - )} -
- )} -
- ); -}; - -const EditDefinitionMenuNav: NavigationComponent = { - Component: EditDefinitionMenu, - Label: (props: EditDefinitionProps) => ( -

Edit {props.dataType?.name.singular}

- ), -}; - -export default EditDefinitionMenuNav; diff --git a/src/components/menus/definitions/CreateDefinitionMenu.tsx b/src/components/menus/definitions/InspectorDefinitionMenu.tsx similarity index 57% rename from src/components/menus/definitions/CreateDefinitionMenu.tsx rename to src/components/menus/definitions/InspectorDefinitionMenu.tsx index 631dad7..30e3c43 100644 --- a/src/components/menus/definitions/CreateDefinitionMenu.tsx +++ b/src/components/menus/definitions/InspectorDefinitionMenu.tsx @@ -6,18 +6,24 @@ import ParameterContainer from '../../containers/ParametersContainer'; import { SubTypeMenuPageProps } from '../SubTypeMenu'; import TabbedMenu from '../TabbedMenu'; -type CreateDefinitionProps = DataModel & { +type InspectorDefinitionProps = DataModel & { values: any; + editing?: boolean; passBackKey?: string; } & SubTypeMenuPageProps; -const CreateDefinitionMenu = (props: CreateDefinitionProps) => { +const InspectorDefinitionMenu = (props: InspectorDefinitionProps) => { const definitions = useStoreState((state) => state.definitions); const generateConfig = useStoreActions((actions) => actions.generateConfig); const navigateBack = useStoreActions((actions) => actions.navigateBack); + const setGuideStep = useStoreActions((actions) => actions.setGuideStep); + const guideStep = useStoreState((state) => state.guideStep); const dataMapping = props.dataType; - const add = useStoreActions( - (actions) => dataMapping?.store.add(actions) || actions.error, + const submitToStore = useStoreActions( + (actions) => + (props.editing + ? dataMapping?.store.update(actions) + : dataMapping?.store.add(actions)) || actions.error, ); const getIcon = (className: string) => { let iconComponent = dataMapping?.components.icon; @@ -30,6 +36,8 @@ const CreateDefinitionMenu = (props: CreateDefinitionProps) => { }; const tabs = ['PROPERTIES']; + const subtype = + props.subtype || dataMapping?.subtypes?.getSubtype(props.values); if (dataMapping?.parameters) { tabs.push('PARAMETERS'); @@ -42,7 +50,7 @@ const CreateDefinitionMenu = (props: CreateDefinitionProps) => {
{getIcon('w-8 h-8 p-1 pl-0 mr-1')}

- New {dataMapping?.name.singular} + {props.editing ? 'Edit' : 'New'} {dataMapping?.name.singular}

@@ -68,8 +76,17 @@ const CreateDefinitionMenu = (props: CreateDefinitionProps) => { const newDefinition = dataMapping.transform(values, definitions); if (!props.passBackKey) { - add(newDefinition); + submitToStore(newDefinition); } + + if ( + !props.editing && + guideStep && + dataMapping.guide?.step === guideStep + ) { + setGuideStep(guideStep + 1); + } + navigateBack({ distance: 1, apply: (values) => { @@ -89,25 +106,38 @@ const CreateDefinitionMenu = (props: CreateDefinitionProps) => {
- {dataMapping.subtypes && ( - - )} + {dataMapping.subtypes && + (props.editing ? ( +
+

+ {dataMapping.subtypes.definitions[subtype]?.text} +

+

+ { + dataMapping.subtypes.definitions[subtype] + ?.description + } +

+
+ ) : ( + + ))} {dataMapping.components.inspector({ ...formikProps, definitions, @@ -127,7 +157,7 @@ const CreateDefinitionMenu = (props: CreateDefinitionProps) => { type="submit" className="text-white text-sm font-medium p-2 m-6 bg-circle-blue duration:50 transition-all rounded-md2" > - Save {dataMapping?.name.singular} + {props.editing ? 'Save' : 'Create'} {dataMapping?.name.singular} )} @@ -137,12 +167,16 @@ const CreateDefinitionMenu = (props: CreateDefinitionProps) => { ); }; -const CreateDefinitionMenuNav: NavigationComponent = { - Component: CreateDefinitionMenu, - Label: (props: CreateDefinitionProps) => { - return

New {props.dataType?.name.singular}

; +const InspectorDefinitionMenuNav: NavigationComponent = { + Component: InspectorDefinitionMenu, + Label: (props: InspectorDefinitionProps) => { + return ( +

+ {props.editing ? 'Edit' : 'New'} {props.dataType?.name.singular} +

+ ); }, - Icon: (props: CreateDefinitionProps) => { + Icon: (props: InspectorDefinitionProps) => { let iconComponent = props.dataType?.components.icon; if (!iconComponent) { @@ -155,4 +189,4 @@ const CreateDefinitionMenuNav: NavigationComponent = { }, }; -export { CreateDefinitionMenuNav, CreateDefinitionMenu }; +export { InspectorDefinitionMenuNav, InspectorDefinitionMenu }; diff --git a/src/components/menus/definitions/subtypes/ExecutorTypePage.tsx b/src/components/menus/definitions/subtypes/ExecutorTypePage.tsx index 8150d2a..def2084 100644 --- a/src/components/menus/definitions/subtypes/ExecutorTypePage.tsx +++ b/src/components/menus/definitions/subtypes/ExecutorTypePage.tsx @@ -3,12 +3,12 @@ import { NavigationComponent } from '../../../../state/Store'; import BreadCrumbs from '../../../containers/BreadCrumbs'; import { executorSubtypes } from '../../../containers/inspector/subtypes/ExecutorSubtypes'; import { SubTypeSelectPageProps } from '../../SubTypeMenu'; +import Card from '../../../atoms/Card'; const ExecutorTypePage = (props: SubTypeSelectPageProps) => { return (
- {/* */}
@@ -24,32 +24,30 @@ const ExecutorTypePage = (props: SubTypeSelectPageProps) => {
{Object.keys(executorSubtypes).map((subtype) => ( - - // + pinned={ +
+ {executorSubtypes[subtype].docsLink && ( + { + e.stopPropagation(); + }} + > + Learn More + + )} +
+ } + /> ))}
diff --git a/src/components/menus/definitions/subtypes/ParameterTypePage.tsx b/src/components/menus/definitions/subtypes/ParameterTypePage.tsx index ab51782..da216c8 100644 --- a/src/components/menus/definitions/subtypes/ParameterTypePage.tsx +++ b/src/components/menus/definitions/subtypes/ParameterTypePage.tsx @@ -33,7 +33,7 @@ const ParameterTypePage = (
{parameters?.types && - parameters.types.map((subtype) => ( + parameters.types.map((subtype: any) => (