feat(project): 项目详情页。

This commit is contained in:
Ivan Li 2021-02-14 15:52:33 +08:00
parent 93946e4923
commit 03c5cacec5
10 changed files with 156 additions and 19 deletions

29
package-lock.json generated
View File

@ -1313,6 +1313,35 @@
} }
} }
}, },
"@fortawesome/fontawesome-common-types": {
"version": "0.2.34",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.34.tgz",
"integrity": "sha512-XcIn3iYbTEzGIxD0/dY5+4f019jIcEIWBiHc3KrmK/ROahwxmZ/s+tdj97p/5K0klz4zZUiMfUlYP0ajhSJjmA=="
},
"@fortawesome/fontawesome-svg-core": {
"version": "1.2.34",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.34.tgz",
"integrity": "sha512-0KNN0nc5eIzaJxlv43QcDmTkDY1CqeN6J7OCGSs+fwGPdtv0yOQqRjieopBCmw+yd7uD3N2HeNL3Zm5isDleLg==",
"requires": {
"@fortawesome/fontawesome-common-types": "^0.2.34"
}
},
"@fortawesome/free-solid-svg-icons": {
"version": "5.15.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.2.tgz",
"integrity": "sha512-ZfCU+QjaFsdNZmOGmfqEWhzI3JOe37x5dF4kz9GeXvKn/sTxhqMtZ7mh3lBf76SvcYY5/GKFuyG7p1r4iWMQqw==",
"requires": {
"@fortawesome/fontawesome-common-types": "^0.2.34"
}
},
"@fortawesome/react-fontawesome": {
"version": "0.1.14",
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.14.tgz",
"integrity": "sha512-4wqNb0gRLVaBm/h+lGe8UfPPivcbuJ6ecI4hIgW0LjI7kzpYB9FkN0L9apbVzg+lsBdcTf0AlBtODjcSX5mmKA==",
"requires": {
"prop-types": "^15.7.2"
}
},
"@fullhuman/postcss-purgecss": { "@fullhuman/postcss-purgecss": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/@fullhuman/postcss-purgecss/-/postcss-purgecss-3.1.3.tgz", "resolved": "https://registry.npmjs.org/@fullhuman/postcss-purgecss/-/postcss-purgecss-3.1.3.tgz",

View File

@ -26,6 +26,9 @@
], ],
"dependencies": { "dependencies": {
"@apollo/client": "^3.3.7", "@apollo/client": "^3.3.7",
"@fortawesome/fontawesome-svg-core": "^1.2.34",
"@fortawesome/free-solid-svg-icons": "^5.15.2",
"@fortawesome/react-fontawesome": "^0.1.14",
"@tailwindcss/postcss7-compat": "^2.0.2", "@tailwindcss/postcss7-compat": "^2.0.2",
"autoprefixer": "^9.8.6", "autoprefixer": "^9.8.6",
"formik": "^2.2.6", "formik": "^2.2.6",

View File

@ -1,7 +1,13 @@
.projectPanel { .projectPanel {
@apply w-1/6;
} }
.app { .app {
@apply flex bg-red-50; @apply flex flex-row-reverse bg-red-50;
aside {
@apply w-1/6 shadow;
}
main {
@apply flex-auto ml-px;
}
} }

View File

@ -2,7 +2,6 @@
declare namespace AppScssNamespace { declare namespace AppScssNamespace {
export interface IAppScss { export interface IAppScss {
app: string; app: string;
projectPanel: string;
} }
} }

View File

@ -29,10 +29,10 @@ const Board = () => {
return useObserver(() => { return useObserver(() => {
return ( return (
<Fragment> <Fragment>
<main>{appStore.main}</main>
<aside> <aside>
<ProjectPanel /> <ProjectPanel />
</aside> </aside>
<main>{appStore.main}</main>
</Fragment> </Fragment>
); );
}); });

View File

@ -0,0 +1,20 @@
.projectDetails {
header {
@apply bg-red-400 text-gray-50 p-2;
@apply grid grid-cols-2 grid-rows-2;
h2 {
@apply text-white text-lg col-start-1 row-start-1 align-middle;
svg {
@apply text-xs ml-2;
}
}
small {
@apply text-sm col-start-1 row-start-2;
}
.operations {
@apply col-start-2 row-span-2 justify-self-end self-center;
}
}
}

View File

@ -0,0 +1,11 @@
// This file is automatically generated from your CSS. Any edits will be overwritten.
declare namespace ProjectDetailsScssNamespace {
export interface IProjectDetailsScss {
operations: string;
projectDetails: string;
}
}
declare const ProjectDetailsScssModule: ProjectDetailsScssNamespace.IProjectDetailsScss;
export = ProjectDetailsScssModule;

View File

@ -0,0 +1,39 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faEdit, faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons';
import { h } from 'preact';
import { Project } from '../../generated/graphql';
import styles from './project-details.scss';
import { createOverlay } from '../commons/overlay/overlay';
import { ProjectEditor } from './project-editor';
interface Props {
project: Project;
}
export const ProjectDetails = ({ project }: Props) => {
const editProject = () => {
createOverlay({
content: <ProjectEditor project={project} />
});
};
return (
<section className={styles.projectDetails}>
<header>
<h2>
{project.name}
{project.webUrl ? (
<a target="blank" href={project.webUrl}>
<FontAwesomeIcon icon={faExternalLinkAlt} />
</a>
) : null}
</h2>
<small>{project.comment}</small>
<div className={styles.operations}>
<button onClick={editProject}>
<FontAwesomeIcon icon={faEdit} />
</button>
</div>
</header>
</section>
);
};

View File

@ -1,14 +1,16 @@
import { gql, useApolloClient, useMutation } from '@apollo/client'; import { gql, useApolloClient, useMutation } from '@apollo/client';
import { Field, Form, Formik } from 'formik'; import { Field, Form, Formik } from 'formik';
import { h } from 'preact'; import { h } from 'preact';
import { useState } from 'preact/compat'; import {
import { CreateProjectInput, Project } from '../../generated/graphql'; CreateProjectInput,
Project,
UpdateProjectInput
} from '../../generated/graphql';
import { useOverlay } from '../commons/overlay/overlay'; import { useOverlay } from '../commons/overlay/overlay';
import styles from './project-editor.scss'; import styles from './project-editor.scss';
interface Props { interface Props {
id?: string; project?: Project;
isCreate: boolean;
} }
const CREATE_PROJECT = gql` const CREATE_PROJECT = gql`
@ -20,16 +22,28 @@ const CREATE_PROJECT = gql`
sshUrl sshUrl
webUrl webUrl
webHookSecret webHookSecret
deletedAt }
}
`;
const MODIFY_PROJECT = gql`
mutation ModifyProject($id: String!, $project: UpdateProjectInput!) {
modifyProject(id: $id, project: $project) {
id
name
comment
sshUrl
webUrl
webHookSecret
} }
} }
`; `;
export const ProjectEditor = (props: Props) => { export const ProjectEditor = (props: Props) => {
useApolloClient(); useApolloClient();
const isCreate = props.id === undefined; const isCreate = props.project === undefined;
const [createProject] = useMutation<Project>(CREATE_PROJECT); const [createProject] = useMutation<Project>(CREATE_PROJECT);
const [updateProject] = useMutation<Project>(MODIFY_PROJECT);
const { close } = useOverlay(); const { close } = useOverlay();
const cancel = (ev: MouseEvent) => { const cancel = (ev: MouseEvent) => {
@ -37,18 +51,25 @@ export const ProjectEditor = (props: Props) => {
close(); close();
}; };
const [project] = useState<CreateProjectInput>({ type FormValues = CreateProjectInput | UpdateProjectInput;
name: '',
comment: '', const project: FormValues = {
sshUrl: '', name: props.project?.name ?? '',
webHookSecret: '' comment: props.project?.comment ?? '',
}); sshUrl: props.project?.sshUrl ?? '',
const submitForm = async (values: CreateProjectInput) => { webUrl: props.project?.webUrl ?? null,
webHookSecret: props.project?.webHookSecret ?? ''
};
const submitForm = async (values: FormValues) => {
try { try {
if (props.id === undefined) { if (isCreate) {
await createProject({ await createProject({
variables: { project: values } variables: { project: values }
}); });
} else {
await updateProject({
variables: { project: values, id: props.project!.id }
});
} }
close(); close();
} finally { } finally {
@ -76,6 +97,14 @@ export const ProjectEditor = (props: Props) => {
placeholder="项目说明、简介" placeholder="项目说明、简介"
></Field> ></Field>
</label> </label>
<label>
<span className={styles.label}></span>
<Field
className={styles.controller}
name="webUrl"
placeholder="项目地址"
></Field>
</label>
<label> <label>
<span className={styles.label}>SSH url</span> <span className={styles.label}>SSH url</span>
<Field <Field

View File

@ -6,6 +6,7 @@ import { useImperativeHandle, useRef } from 'preact/hooks';
import { appStore } from '../../app.store'; import { appStore } from '../../app.store';
import { Project } from '../../generated/graphql'; import { Project } from '../../generated/graphql';
import { createOverlay } from '../commons/overlay/overlay'; import { createOverlay } from '../commons/overlay/overlay';
import { ProjectDetails } from './project-details';
import { ProjectEditor } from './project-editor'; import { ProjectEditor } from './project-editor';
import styles from './project-panel.scss'; import styles from './project-panel.scss';
@ -63,7 +64,7 @@ const List = forwardRef<ListRef>((_, ref) => {
const setCurrProject = (project: Project) => { const setCurrProject = (project: Project) => {
state.currentProject = project; state.currentProject = project;
appStore.setMain(JSON.stringify(project)); appStore.setMain(<ProjectDetails project={project} />);
}; };
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({