feat(projects): 添加检出功能。

This commit is contained in:
Ivan Li 2021-02-28 21:52:20 +08:00
parent a137c0efb8
commit 61dad695fb
15 changed files with 339 additions and 0 deletions

View File

@ -887,6 +887,39 @@
}, },
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
},
{
"name": "checkout",
"description": null,
"args": [
{
"name": "checkoutInput",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "CheckoutInput",
"ofType": null
}
},
"defaultValue": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
} }
], ],
"inputFields": null, "inputFields": null,
@ -1060,6 +1093,57 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "INPUT_OBJECT",
"name": "CheckoutInput",
"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
},
{
"name": "commitNumber",
"description": null,
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "__Schema", "name": "__Schema",

10
package-lock.json generated
View File

@ -2949,6 +2949,11 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"@types/classnames": {
"version": "2.2.11",
"resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.11.tgz",
"integrity": "sha512-2koNhpWm3DgWRp5tpkiJ8JGc1xTn2q0l+jUNUE7oMKXUf5NpI9AIdC4kbjGNFBdHtcxBD18LAksoudAVhFKCjw=="
},
"@types/enzyme": { "@types/enzyme": {
"version": "3.10.8", "version": "3.10.8",
"resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.8.tgz", "resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.8.tgz",
@ -5181,6 +5186,11 @@
} }
} }
}, },
"classnames": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
"integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
},
"clean-css": { "clean-css": {
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz",

View File

@ -30,7 +30,9 @@
"@fortawesome/free-solid-svg-icons": "^5.15.2", "@fortawesome/free-solid-svg-icons": "^5.15.2",
"@fortawesome/react-fontawesome": "^0.1.14", "@fortawesome/react-fontawesome": "^0.1.14",
"@tailwindcss/postcss7-compat": "^2.0.3", "@tailwindcss/postcss7-compat": "^2.0.3",
"@types/classnames": "^2.2.11",
"autoprefixer": "^9.8.6", "autoprefixer": "^9.8.6",
"classnames": "^2.2.6",
"formik": "^2.2.6", "formik": "^2.2.6",
"graphql": "^15.5.0", "graphql": "^15.5.0",
"mobx": "^6.1.7", "mobx": "^6.1.7",

View File

@ -0,0 +1,12 @@
.btn {
@apply p-2 w-4 h-4 box-content inline-flex items-center justify-center;
@apply rounded-full;
@apply text-red-500;
@apply hover:bg-red-100;
&[disabled] {
@apply text-red-300;
}
&:global(.waiting) {
@apply text-red-500;
}
}

View File

@ -0,0 +1,10 @@
// This file is automatically generated from your CSS. Any edits will be overwritten.
declare namespace ActionButtonScssNamespace {
export interface IActionButtonScss {
btn: string;
}
}
declare const ActionButtonScssModule: ActionButtonScssNamespace.IActionButtonScss;
export = ActionButtonScssModule;

View File

@ -0,0 +1,62 @@
import { RenderableProps } from 'preact';
import { FC } from 'preact/compat';
import { h } from 'preact';
import styles from './action-button.scss';
import { makeAutoObservable, makeObservable } from 'mobx';
import { observer, useLocalObservable } from 'mobx-react';
import classNames from 'classnames';
import { useCommitActionsStore } from './commit-actions.store';
interface Props {
onClick?: () => Promise<void>;
waiting?: boolean;
disabled?: boolean;
}
class Store {
constructor(public waiting: boolean = false) {
makeAutoObservable(this);
}
setWaiting(val: boolean) {
this.waiting = val;
}
}
export const ActionButton: FC<Props> = observer(
({ onClick, waiting, children }: RenderableProps<Props>) => {
const commitActionsStore = useCommitActionsStore();
const store = useLocalObservable(() => new Store(waiting));
const doAction = () => {
if (!onClick) {
return;
}
store.setWaiting(true);
commitActionsStore?.setCurrentWork?.('checkout');
onClick().finally(() => {
store.setWaiting(false);
commitActionsStore?.clearCurrentWork?.('checkout');
});
};
return (
<button
className={classNames([
styles.btn,
{
'waiting animate-pulse': store.waiting
}
])}
disabled={!!commitActionsStore.currentWork}
onClick={doAction}
>
{children}
</button>
);
}
);
ActionButton.defaultProps = {
waiting: false,
disabled: false
};

View File

@ -0,0 +1,3 @@
.component {
@apply inline;
}

View File

@ -0,0 +1,10 @@
// This file is automatically generated from your CSS. Any edits will be overwritten.
declare namespace CommitActionsScssNamespace {
export interface ICommitActionsScss {
component: string;
}
}
declare const CommitActionsScssModule: CommitActionsScssNamespace.ICommitActionsScss;
export = CommitActionsScssModule;

View File

@ -0,0 +1,30 @@
import { makeAutoObservable } from 'mobx';
import { useContext } from 'preact/hooks';
import { createContext } from 'preact';
type Work = string;
export class CommitActionsStore {
clearCurrentWork(work: Work) {
if (work === this.currentWork) {
this.currentWork = undefined;
}
}
constructor() {
makeAutoObservable(this);
}
currentWork?: Work;
setCurrentWork(work: Work) {
this.currentWork = work;
}
}
const commitActionsContext = createContext<CommitActionsStore>(
new CommitActionsStore()
);
export const CommitActionsStoreProvider = commitActionsContext.Provider;
export const useCommitActionsStore = () => {
return useContext(commitActionsContext);
};

View File

@ -0,0 +1,69 @@
import { h } from 'preact';
import styles from './commit-actions.scss';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { autorun, makeAutoObservable } from 'mobx';
import { ActionButton } from './action-button';
import { gql, useMutation } from '@apollo/client';
import { CheckoutInput, Project } from '../../generated/graphql';
import {
CommitActionsStoreProvider,
CommitActionsStore
} from './commit-actions.store';
import {
faCloudDownloadAlt,
faDownload,
faGlobeAsia,
faVial
} from '@fortawesome/free-solid-svg-icons';
import { useLocalObservable } from 'mobx-react';
const CHECKOUT = gql`
mutation Checkout($input: CheckoutInput!) {
checkout(checkoutInput: $input)
}
`;
interface Props {
project: Project;
commitNumber: string;
branch?: string;
}
class Store {
constructor() {
makeAutoObservable(this);
}
isTasksWorking = [false, false, false, false];
}
export const CommitActions = ({ project, commitNumber }: Props) => {
const [checkout] = useMutation<CheckoutInput>(CHECKOUT);
const onCheckoutBtnClick = async () => {
await checkout({
variables: {
input: { projectId: project.id, commitNumber } as CheckoutInput
}
});
};
const commitActionsStore = useLocalObservable(() => new CommitActionsStore());
return (
<section className={[styles.component, 'commit-actions'].join(' ')}>
<CommitActionsStoreProvider value={commitActionsStore}>
<ActionButton onClick={onCheckoutBtnClick}>
<FontAwesomeIcon icon={faDownload}></FontAwesomeIcon>
</ActionButton>
<ActionButton>
<FontAwesomeIcon icon={faCloudDownloadAlt}></FontAwesomeIcon>
</ActionButton>
<ActionButton>
<FontAwesomeIcon icon={faVial}></FontAwesomeIcon>
</ActionButton>
<ActionButton>
<FontAwesomeIcon icon={faGlobeAsia}></FontAwesomeIcon>
</ActionButton>
</CommitActionsStoreProvider>
</section>
);
};

View File

@ -5,11 +5,16 @@
@apply bg-gray-100 overflow-auto max-h-full; @apply bg-gray-100 overflow-auto max-h-full;
} }
.item { .item {
@apply grid grid-cols-2 grid-rows-2;
@apply bg-white text-gray-700; @apply bg-white text-gray-700;
@apply py-2 px-4 my-px; @apply py-2 px-4 my-px;
@apply hover:bg-gray-50; @apply hover:bg-gray-50;
time { time {
@apply col-start-1;
@apply text-sm text-gray-400; @apply text-sm text-gray-400;
} }
:global(.commit-actions) {
@apply col-start-2 row-start-1 row-span-2 justify-self-end self-center;
}
} }

View File

@ -2,6 +2,7 @@ import { LogList, Project, ListLogsArgs } from '../../generated/graphql';
import { h } from 'preact'; import { h } from 'preact';
import { gql, useQuery } from '@apollo/client'; import { gql, useQuery } from '@apollo/client';
import styles from './commit-log-list.scss'; import styles from './commit-log-list.scss';
import { CommitActions } from '../commit-actions/commit-actions';
const LIST_LOGS = gql` const LIST_LOGS = gql`
query ListLogs($args: ListLogsArgs!) { query ListLogs($args: ListLogsArgs!) {
@ -36,6 +37,7 @@ export const CommitLogList = ({ project, branch }: Props) => {
<li className={styles.item} key={log.hash}> <li className={styles.item} key={log.hash}>
<h4>{log.message}</h4> <h4>{log.message}</h4>
<time dateTime={log.date}>{log.date}</time> <time dateTime={log.date}>{log.date}</time>
<CommitActions project={project} commitNumber={log.hash} />
</li> </li>
); );
}); });

View File

@ -0,0 +1,4 @@
.component {
@apply py-2 px-4;
@apply bg-transparent border-gray-400 border rounded-md;
}

View File

@ -0,0 +1,24 @@
import { FC } from 'preact/compat';
import { h } from 'preact';
interface Props {
loading?: boolean;
title?: string;
}
export const Button: FC<Props> = ({
loading,
title,
children
}: RenderableProps<Props>) => {
const disabled = loading;
return (
<button disabled={disabled} title={title}>
{children}
</button>
);
};
Button.defaultProps = {
loading: false
};

View File

@ -103,6 +103,7 @@ export type Mutation = {
createProject: Project; createProject: Project;
modifyProject: Project; modifyProject: Project;
deleteProject: Scalars['Float']; deleteProject: Scalars['Float'];
checkout: Scalars['Boolean'];
}; };
@ -121,6 +122,11 @@ export type MutationDeleteProjectArgs = {
id: Scalars['String']; id: Scalars['String'];
}; };
export type MutationCheckoutArgs = {
checkoutInput: CheckoutInput;
};
export type CreateProjectInput = { export type CreateProjectInput = {
name: Scalars['String']; name: Scalars['String'];
comment: Scalars['String']; comment: Scalars['String'];
@ -136,3 +142,9 @@ export type UpdateProjectInput = {
webUrl?: Maybe<Scalars['String']>; webUrl?: Maybe<Scalars['String']>;
webHookSecret?: Maybe<Scalars['String']>; webHookSecret?: Maybe<Scalars['String']>;
}; };
export type CheckoutInput = {
projectId: Scalars['String'];
branch?: Maybe<Scalars['String']>;
commitNumber?: Maybe<Scalars['String']>;
};