feat(pipelines): 编辑和删除功能。

This commit is contained in:
Ivan Li 2021-03-07 17:44:51 +08:00
parent 282366cd72
commit 611341a8ce
7 changed files with 203 additions and 64 deletions

View File

@ -5,6 +5,8 @@ export const LIST_PIPELINES = gql`
pipelines: listPipelines(projectId: $projectId) {
id
name
branch
projectId
}
}
`;

View File

@ -1,22 +1,45 @@
.pipelineList {
@apply bg-red-200;
@apply bg-red-200 w-40;
& > header {
@apply bg-white flex justify-between items-center;
}
}
.list {
@apply bg-gray-100 overflow-y-auto max-h-full;
}
.item {
@apply bg-gray-50;
@apply bg-white text-gray-700;
@apply border-l-4 border-white transition-colors;
@apply px-2 py-1 my-px;
@apply relative;
small {
@apply text-sm text-gray-500;
svg {
@apply mr-1 h-3 text-gray-300;
}
}
}
.activeItem {
@apply border-red-500;
}
.addBtn {
@apply bg-red-400 text-white;
@apply py-1 px-2 m-2 rounded-lg;
@apply py-1 px-2 m-1 rounded-lg mr-auto;
@apply hover:bg-red-500;
}
.refetchBtn {
.actionBtn {
@apply text-red-400 flex items-center;
@apply w-4 h-4 m-2 rounded-full;
}
@apply w-4 h-4 flex items-center justify-center overflow-hidden text-center m-1;
svg {
}
}
.editPanel {
@apply absolute right-1 top-0 bottom-0;
@apply flex flex-col items-center justify-evenly;
@apply text-red-400 text-xs;
button {
@apply w-3;
}
}

View File

@ -1,10 +1,13 @@
// This file is automatically generated from your CSS. Any edits will be overwritten.
declare namespace PipelineListScssNamespace {
export interface IPipelineListScss {
actionBtn: string;
activeItem: string;
addBtn: string;
editPanel: string;
item: string;
list: string;
pipelineList: string;
refetchBtn: string;
}
}

View File

@ -1,16 +1,29 @@
import { useQuery } from '@apollo/client';
import { gql, useQuery, useMutation } from '@apollo/client';
import { makeAutoObservable } from 'mobx';
import { useLocalObservable, useObserver } from 'mobx-react-lite';
import { useMemo } from 'preact/hooks';
import { useLocalObservable } from 'mobx-react-lite';
import { useCallback, useMemo } from 'preact/hooks';
import { h } from 'preact';
import { Pipeline } from '../../generated/graphql';
import styles from './pipeline-list.scss';
import classNames from 'classnames';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faRedoAlt } from '@fortawesome/free-solid-svg-icons';
import {
faCodeBranch,
faRedoAlt,
faEdit,
faEllipsisV,
faTrash
} from '@fortawesome/free-solid-svg-icons';
import { createOverlay } from '../commons/overlay/overlay';
import { PipelineEditor } from '../../routes/pipelines/pipeline-editor';
import { LIST_PIPELINES } from './pipeline-list.constants';
import { Observer, observer } from 'mobx-react';
const DELETE_PIPELINE = gql`
mutation DeletePipeline($id: String!) {
deletePipeline(id: $id)
}
`;
interface Props {
projectId: string;
@ -19,36 +32,28 @@ interface Props {
class Store {
pipelines?: Pipeline[];
currPipelineId?: string;
editMode = false;
constructor() {
makeAutoObservable(this);
}
get items() {
return this.pipelines?.map(pipeline => {
const isActive = this.currPipelineId === pipeline.id;
return (
<li
key={pipeline.id}
className={classNames(
{
isActive
},
styles.item
)}
>
<h3>{pipeline.name}</h3>
</li>
);
});
}
refetching = false;
setPipelines(pipelines: any[] | undefined) {
this.pipelines = pipelines;
}
setRefetching(val: boolean) {
this.refetching = val;
}
setCurrPipelineId(id: string) {
this.currPipelineId = id;
}
toggleEditMode() {
this.editMode = !this.editMode;
}
}
export const PipelineList = ({ projectId }: Props) => {
const { data, refetch } = useQuery<{ pipelines: Pipeline[] }>(
const { data, refetch, loading } = useQuery<{ pipelines: Pipeline[] }>(
LIST_PIPELINES,
{
variables: {
@ -63,25 +68,123 @@ export const PipelineList = ({ projectId }: Props) => {
useMemo(() => store.setPipelines(pipelines), [store, pipelines]);
const items = useObserver(() => store.items);
const addPipeline = () => {
createOverlay({
content: <PipelineEditor projectId={projectId} />
});
};
return (
<section className={styles.pipelineList}>
const Header = observer(() => {
const addPipeline = () => {
createOverlay({
content: <PipelineEditor projectId={projectId} />
});
};
const onRefetchBtnClick = () => {
store.setRefetching(true);
refetch().finally(() => store.setRefetching(false));
};
return (
<header>
<button className={styles.addBtn} onClick={addPipeline}>
Add
</button>
<button className={styles.refetchBtn} onClick={() => refetch()}>
<FontAwesomeIcon icon={faRedoAlt} />
<button className={styles.actionBtn} onClick={onRefetchBtnClick}>
<FontAwesomeIcon
className={classNames({
'animate-spin': store.refetching
})}
icon={faRedoAlt}
/>
</button>
<button
className={styles.actionBtn}
onClick={() => store.toggleEditMode()}
>
<FontAwesomeIcon icon={faEllipsisV} />
</button>
</header>
<ul>{items}</ul>
);
});
const items = pipelines?.map(pipeline => (
<Observer key={pipeline.id}>
{(): any => <Item pipeline={pipeline} store={store} />}
</Observer>
));
return (
<section className={styles.pipelineList}>
<Header />
<ul className={styles.list}>{items}</ul>
</section>
);
};
const Item = observer(
({ pipeline, store }: { pipeline: Pipeline; store: Store }) => {
const [remove] = useMutation(DELETE_PIPELINE, {
variables: { id: pipeline.id },
update(cache) {
const cacheData = cache.readQuery<{ pipelines: Pipeline[] }>({
query: LIST_PIPELINES,
variables: { projectId: pipeline.projectId }
});
const pipelines = cacheData?.pipelines?.slice();
if (!pipelines) {
return;
}
const index = pipelines.findIndex(
(item: Pipeline) => item.id === pipeline.id
);
if (index === -1) {
return;
}
pipelines.splice(index, 1);
cache.writeQuery({
query: LIST_PIPELINES,
variables: { projectId: pipeline.projectId },
data: {
pipelines
}
});
}
});
const isActive = store.currPipelineId === pipeline.id;
const openModifyPanel = () => {
createOverlay({
content: (
<PipelineEditor projectId={pipeline.projectId} id={pipeline.id} />
)
});
};
const onRemoveBtnClick = () => {
remove();
};
const EditPanel = observer(() => {
return store.editMode ? (
<div className={styles.editPanel}>
<button onClick={openModifyPanel}>
<FontAwesomeIcon icon={faEdit} />
</button>
<button onClick={onRemoveBtnClick}>
<FontAwesomeIcon icon={faTrash} />
</button>
</div>
) : null;
});
return (
<li
key={pipeline.id}
className={classNames(
{
[styles.activeItem]: isActive
},
styles.item
)}
onClick={() => store.setCurrPipelineId(pipeline.id)}
>
<h3>{pipeline.name}</h3>
<small>
<FontAwesomeIcon icon={faCodeBranch} />
{pipeline.branch}
</small>
<Observer>{(): any => <EditPanel />}</Observer>
</li>
);
}
);

View File

@ -27,7 +27,6 @@ const FIND_PROJECTS = gql`
sshUrl
webUrl
webHookSecret
deletedAt
}
}
`;

View File

@ -1,17 +1,18 @@
import { Field, Form, Formik } from 'formik';
import { Field, Form, Formik, useFormik } from 'formik';
import { h } from 'preact';
import { RoutableProps } from 'preact-router';
import styles from './pipeline-editor.scss';
import {
CreatePipelineInput,
Pipeline,
UpdatePipelineInput
} from '../../generated/graphql';
import { useOverlay } from '../../components/commons/overlay/overlay';
import { gql, useLazyQuery, useMutation } from '@apollo/client';
import { useMemo } from 'preact/hooks';
import classNames from 'classnames';
import { WorkUnitMetadata, PipelineUnits } from '../../generated/graphql';
import {
CreatePipelineInput,
UpdatePipelineInput,
WorkUnitMetadata,
PipelineUnits,
Pipeline
} from '../../generated/graphql';
import { Message } from '../../components/commons/message/index';
import { LIST_PIPELINES } from '../../components/pipelines/pipeline-list.constants';
@ -41,7 +42,7 @@ const FIND_PIPELINE = gql`
query FindPipeline($id: String!) {
pipeline: findPipeline(id: $id) {
name
id
branch
projectId
workUnitMetadata {
version
@ -98,18 +99,28 @@ export const PipelineEditor = ({ projectId, id }: Props) => {
};
const isCreate = !id;
const [loadPipeline, { data: pipeline }] = useLazyQuery(FIND_PIPELINE);
const [loadPipeline, { data }] = useLazyQuery(FIND_PIPELINE, {
variables: { id }
});
useMemo(() => {
if (!isCreate) {
loadPipeline();
}
}, []);
const formData: FormValues = pipeline ?? {
name: 'test',
projectId,
workUnitMetadata: JSON.stringify(defaultWorkUnitMetadata, null, 2)
};
const formData: FormValues = useMemo(() => {
if (data?.pipeline) {
const fd = JSON.parse(JSON.stringify(data.pipeline));
delete fd?.workUnitMetadata.__typename;
fd.workUnitMetadata.units.forEach((item: any) => {
delete item.__typename;
});
fd.workUnitMetadata = JSON.stringify(fd.workUnitMetadata, null, 2);
delete fd.__typename;
return fd;
}
return {};
}, [data, data?.pipeline]);
const [createPipeline] = useMutation<{ pipeline: Pipeline }>(
CREATE_PIPELINE,
@ -167,10 +178,9 @@ export const PipelineEditor = ({ projectId, id }: Props) => {
//
}
};
return (
<section className={styles.editor}>
<Formik initialValues={formData} onSubmit={submitForm}>
<Formik initialValues={formData} onSubmit={submitForm} enableReinitialize>
<Form className={styles.form}>
<label>
<span className={styles.label}></span>

View File

@ -21,7 +21,6 @@ const FIND_PROJECT = gql`
sshUrl
webUrl
webHookSecret
deletedAt
}
}
`;