Skip to content
This repository was archived by the owner on Jun 13, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all 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
55 changes: 55 additions & 0 deletions src/components/containers/DropdownContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { ReactChild, useEffect, useRef, useState } from 'react';

const DropdownContainer = (props: {
className?: string;
children: ReactChild[] | ReactChild;
}) => {
const [isExtended, setExtended] = useState(false);
const [pos, setPos] = useState({ left: 0, top: 0 });
const buttonRef = useRef<HTMLButtonElement>(null);

const updatePos = () => {
if (!buttonRef.current) {
return;
}

const rect = buttonRef.current.getBoundingClientRect();

setPos({ left: rect.x, top: rect.y + rect.height });
};

useEffect(() => {
updatePos();

// hide the box when a click event fires
window.addEventListener(
'click',
() => {
setExtended(false);
},
);
}, [isExtended]);

return (
<>
<button
className={props.className}
ref={buttonRef}
onClick={(event) => {
setExtended(!isExtended);
// prevent the click event from bubbling up to event that will close the drawer
event.stopPropagation();
}}
>
•••
</button>
{isExtended && (
<div className="fixed w-max h-max z-20 flex flex-col" style={pos}>
{React.Children.toArray(props.children)}
</div>
)}
</>
);
};

export default DropdownContainer;
69 changes: 51 additions & 18 deletions src/components/panes/EditorPane.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import Editor, { DiffEditor } from '@monaco-editor/react';
import { useStoreState } from '../../state/Hooks';
import { useRef } from 'react';
import { useStoreActions, useStoreState } from '../../state/Hooks';
import DropdownContainer from '../containers/DropdownContainer';

const EditorPane = () => {
const config = useStoreState((state) => state.config);
const editingConfig = useStoreState((state) => state.editingConfig);
const loadConfig = useStoreActions((actions) => actions.loadConfig);
const inputFile = useRef<HTMLInputElement>(null);

const configYAML = (yml: string) => {
const matchSDKComment = yml?.match('# SDK Version: .*\n');
Expand All @@ -24,28 +28,57 @@ const EditorPane = () => {

return (
<div className="bg-circle-gray-900 h-2/5 w-full flex flex-col">
<div className="border-b text-xl border-circle-gray-800 font-bold">
<div className="border-b text-xl border-circle-gray-800 font-bold flex flex-row">
<div className="ml-4 border-b-4 px-3 py-3 w-max text-sm tracking-wide font-bold text-white border-white">
CONFIG
</div>
<div className="p-2 ml-auto">
<input
type="file"
accept=".yml,.yaml"
ref={inputFile}
className="hidden"
onChange={(e) => {
if (!e.target.files) {
return;
}

e.target.files[0].text().then((yml) => {
loadConfig(yml);
});
}}
/>
<DropdownContainer className="rounded-md bg-circle-blue text-white px-2">
<div className="bg-white flex-col flex rounded shadow text-base">
<button
className="border-b border-circle-gray-300 px-8"
onClick={(e) => {
inputFile.current?.click();
e.stopPropagation();
}}
>
Open
</button>
<button>Save</button>
</div>
</DropdownContainer>
</div>
</div>
<div className="flex-1 overflow-hidden">
{
editingConfig ?
(<DiffEditor
theme="vs-dark"
language="yaml"
original={config && configYAML(config)}
modified={editingConfig && configYAML(editingConfig)}
/>)
:
(<Editor
theme="vs-dark"
language="yaml"
value={config && configYAML(config)}
/>
)
}
{editingConfig ? (
<DiffEditor
theme="vs-dark"
language="yaml"
original={config && configYAML(config)}
modified={editingConfig && configYAML(editingConfig)}
/>
) : (
<Editor
theme="vs-dark"
language="yaml"
value={config && configYAML(config)}
/>
)}
</div>
</div>
);
Expand Down
1 change: 1 addition & 0 deletions src/components/panes/WorkflowsPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const WorkflowsPane = () => {
bgClassName="bg-circle-gray-200"
className="border border-r-0 h-full border-b-0 border-circle-gray-300"
/>

</div>
);
};
Expand Down
19 changes: 17 additions & 2 deletions src/state/Store.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import {
Config,
Job,
parameters,
parseConfig,
reusable,
Workflow,
Workflow
} from '@circleci/circleci-config-sdk';
import { CustomCommand } from '@circleci/circleci-config-sdk/dist/src/lib/Components/Commands/exports/Reusable';
import { CustomParameter } from '@circleci/circleci-config-sdk/dist/src/lib/Components/Parameters';
Expand All @@ -16,7 +17,7 @@ import {
isNode,
Node,
SetConnectionId,
XYPosition,
XYPosition
} from 'react-flow-renderer';
import { v4 } from 'uuid';
import DefinitionsMenu from '../components/menus/definitions/DefinitionsMenu';
Expand Down Expand Up @@ -165,6 +166,7 @@ export interface StoreActions {
CustomParameter<PipelineParameterLiteral>
>;

loadConfig: Action<StoreModel, string>;
generateConfig: Action<StoreModel, void | Partial<DefinitionModel>>;
error: Action<StoreModel, any>;
}
Expand Down Expand Up @@ -356,6 +358,19 @@ const Actions: StoreActions = {
console.error('An action was not found! ', payload);
}),

loadConfig: action((state, payload) => {
const config = parseConfig(payload);

state.definitions = {
workflows: config.workflows,
jobs: config.jobs,
executors: config.executors || [],
parameters: config.parameters?.parameters || [],
commands: config.commands || [],
};

state.config = config.stringify();
}),
generateConfig: action((state, payload) => {
const workflows = state.workflows.map((flow) => {
const jobs = flow.elements
Expand Down