mvp progress

This commit is contained in:
IDONTSUDO 2023-11-10 12:06:40 +03:00
parent 9b16b25187
commit 6446da7e76
75 changed files with 1865 additions and 244 deletions

3
ui/.gitignore vendored
View file

@ -21,4 +21,5 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
package-lock.json
package-lock.json
todo.md

View file

@ -1,5 +1,5 @@
{
"name": "infinite-todo-list",
"name": "ui-robossembler",
"version": "0.1.0",
"private": true,
"dependencies": {
@ -11,14 +11,20 @@
"@types/node": "^16.18.46",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
"@types/socket.io-client": "^3.0.0",
"@types/uuid": "^9.0.2",
"formik-antd": "^2.0.4",
"i18next": "^23.6.0",
"mobx": "^6.10.0",
"mobx-react-lite": "^4.0.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-i18next": "^13.3.1",
"react-infinite-scroll-component": "^6.1.0",
"react-router-dom": "^6.18.0",
"react-scripts": "5.0.1",
"sass": "^1.66.1",
"socket.io-client": "^4.7.2",
"typescript": "^4.9.5",
"uuid": "^9.0.0",
"web-vitals": "^2.1.4"

View file

@ -11,7 +11,7 @@
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Infinite Todo List</title>
<title>robossembler: pipeline </title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

View file

Before

Width:  |  Height:  |  Size: 769 B

After

Width:  |  Height:  |  Size: 769 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 628 B

After

Width:  |  Height:  |  Size: 628 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 664 B

After

Width:  |  Height:  |  Size: 664 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 701 B

After

Width:  |  Height:  |  Size: 701 B

Before After
Before After

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" viewBox="0 0 64 64" id="error"><circle cx="32" cy="32" r="28" fill="none" stroke="#010101" stroke-miterlimit="10" stroke-width="4"></circle><line x1="32" x2="32" y1="18" y2="38" fill="none" stroke="#010101" stroke-miterlimit="10" stroke-width="4"></line><line x1="32" x2="32" y1="42" y2="46" fill="none" stroke="#010101" stroke-miterlimit="10" stroke-width="4"></line></svg>

After

Width:  |  Height:  |  Size: 434 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="800px" height="800px" viewBox="0 0 24 24" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.7071 4.29289C12.0976 4.68342 12.0976 5.31658 11.7071 5.70711L6.41421 11H20C20.5523 11 21 11.4477 21 12C21 12.5523 20.5523 13 20 13H6.41421L11.7071 18.2929C12.0976 18.6834 12.0976 19.3166 11.7071 19.7071C11.3166 20.0976 10.6834 20.0976 10.2929 19.7071L3.29289 12.7071C3.10536 12.5196 3 12.2652 3 12C3 11.7348 3.10536 11.4804 3.29289 11.2929L10.2929 4.29289C10.6834 3.90237 11.3166 3.90237 11.7071 4.29289Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 584 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="800px" height="800px" viewBox="0 0 24 24" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.7071 1.29289C14.0976 1.68342 14.0976 2.31658 13.7071 2.70711L12.4053 4.00896C17.1877 4.22089 21 8.16524 21 13C21 17.9706 16.9706 22 12 22C7.02944 22 3 17.9706 3 13C3 12.4477 3.44772 12 4 12C4.55228 12 5 12.4477 5 13C5 16.866 8.13401 20 12 20C15.866 20 19 16.866 19 13C19 9.2774 16.0942 6.23349 12.427 6.01281L13.7071 7.29289C14.0976 7.68342 14.0976 8.31658 13.7071 8.70711C13.3166 9.09763 12.6834 9.09763 12.2929 8.70711L9.29289 5.70711C9.10536 5.51957 9 5.26522 9 5C9 4.73478 9.10536 4.48043 9.29289 4.29289L12.2929 1.29289C12.6834 0.902369 13.3166 0.902369 13.7071 1.29289Z" fill="#0F1729"/>
</svg>

After

Width:  |  Height:  |  Size: 755 B

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

View file

@ -0,0 +1,45 @@
export {};
declare global {
interface Array<T> {
// @strict: The parameter is determined whether the arrays must be exactly the same in content and order of this relationship or simply follow the same requirements.
equals(array: Array<T>, strict: boolean): boolean;
lastElement(): T | undefined;
}
}
export const ArrayExtensions = () => {
if ([].equals === undefined) {
// eslint-disable-next-line no-extend-native
Array.prototype.equals = function (array, strict = true) {
if (!array) return false;
if (arguments.length === 1) strict = true;
if (this.length !== array.length) return false;
for (let i = 0; i < this.length; i++) {
if (this[i] instanceof Array && array[i] instanceof Array) {
if (!this[i].equals(array[i], strict)) return false;
} else if (strict && this[i] !== array[i]) {
return false;
} else if (!strict) {
return this.sort().equals(array.sort(), true);
}
}
return true;
};
}
if ([].lastElement === undefined) {
// eslint-disable-next-line no-extend-native
Array.prototype.lastElement = function () {
let instanceCheck = this;
if (instanceCheck === undefined) {
return undefined;
} else {
let instance = instanceCheck as [];
return instance[instance.length - 1];
}
};
}
};

View file

@ -0,0 +1,6 @@
import { ArrayExtensions } from "./array";
export const extensions = () =>{
ArrayExtensions()
}

View file

@ -0,0 +1,535 @@
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
/* eslint-disable @typescript-eslint/no-namespace */
function isAsyncFn(fn: Function) {
return fn.constructor.name === "AsyncFunction";
}
function isResult(value: unknown): value is Result<any, any, any> {
return value instanceof Ok || value instanceof Err;
}
interface SyncThenable {
isSync: true;
then<Fn extends () => Promise<any>>(cb: Fn): ReturnType<Fn>;
then<Fn extends () => any>(cb: Fn): SyncThenable;
}
function syncThenable(): SyncThenable {
function then<Fn extends () => Promise<any>>(cb: Fn): ReturnType<Fn>;
function then<Fn extends () => any>(cb: Fn): SyncThenable;
function then(cb: any) {
const result = cb();
if (result instanceof Promise) {
return result;
}
return syncThenable();
}
return {
isSync: true,
then,
};
}
function forEachValueThunkOrPromise<T>(
items: unknown[],
execFn: (value: T) => boolean,
foldFn: () => unknown
) {
let shouldBreak = false;
const result: any = items.reduce((prev: { then: Function }, valueOrThunk) => {
return prev.then(() => {
if (shouldBreak) {
return null;
}
function run(value: T) {
const isSuccess = execFn(value);
if (!isSuccess) {
shouldBreak = true;
}
}
const valueOrPromise =
typeof valueOrThunk === "function" ? valueOrThunk() : valueOrThunk;
if (valueOrPromise instanceof Promise) {
return valueOrPromise.then(run);
}
return run(valueOrPromise);
});
}, syncThenable());
if ((result as SyncThenable).isSync) {
return foldFn();
}
return result.then(() => {
return foldFn();
});
}
export type Result<
ErrorType,
OkType,
RollbackFn extends RollbackFunction = any
> = Ok<ErrorType, OkType, RollbackFn> | Err<ErrorType, OkType, RollbackFn>;
interface IResult<ErrorType, OkType> {
isSuccess(): this is Ok<ErrorType, OkType, any>;
isFailure(): this is Err<ErrorType, OkType, any>;
getOrNull(): OkType | null;
toString(): string;
inspect(): string;
fold<R>(
onSuccess: (value: OkType) => R,
onFailure: (error: ErrorType) => R
): R;
fold<R>(
onSuccess: (value: OkType) => Promise<R>,
onFailure: (error: ErrorType) => Promise<R>
): Promise<R>;
getOrDefault(defaultValue: OkType): OkType;
getOrElse(onFailure: (error: ErrorType) => OkType): OkType;
getOrElse(onFailure: (error: ErrorType) => Promise<OkType>): Promise<OkType>;
getOrThrow(): OkType;
map<T>(
fn: (value: OkType) => Promise<T>
): Promise<
JoinErrorTypes<
ErrorType,
T extends Result<any, any, any> ? T : Result<Error, T, any>
>
>;
map<T>(
fn: (value: OkType) => T
): JoinErrorTypes<
ErrorType,
T extends Result<any, any, any> ? T : Result<Error, T, any>
>;
rollback(): Result<Error, void> | Promise<Result<Error, void>>;
}
type InferErrorType<T extends Result<any, any, any>> = T extends Result<
infer Errortype,
any,
any
>
? Errortype
: never;
type InferOkType<T extends Result<any, any, any>> = T extends Result<
any,
infer OkType,
any
>
? OkType
: never;
type JoinErrorTypes<ErrorType, B extends Result<any, any, any>> = Result<
ErrorType | InferErrorType<B>,
InferOkType<B>,
any
>;
type ExtractErrorTypes<Tuple extends any[]> = {
[Index in keyof Tuple]: Tuple[Index] extends Result<any, any, any>
? InferErrorType<Tuple[Index]>
: never;
}[number];
type MapResultTupleToOkTypeTuple<Tuple extends any[]> = {
[Index in keyof Tuple]: Tuple[Index] extends Result<any, any, any>
? InferOkType<Tuple[Index]>
: never;
};
type RollbackFunction = (() => void) | (() => Promise<void>);
type HasAsyncRollbackFunction<T extends any[]> = {
[Index in keyof T]: T[Index] extends () => Promise<infer U> | infer U
? U extends Result<any, any, () => Promise<void>>
? true
: false
: false;
}[number] extends false
? false
: true;
type UnwrapThunks<T extends any[]> = {
[Index in keyof T]: T[Index] extends () => Promise<infer U>
? U
: T[Index] extends () => infer U
? U
: T[Index];
};
type HasAsyncThunk<T extends any[]> = {
[Index in keyof T]: T[Index] extends () => Promise<any> ? true : false;
}[number] extends false
? false
: true;
type PromiseReturnType<T extends (...args: any) => any> = T extends (
...args: any
) => Promise<infer U>
? U
: never;
// eslint-disable-next-line @typescript-eslint/no-redeclare
export namespace Result {
export function ok<
ErrorType extends unknown,
OkType,
RollbackFn extends RollbackFunction = any
>(
value?: OkType,
rollbackFn?: RollbackFn
): Result<ErrorType, OkType, RollbackFn> {
return new Ok<ErrorType, OkType, RollbackFn>(
value || null!,
rollbackFn
) as any;
}
export function error<
ErrorType extends unknown,
OkType extends unknown,
RollbackFn extends RollbackFunction = any
>(
error: ErrorType,
rollbackFn?: RollbackFn
): Result<ErrorType, OkType, RollbackFn> {
return new Err<ErrorType, OkType, RollbackFn>(error, rollbackFn);
}
type SafeReturnType<E, T> = T extends Result<any, any, any>
? Result<E | InferErrorType<T>, InferOkType<T>, never>
: Result<E, T, never>;
export function safe<T>(
fn: () => Promise<T>
): Promise<SafeReturnType<Error, T>>;
export function safe<T>(fn: () => T): SafeReturnType<Error, T>;
export function safe<ErrorType, T>(
err: ErrorType | (new (...args: any[]) => ErrorType),
fn: () => Promise<T>
): Promise<SafeReturnType<ErrorType, T>>;
export function safe<ErrorType, T>(
err: ErrorType | (new (...args: any[]) => ErrorType),
fn: () => T
): SafeReturnType<ErrorType, T>;
export function safe(errOrFn: any, fn?: any) {
const hasCustomError = fn !== undefined;
const execute = hasCustomError ? fn : errOrFn;
function getError(caughtError: Error) {
if (!hasCustomError) {
return caughtError;
}
if (typeof errOrFn === "function") {
return new errOrFn(caughtError);
}
return errOrFn;
}
try {
const resultOrPromise = execute();
if (resultOrPromise instanceof Promise) {
return resultOrPromise
.then((okValue) => {
return isResult(okValue) ? okValue : Result.ok(okValue);
})
.catch((caughtError) => error(getError(caughtError)));
}
return isResult(resultOrPromise)
? resultOrPromise
: Result.ok(resultOrPromise);
} catch (caughtError) {
return error(getError(caughtError as Error));
}
}
type CombineResult<
T extends (unknown | (() => unknown) | (() => Promise<unknown>))[]
> = Result<
ExtractErrorTypes<UnwrapThunks<T>>,
MapResultTupleToOkTypeTuple<UnwrapThunks<T>>,
HasAsyncRollbackFunction<T> extends true ? () => Promise<void> : () => void
>;
export function combine<
T extends (unknown | (() => unknown) | (() => Promise<unknown>))[]
>(
...items: T
): HasAsyncThunk<T> extends true
? Promise<CombineResult<T>>
: CombineResult<T> {
if (!items.length) {
throw new Error("Expected at least 1 argument");
}
const values: unknown[] = [];
const rollbacks: RollbackFunction[] = [];
let error: Err<unknown, unknown, any> | null = null;
function rollback() {
const reversedRollbacks = rollbacks.reverse();
const wrappedRollbackFns = reversedRollbacks.map((fn) => Result.wrap(fn));
let error: Err<unknown, unknown, any> | null = null;
return forEachValueThunkOrPromise(
wrappedRollbackFns,
(result: Result<unknown, unknown>) => {
if (result.isFailure()) {
error = Result.error<unknown, unknown, any>(result.error) as any;
return false;
}
return true;
},
() => error || ok()
);
}
return forEachValueThunkOrPromise(
items,
(result: Result<unknown, unknown>) => {
if (result.isFailure()) {
error = Result.error<unknown, unknown>(result.error, rollback) as any;
return false;
}
values.push(result.value);
rollbacks.push(() => result.rollback());
return true;
},
() => error || ok(values, rollback)
);
}
export function wrap<Fn extends (...args: any) => Promise<any>>(
fn: Fn
): (
...args: Parameters<Fn>
) => Promise<Result<Error, PromiseReturnType<Fn>, never>>;
export function wrap<Fn extends (...args: any) => any>(
fn: Fn
): (...args: Parameters<Fn>) => Result<Error, ReturnType<Fn>, never>;
export function wrap(fn: any) {
return function wrapped(...args: any) {
try {
const resultOrPromise = fn(...args);
if (resultOrPromise instanceof Promise) {
return resultOrPromise
.then((okValue) => Result.ok(okValue))
.catch((err) => error(err));
}
return ok(resultOrPromise);
} catch (err) {
return error(err);
}
};
}
}
abstract class Base<
ErrorType extends unknown,
OkType extends unknown,
RollbackFn extends RollbackFunction
> implements IResult<ErrorType, OkType>
{
constructor(protected readonly rollbackFn?: RollbackFn) {}
errorOrNull(): ErrorType | null {
if (this.isSuccess()) {
return null;
}
return (this as any).error as ErrorType;
}
getOrNull(): OkType | null {
if (this.isFailure()) {
return null;
}
return (this as any).value as OkType;
}
toString(): string {
throw new Error("Method not implemented.");
}
inspect(): string {
return this.toString();
}
fold<R>(
onSuccess: (value: OkType) => R,
onFailure: (error: ErrorType) => R
): R;
fold<R>(
onSuccess: (value: OkType) => Promise<R>,
onFailure: (error: ErrorType) => Promise<R>
): Promise<R>;
fold(onSuccess: any, onFailure: any) {
if (this.isFailure()) {
return onFailure(this.error);
}
return onSuccess((this as any).value as OkType);
}
getOrDefault(defaultValue: OkType): OkType {
if (this.isSuccess()) {
return this.value;
}
return defaultValue;
}
getOrElse(onFailure: (error: ErrorType) => OkType): OkType;
getOrElse(onFailure: (error: ErrorType) => Promise<OkType>): Promise<OkType>;
getOrElse(onFailure: any) {
if (this.isSuccess()) {
return isAsyncFn(onFailure) ? Promise.resolve(this.value) : this.value;
}
return onFailure((this as any).error as ErrorType);
}
getOrThrow(): OkType {
if (this.isFailure()) {
throw this.error;
}
return (this as any).value as OkType;
}
isSuccess(): this is Ok<ErrorType, OkType, RollbackFn> {
throw new Error("Method not implemented.");
}
isFailure(): this is Err<ErrorType, OkType, RollbackFn> {
throw new Error("Method not implemented.");
}
map<T>(
fn: (value: OkType) => Promise<T>
): Promise<
JoinErrorTypes<
ErrorType,
T extends Result<any, any, any> ? T : Result<Error, T, any>
>
>;
map<T>(
fn: (value: OkType) => T
): JoinErrorTypes<
ErrorType,
T extends Result<any, any, any> ? T : Result<Error, T, any>
>;
map(fn: any) {
if (this.isFailure()) {
return isAsyncFn(fn) ? Promise.resolve(this) : this;
}
const result = Result.safe(() => fn((this as any).value) as any);
return result as any;
}
rollback(): RollbackFn extends RollbackFunction
? RollbackFn extends () => Promise<void>
? Promise<Result<Error, void>>
: Result<Error, void>
: void {
if (this.rollbackFn) {
return this.rollbackFn() as any;
}
return null as any;
}
}
class Ok<
ErrorType extends unknown,
OkType extends unknown,
RollbackFn extends RollbackFunction
> extends Base<ErrorType, OkType, RollbackFn> {
public readonly value: OkType;
constructor(val: OkType, rollbackFn?: RollbackFn) {
super(rollbackFn);
this.value = val;
}
isSuccess(): this is Ok<ErrorType, OkType, RollbackFn> {
return true;
}
isFailure(): this is Err<ErrorType, OkType, RollbackFn> {
return false;
}
toString(): string {
return `Result.Ok(${this.value})`;
}
forward(): Result<any, OkType, RollbackFn> {
return Result.ok(this.value);
}
}
class Err<
ErrorType extends unknown,
OkType extends unknown,
RollbackFn extends RollbackFunction
> extends Base<ErrorType, OkType, RollbackFn> {
public readonly error: ErrorType;
constructor(err: ErrorType, rollbackFn?: RollbackFn) {
super(rollbackFn);
this.error = err;
}
isSuccess(): this is Ok<ErrorType, OkType, RollbackFn> {
return false;
}
isFailure(): this is Err<ErrorType, OkType, RollbackFn> {
return true;
}
toString(): string {
return `Result.Error(${this.error})`;
}
forward(): Result<ErrorType, any, RollbackFn> {
return Result.error(this.error);
}
}

View file

@ -0,0 +1,3 @@
export function validateRequired(value: string) {
return value ? undefined : "required";
}

15
ui/src/core/l10n/i18n.ts Normal file
View file

@ -0,0 +1,15 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import { resources } from "./locale";
i18n
.use(initReactI18next) // passes i18n down to react-i18next
.init({
resources,
lng: "en", // language to use, more information here: https://www.i18next.com/overview/configuration-options#languages-namespaces-resources
// you can use the i18n.changeLanguage function to change the language manually: https://www.i18next.com/overview/api#changelanguage
// if you're using a language detector, do not define the lng option
interpolation: {
escapeValue: false, // react already safes from xss
},
});

View file

@ -0,0 +1,12 @@
export const resources = {
en: {
translation: {
"Welcome to React": "Welcome to React and react-i18next",
},
},
fr: {
translation: {
"Welcome to React": "Bienvenue à React et react-i18next",
},
},
};

View file

@ -1,6 +1,7 @@
export interface ITriggerModel {
_id?: string;
type: string;
description:string;
value: string[];
}

View file

@ -5,7 +5,7 @@ export enum HttpMethod {
export class HttpRepository {
private server = 'http://localhost:3000'
private server = 'http://localhost:4001'
public async jsonRequest<T>(method: HttpMethod, url: string, data?: any): Promise<T> {
const reqInit = {

View file

@ -0,0 +1,14 @@
import { Socket, io } from "socket.io-client";
export class SocketRepository {
serverURL = "ws://localhost:4001";
socket: Socket | undefined;
async connect() {
const socket = io(this.serverURL);
this.socket = socket;
socket.connect();
socket.on('mock', (d) =>{
console.log(d)
})
}
}

View file

@ -0,0 +1,36 @@
import { createBrowserRouter } from "react-router-dom";
import {
AllProjectScreen,
AllProjectScreenPath,
} from "../../features/all_projects/presentation/all_projects_screen";
import {
PipelineInstanceScreen,
PipelineScreenPath,
} from "../../features/pipeline_instance_main_screen/pipeline_instance_screen";
import {
SelectProjectScreen,
SelectProjectScreenPath,
} from "../../features/select_project/presentation/select_project";
import {
CreatePipelineScreen,
CreatePipelineScreenPath,
} from "../../features/create_pipeline/presentation/create_pipeline_screen";
export const router = createBrowserRouter([
{
path: AllProjectScreenPath,
element: <AllProjectScreen />,
},
{
path: PipelineScreenPath,
element: <PipelineInstanceScreen />,
},
{
path: SelectProjectScreenPath,
element: <SelectProjectScreen />,
},
{
path: CreatePipelineScreenPath,
element: <CreatePipelineScreen />,
},
]);

View file

@ -0,0 +1,94 @@
import * as React from "react";
import { Typography } from "antd";
import { Col, Row } from "antd";
import { LinkTypography } from "../link/link";
import { ReactComponent as LeftIcon } from "../../assets/icons/left_icon.svg";
import { useNavigate } from "react-router-dom";
const { Title } = Typography;
export interface IHeader {
largeText: string;
minText?: string;
path?: string;
needBackButton?: undefined | any;
}
export const Header: React.FunctionComponent<IHeader> = (props: IHeader) => {
const navigate = useNavigate();
const needBackButton = props.needBackButton !== undefined ? false : true;
return (
<Col style={{ textAlign: "center" }}>
<Row
style={{
marginTop: "20px",
marginRight: "20px",
display: "contents",
}}
>
{needBackButton ? (
<>
<div
onClick={() => {
navigate(-1);
}}
style={{
position: "absolute",
zIndex: 1,
left: "10px",
backgroundColor: "#456BD9",
border: "0.1875em solid #0F1C3F",
borderRadius: "50%",
height: "60px",
width: "60px",
cursor: "pointer",
}}
>
<LeftIcon
style={{
pointerEvents: "none",
cursor: "pointer",
width: "40px",
height: "40px",
position: "relative",
bottom: "-8px",
}}
/>
</div>
<Title
style={{
position: "relative",
bottom: "-8px",
left: "20px",
width: "100vw",
}}
level={2}
>
{props.largeText}
</Title>
</>
) : (
<>
<Title style={{ justifyContent: "center" }} level={2}>
{props.largeText}
</Title>
</>
)}
</Row>
{props.minText !== undefined ? (
<LinkTypography
style={{
marginBottom: "40px",
}}
path={props.path!}
text={props.minText}
/>
) : (
<></>
)}
</Col>
);
};

View file

@ -0,0 +1,28 @@
import * as React from "react";
import { Typography } from "antd";
import { useNavigate } from "react-router-dom";
const { Link } = Typography;
export interface ILinkTypography {
path: string;
text: string;
style?: React.CSSProperties;
}
export const LinkTypography: React.FunctionComponent<ILinkTypography> = (
props: ILinkTypography
) => {
const navigate = useNavigate();
return (
<Link
style={props.style}
onClick={() => {
navigate(props.path);
}}
>
{props.text}
</Link>
);
};

View file

@ -0,0 +1,66 @@
import { Row } from "antd";
import { ReactComponent as AddIcon } from "../../assets/icons/add.svg";
import { observer } from "mobx-react-lite";
export type CallBackFunction = (a: string) => void;
export interface ListElement {
text: string;
color?: string;
}
export interface IPropsList {
values: ListElement[];
headers?: string;
onClick?: CallBackFunction;
}
export const List: React.FunctionComponent<IPropsList> = observer(
(props) => {
return (
<div>
{props.headers !== undefined ? <>{props.headers}</> : <></>}
{props.values.map((el) => {
return (
<Row
style={{
width: "300px",
backgroundColor: el.color ?? "ActiveBorder",
}}
>
<div
style={{
width: "-webkit-fill-available",
margin: "5px",
marginLeft: "40px",
}}
/>
<div
style={{
marginLeft: "40px",
}}
>
{el.text}
</div>
<div style={{ flexGrow: "1" }}></div>
<AddIcon
style={{
width: "50px",
cursor: "pointer",
height: "50px",
marginRight: "40px",
}}
onClick={() => {
if (props.onClick !== undefined) {
props.onClick(el.text);
}
}}
/>
</Row>
);
})}
</div>
);
}
);

View file

@ -0,0 +1,31 @@
.loader {
position: relative;
width: 64px;
height: 64px;
background-color: rgba(0, 0, 0, 0.5);
transform: rotate(45deg);
overflow: hidden;
}
.loader:after{
content: '';
position: absolute;
inset: 8px;
margin: auto;
background: #222b32;
}
.loader:before{
content: '';
position: absolute;
inset: -15px;
margin: auto;
background: #de3500;
animation: diamondLoader 2s linear infinite;
}
@keyframes diamondLoader {
0% ,10% {
transform: translate(-64px , -64px) rotate(-45deg)
}
90% , 100% {
transform: translate(0px , 0px) rotate(-45deg)
}
}

View file

@ -0,0 +1,16 @@
import * as React from "react";
import "./loader.css";
export const Loader: React.FunctionComponent = () => {
return (
<div
style={{
display: "flex",
justifyContent: "center",
placeItems: "center",
}}
>
<div className="loader" />
</div>
);
};

View file

@ -0,0 +1,49 @@
import * as React from "react";
import { Header, IHeader } from "../header/header";
import { Loader } from "../loader/loader";
import { ReactComponent as ErrorIcon } from "../../assets/icons/error.svg";
import { Typography } from "antd";
import { observer } from "mobx-react-lite";
const { Title } = Typography;
interface ILoadPage extends IHeader {
isLoading: boolean;
isError: boolean;
children?: JSX.Element | JSX.Element[];
}
export const LoadPage: React.FunctionComponent<ILoadPage> = observer((
props: ILoadPage
) => {
return (
<>
<Header
path={props.path}
largeText={props.largeText}
minText={props.minText}
/>
{props.isError ? (
<>
<ErrorIcon
style={{
height: "100px",
width: "-webkit-fill-available",
}}
/>
<Title style={{ textAlign: "center" }} level={3} type="danger">
not expected error
</Title>
</>
) : (
<></>
)}
{props.isLoading ? (
<div style={{'marginTop':'50px'}}>
<Loader />
</div>
) : (
<>{props.children}</>
)}
</>
);
});

View file

@ -0,0 +1,3 @@
import { HttpRepository } from "../../../core/repository/http_repository";
export class ProjectRepository extends HttpRepository {}

View file

@ -0,0 +1,18 @@
import * as React from "react";
import { SelectProjectScreenPath } from "../../select_project/presentation/select_project";
import { Header } from "../../../core/ui/header/header";
export const AllProjectScreenPath = "/";
export const AllProjectScreen: React.FunctionComponent = () => {
return (
<>
<Header
path={SelectProjectScreenPath}
largeText={"All Projects"}
minText={"create new pipiline?"}
needBackButton={true}
/>
</>
);
};

View file

@ -0,0 +1,12 @@
import { makeAutoObservable } from "mobx";
import { ProjectRepository } from "../data/project_repository";
class AllProjectStore {
constructor(repository: ProjectRepository) {
makeAutoObservable(this);
}
}
export const allProjectStore = new AllProjectStore(new ProjectRepository());

View file

@ -0,0 +1,29 @@
import {
HttpMethod,
HttpRepository,
} from "../../../core/repository/http_repository";
import { ITriggerModel } from "../../../core/model/trigger_model";
import { Result } from "../../../core/helper/result";
import { IProcess } from "../../create_process/model/process_model";
export class CreatePipelineRepository extends HttpRepository {
async getTriggers(page = 1): Promise<Result<Error, ITriggerModel[]>> {
try {
return Result.ok(
await this.jsonRequest(HttpMethod.GET, `/trigger?${page}`)
);
} catch (error) {
return Result.error(error as Error);
}
}
async getProcessed(page = 1): Promise<Result<Error, IProcess[]>> {
try {
return Result.ok(
await this.jsonRequest<IProcess[]>(HttpMethod.GET, `/process?${page}`)
);
} catch (error) {
return Result.error(error as Error);
}
}
}

View file

@ -0,0 +1,3 @@
export interface IColor {
color:string;
}

View file

@ -0,0 +1,51 @@
import * as React from "react";
import { Row, Input, Button } from "antd";
import { LoadPage } from "../../../core/ui/pages/load_page";
import { createPipelineStore } from "./create_pipeline_store";
import { observer } from "mobx-react-lite";
import { List } from "../../../core/ui/list/list";
export const CreatePipelineScreenPath = "/create_pipeline";
export const CreatePipelineScreen: React.FunctionComponent = observer(() => {
return (
<>
<LoadPage
largeText={"Create pipeline"}
isError={createPipelineStore.isError}
isLoading={createPipelineStore.isLoading}
children={
<>
<Row>
<List
headers={"process"}
values={createPipelineStore.processModels.map((el) => {
return { text: el.description };
})}
onClick={(e) => createPipelineStore.addProcess(e)}
/>
<div style={{ flexGrow: "1" }}>
<Input style={{ width: "300px" }} placeholder="description" />
<Button onClick={() => createPipelineStore.createPipeline()}>
Save result
</Button>
<List
headers="new pipeline"
values={createPipelineStore.pipelineViewModel }
/>
</div>
<List
headers="triggers"
values={createPipelineStore.triggersModels.map((el) => {
return { text: el.description };
})}
onClick={(e) => createPipelineStore.addTrigger(e)}
/>
</Row>
</>
}
/>
</>
);
});

View file

@ -0,0 +1,95 @@
import { makeAutoObservable } from "mobx";
import { CreatePipelineRepository } from "../data/create_pipeline_repository";
import { ITriggerModel } from "../../../core/model/trigger_model";
import { IProcess } from "../../create_process/model/process_model";
// TODO:()rename
enum Direction {
PROCESS,
TRIGGER,
}
interface CommonView {
text: string;
color: string;
type: Direction;
}
export class CreatePipelineStore {
repository: CreatePipelineRepository;
triggersModels: ITriggerModel[] = [];
processModels: IProcess[] = [];
pipelineViewModel: CommonView[] = [];
isLoading = false;
isError = false;
page = 1;
constructor(repository: CreatePipelineRepository) {
this.repository = repository;
makeAutoObservable(this);
this.loadTriggers();
this.loadProcess();
}
addProcess(e: string): void {
const lastElement = this.pipelineViewModel.lastElement()
if(lastElement !== undefined){
if(lastElement.type !== Direction.TRIGGER){
// need UI say
return
}
}
this.pipelineViewModel.push({
text: e,
color: "activeborder",
type: Direction.PROCESS,
});
}
addTrigger(e: string): void {
const lastElement = this.pipelineViewModel.lastElement()
if(lastElement !== undefined){
if(lastElement.type !== Direction.PROCESS){
// need UI say
return
}
}
this.pipelineViewModel.push({
text: e,
color: "blanchedalmond",
type: Direction.TRIGGER,
});
}
createPipeline(): void {}
async loadProcess() {
this.isLoading = true;
const result = await this.repository.getProcessed();
result.fold(
(s) => {
this.processModels = s;
},
(_e) => {
this.isError = true;
}
);
this.isLoading = false;
}
async loadTriggers() {
this.isLoading = true;
const result = await this.repository.getTriggers(this.page);
result.fold(
(s) => {
this.triggersModels = s;
},
(_e) => {
this.isError = true;
}
);
this.isLoading = false;
}
}
export const createPipelineStore = new CreatePipelineStore(
new CreatePipelineRepository()
);

View file

@ -0,0 +1,11 @@
import {
HttpMethod,
HttpRepository,
} from "../../../core/repository/http_repository";
import { IProcess } from "../model/process_model";
export class ProcessRepository extends HttpRepository {
async save(model: IProcess): Promise<void> {
await this.jsonRequest(HttpMethod.POST, "/process", model);
}
}

View file

@ -0,0 +1,26 @@
export interface IProcess {
description: string;
type: EXEC_TYPE | string;
command: string;
isGenerating: boolean;
isLocaleCode: boolean;
issueType: IssueType | string;
timeout?: number;
commit?: string | undefined;
}
export enum EXEC_TYPE {
SPAWN = "SPAWN",
EXEC = "EXEC",
}
export enum IssueType {
WARNING = "WARNING",
ERROR = "ERROR",
}
export const processModelMock: IProcess = {
description: "",
type: EXEC_TYPE.SPAWN,
command: "",
isGenerating: true,
isLocaleCode: true,
issueType: IssueType.WARNING,
};

View file

@ -0,0 +1,83 @@
import * as React from "react";
import { processStore } from "./logic/process_store";
import { observer } from "mobx-react-lite";
import {
SubmitButton,
Input,
ResetButton,
Form,
Radio,
Switch,
} from "formik-antd";
import { Formik } from "formik";
import { Row, Col } from "antd";
import { EXEC_TYPE, IssueType, processModelMock } from "../model/process_model";
export const CreateProcessScreen = observer(() => {
return (
<div>
<div style={{ marginTop: 80 }}>
<Formik
initialValues={processModelMock}
onSubmit={async (values, actions) => {
await processStore.saveResult(values);
actions.setSubmitting(false);
actions.resetForm();
}}
validate={(values) => {
if (!values.command) {
return { command: "required" };
}
if (!values.description) {
return { description: "required" };
}
return {};
}}
render={() => (
<Form>
<div
style={{
background: "white",
flex: 1,
padding: 40,
width: "400px",
}}
>
<Input name="description" placeholder="Description" />
<Input name="command" placeholder="Command process" />
<Radio.Group name="type" options={Object.values(EXEC_TYPE)} />
<Radio.Group
name="issueType"
options={Object.values(IssueType)}
/>
<Col style={{ marginTop: 20, justifyContent: "center" }}>
<Switch
name="isGenerating"
checkedChildren="is generating"
unCheckedChildren="is generating"
/>
<Switch
name="isLocalCode"
checkedChildren="is local code"
unCheckedChildren="is local code"
/>
</Col>
<Row style={{ marginTop: 20, justifyContent: "center" }}>
<Col>
<ResetButton>Reset</ResetButton>
<SubmitButton>Submit</SubmitButton>
</Col>
</Row>
</div>
</Form>
)}
/>
</div>
</div>
);
});

View file

@ -0,0 +1,17 @@
import { makeAutoObservable } from "mobx";
import { v4 as uuidv4 } from "uuid";
import { ProcessRepository } from "../../data/process_repostiory";
import { IProcess } from "../../model/process_model";
class ProcessStore {
repository: ProcessRepository;
constructor(repository: ProcessRepository) {
this.repository = repository;
makeAutoObservable(this);
}
async saveResult(model:IProcess) {
await this.repository.save(model)
}
}
export const processStore = new ProcessStore(new ProcessRepository());

View file

@ -2,7 +2,7 @@ import {
HttpMethod,
HttpRepository,
} from "../../../core/repository/http_repository";
import { ITriggerModel } from "../model/trigger_model";
import { ITriggerModel } from "../../../core/model/trigger_model";
export class TriggerRepository extends HttpRepository {
public async save(model: ITriggerModel) {

View file

@ -1,4 +1,4 @@
import { TriggerType } from "./trigger_model";
import { TriggerType } from "../../../core/model/trigger_model";
export interface ITriggerViewValueModel {
value: string;

View file

@ -2,13 +2,13 @@ import * as React from "react";
import Editor from "@monaco-editor/react";
import { Button } from "antd";
import { observer } from "mobx-react-lite";
import { triggerStore } from "../logic/trigger_store";
import { triggerStore } from "../trigger_store";
export const CodeTriggerForm: React.FunctionComponent = observer(() => {
return (
<>
<div style={{ width: "100%", backgroundColor: "black", height: "1px" }} />
<Editor
height="40vh"
defaultLanguage="javascript"
@ -16,14 +16,12 @@ export const CodeTriggerForm: React.FunctionComponent = observer(() => {
onChange={(v) => {
triggerStore.writeNewTrigger(v);
}}
onValidate={(m) => {
console.log(m);
}}
onValidate={(_m) => {}}
/>
<div style={{ width: "100%", backgroundColor: "black", height: "1px" }} />
<div style={{ height: "10px" }}/>
<div style={{ height: "10px" }} />
<Button
onClick={() => triggerStore.saveCode()}
style={{ marginLeft: "10px", marginRight: "10px" }}

View file

@ -3,13 +3,10 @@ import { Formik } from "formik";
import { SubmitButton, Input, ResetButton, Form, FormItem } from "formik-antd";
import { Row, Col } from "antd";
import { observer } from "mobx-react-lite";
import { triggerStore } from "../logic/trigger_store";
import { TriggerType } from "../../model/trigger_model";
function validateRequired(value: string) {
return value ? undefined : "required";
}
import { triggerStore } from "../trigger_store";
import { TriggerType } from "../../../../core/model/trigger_model";
import { validateRequired } from "../../../../core/helper/validate";
export const FileTriggerForm: React.FunctionComponent = observer(() => {
return (
<>

View file

@ -1,10 +1,11 @@
import * as React from "react";
import { Button, Col, Row, Switch, Typography } from "antd";
import { Button, Col, Row, Switch, Typography, Input } from "antd";
import { CodeTriggerForm } from "./components/code_trigger_form";
import { observer } from "mobx-react-lite";
import { triggerStore } from "./logic/trigger_store";
import { triggerStore } from "./trigger_store";
import { FileTriggerForm } from "./components/file_trigger_form";
import { ReactComponent as DeleteIcon } from "../../../assets/icons/delete.svg";
import { Loader } from "../../../core/ui/loader/loader";
const { Title } = Typography;
@ -48,18 +49,31 @@ const Bottom = observer(() => {
export const TriggerScreen: React.FunctionComponent = observer(() => {
return (
<>
<main className="test">
<Header />,
{triggerStore.getTriggerType() ? (
<main>
{!triggerStore.isLoading ? (
<>
<FileTriggerForm />
<Header />,
<Input
placeholder="trigger description"
onChange={() => triggerStore.changeTriggerDescription}
/>
{triggerStore.getTriggerType() ? (
<>
<FileTriggerForm />
</>
) : (
<>
<CodeTriggerForm />
</>
)}
<Bottom />
</>
) : (
<>
<CodeTriggerForm />
<Loader/>
</>
)}
<Bottom />
</main>
</>
);

View file

@ -1,8 +1,8 @@
import { makeAutoObservable } from "mobx";
import { v4 as uuidv4 } from "uuid";
import { TriggerType } from "../../model/trigger_model";
import { TriggerRepository } from "../../data/trigger_repository";
import { TriggerViewModel } from "../../model/trigger_form_view_model";
import { TriggerType } from "../../../core/model/trigger_model";
import { TriggerRepository } from "../data/trigger_repository";
import { TriggerViewModel } from "../model/trigger_form_view_model";
class TriggerStore {
constructor(repository: TriggerRepository) {
@ -11,15 +11,19 @@ class TriggerStore {
makeAutoObservable(this);
}
isLoading = false;
triggerDescription: string = "";
triggerType: TriggerType;
codeTriggerValue = "";
triggers: TriggerViewModel[] = [];
repository: TriggerRepository;
changeTriggerDescription(value: string): void {
this.triggerDescription = value;
}
deleteItem(id: string): void {
this.triggers = this.triggers.filter((el) => el.id !== id);
console.log(id);
}
getTriggerType = (): boolean => {
@ -39,7 +43,6 @@ class TriggerStore {
: TriggerType.PROCESS;
};
pushTrigger = (value: string, type: TriggerType): void => {
console.log(value);
this.triggers.push({
value: value,
id: uuidv4(),
@ -68,12 +71,15 @@ class TriggerStore {
}
}
async saveResult(): Promise<void> {
this.isLoading = true
await this.repository.save({
type: this.getTriggerDescription(),
description: this.triggerDescription,
value: this.triggers.map((el) => {
return el.value;
}),
});
this.isLoading = false
}
}

View file

@ -0,0 +1,14 @@
import * as React from "react";
import { Button } from "antd";
export const PipelineScreenPath = '/pipeline_instance/:id'
export const PipelineInstanceScreen: React.FunctionComponent = () => {
return (
<>
<Button></Button>
</>
);
};

View file

@ -0,0 +1,18 @@
import { Result } from "../../../core/helper/result";
import {
HttpMethod,
HttpRepository,
} from "../../../core/repository/http_repository";
import { IProjectModel } from "../model/project_model";
export class SelectProjectRepository extends HttpRepository {
async getAllProjects(page = 1): Promise<Result<Error, IProjectModel[]>> {
try {
return Result.ok(
await this.jsonRequest(HttpMethod.GET, `/project?${page}`)
);
} catch (error) {
return Result.error(error as Error);
}
}
}

View file

@ -0,0 +1,8 @@
import { IProcess } from "../../create_process/model/process_model";
export interface IProjectModel {
pipelines: [IProcess];
rootDir: string;
description: string;
}

View file

@ -0,0 +1,31 @@
import * as React from "react";
import { selectProjectStore } from "./select_project_store";
import { Loader } from "../../../core/ui/loader/loader";
import { observer } from "mobx-react-lite";
import { Header } from "../../../core/ui/header/header";
import { CreatePipelineScreenPath } from "../../create_pipeline/presentation/create_pipeline_screen";
import { LoadPage } from "../../../core/ui/pages/load_page";
export const SelectProjectScreenPath = "/select_project";
export const SelectProjectScreen: React.FunctionComponent = observer(() => {
return (
<>
<LoadPage
path={CreatePipelineScreenPath}
largeText={"Select project"}
minText={"add new project?"}
isLoading={selectProjectStore.isLoading}
isError={selectProjectStore.isError}
children={selectProjectStore.projects.map((el) => {
return (
<>
<div>{el.description}</div>
<div>+(РЕАЛИЗУЙ ТУТ ПЛЮСИК БЛЯТЬ ИЛИ КНОПКУ)</div>
</>
);
})}
/>
</>
);
});

View file

@ -0,0 +1,35 @@
import { makeAutoObservable } from "mobx";
import { SelectProjectRepository } from "../data/select_project_repository";
import { IProjectModel } from "../model/project_model";
class SelectProjectStore {
repository: SelectProjectRepository;
isLoading = false;
isError = false;
page = 1;
projects: IProjectModel[] = [];
constructor(repository: SelectProjectRepository) {
this.repository = repository;
makeAutoObservable(this);
this.getPipelines();
}
async getPipelines(): Promise<void> {
this.isLoading = true;
const result = await this.repository.getAllProjects(this.page);
result.fold(
(s) => {
this.projects = s;
},
(_e) => {
this.isError = true;
}
);
this.isLoading = false;
}
}
export const selectProjectStore = new SelectProjectStore(
new SelectProjectRepository()
);

View file

@ -0,0 +1,33 @@
import * as React from "react";
import { ReactComponent as ReloadIcon } from "../../core/assets/icons/reload.svg";
import { socketListerStore } from "./socket_lister_store";
import { observer } from "mobx-react-lite";
export interface ISocketListerProps {
children?: JSX.Element;
}
export const SocketLister = observer((props: ISocketListerProps) => {
return (
<>
{socketListerStore.socketDisconnect ? (
<ReloadIcon
onClick={() => {
socketListerStore.reconnect();
}}
style={{
height: "70px",
backgroundColor: "silver",
width: "-webkit-fill-available",
padding: "10px",
cursor: "pointer",
}}
/>
) : (
<></>
)}
{props.children}
</>
);
});

View file

@ -0,0 +1,18 @@
import { makeAutoObservable } from "mobx";
import { SocketRepository } from "../../core/repository/socket_repository";
class SocketListerStore {
repository: SocketRepository;
socketDisconnect = false;
constructor(repository: SocketRepository) {
this.repository = repository;
makeAutoObservable(this);
}
async reconnect() {
await this.repository.connect()
this.socketDisconnect = false
}
}
export const socketListerStore = new SocketListerStore(new SocketRepository());

View file

@ -1,16 +1,22 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import React from "react";
import ReactDOM from "react-dom/client";
import { TriggerScreen } from './features/trigger/presentation/trigger_presentation';
import "antd/dist/antd.css";
import "antd/dist/antd.min.css";
import { RouterProvider } from "react-router-dom";
import { router } from "./core/routers/routers";
import { SocketLister } from "./features/socket_lister/socket_lister";
import { extensions } from "./core/extensions/extensions";
extensions();
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<TriggerScreen />
</React.StrictMode>
<>
<SocketLister>
<RouterProvider router={router} />
</SocketLister>
</>
);

View file

@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
@ -21,7 +17,5 @@
"jsx": "react-jsx",
"useDefineForClassFields": true
},
"include": [
"src"
]
"include": ["src"]
}