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": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@fullhuman/postcss-purgecss/-/postcss-purgecss-3.1.3.tgz",

View File

@ -26,6 +26,9 @@
],
"dependencies": {
"@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",
"autoprefixer": "^9.8.6",
"formik": "^2.2.6",

View File

@ -1,7 +1,13 @@
.projectPanel {
@apply w-1/6;
}
.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 {
export interface IAppScss {
app: string;
projectPanel: string;
}
}

View File

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

View File

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