diff --git a/.eslintignore b/.eslintignore
index b47577a..3a04567 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -2,4 +2,4 @@ preact.config.js
src/generated/**/*
postcss.js
*.config.js
-tests/**/*
+tests/**/*
\ No newline at end of file
diff --git a/.eslintrc.js b/.eslintrc.js
index 8fecf35..ba3abc4 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -38,7 +38,8 @@ module.exports = {
files: ['*.js', '*.ts', '*.tsx'],
rules: {
'@typescript-eslint/explicit-function-return-type': 'off',
- '@typescript-eslint/interface-name-prefix': 'off'
+ '@typescript-eslint/interface-name-prefix': 'off',
+ '@typescript-eslint/no-non-null-assertion': 'off'
}
},
]
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 763f00a..09a697c 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -4,5 +4,8 @@
"editor.quickSuggestions": {
"strings": true,
"other": true,
- }
+ },
+ "cSpell.words": [
+ "Formik"
+ ]
}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index f5ce389..2757784 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8886,6 +8886,27 @@
"mime-types": "^2.1.12"
}
},
+ "formik": {
+ "version": "2.2.6",
+ "resolved": "https://registry.npmjs.org/formik/-/formik-2.2.6.tgz",
+ "integrity": "sha512-Kxk2zQRafy56zhLmrzcbryUpMBvT0tal5IvcifK5+4YNGelKsnrODFJ0sZQRMQboblWNym4lAW3bt+tf2vApSA==",
+ "requires": {
+ "deepmerge": "^2.1.1",
+ "hoist-non-react-statics": "^3.3.0",
+ "lodash": "^4.17.14",
+ "lodash-es": "^4.17.14",
+ "react-fast-compare": "^2.0.1",
+ "tiny-warning": "^1.0.2",
+ "tslib": "^1.10.0"
+ },
+ "dependencies": {
+ "deepmerge": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
+ "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA=="
+ }
+ }
+ },
"forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
@@ -12448,6 +12469,11 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
},
+ "lodash-es": {
+ "version": "4.17.20",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.20.tgz",
+ "integrity": "sha512-JD1COMZsq8maT6mnuz1UMV0jvYD0E0aUsSOdrr1/nAG3dhqQXwRRgeW0cSqH1U43INKcqxaiVIQNOUDld7gRDA=="
+ },
"lodash._reinterpolate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
@@ -17943,6 +17969,11 @@
}
}
},
+ "react-fast-compare": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
+ "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw=="
+ },
"react-is": {
"version": "17.0.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz",
@@ -20643,6 +20674,11 @@
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
"dev": true
},
+ "tiny-warning": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
+ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
+ },
"tinydate": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/tinydate/-/tinydate-1.3.0.tgz",
diff --git a/package.json b/package.json
index ec87a8b..b5e0f9c 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,7 @@
"@apollo/client": "^3.3.7",
"@tailwindcss/postcss7-compat": "^2.0.2",
"autoprefixer": "^9.8.6",
+ "formik": "^2.2.6",
"graphql": "^15.5.0",
"mobx": "^6.1.4",
"mobx-react": "^7.1.0",
diff --git a/src/app.store.ts b/src/app.store.ts
new file mode 100644
index 0000000..d537ec7
--- /dev/null
+++ b/src/app.store.ts
@@ -0,0 +1,21 @@
+import { action, makeObservable, observable } from 'mobx';
+import { ComponentChild } from 'preact';
+
+export class AppStore {
+ @observable.ref nav: ComponentChild;
+ @observable.ref main: ComponentChild;
+ constructor() {
+ makeObservable(this);
+ }
+
+ @action
+ setNav(component: ComponentChild) {
+ this.nav = component;
+ }
+ @action
+ setMain(component: ComponentChild) {
+ this.main = component;
+ }
+}
+
+export const appStore = new AppStore();
diff --git a/src/components/app.tsx b/src/components/app.tsx
index 9d14fcb..d8b130b 100644
--- a/src/components/app.tsx
+++ b/src/components/app.tsx
@@ -1,26 +1,8 @@
-import {
- ApolloClient,
- ApolloProvider,
- gql,
- InMemoryCache,
- useQuery
-} from '@apollo/client';
+import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client';
import { FunctionalComponent, FunctionComponent, h } from 'preact';
-import { globalState } from '../global.state';
import { ProjectPanel } from './projects/project-panel';
-import * as styles from './app.scss';
-
-const LIST_PROJECT = gql`
- query listProject {
- findProjects {
- id
- name
- sshUrl
- comment
- webUrl
- }
- }
-`;
+import styles from './app.scss';
+import { OverlayContainer } from './commons/overlay/overlay';
const client = new ApolloClient({
uri: '/api/graphql',
@@ -32,18 +14,15 @@ const App: FunctionalComponent = () => {
+
);
};
const Content: FunctionComponent = () => {
- const { loading, data } = useQuery(LIST_PROJECT);
- globalState.projects = data?.findProjects;
- console.log(globalState.projects);
- const Loading: FunctionComponent = () =>
Loading...
;
const Board: FunctionComponent = () => ;
- return loading ? : ;
+ return ;
};
export default App;
diff --git a/src/components/commons/overlay/overlay.scss b/src/components/commons/overlay/overlay.scss
new file mode 100644
index 0000000..96056ed
--- /dev/null
+++ b/src/components/commons/overlay/overlay.scss
@@ -0,0 +1,6 @@
+.mask {
+ @apply opacity-50 bg-white fixed top-0 bottom-0 left-0 right-0;
+}
+.body {
+ @apply fixed top-0 bottom-0 left-0 right-0;
+}
diff --git a/src/components/commons/overlay/overlay.scss.d.ts b/src/components/commons/overlay/overlay.scss.d.ts
new file mode 100644
index 0000000..a0348e3
--- /dev/null
+++ b/src/components/commons/overlay/overlay.scss.d.ts
@@ -0,0 +1,11 @@
+// This file is automatically generated from your CSS. Any edits will be overwritten.
+declare namespace OverlayScssNamespace {
+ export interface IOverlayScss {
+ body: string;
+ mask: string;
+ }
+}
+
+declare const OverlayScssModule: OverlayScssNamespace.IOverlayScss;
+
+export = OverlayScssModule;
diff --git a/src/components/commons/overlay/overlay.store.ts b/src/components/commons/overlay/overlay.store.ts
new file mode 100644
index 0000000..5ee48f9
--- /dev/null
+++ b/src/components/commons/overlay/overlay.store.ts
@@ -0,0 +1,26 @@
+import { makeAutoObservable, observable } from 'mobx';
+import { ComponentChild } from 'preact';
+
+export class OverlayStore {
+ @observable.ref overlays: [string, ComponentChild][] = [];
+
+ constructor() {
+ makeAutoObservable(this, {
+ overlays: observable.shallow
+ });
+ }
+
+ addOverlay(id: string, overlay: ComponentChild) {
+ this.overlays = [...this.overlays, [id, overlay]];
+ }
+
+ removeOverlay(id: string) {
+ const index = this.overlays.findIndex(it => it[0] === id);
+ if (index === -1) {
+ return;
+ }
+ this.overlays.splice(index, 1);
+ }
+}
+
+export const overlayStore = new OverlayStore();
diff --git a/src/components/commons/overlay/overlay.tsx b/src/components/commons/overlay/overlay.tsx
new file mode 100644
index 0000000..f417399
--- /dev/null
+++ b/src/components/commons/overlay/overlay.tsx
@@ -0,0 +1,78 @@
+import { ComponentChild, createContext, Fragment, h } from 'preact';
+import styles from './overlay.scss';
+
+import { observer } from 'mobx-react';
+import { overlayStore } from './overlay.store';
+import { useContext, useState } from 'preact/hooks';
+import { createPortal } from 'preact/compat';
+
+interface Props {
+ content: ComponentChild;
+ overlayId?: string;
+ onClose?: () => void;
+ onOk?: () => void;
+ onCancel?: () => void;
+}
+
+interface OverlayContext {
+ close: () => void;
+}
+
+const rootElement = document.createElement('div');
+rootElement.classList.add('overlay-root');
+document.body.appendChild(rootElement);
+
+export const createOverlay = (props: Props) => {
+ return new Promise(resolve => {
+ const id = Math.random()
+ .toString(36)
+ .substr(2, 6);
+ props.overlayId = props.overlayId ?? id;
+ const overlay = (
+ resolve(undefined)}>
+ );
+ overlayStore.addOverlay(props.overlayId, overlay);
+ });
+};
+
+export const overlayContext = createContext({
+ close: () => undefined
+});
+
+const OverlayProvider = overlayContext.Provider;
+
+export const useOverlay = () => {
+ return useContext(overlayContext);
+};
+
+export const Overlay = (props: Props) => {
+ const [isVisible, setIsVisible] = useState(true);
+ const close = () => {
+ setIsVisible(false);
+ overlayStore.removeOverlay(props.overlayId!);
+ props.onClose?.();
+ };
+ const controller = {
+ close
+ };
+ return createPortal(
+ isVisible ? (
+
+
+
+ {props.content}
+
+
+ ) : (
+
+ ),
+ rootElement
+ );
+};
+
+export const OverlayContainer = observer(() => {
+ const list = overlayStore.overlays.map(item => (
+ {item[1]}
+ ));
+ return {list}
;
+});
diff --git a/src/components/projects/project-editor.scss b/src/components/projects/project-editor.scss
new file mode 100644
index 0000000..80462db
--- /dev/null
+++ b/src/components/projects/project-editor.scss
@@ -0,0 +1,34 @@
+.editor {
+ @apply bg-white shadow-lg p-4 rounded-lg text-gray-800
+ top-1/4 left-1/2 absolute
+ transform -translate-x-1/2 -translate-y-1/2
+ w-5/6;
+}
+.form {
+ @apply bg-white;
+ & > * {
+ @apply block;
+ }
+ label {
+ @apply my-4 relative flex;
+ }
+ .label {
+ @apply text-gray-700 w-20 inline-block text-right;
+
+ &::after {
+ content: ':';
+ }
+ }
+ .controller {
+ @apply border-b border-gray-300 flex-auto;
+ }
+ .submitBtn {
+ @apply px-2 py-1 rounded-full bg-red-400 text-white;
+ }
+ .cancelBtn {
+ @apply px-2 py-1 rounded-full bg-gray-100 text-gray-700 ml-2;
+ }
+ .footer {
+ @apply text-right;
+ }
+}
diff --git a/src/components/projects/project-editor.scss.d.ts b/src/components/projects/project-editor.scss.d.ts
new file mode 100644
index 0000000..21468e0
--- /dev/null
+++ b/src/components/projects/project-editor.scss.d.ts
@@ -0,0 +1,16 @@
+// This file is automatically generated from your CSS. Any edits will be overwritten.
+declare namespace ProjectEditorScssNamespace {
+ export interface IProjectEditorScss {
+ cancelBtn: string;
+ controller: string;
+ editor: string;
+ footer: string;
+ form: string;
+ label: string;
+ submitBtn: string;
+ }
+}
+
+declare const ProjectEditorScssModule: ProjectEditorScssNamespace.IProjectEditorScss;
+
+export = ProjectEditorScssModule;
diff --git a/src/components/projects/project-editor.tsx b/src/components/projects/project-editor.tsx
new file mode 100644
index 0000000..0921433
--- /dev/null
+++ b/src/components/projects/project-editor.tsx
@@ -0,0 +1,108 @@
+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 { useOverlay } from '../commons/overlay/overlay';
+import styles from './project-editor.scss';
+
+interface Props {
+ id?: string;
+ isCreate: boolean;
+}
+
+const CREATE_PROJECT = gql`
+ mutation CreateProject($project: CreateProjectInput!) {
+ createProject(project: $project) {
+ id
+ name
+ comment
+ sshUrl
+ webUrl
+ webHookSecret
+ deletedAt
+ }
+ }
+`;
+
+export const ProjectEditor = (props: Props) => {
+ useApolloClient();
+ const isCreate = props.id === undefined;
+
+ const [createProject] = useMutation(CREATE_PROJECT);
+
+ const { close } = useOverlay();
+ const cancel = (ev: MouseEvent) => {
+ ev.preventDefault();
+ close();
+ };
+
+ const [project] = useState({
+ name: '',
+ comment: '',
+ sshUrl: '',
+ webHookSecret: ''
+ });
+ const submitForm = async (values: CreateProjectInput) => {
+ try {
+ if (props.id === undefined) {
+ await createProject({
+ variables: { project: values }
+ });
+ }
+ close();
+ } finally {
+ //
+ }
+ };
+
+ return (
+
+ );
+};
diff --git a/src/components/projects/project-panel.scss b/src/components/projects/project-panel.scss
index 73cfed1..7ddd8f8 100644
--- a/src/components/projects/project-panel.scss
+++ b/src/components/projects/project-panel.scss
@@ -27,3 +27,14 @@
@apply text-red-100;
}
}
+
+.operations {
+ @apply p-2 bg-gray-50;
+
+ button {
+ @apply shadow hover:shadow-md py-1 px-2 rounded-lg transition-all;
+ &.primaryBtn {
+ @apply bg-red-400 text-white hover:bg-red-500;
+ }
+ }
+}
diff --git a/src/components/projects/project-panel.scss.d.ts b/src/components/projects/project-panel.scss.d.ts
index 22f6ea3..a17c296 100644
--- a/src/components/projects/project-panel.scss.d.ts
+++ b/src/components/projects/project-panel.scss.d.ts
@@ -4,7 +4,9 @@ declare namespace ProjectPanelScssNamespace {
item: string;
itemActive: string;
list: string;
+ operations: string;
panel: string;
+ primaryBtn: string;
}
}
diff --git a/src/components/projects/project-panel.tsx b/src/components/projects/project-panel.tsx
index 6fda746..604d19a 100644
--- a/src/components/projects/project-panel.tsx
+++ b/src/components/projects/project-panel.tsx
@@ -1,37 +1,86 @@
-import { observer } from 'mobx-react';
-import { FunctionComponent, h } from 'preact';
+import { gql, useQuery } from '@apollo/client';
+import { useLocalStore } from 'mobx-react';
+import { h } from 'preact';
+import { forwardRef } from 'preact/compat';
+import { useImperativeHandle, useRef } from 'preact/hooks';
+import { appStore } from '../../app.store';
import { Project } from '../../generated/graphql';
-import { GlobalState, globalState } from '../../global.state';
+import { createOverlay } from '../commons/overlay/overlay';
+import { ProjectEditor } from './project-editor';
import * as styles from './project-panel.scss';
+const FIND_PROJECTS = gql`
+ query FindProjects {
+ projects: findProjects {
+ id
+ name
+ comment
+ sshUrl
+ webUrl
+ webHookSecret
+ deletedAt
+ }
+ }
+`;
+interface ListRef {
+ refetch: () => void;
+}
export function ProjectPanel() {
+ const listRef = useRef();
+ const addProject = () => {
+ createOverlay({
+ content:
+ }).then(() => {
+ listRef.current.refetch();
+ });
+ };
+
return (
);
}
-const List: FunctionComponent<{ globalState: GlobalState }> = observer(
- ({ globalState }) => {
- const setCurrProejct = (project: Project) => {
- globalState.setCurrentProject(project);
- };
- console.log('change');
+const List = forwardRef((_, ref) => {
+ const { data, refetch } = useQuery<{
+ projects: Project[];
+ }>(FIND_PROJECTS);
- const list = globalState.projects?.map(item => (
- setCurrProejct(item)}
- >
- {item.name}
- {item.comment}
-
- ));
- return {list}
;
- }
-);
+ const state = useLocalStore<{ currentProject?: Project }>(() => ({
+ currentProject: undefined
+ }));
+
+ const setCurrProject = (project: Project) => {
+ state.currentProject = project;
+ appStore.setMain(JSON.stringify(project));
+ };
+
+ useImperativeHandle(ref, () => ({
+ refetch
+ }));
+
+ const list = data?.projects?.map(item => (
+ setCurrProject(item)}
+ >
+ {item.name}
+ {item.comment}
+
+ ));
+ return {list}
;
+});
diff --git a/src/generated/graphql.tsx b/src/generated/graphql.tsx
index 1661e38..3916ef0 100644
--- a/src/generated/graphql.tsx
+++ b/src/generated/graphql.tsx
@@ -1,12 +1,8 @@
import { gql } from '@apollo/client';
export type Maybe = T | null;
-export type Exact = {
- [K in keyof T]: T[K];
-};
-export type MakeOptional = Omit &
- { [SubKey in K]?: Maybe };
-export type MakeMaybe = Omit &
- { [SubKey in K]: Maybe };
+export type Exact = { [K in keyof T]: T[K] };
+export type MakeOptional = Omit & { [SubKey in K]?: Maybe };
+export type MakeMaybe = Omit & { [SubKey in K]: Maybe };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: string;
@@ -34,6 +30,7 @@ export type Project = {
deletedAt?: Maybe;
};
+
export type Query = {
__typename?: 'Query';
hello: Hello;
@@ -41,6 +38,7 @@ export type Query = {
findProject: Project;
};
+
export type QueryFindProjectArgs = {
id: Scalars['String'];
};
@@ -52,15 +50,18 @@ export type Mutation = {
deleteProject: Scalars['Float'];
};
+
export type MutationCreateProjectArgs = {
project: CreateProjectInput;
};
+
export type MutationModifyProjectArgs = {
project: UpdateProjectInput;
id: Scalars['String'];
};
+
export type MutationDeleteProjectArgs = {
id: Scalars['String'];
};