diff --git a/server/src/core/controllers/app.ts b/server/src/core/controllers/app.ts index 795e556..36037a5 100644 --- a/server/src/core/controllers/app.ts +++ b/server/src/core/controllers/app.ts @@ -84,7 +84,6 @@ export class App { } async appStartController() { - console.log(App.staticFilesStoreDir()); if (await dirIsExists(App.staticFilesStoreDir())) { return; } diff --git a/server/src/core/controllers/crud_controller.ts b/server/src/core/controllers/crud_controller.ts index 5a0862e..e3916d8 100644 --- a/server/src/core/controllers/crud_controller.ts +++ b/server/src/core/controllers/crud_controller.ts @@ -12,7 +12,7 @@ export class CrudController extends CoreHttpController { constructor(routerModel: IRouteModel) { super(routerModel); - this.url = "/" + routerModel.url; + this.mainURL = "/" + routerModel.url; this.validationModel = routerModel.validationModel; this.dataBaseModel = routerModel.databaseModel; this.init(); diff --git a/server/src/core/controllers/http_controller.ts b/server/src/core/controllers/http_controller.ts index 9767e6b..fc760cf 100644 --- a/server/src/core/controllers/http_controller.ts +++ b/server/src/core/controllers/http_controller.ts @@ -3,16 +3,33 @@ import { Result } from "../helper/result"; import { Router, Request, Response } from "express"; import { IRouteModel, Routes } from "../interfaces/router"; -export type CallBackFunction = (a: T) => Promise>; +type Method = + | "all" + | "get" + | "post" + | "put" + | "delete" + | "patch" + | "options" + | "head"; + +export type CallbackStrategyWithValidationModel = ( + a: T +) => Promise>; +// TODO(IDOTSUDO):NEED IMPLEMENTS +// interface ISubSetFeatureRouter{ +// method:Method, +// fn:CallbackStrategyWithValidationModel +// } abstract class ICoreHttpController { - abstract url: string; + abstract mainURL: string; public router = Router(); abstract call(): Routes; } export class CoreHttpController implements ICoreHttpController { - url: string; + mainURL: string; validationModel: any; routes = { @@ -25,35 +42,34 @@ export class CoreHttpController implements ICoreHttpController { public router = Router(); constructor(routerModel: IRouteModel) { - this.url = "/" + routerModel.url; + this.mainURL = "/" + routerModel.url; this.validationModel = routerModel.validationModel; } call(): Routes { if (this.routes["POST"] != null) { - this.router.post( - this.url, + this.mainURL, validationModelMiddleware(this.validationModel), (req, res) => this.requestResponseController(req, res, this.routes["POST"]) ); } if (this.routes["DELETE"] != null) { - this.router.delete(this.url, (req, res) => + this.router.delete(this.mainURL, (req, res) => this.requestResponseController(req, res, this.routes["DELETE"]) ); } if (this.routes["PUT"] != null) { this.router.put( - this.url, + this.mainURL, validationModelMiddleware(this.validationModel), (req, res) => this.requestResponseController(req, res, this.routes["PUT"]) ); } if (this.routes["GET"] != null) { - this.router.get(this.url, (req, res) => + this.router.get(this.mainURL, (req, res) => this.requestResponseController(req, res, this.routes["GET"]) ); } @@ -62,18 +78,17 @@ export class CoreHttpController implements ICoreHttpController { router: this.router, }; } - public put(usecase: CallBackFunction) { + public put(usecase: CallbackStrategyWithValidationModel) { this.routes["PUT"] = usecase; } - public delete(usecase: CallBackFunction) { + public delete(usecase: CallbackStrategyWithValidationModel) { this.routes["DELETE"] = usecase; } public async requestResponseController( req: Request, res: Response, - usecase: CallBackFunction + usecase: CallbackStrategyWithValidationModel ) { - let payload = null; if (req["model"] != undefined) { @@ -98,11 +113,11 @@ export class CoreHttpController implements ICoreHttpController { } ); } - public post(usecase: CallBackFunction) { + public post(usecase: CallbackStrategyWithValidationModel) { this.routes["POST"] = usecase; } - public get(usecase: CallBackFunction) { + public get(usecase: CallbackStrategyWithValidationModel) { this.routes["GET"] = usecase; } } diff --git a/server/src/core/helper/cancelable_promise.ts b/server/src/core/helper/cancelable_promise.ts deleted file mode 100644 index 0519ecb..0000000 --- a/server/src/core/helper/cancelable_promise.ts +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/server/src/core/middlewares/validation_auth.ts b/server/src/core/middlewares/validation_auth.ts index d044c30..178441e 100644 --- a/server/src/core/middlewares/validation_auth.ts +++ b/server/src/core/middlewares/validation_auth.ts @@ -1,12 +1,15 @@ -// import { RequestHandler } from "express"; +import { RequestHandler } from "express"; -// export const validationMiddleware = ( -// type: any, -// value = 'body', -// skipMissingProperties = false, -// whitelist = true, -// forbidNonWhitelisted = true, -// ): RequestHandler => { - - -// } \ No newline at end of file +export const validationMiddleware = ( + type: any, + value = "body", + skipMissingProperties = false, + whitelist = true, + forbidNonWhitelisted = true +): RequestHandler => { + // TODO:(IDONTSUDO) need TOKEN + // return nextTick + return (req, res, next) => { + + } +}; diff --git a/server/src/core/model/meta_data_file_manager_model.ts b/server/src/core/model/meta_data_file_manager_model.ts index 21097c5..6520b80 100644 --- a/server/src/core/model/meta_data_file_manager_model.ts +++ b/server/src/core/model/meta_data_file_manager_model.ts @@ -1,5 +1,4 @@ export enum EventsFileChanger { - remove = "remove", update = "update", delete = "delete", create = "create", @@ -18,6 +17,7 @@ export class MetaDataFileManagerModel { this.event = event; this.unixTime = Date.now(); } + public get timeString(): string { const date = new Date(this.unixTime * 1000); const hours = date.getHours(); diff --git a/server/src/core/model/pipeline_meta.ts b/server/src/core/model/pipeline_meta.ts index cc227ee..749f0d3 100644 --- a/server/src/core/model/pipeline_meta.ts +++ b/server/src/core/model/pipeline_meta.ts @@ -1,7 +1,6 @@ export interface IPipelineMeta { - pipelineIsRunning: boolean; - projectUUID?: string | null; - lastProcessCompleteCount: number | null; - error: any; - } - \ No newline at end of file + pipelineIsRunning: boolean; + projectUUID?: string | null; + lastProcessCompleteCount: number | null; + error: any; +} diff --git a/server/src/core/services/executor_program_service.ts b/server/src/core/services/executor_program_service.ts index 67b4666..102b5ae 100644 --- a/server/src/core/services/executor_program_service.ts +++ b/server/src/core/services/executor_program_service.ts @@ -9,9 +9,7 @@ import { EXEC_TYPE, ExecError, SpawnError } from "../model/exec_error_model"; abstract class IExecutorProgramService { abstract execPath: string; } -class P{ -} export class ExecutorProgramService extends TypedEvent> implements IExecutorProgramService diff --git a/server/src/core/services/pipeline_real_time_service.ts b/server/src/core/services/pipeline_real_time_service.ts index 374e732..070ac61 100644 --- a/server/src/core/services/pipeline_real_time_service.ts +++ b/server/src/core/services/pipeline_real_time_service.ts @@ -34,11 +34,12 @@ export class PipelineRealTimeService extends TypedEvent { this.iterationErrorObserver(iteration); // TODO(IDONTSUDO): implements - // this.iterationLogSaver() + this.iterationLogSaver(iteration); } - iterationLogSaver() { - throw new Error("Method not implemented."); + iterationLogSaver(iteration: Iteration): void { + // throw new Error("Method not implemented."); + // citeration.result.data } iterationErrorObserver(iteration: Iteration): void { @@ -70,8 +71,7 @@ export class PipelineRealTimeService extends TypedEvent { path: string, projectUUID: string ): void { - const testPath = path + "/context"; - const stack = new StackService(pipelineModels, testPath); + const stack = new StackService(pipelineModels, path); this.status["projectUUID"] = projectUUID; this.status["pipelineIsRunning"] = true; stack.on(this.pipelineSubscriber); diff --git a/server/src/core/services/stack_service.ts b/server/src/core/services/stack_service.ts index d6f5e33..3d8d3c3 100644 --- a/server/src/core/services/stack_service.ts +++ b/server/src/core/services/stack_service.ts @@ -51,6 +51,7 @@ export class StackService "$PATH", this.path ); + console.log(processMetaData.process.command) return processMetaData; } public async call() { diff --git a/server/src/features/pipelines/pipeline_model.ts b/server/src/features/pipelines/pipeline_model.ts index f321f52..28170cf 100644 --- a/server/src/features/pipelines/pipeline_model.ts +++ b/server/src/features/pipelines/pipeline_model.ts @@ -1,4 +1,4 @@ -import { IsMongoId, IsEnum, IsOptional } from "class-validator"; +import { IsMongoId, IsOptional } from "class-validator"; import { Schema, model } from "mongoose"; import { IProcess, StackGenerateType } from "../../core/model/process_model"; import { TriggerModel, triggerSchema } from "../triggers/trigger_model"; diff --git a/server/src/features/pipelines/pipeline_presentation.ts b/server/src/features/pipelines/pipeline_presentation.ts index 6476392..d213884 100644 --- a/server/src/features/pipelines/pipeline_presentation.ts +++ b/server/src/features/pipelines/pipeline_presentation.ts @@ -12,5 +12,4 @@ export class PipelinePresentation extends CrudController< databaseModel: PipelineDBModel, }); } - } diff --git a/server/src/features/projects/projects_model.ts b/server/src/features/projects/projects_model.ts index 5bc18f3..5aa7f6a 100644 --- a/server/src/features/projects/projects_model.ts +++ b/server/src/features/projects/projects_model.ts @@ -3,7 +3,7 @@ import { PipelineModel, schemaPipeline } from "../pipelines/pipeline_model"; import { IsArray, IsOptional, IsString } from "class-validator"; export interface IProjectModel { - _id: string; + _id?:string; pipelines: [PipelineModel]; rootDir: string; description: string; diff --git a/server/src/features/realtime/realtime_presentation.ts b/server/src/features/realtime/realtime_presentation.ts index eb99350..06ed145 100644 --- a/server/src/features/realtime/realtime_presentation.ts +++ b/server/src/features/realtime/realtime_presentation.ts @@ -11,6 +11,7 @@ export class RealTimeValidationModel { public id: string; } + export class RealTimePresentation extends CoreHttpController { constructor() { super({ @@ -20,5 +21,6 @@ export class RealTimePresentation extends CoreHttpController { + el.process.type = EXEC_TYPE.EXEC; + }); + pipelineRealTimeService.runPipeline( projectModel.pipelines, - projectModel.rootDir, + App.staticFilesStoreDir() + projectModel.rootDir + "/", projectModel._id ); - + return Result.ok({ status: "ok" }); } } + +// /Users/idontsudo/Desktop/testdeck-mocha-seed/server/build/public/ce4e7710-73dc-47fc-87ee-d448ea2412ce +// new ObjectId("6554c22d2ef337587505a494") diff --git a/server/test/model/mock_pipelines.ts b/server/test/model/mock_pipelines.ts index c166955..b136d0e 100644 --- a/server/test/model/mock_pipelines.ts +++ b/server/test/model/mock_pipelines.ts @@ -1,11 +1,12 @@ import { EXEC_TYPE } from "../../src/core/model/exec_error_model"; import { + IPipeline, IssueType, StackGenerateType, } from "../../src/core/model/process_model"; import { TriggerType } from "../../src/features/triggers/trigger_model"; -export const mockSimplePipeline = [ +export const mockSimplePipeline:IPipeline[] = [ { process: { type: EXEC_TYPE.EXEC, diff --git a/ui/package.json b/ui/package.json index 4c422a8..853ca31 100644 --- a/ui/package.json +++ b/ui/package.json @@ -17,6 +17,7 @@ "i18next": "^23.6.0", "mobx": "^6.10.0", "mobx-react-lite": "^4.0.4", + "mobx-store-inheritance": "^1.0.6", "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^13.3.1", diff --git a/ui/src/core/repository/socket_repository.ts b/ui/src/core/repository/socket_repository.ts index 6eda6e7..406e0d6 100644 --- a/ui/src/core/repository/socket_repository.ts +++ b/ui/src/core/repository/socket_repository.ts @@ -4,6 +4,7 @@ export class SocketRepository { serverURL = "ws://localhost:4001"; socket: Socket | undefined; async connect() { + console.log('connect') const socket = io(this.serverURL); this.socket = socket; socket.connect(); diff --git a/ui/src/core/routers/routers.tsx b/ui/src/core/routers/routers.tsx index cd0d02e..0223c37 100644 --- a/ui/src/core/routers/routers.tsx +++ b/ui/src/core/routers/routers.tsx @@ -5,7 +5,7 @@ import { } from "../../features/all_projects/presentation/all_projects_screen"; import { PipelineInstanceScreen, - PipelineScreenPath, + PipelineInstanceScreenPath, } from "../../features/pipeline_instance_main_screen/pipeline_instance_screen"; import { SelectProjectScreen, @@ -15,7 +15,15 @@ import { CreatePipelineScreen, CreatePipelineScreenPath, } from "../../features/create_pipeline/presentation/create_pipeline_screen"; -import { CreateProjectScreen, CreateProjectScreenPath } from "../../features/create_project/create_project_screen"; +import { + CreateProjectScreen, + CreateProjectScreenPath, +} from "../../features/create_project/create_project_screen"; +import { + CreateTriggerScreenPath, + TriggerScreen, +} from "../../features/create_trigger/presentation/create_trigger_screen"; +import { CreateProcessScreen, CreateProcessScreenPath } from "../../features/create_process/presentation/create_process_screen"; export const router = createBrowserRouter([ { @@ -23,7 +31,7 @@ export const router = createBrowserRouter([ element: , }, { - path: PipelineScreenPath, + path: PipelineInstanceScreenPath, element: , }, { @@ -38,4 +46,12 @@ export const router = createBrowserRouter([ path: CreateProjectScreenPath, element: , }, + { + path: CreateTriggerScreenPath, + element: , + }, + { + path: CreateProcessScreenPath, + element: , + }, ]); diff --git a/ui/src/core/ui/list/list.tsx b/ui/src/core/ui/list/list.tsx index c60af76..7a165fb 100644 --- a/ui/src/core/ui/list/list.tsx +++ b/ui/src/core/ui/list/list.tsx @@ -4,6 +4,7 @@ import { ReactComponent as DeleteIcon } from "../../assets/icons/delete.svg"; import { observer } from "mobx-react-lite"; import { v4 } from "uuid"; +import { ILinkTypography, LinkTypography } from "../link/link"; export type CallBackFunction = (el: ListElement, index: number) => void; @@ -20,6 +21,7 @@ export enum Icon { export interface IPropsList { values: ListElement[]; headers?: string; + link?: ILinkTypography; onClick?: CallBackFunction; icon: Icon; } @@ -28,13 +30,20 @@ export const List: React.FunctionComponent = observer((props) => { props.values.map((el) => { if (el.id === undefined) { el.id = v4(); - return el + return el; } - return el + return el; }); return (
{props.headers !== undefined ? <>{props.headers} : <>} + {props.link !== undefined ? ( +
+ +
+ ) : ( + <> + )} {props.values.map((el, index) => { return ( = observer( path={props.path} largeText={props.largeText} minText={props.minText} + needBackButton={props.needBackButton} /> {props.isError ? ( <> diff --git a/ui/src/features/all_projects/data/project_repository.ts b/ui/src/features/all_projects/data/project_repository.ts index 247538d..4d4609e 100644 --- a/ui/src/features/all_projects/data/project_repository.ts +++ b/ui/src/features/all_projects/data/project_repository.ts @@ -1,3 +1,8 @@ -import { HttpRepository } from "../../../core/repository/http_repository"; +import { HttpMethod, HttpRepository } from "../../../core/repository/http_repository"; +import { IProjectModel } from "../model/project_model"; -export class ProjectRepository extends HttpRepository {} +export class ProjectRepository extends HttpRepository { + async getAllProject() { + return this.jsonRequest(HttpMethod.GET,'/project') + } +} diff --git a/ui/src/features/all_projects/model/project_model.ts b/ui/src/features/all_projects/model/project_model.ts new file mode 100644 index 0000000..e74166b --- /dev/null +++ b/ui/src/features/all_projects/model/project_model.ts @@ -0,0 +1,8 @@ +import { PipelineModel } from "../../create_project/create_project_repository"; + +export interface IProjectModel { + _id?: string; + pipelines: [PipelineModel]; + rootDir: string; + description: string; +} diff --git a/ui/src/features/all_projects/presentation/all_projects_screen.tsx b/ui/src/features/all_projects/presentation/all_projects_screen.tsx index f62927c..fd5af5b 100644 --- a/ui/src/features/all_projects/presentation/all_projects_screen.tsx +++ b/ui/src/features/all_projects/presentation/all_projects_screen.tsx @@ -1,18 +1,35 @@ import * as React from "react"; +import { AllProjectStore } from "./all_projects_store"; +import { ProjectRepository } from "../data/project_repository"; +import { LoadPage } from "../../../core/ui/pages/load_page"; +import { observer } from "mobx-react-lite"; import { SelectProjectScreenPath } from "../../select_project/presentation/select_project"; -import { Header } from "../../../core/ui/header/header"; export const AllProjectScreenPath = "/"; -export const AllProjectScreen: React.FunctionComponent = () => { +export const AllProjectScreen: React.FunctionComponent = observer(() => { + const [allProjectStore] = React.useState( + () => new AllProjectStore(new ProjectRepository()) + ); + return ( <> -
+ {allProjectStore.projectsModels?.map((el) => { + return
{el.description}
; + })} +
+ } /> ); -}; +}); diff --git a/ui/src/features/all_projects/presentation/all_projects_store.ts b/ui/src/features/all_projects/presentation/all_projects_store.ts index c649338..10c5a89 100644 --- a/ui/src/features/all_projects/presentation/all_projects_store.ts +++ b/ui/src/features/all_projects/presentation/all_projects_store.ts @@ -1,12 +1,42 @@ -import { makeAutoObservable } from "mobx"; +import makeAutoObservable from "mobx-store-inheritance"; import { ProjectRepository } from "../data/project_repository"; - -class AllProjectStore { +import { IProjectModel } from "../model/project_model"; +import { Result } from "../../../core/helper/result"; + +// TODO(IDONTSUDO): нужно переписать все сторы под BaseStore +class BaseStore { + isLoading = false; + isError = false; + + async loadingHelper(callBack: Promise>) { + this.isLoading = true; + + const result = await callBack; + if (result.isFailure()) { + this.isError = true; + this.isLoading = false; + return result.forward(); + } + + this.isLoading = false; + return result; + } +} +export class AllProjectStore extends BaseStore { + projectsModels?: IProjectModel[]; + repository: ProjectRepository; constructor(repository: ProjectRepository) { - + super(); + this.repository = repository; + this.getProjects(); makeAutoObservable(this); } - + async getProjects() { + const result = await this.loadingHelper(this.repository.getAllProject()); + if (result.isSuccess()) { + this.projectsModels = result.value; + } + } } -export const allProjectStore = new AllProjectStore(new ProjectRepository()); \ No newline at end of file + \ No newline at end of file diff --git a/ui/src/features/create_pipeline/presentation/create_pipeline_screen.tsx b/ui/src/features/create_pipeline/presentation/create_pipeline_screen.tsx index 74b966b..2d95a95 100644 --- a/ui/src/features/create_pipeline/presentation/create_pipeline_screen.tsx +++ b/ui/src/features/create_pipeline/presentation/create_pipeline_screen.tsx @@ -4,6 +4,8 @@ import { LoadPage } from "../../../core/ui/pages/load_page"; import { createPipelineStore } from "./create_pipeline_store"; import { observer } from "mobx-react-lite"; import { Icon, List } from "../../../core/ui/list/list"; +import { CreateTriggerScreenPath } from "../../create_trigger/presentation/create_trigger_screen"; +import { CreateProcessScreenPath } from "../../create_process/presentation/create_process_screen"; export const CreatePipelineScreenPath = "/create_pipeline"; @@ -19,6 +21,7 @@ export const CreatePipelineScreen: React.FunctionComponent = observer(() => { { return { text: el.description, id: el._id }; })} @@ -41,6 +44,7 @@ export const CreatePipelineScreen: React.FunctionComponent = observer(() => { { return { text: el.description, id: el._id }; })} diff --git a/ui/src/features/create_process/model/process_model.ts b/ui/src/features/create_process/model/process_model.ts index f047388..ac128f4 100644 --- a/ui/src/features/create_process/model/process_model.ts +++ b/ui/src/features/create_process/model/process_model.ts @@ -10,6 +10,7 @@ export interface IProcess extends DatabaseModel { timeout?: number; commit?: string | undefined; } + export enum EXEC_TYPE { SPAWN = "SPAWN", EXEC = "EXEC", @@ -19,7 +20,6 @@ export enum IssueType { ERROR = "ERROR", } export const processModelMock: IProcess = { - _id: "", description: "", type: EXEC_TYPE.SPAWN, command: "", diff --git a/ui/src/features/create_process/presentation/create_process_screen.tsx b/ui/src/features/create_process/presentation/create_process_screen.tsx index a660268..27159ce 100644 --- a/ui/src/features/create_process/presentation/create_process_screen.tsx +++ b/ui/src/features/create_process/presentation/create_process_screen.tsx @@ -12,7 +12,7 @@ import { import { Formik } from "formik"; import { Row, Col } from "antd"; import { EXEC_TYPE, IssueType, processModelMock } from "../model/process_model"; - +export const CreateProcessScreenPath = '/create/process' export const CreateProcessScreen = observer(() => { return (
diff --git a/ui/src/features/create_project/create_project_screen.tsx b/ui/src/features/create_project/create_project_screen.tsx index cd4522f..f12cc10 100644 --- a/ui/src/features/create_project/create_project_screen.tsx +++ b/ui/src/features/create_project/create_project_screen.tsx @@ -87,3 +87,4 @@ export const CreateProjectScreen: React.FunctionComponent = observer(() => { ); }); + \ No newline at end of file diff --git a/ui/src/features/create_trigger/presentation/create_trigger_screen.tsx b/ui/src/features/create_trigger/presentation/create_trigger_screen.tsx index fd16285..73524e2 100644 --- a/ui/src/features/create_trigger/presentation/create_trigger_screen.tsx +++ b/ui/src/features/create_trigger/presentation/create_trigger_screen.tsx @@ -4,7 +4,7 @@ import { CodeTriggerForm } from "./components/code_trigger_form"; import { observer } from "mobx-react-lite"; import { triggerStore } from "./trigger_store"; import { FileTriggerForm } from "./components/file_trigger_form"; -import { ReactComponent as DeleteIcon } from "../../../assets/icons/delete.svg"; +import { ReactComponent as DeleteIcon } from "../../../core/assets/icons/delete.svg"; import { Loader } from "../../../core/ui/loader/loader"; const { Title } = Typography; @@ -45,7 +45,7 @@ const Bottom = observer(() => { ); }); - +export const CreateTriggerScreenPath = '/create/trigger' export const TriggerScreen: React.FunctionComponent = observer(() => { return ( <> diff --git a/ui/src/features/pipeline_instance_main_screen/pipeline_instance_screen.tsx b/ui/src/features/pipeline_instance_main_screen/pipeline_instance_screen.tsx index 461be19..196fff4 100644 --- a/ui/src/features/pipeline_instance_main_screen/pipeline_instance_screen.tsx +++ b/ui/src/features/pipeline_instance_main_screen/pipeline_instance_screen.tsx @@ -3,7 +3,7 @@ import { Button } from "antd"; -export const PipelineScreenPath = '/pipeline_instance/:id' +export const PipelineInstanceScreenPath = '/pipeline_instance/:id' export const PipelineInstanceScreen: React.FunctionComponent = () => { return ( <> diff --git a/ui/src/features/select_project/presentation/select_project.tsx b/ui/src/features/select_project/presentation/select_project.tsx index 29eaf7d..1329709 100644 --- a/ui/src/features/select_project/presentation/select_project.tsx +++ b/ui/src/features/select_project/presentation/select_project.tsx @@ -1,12 +1,18 @@ import * as React from "react"; -import { selectProjectStore } from "./select_project_store"; + import { observer } from "mobx-react-lite"; import { LoadPage } from "../../../core/ui/pages/load_page"; import { CreateProjectScreenPath } from "../../create_project/create_project_screen"; +import { SelectProjectStore } from "./select_project_store"; +import { SelectProjectRepository } from "../data/select_project_repository"; export const SelectProjectScreenPath = "/select_project"; export const SelectProjectScreen: React.FunctionComponent = observer(() => { + const [selectProjectStore] = React.useState( + () => new SelectProjectStore(new SelectProjectRepository()) + ); + return ( <>