feat(project-details): 添加分支列表和选择。

This commit is contained in:
Ivan Li 2021-02-28 02:06:55 +08:00
parent 54a164ddc6
commit d548793d0f
16 changed files with 4135 additions and 5853 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@ -176,6 +176,382 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "LogFields",
"description": null,
"fields": [
{
"name": "hash",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "date",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "message",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "refs",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "body",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "author_name",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "author_email",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "LogList",
"description": null,
"fields": [
{
"name": "all",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "LogFields",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "total",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Float",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "latest",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "LogFields",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "Float",
"description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "Branch",
"description": null,
"fields": [
{
"name": "current",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "name",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "commit",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "label",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "Boolean",
"description": "The `Boolean` scalar type represents `true` or `false`.",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "BranchList",
"description": null,
"fields": [
{
"name": "branches",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Branch",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "detached",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "current",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "all",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "Query",
@ -253,6 +629,72 @@
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "listLogs",
"description": null,
"args": [
{
"name": "listLogsArgs",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "ListLogsArgs",
"ofType": null
}
},
"defaultValue": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "LogList",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "listBranches",
"description": null,
"args": [
{
"name": "listBranchesArgs",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "ListBranchesArgs",
"ofType": null
}
},
"defaultValue": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "BranchList",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
@ -260,6 +702,72 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "ListLogsArgs",
"description": null,
"fields": null,
"inputFields": [
{
"name": "projectId",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "branch",
"description": null,
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "ListBranchesArgs",
"description": null,
"fields": null,
"inputFields": [
{
"name": "projectId",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "Mutation",
@ -386,16 +894,6 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "Float",
"description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "CreateProjectInput",
@ -562,16 +1060,6 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "Boolean",
"description": "The `Boolean` scalar type represents `true` or `false`.",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "__Schema",

9145
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -25,26 +25,26 @@
"build/*"
],
"dependencies": {
"@apollo/client": "^3.3.7",
"@apollo/client": "^3.3.11",
"@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.3",
"autoprefixer": "^9.8.6",
"formik": "^2.2.6",
"graphql": "^15.5.0",
"mobx": "^6.1.4",
"mobx": "^6.1.7",
"mobx-react": "^7.1.0",
"postcss": "^7.0.35",
"preact": "^10.3.1",
"preact-jsx-chai": "^3.0.0",
"preact-markup": "^2.0.0",
"preact-markup": "^2.1.1",
"preact-render-to-string": "^5.1.4",
"preact-router": "^3.2.1",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.2"
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.3"
},
"devDependencies": {
"@graphql-codegen/cli": "^1.20.1",
"@graphql-codegen/cli": "^1.21.1",
"@graphql-codegen/introspection": "^1.18.1",
"@graphql-codegen/typescript-operations": "^1.17.14",
"@graphql-codegen/typescript-react-apollo": "^2.2.1",
@ -65,12 +65,12 @@
"husky": "^4.2.1",
"jest": "^26.2.2",
"jest-preset-preact": "^4.0.2",
"lint-staged": "^10.0.7",
"lint-staged": "^10.5.4",
"preact-cli": "^3.0.0",
"prettier": "^1.19.1",
"sass": "^1.32.5",
"sass": "^1.32.8",
"sass-loader": "^10.1.1",
"sirv-cli": "^1.0.0-next.3",
"typescript": "^3.7.5"
"sirv-cli": "^1.0.11",
"typescript": "^3.9.9"
}
}

View File

@ -0,0 +1,23 @@
.component {
@apply self-stretch;
}
.item {
@apply bg-gray-50 p-2 text-red-400 border-red-500 my-px
transition-colors
flex justify-between items-center w-32
overflow-ellipsis select-none cursor-pointer;
@apply hover:bg-red-50;
svg {
@apply hidden;
}
}
.itemActive {
@apply bg-red-400 text-white;
@apply hover:bg-red-400;
svg {
@apply inline-block;
}
}

View File

@ -0,0 +1,12 @@
// This file is automatically generated from your CSS. Any edits will be overwritten.
declare namespace BranchesListScssNamespace {
export interface IBranchesListScss {
component: string;
item: string;
itemActive: string;
}
}
declare const BranchesListScssModule: BranchesListScssNamespace.IBranchesListScss;
export = BranchesListScssModule;

View File

@ -0,0 +1,78 @@
import {
LogList,
Project,
ListLogsArgs,
ListBranchesArgs,
BranchList
} from '../../generated/graphql';
import { h } from 'preact';
import { gql, useQuery } from '@apollo/client';
import styles from './branches-list.scss';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faChevronRight } from '@fortawesome/free-solid-svg-icons';
import { action, makeObservable, observable } from 'mobx';
import { PropTypes, useLocalObservable, useObserver } from 'mobx-react';
const LIST_BRANCHES = gql`
query ListBranches($args: ListBranchesArgs!) {
listBranches(listBranchesArgs: $args) {
all
}
}
`;
interface Props {
project: Project;
onSelect?: (branch: string) => void;
}
class Store {
constructor() {
makeObservable(this);
}
@observable
currBranch?: string;
@action
setCurrBranch(branch?: string) {
this.currBranch = branch;
}
}
export const BranchesList = ({ project, onSelect }: Props) => {
const store = useLocalObservable(() => new Store());
const { data, loading } = useQuery<
{ listBranches: BranchList },
{ args: ListBranchesArgs }
>(LIST_BRANCHES, {
variables: { args: { projectId: project.id } }
});
const list = useObserver(() =>
data?.listBranches?.all?.map(text => {
return (
<li
key={text}
className={[
styles.item,
store.currBranch === text ? styles.itemActive : ''
].join(' ')}
onClick={() => {
store.setCurrBranch(text);
onSelect?.(text);
}}
>
{text.replace(/^.*\/(.+?)$/, '$1')}
<FontAwesomeIcon icon={faChevronRight} />
</li>
);
})
);
return (
<section className={styles.component}>
{loading ? <span>Loading...</span> : <ol>{list}</ol>}
</section>
);
};

View File

@ -0,0 +1,43 @@
import { LogList, Project, ListLogsArgs } from '../../generated/graphql';
import { h } from 'preact';
import { gql, useQuery } from '@apollo/client';
const LIST_LOGS = gql`
query ListLogs($args: ListLogsArgs!) {
listLogs(listLogsArgs: $args) {
all {
author_name
author_email
hash
message
body
date
}
}
}
`;
interface Props {
project: Project;
branch?: string;
}
export const CommitLogList = ({ project, branch }: Props) => {
const { data, loading } = useQuery<
{ listLogs: LogList },
{ args: ListLogsArgs }
>(LIST_LOGS, {
variables: { args: { projectId: project.id, branch } }
});
const list = data?.listLogs?.all?.map(log => {
return (
<li key={log.hash}>
<h4>{log.message}</h4>
<time dateTime={log.date}>{log.date}</time>
</li>
);
});
return loading ? <span>Loading...</span> : <ol>{list}</ol>;
};

View File

@ -18,3 +18,7 @@
}
}
}
.body {
@apply flex;
}

View File

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

View File

@ -5,17 +5,37 @@ import { Project } from '../../generated/graphql';
import styles from './project-details.scss';
import { createOverlay } from '../commons/overlay/overlay';
import { ProjectEditor } from './project-editor';
import { CommitLogList } from '../commit-logs/commit-log-list';
import { BranchesList } from '../branches-list/branches-list';
import { makeAutoObservable } from 'mobx';
import { Observer, useLocalObservable } from 'mobx-react';
interface Props {
project: Project;
}
class Store {
setBranch(branch?: string) {
this.branch = branch;
}
constructor() {
makeAutoObservable(this);
}
branch?: string;
}
export const ProjectDetails = ({ project }: Props) => {
const store = useLocalObservable(() => new Store());
const editProject = () => {
createOverlay({
content: <ProjectEditor project={project} />
});
};
const onSelectBranch = (branch?: string) => {
store.setBranch(branch);
};
return (
<section className={styles.projectDetails}>
<header>
@ -34,6 +54,12 @@ export const ProjectDetails = ({ project }: Props) => {
</button>
</div>
</header>
<div className={styles.body}>
<BranchesList project={project} onSelect={onSelectBranch} />
<Observer>
{(): any => <CommitLogList project={project} branch={store.branch} />}
</Observer>
</div>
</section>
);
};

View File

@ -1,14 +1,6 @@
import { gql, useQuery } from '@apollo/client';
import {
action,
autorun,
computed,
makeObservable,
observable,
reaction,
when
} from 'mobx';
import { useLocalStore, useObserver } from 'mobx-react';
import { action, computed, makeObservable, observable, reaction } from 'mobx';
import { useLocalObservable, useObserver } from 'mobx-react';
import { h } from 'preact';
import { forwardRef } from 'preact/compat';
import { useImperativeHandle, useMemo, useRef } from 'preact/hooks';
@ -65,6 +57,11 @@ class Store {
setCurrentProjectId = (id: string) => {
this.currentProjectId = id;
};
@action
setProjects = (projects?: Project[]) => {
this.projects = projects;
};
}
export function ProjectPanel() {
const listRef = useRef<ListRef>();
@ -98,29 +95,27 @@ const List = forwardRef<ListRef>((_, ref) => {
projects: Project[];
}>(FIND_PROJECTS);
const store = useLocalStore(() => new Store());
// store.projects = data?.projects;
const store = useLocalObservable(() => new Store());
useMemo(() => {
store.projects = data?.projects;
store.setProjects(data?.projects);
}, [data?.projects]);
useImperativeHandle(ref, () => ({
refetch
}));
return useObserver(() => {
const list = store.projects?.map(item => (
<li
class={`${styles.item} ${
item.id === store.currentProject?.id ? styles.itemActive : ''
}`}
key={item.id}
onClick={() => store.setCurrentProjectId(item.id)}
>
<h3>{item.name}</h3>
<small>{item.comment}</small>
</li>
));
return <ol class={styles.list}>{list}</ol>;
});
const list = store.projects?.map(item => (
<li
class={`${styles.item} ${
item.id === store.currentProject?.id ? styles.itemActive : ''
}`}
key={item.id}
onClick={() => store.setCurrentProjectId(item.id)}
>
<h3>{item.name}</h3>
<small>{item.comment}</small>
</li>
));
return useObserver(() => <ol class={styles.list}>{list}</ol>);
});

View File

@ -31,11 +31,47 @@ export type Project = {
};
export type LogFields = {
__typename?: 'LogFields';
hash: Scalars['String'];
date: Scalars['String'];
message: Scalars['String'];
refs: Scalars['String'];
body: Scalars['String'];
author_name: Scalars['String'];
author_email: Scalars['String'];
};
export type LogList = {
__typename?: 'LogList';
all: Array<LogFields>;
total: Scalars['Float'];
latest: LogFields;
};
export type Branch = {
__typename?: 'Branch';
current: Scalars['Boolean'];
name: Scalars['String'];
commit: Scalars['String'];
label: Scalars['String'];
};
export type BranchList = {
__typename?: 'BranchList';
branches: Array<Branch>;
detached: Scalars['Boolean'];
current: Scalars['String'];
all: Array<Scalars['String']>;
};
export type Query = {
__typename?: 'Query';
hello: Hello;
findProjects: Array<Project>;
findProject: Project;
listLogs: LogList;
listBranches: BranchList;
};
@ -43,6 +79,25 @@ export type QueryFindProjectArgs = {
id: Scalars['String'];
};
export type QueryListLogsArgs = {
listLogsArgs: ListLogsArgs;
};
export type QueryListBranchesArgs = {
listBranchesArgs: ListBranchesArgs;
};
export type ListLogsArgs = {
projectId: Scalars['String'];
branch?: Maybe<Scalars['String']>;
};
export type ListBranchesArgs = {
projectId: Scalars['String'];
};
export type Mutation = {
__typename?: 'Mutation';
createProject: Project;

View File

@ -1,4 +1,4 @@
import './style/index.css';
import './style/index.scss';
import App from './components/app.tsx';
export default App;