diff --git a/src/app/app.html b/src/app/app.html
index 67e7bd4..8c47805 100644
--- a/src/app/app.html
+++ b/src/app/app.html
@@ -1 +1,2 @@
+
diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts
index d536e16..f01e580 100644
--- a/src/app/app.routes.ts
+++ b/src/app/app.routes.ts
@@ -4,7 +4,7 @@ import { Signup } from '@pages/auth/signup/signup';
import { AppLayout } from '@layouts/app-layout/app-layout';
import { authGuard } from '@guards/auth-guard';
import { FlowEditor } from '@layouts/flow-editor/flow-editor';
-import { Tasks } from '@layouts/tasks/tasks';
+import { Tasks } from '@pages/main/tasks/tasks';
export const routes: Routes = [
{
diff --git a/src/app/app.ts b/src/app/app.ts
index 971a834..a46483b 100644
--- a/src/app/app.ts
+++ b/src/app/app.ts
@@ -1,9 +1,10 @@
import { Component, signal } from '@angular/core';
import { RouterOutlet } from '@angular/router';
+import { ConfirmDialogHostComponent } from '@shared/confirm-dialog/confirm-dialog';
@Component({
selector: 'app-root',
- imports: [RouterOutlet],
+ imports: [RouterOutlet, ConfirmDialogHostComponent],
templateUrl: './app.html',
styleUrl: './app.css'
})
diff --git a/src/app/layouts/flow-editor/flow-editor.html b/src/app/layouts/flow-editor/flow-editor.html
index accabde..b7e28cf 100644
--- a/src/app/layouts/flow-editor/flow-editor.html
+++ b/src/app/layouts/flow-editor/flow-editor.html
@@ -1 +1,32 @@
-
flow-editor works!
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @if (flow()) {
+
+
{{ flow()?.name }}
+
+
+ } @else {
+
+ Select a document
+
+ }
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/layouts/flow-editor/flow-editor.ts b/src/app/layouts/flow-editor/flow-editor.ts
index 801d544..bab1f96 100644
--- a/src/app/layouts/flow-editor/flow-editor.ts
+++ b/src/app/layouts/flow-editor/flow-editor.ts
@@ -1,11 +1,21 @@
-import { Component } from '@angular/core';
+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';
@Component({
selector: 'app-flow-editor',
- imports: [],
+ imports: [EditorSidebar],
templateUrl: './flow-editor.html',
styleUrl: './flow-editor.css',
})
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
new file mode 100644
index 0000000..6bd9708
--- /dev/null
+++ b/src/app/models/flow.ts
@@ -0,0 +1,6 @@
+export type Flow = {
+ id: string;
+ name: string;
+ visibility: 'public' | 'private';
+ data: any;
+};
\ No newline at end of file
diff --git a/src/app/layouts/tasks/tasks.css b/src/app/pages/main/editor-sidebar/editor-sidebar.css
similarity index 100%
rename from src/app/layouts/tasks/tasks.css
rename to src/app/pages/main/editor-sidebar/editor-sidebar.css
diff --git a/src/app/pages/main/editor-sidebar/editor-sidebar.html b/src/app/pages/main/editor-sidebar/editor-sidebar.html
new file mode 100644
index 0000000..7dc558d
--- /dev/null
+++ b/src/app/pages/main/editor-sidebar/editor-sidebar.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
Input
+
Transform
+
Output
+
+
\ No newline at end of file
diff --git a/src/app/pages/main/editor-sidebar/editor-sidebar.spec.ts b/src/app/pages/main/editor-sidebar/editor-sidebar.spec.ts
new file mode 100644
index 0000000..39d3ea6
--- /dev/null
+++ b/src/app/pages/main/editor-sidebar/editor-sidebar.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { EditorSidebar } from './editor-sidebar';
+
+describe('EditorSidebar', () => {
+ let component: EditorSidebar;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [EditorSidebar]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(EditorSidebar);
+ component = fixture.componentInstance;
+ await fixture.whenStable();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/pages/main/editor-sidebar/editor-sidebar.ts b/src/app/pages/main/editor-sidebar/editor-sidebar.ts
new file mode 100644
index 0000000..0b6bea4
--- /dev/null
+++ b/src/app/pages/main/editor-sidebar/editor-sidebar.ts
@@ -0,0 +1,26 @@
+import { Component } from '@angular/core';
+import { Accordion } from '@shared/accordion/accordion';
+import { FlowsList } from '@shared/flows-list/flows-list';
+
+type AccordionId = 'flows' | 'blocks';
+
+@Component({
+ selector: 'app-editor-sidebar',
+ imports: [Accordion, FlowsList],
+ templateUrl: './editor-sidebar.html',
+ styleUrl: './editor-sidebar.css',
+})
+export class EditorSidebar {
+
+ collapsed = false;
+
+ openAccordion: AccordionId | null = null;
+
+ collapse() {
+ this.collapsed = !this.collapsed;
+ }
+
+ toggle(id: AccordionId) {
+ this.openAccordion = this.openAccordion === id ? null : id;
+ }
+}
diff --git a/src/app/pages/main/tasks/tasks.css b/src/app/pages/main/tasks/tasks.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/layouts/tasks/tasks.html b/src/app/pages/main/tasks/tasks.html
similarity index 100%
rename from src/app/layouts/tasks/tasks.html
rename to src/app/pages/main/tasks/tasks.html
diff --git a/src/app/layouts/tasks/tasks.spec.ts b/src/app/pages/main/tasks/tasks.spec.ts
similarity index 100%
rename from src/app/layouts/tasks/tasks.spec.ts
rename to src/app/pages/main/tasks/tasks.spec.ts
diff --git a/src/app/layouts/tasks/tasks.ts b/src/app/pages/main/tasks/tasks.ts
similarity index 100%
rename from src/app/layouts/tasks/tasks.ts
rename to src/app/pages/main/tasks/tasks.ts
diff --git a/src/app/services/dialogs/confirm-dialog.spec.ts b/src/app/services/dialogs/confirm-dialog.spec.ts
new file mode 100644
index 0000000..5cf3c85
--- /dev/null
+++ b/src/app/services/dialogs/confirm-dialog.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { ConfirmDialog } from './confirm-dialog';
+
+describe('ConfirmDialog', () => {
+ let service: ConfirmDialog;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(ConfirmDialog);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/src/app/services/dialogs/confirm-dialog.ts b/src/app/services/dialogs/confirm-dialog.ts
new file mode 100644
index 0000000..21f8bf6
--- /dev/null
+++ b/src/app/services/dialogs/confirm-dialog.ts
@@ -0,0 +1,26 @@
+import { Injectable, signal } from "@angular/core";
+
+@Injectable({ providedIn: 'root' })
+export class ConfirmDialogService {
+
+ private _state = signal<{
+ message: string;
+ resolve: (v: boolean) => void;
+ } | null>(null);
+
+ readonly state = this._state.asReadonly();
+
+ open(message: string): Promise {
+ return new Promise(resolve => {
+ this._state.set({ message, resolve });
+ });
+ }
+
+ confirm(result: boolean) {
+ const s = this._state();
+ if (!s) return;
+
+ s.resolve(result);
+ this._state.set(null);
+ }
+}
diff --git a/src/app/shared/accordion/accordion.css b/src/app/shared/accordion/accordion.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/shared/accordion/accordion.html b/src/app/shared/accordion/accordion.html
new file mode 100644
index 0000000..5887c3c
--- /dev/null
+++ b/src/app/shared/accordion/accordion.html
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
diff --git a/src/app/shared/accordion/accordion.spec.ts b/src/app/shared/accordion/accordion.spec.ts
new file mode 100644
index 0000000..fa15d4c
--- /dev/null
+++ b/src/app/shared/accordion/accordion.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { Accordion } from './accordion';
+
+describe('Accordion', () => {
+ let component: Accordion;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [Accordion]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(Accordion);
+ component = fixture.componentInstance;
+ await fixture.whenStable();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/shared/accordion/accordion.ts b/src/app/shared/accordion/accordion.ts
new file mode 100644
index 0000000..ca6faa7
--- /dev/null
+++ b/src/app/shared/accordion/accordion.ts
@@ -0,0 +1,14 @@
+import { Component, EventEmitter, input, Input, Output } from '@angular/core';
+
+@Component({
+ selector: 'app-accordion',
+ imports: [],
+ templateUrl: './accordion.html',
+ styleUrl: './accordion.css',
+})
+export class Accordion {
+ @Input() title = '';
+ @Input() icon = '';
+ open = input(false);
+ @Output() toggle = new EventEmitter();
+}
diff --git a/src/app/shared/confirm-dialog/confirm-dialog.css b/src/app/shared/confirm-dialog/confirm-dialog.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/shared/confirm-dialog/confirm-dialog.html b/src/app/shared/confirm-dialog/confirm-dialog.html
new file mode 100644
index 0000000..7b33132
--- /dev/null
+++ b/src/app/shared/confirm-dialog/confirm-dialog.html
@@ -0,0 +1,35 @@
+@if(!!this.state()) {
+
+
+
+
+
+
+
+
+
+
+
+ Confirm
+
+
+
+ {{ this.state()!.message }}
+
+
+
+
+
+
+
+
+
+
+
+}
\ No newline at end of file
diff --git a/src/app/shared/confirm-dialog/confirm-dialog.spec.ts b/src/app/shared/confirm-dialog/confirm-dialog.spec.ts
new file mode 100644
index 0000000..85ecf02
--- /dev/null
+++ b/src/app/shared/confirm-dialog/confirm-dialog.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ConfirmDialog } from './confirm-dialog';
+
+describe('ConfirmDialog', () => {
+ let component: ConfirmDialog;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [ConfirmDialog]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(ConfirmDialog);
+ component = fixture.componentInstance;
+ await fixture.whenStable();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/shared/confirm-dialog/confirm-dialog.ts b/src/app/shared/confirm-dialog/confirm-dialog.ts
new file mode 100644
index 0000000..305a068
--- /dev/null
+++ b/src/app/shared/confirm-dialog/confirm-dialog.ts
@@ -0,0 +1,18 @@
+import { Component, inject, Input } from '@angular/core';
+import { ConfirmDialogService } from '@services/dialogs/confirm-dialog';
+
+@Component({
+ selector: 'app-confirm-dialog-host',
+ standalone: true,
+ templateUrl: './confirm-dialog.html',
+})
+export class ConfirmDialogHostComponent {
+
+ private confirm: ConfirmDialogService = inject(ConfirmDialogService);
+
+ state = this.confirm.state;
+
+ close(result: boolean) {
+ this.confirm.confirm(result);
+ }
+}
diff --git a/src/app/shared/flows-list/flows-list.css b/src/app/shared/flows-list/flows-list.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/shared/flows-list/flows-list.html b/src/app/shared/flows-list/flows-list.html
new file mode 100644
index 0000000..d1b4666
--- /dev/null
+++ b/src/app/shared/flows-list/flows-list.html
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+ @for (flow of filteredFlows; track flow.id) {
+
+
+
+
+
+
+
+
+
+ {{ flow.name }}
+
+
+
+
+
+
+ }
+
+
\ No newline at end of file
diff --git a/src/app/shared/flows-list/flows-list.spec.ts b/src/app/shared/flows-list/flows-list.spec.ts
new file mode 100644
index 0000000..1470b75
--- /dev/null
+++ b/src/app/shared/flows-list/flows-list.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { FlowsList } from './flows-list';
+
+describe('FlowsList', () => {
+ let component: FlowsList;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [FlowsList]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(FlowsList);
+ component = fixture.componentInstance;
+ await fixture.whenStable();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/shared/flows-list/flows-list.ts b/src/app/shared/flows-list/flows-list.ts
new file mode 100644
index 0000000..fcae93c
--- /dev/null
+++ b/src/app/shared/flows-list/flows-list.ts
@@ -0,0 +1,35 @@
+import { CommonModule } from '@angular/common';
+import { Component, inject } from '@angular/core';
+import { Flow } from '@models/flow';
+import { EditorStateHolder } from 'app/stores/editor';
+
+type FlowFilter = 'all' | 'public' | 'private';
+
+@Component({
+ selector: 'app-flows-list',
+ imports: [CommonModule],
+ templateUrl: './flows-list.html',
+ styleUrl: './flows-list.css',
+})
+export class FlowsList {
+ filter: FlowFilter = 'all';
+
+ private editorState = inject(EditorStateHolder);
+
+ flows : Flow[] = [
+ { id: '1', name: 'Flow One', visibility: 'public', data: {} },
+ { id: '2', name: 'Flow Two', visibility: 'private', data: {} },
+ { id: '3', name: 'Flow Three', visibility: 'public', data: {} },
+ { id: '4', name: 'Flow Four', visibility: 'private', data: {} },
+ ]
+
+ get filteredFlows() {
+ if (this.filter === 'all') return this.flows;
+ 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/stores/editor.spec.ts b/src/app/stores/editor.spec.ts
new file mode 100644
index 0000000..57a1234
--- /dev/null
+++ b/src/app/stores/editor.spec.ts
@@ -0,0 +1,16 @@
+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
new file mode 100644
index 0000000..b8fa857
--- /dev/null
+++ b/src/app/stores/editor.ts
@@ -0,0 +1,46 @@
+import { computed, Injectable, signal } from '@angular/core';
+import { Flow } from '@models/flow';
+import { ConfirmDialogService } from '@services/dialogs/confirm-dialog';
+
+@Injectable({ providedIn: 'root' })
+export class EditorStateHolder {
+
+ /** Stato */
+ readonly currentFlow = signal(null);
+ readonly isDirty = signal(false);
+
+ /** Derived state */
+ readonly hasFlow = computed(() => !!this.currentFlow());
+
+ constructor(private confirm: ConfirmDialogService) { }
+
+ /** Intent: open document */
+ async openDocument(doc: Flow): Promise {
+ 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 */
+ markDirty() {
+ this.isDirty.set(true);
+ }
+
+ /** Intent: save document */
+ markSaved() {
+ this.isDirty.set(false);
+ }
+
+ /** Intent: close document */
+ closeDocument() {
+ this.currentFlow.set(null);
+ this.isDirty.set(false);
+ }
+}