diff --git a/src/app/layouts/flow-editor/flow-editor.ts b/src/app/layouts/flow-editor/flow-editor.ts index e36a39d..f7f3064 100644 --- a/src/app/layouts/flow-editor/flow-editor.ts +++ b/src/app/layouts/flow-editor/flow-editor.ts @@ -1,7 +1,7 @@ import { Component, inject } from '@angular/core'; import { Flow } from '@models/flow'; import { EditorSidebar } from "@pages/main/editor-sidebar/editor-sidebar"; -import { EditorStateHolder } from 'app/stores/editor'; +import { EditorStateHolder } from '@stores/flow-editor'; import { TitleToolbar } from "@shared/title-toolbar/title-toolbar"; @Component({ @@ -14,9 +14,5 @@ export class FlowEditor { private editorState: EditorStateHolder = inject(EditorStateHolder); - flow = this.editorState.currentFlow; - - - } diff --git a/src/app/models/flow.ts b/src/app/models/flow.ts index 6bd9708..9c6bbc6 100644 --- a/src/app/models/flow.ts +++ b/src/app/models/flow.ts @@ -3,4 +3,7 @@ export type Flow = { name: string; visibility: 'public' | 'private'; data: any; + author: string; + createdAt: Date; + updatedAt: Date; }; \ No newline at end of file diff --git a/src/app/pages/main/editor-sidebar/editor-sidebar.ts b/src/app/pages/main/editor-sidebar/editor-sidebar.ts index 2800ce7..c787013 100644 --- a/src/app/pages/main/editor-sidebar/editor-sidebar.ts +++ b/src/app/pages/main/editor-sidebar/editor-sidebar.ts @@ -1,7 +1,7 @@ import { Component, computed, inject, signal } from '@angular/core'; import { GroupHolder } from '@shared/group-holder/group-holder'; import { FlowsList } from '@shared/flows-list/flows-list'; -import { EditorStateHolder } from 'app/stores/editor'; +import { EditorStateHolder } from '@stores/flow-editor'; import { CommonModule } from '@angular/common'; type OpenedId = 'flows' | 'blocks'; diff --git a/src/app/services/flows/flows-call.base.ts b/src/app/services/flows/flows-call.base.ts index 076a0f2..0c0d4ab 100644 --- a/src/app/services/flows/flows-call.base.ts +++ b/src/app/services/flows/flows-call.base.ts @@ -3,8 +3,14 @@ import { Observable } from "rxjs"; export abstract class FlowsCallServiceBase { + abstract getFlowById(flowId: string) : Observable; + abstract retrieveAllFlows() : Observable; abstract updateFlow(flow: Flow) : Observable; + abstract createNewFlow() : Observable; + + abstract deleteFlow(flowId: string) : Observable; + } \ No newline at end of file diff --git a/src/app/services/flows/flows-call.fake.ts b/src/app/services/flows/flows-call.fake.ts index 2d2e135..01b5b4b 100644 --- a/src/app/services/flows/flows-call.fake.ts +++ b/src/app/services/flows/flows-call.fake.ts @@ -1,20 +1,38 @@ import { Flow } from "@models/flow"; import { FlowsCallServiceBase } from "./flows-call.base"; -import { BehaviorSubject, of } from "rxjs"; +import { BehaviorSubject, Observable, of } from "rxjs"; export class FlowsCallServiceFake extends FlowsCallServiceBase { + override getFlowById(flowId: string): Observable { + const flow = this.data[flowId]; + if (!flow) { + throw new Error(`Flow with id ${flowId} not found`); + } + return of(flow); + } private data: Record = { - '1': { id: '1', name: 'Test Flow 1', data: {}, visibility: 'public' }, - '2': { id: '2', name: 'Test Flow 2', data: {}, visibility: 'private' }, + '1': { id: '1', name: 'Test Flow 1', data: {}, visibility: 'public', author: 'Alice', createdAt: new Date(), updatedAt: new Date() }, + '2': { id: '2', name: 'Test Flow 2', data: {}, visibility: 'private', author: 'Bob', createdAt: new Date(), updatedAt: new Date() }, }; - retrieveAllFlows() { + override retrieveAllFlows() { return of(Object.values(this.data)); } - updateFlow(flow: Flow) { + override updateFlow(flow: Flow) { this.data[flow.id] = flow; return of(void 0); } + + override createNewFlow(): Observable { + const newId = (Object.keys(this.data).length + 1).toString(); + this.data[newId] = { id: newId, name: 'New Flow', data: {}, visibility: 'private', author: 'Charlie', createdAt: new Date(), updatedAt: new Date() }; + return of(this.data[newId]); + } + + override deleteFlow(flowId: string): Observable { + delete this.data[flowId]; + return of(void 0); + } } diff --git a/src/app/services/flows/flows-call.ts b/src/app/services/flows/flows-call.ts index 5cfb05d..7e3df3c 100644 --- a/src/app/services/flows/flows-call.ts +++ b/src/app/services/flows/flows-call.ts @@ -3,6 +3,15 @@ import { FlowsCallServiceBase } from "./flows-call.base"; import { Observable } from "rxjs"; export class FlowsCallService extends FlowsCallServiceBase { + override getFlowById(flowId: string): Observable { + throw new Error("Method not implemented."); + } + override createNewFlow(): Observable { + throw new Error("Method not implemented."); + } + override deleteFlow(flowId: string): Observable { + throw new Error("Method not implemented."); + } override retrieveAllFlows(): Observable { throw new Error("Method not implemented."); } diff --git a/src/app/services/flows/flows.ts b/src/app/services/flows/flows.ts index fa47a16..67e5cc4 100644 --- a/src/app/services/flows/flows.ts +++ b/src/app/services/flows/flows.ts @@ -2,27 +2,27 @@ import { Injectable, signal } from '@angular/core'; import { environment } from '@environment'; import { Flow } from '@models/flow'; import { FlowsCallServiceBase } from './flows-call.base'; -import { BehaviorSubject, catchError, tap, throwError } from 'rxjs'; +import { BehaviorSubject, catchError, combineLatest, Observable, switchMap, tap, throwError } from 'rxjs'; @Injectable({ providedIn: 'root', }) export class FlowsService { - + flowsCallService: FlowsCallServiceBase = new environment.flowsCallService(); - + toInit: boolean = true; private _flows = signal([]); async getAllFlows() { - console.log('Flows signal accessed'); + console.log('Flows signal accessed'); if (this.toInit) { this.refresh(); this.toInit = false; - + } - + return this._flows.asReadonly(); } @@ -36,9 +36,49 @@ export class FlowsService { return this.flowsCallService.updateFlow(flow).pipe( tap(() => this.refresh()), catchError(err => { - console.error('Update flow failed', err); - return throwError(() => err); - }) + console.error('Update flow failed', err); + return throwError(() => err); + }) + ); + } + + deleteFlow(flowId: string) { + return this.flowsCallService.deleteFlow(flowId).pipe( + tap(() => this.refresh()), + catchError(err => { + console.error('Delete flow failed', err); + return throwError(() => err); + }) + ); + } + + cloneFlow(flowId: string): Observable { + const originalFlow$ = this.flowsCallService.getFlowById(flowId); + const newFlow$ = this.flowsCallService.createNewFlow(); + + return combineLatest([originalFlow$, newFlow$]).pipe( + switchMap(([originalFlow, newFlow]) => { + newFlow.name = originalFlow.name + ' (Clone)'; + newFlow.data = { ...originalFlow.data }; + + return this.flowsCallService.updateFlow(newFlow); + }), + tap(() => this.refresh()), + catchError(err => { + console.error('Cloning flow failed', err); + return throwError(() => err); + }) + ); + + } + + createNewFlow() { + return this.flowsCallService.createNewFlow().pipe( + tap(() => this.refresh()), + catchError(err => { + console.error('Create new flow failed', err); + return throwError(() => err); + }) ); } diff --git a/src/app/shared/flows-list/flows-list.html b/src/app/shared/flows-list/flows-list.html index 0dc0ef0..35d46b6 100644 --- a/src/app/shared/flows-list/flows-list.html +++ b/src/app/shared/flows-list/flows-list.html @@ -26,30 +26,7 @@
@for (flow of filteredFlows(); track flow.id) { -
- -
- - - -
- - - {{ flow.name }} -
- - - -
-
+ }
diff --git a/src/app/shared/flows-list/flows-list.ts b/src/app/shared/flows-list/flows-list.ts index 47001ea..165b271 100644 --- a/src/app/shared/flows-list/flows-list.ts +++ b/src/app/shared/flows-list/flows-list.ts @@ -2,20 +2,20 @@ import { CommonModule } from '@angular/common'; import { Component, computed, inject, signal, Signal, WritableSignal } from '@angular/core'; import { Flow } from '@models/flow'; import { FlowsService } from '@services/flows/flows'; -import { EditorStateHolder } from 'app/stores/editor'; +import { FlowItem } from './flow-item/flow-item'; type FlowFilter = 'all' | 'public' | 'private'; @Component({ selector: 'app-flows-list', - imports: [CommonModule], + imports: [FlowItem], templateUrl: './flows-list.html', styleUrl: './flows-list.css', }) export class FlowsList { filter = signal('all'); - private editorState = inject(EditorStateHolder); + private flowsService = inject(FlowsService); loading: WritableSignal = signal(true); @@ -35,8 +35,5 @@ export class FlowsList { return this.flows().filter(f => f.visibility === this.filter()); }); - open(flow: Flow) { - console.log('Opening flow:', flow); - this.editorState.openDocument(flow); - } + } diff --git a/src/app/shared/title-toolbar/title-toolbar.html b/src/app/shared/title-toolbar/title-toolbar.html index 6d2a02a..7e54a2f 100644 --- a/src/app/shared/title-toolbar/title-toolbar.html +++ b/src/app/shared/title-toolbar/title-toolbar.html @@ -21,7 +21,7 @@ #titleInput /> - @if (unsaved()) { + @if (notSaved()) { not saved } @@ -32,16 +32,16 @@ @@ -49,7 +49,7 @@ @@ -59,7 +59,7 @@ diff --git a/src/app/shared/title-toolbar/title-toolbar.ts b/src/app/shared/title-toolbar/title-toolbar.ts index 42cd9b2..b417ded 100644 --- a/src/app/shared/title-toolbar/title-toolbar.ts +++ b/src/app/shared/title-toolbar/title-toolbar.ts @@ -1,5 +1,5 @@ import { Component, computed, ElementRef, inject, linkedSignal, ViewChild, WritableSignal } from '@angular/core'; -import { EditorStateHolder } from 'app/stores/editor'; +import { EditorStateHolder } from '@stores/flow-editor'; @Component({ selector: 'app-title-toolbar', @@ -28,7 +28,7 @@ export class TitleToolbar { return Math.min(len, 20)-2; }); - unsaved = computed(() => this.editorState.isDirty()); + notSaved = computed(() => this.editorState.isDirty()); changeTitle(value: string) { @@ -45,6 +45,7 @@ export class TitleToolbar { } save() { + if (!this.notSaved()) return; this.editorState.save().subscribe( err => console.error('Save failed', err) ); diff --git a/src/app/stores/editor.spec.ts b/src/app/stores/editor.spec.ts deleted file mode 100644 index 57a1234..0000000 --- a/src/app/stores/editor.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { Editor } from './editor'; - -describe('Editor', () => { - let service: Editor; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(Editor); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/src/app/stores/editor.ts b/src/app/stores/editor.ts deleted file mode 100644 index bfef24a..0000000 --- a/src/app/stores/editor.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { computed, inject, Injectable, signal } from '@angular/core'; -import { Flow } from '@models/flow'; -import { ConfirmDialogService } from '@services/dialogs/confirm-dialog'; -import { FlowsService } from '@services/flows/flows'; -import { FlowsCallService } from '@services/flows/flows-call'; -import { tap } from 'rxjs'; - -@Injectable({ providedIn: 'root' }) -export class EditorStateHolder { - - /** Stato */ - readonly currentFlow = signal(null); - readonly isDirty = signal(false); - - /** Derived state */ - readonly hasFlow = computed(() => !!this.currentFlow()); - - flowsService: FlowsService = inject(FlowsService); - - constructor(private confirm: ConfirmDialogService) { } - - - /** Intent: open document */ - async openDocument(doc: Flow): Promise { - if (this.isDirty()) { - const confirmed = await this.confirm.open( - 'You have unsaved changes. Open another document?' - ); - - if (!confirmed) return false; - } - - this.currentFlow.set(doc); - this.isDirty.set(false); - return true; - } - - /** Intent: mark editor dirty */ - private markDirty() { - this.isDirty.set(true); - } - - /** Intent: save document */ - private markSaved() { - this.isDirty.set(false); - } - - /** Intent: close document */ - closeDocument() { - this.currentFlow.set(null); - this.isDirty.set(false); - } - - updateFlow(updated: Flow) { - this.currentFlow.set(updated); - this.markDirty(); - } - - save() { - return this.flowsService.updateFlow(this.currentFlow()!).pipe( - tap(() => this.markSaved()) - ) - } -} diff --git a/tsconfig.json b/tsconfig.json index a788df1..b95aabb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -31,6 +31,9 @@ ], "@assets/*": [ "/assets/*" + ], + "@stores/*": [ + "app/stores/*" ] }, "strict": true,