diff --git a/src/app/components/components.module.ts b/src/app/components/components.module.ts
index 6bea45d..c75dc64 100644
--- a/src/app/components/components.module.ts
+++ b/src/app/components/components.module.ts
@@ -3,11 +3,15 @@ import { CommonModule } from '@angular/common';
import { HostComponent } from './editor/host/host.component';
import {NodePickerComponent} from './editor/node-picker/node-picker.component';
import {IonicModule} from '@ionic/angular';
+import {DatabaseComponent} from './editor/database/database.component';
+import {AgGridModule} from 'ag-grid-angular';
+import {ColumnsComponent} from './editor/database/columns/columns.component';
+import {FormsModule} from '@angular/forms';
@NgModule({
- declarations: [HostComponent, NodePickerComponent],
- imports: [CommonModule, IonicModule],
- entryComponents: [HostComponent, NodePickerComponent],
- exports: [HostComponent, NodePickerComponent]
+ declarations: [HostComponent, NodePickerComponent, DatabaseComponent, ColumnsComponent],
+ imports: [CommonModule, IonicModule, AgGridModule, FormsModule],
+ entryComponents: [HostComponent, NodePickerComponent, DatabaseComponent, ColumnsComponent],
+ exports: [HostComponent, NodePickerComponent, DatabaseComponent, ColumnsComponent]
})
export class ComponentsModule {}
diff --git a/src/app/components/editor/database/columns/columns.component.html b/src/app/components/editor/database/columns/columns.component.html
new file mode 100644
index 0000000..e9043ed
--- /dev/null
+++ b/src/app/components/editor/database/columns/columns.component.html
@@ -0,0 +1,46 @@
+
+
+ Manage Database Columns
+
+
+
+
+
+
+
+
+
+
+
+
+ Add Column
+ Save
+
+
+
+
+
+ Field Label
+
+
+
+
+
+ Data Type
+
+ Text
+ Number
+ Text-Area
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/components/editor/database/columns/columns.component.scss b/src/app/components/editor/database/columns/columns.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/components/editor/database/columns/columns.component.ts b/src/app/components/editor/database/columns/columns.component.ts
new file mode 100644
index 0000000..aa4255c
--- /dev/null
+++ b/src/app/components/editor/database/columns/columns.component.ts
@@ -0,0 +1,41 @@
+import {Component, Input, OnInit} from '@angular/core';
+import {ModalController} from '@ionic/angular';
+
+@Component({
+ selector: 'editor-database-columns',
+ templateUrl: './columns.component.html',
+ styleUrls: ['./columns.component.scss'],
+})
+export class ColumnsComponent implements OnInit {
+ @Input() columnSets: Array<{headerName: string, field: string, Type: string}> = [];
+
+ constructor(
+ protected modals: ModalController
+ ) { }
+
+ ngOnInit() {}
+
+ onAddColumnClick() {
+ this.columnSets.push({headerName: '', field: '', Type: ''});
+ }
+
+ dismissModal(doSave = true) {
+ if ( doSave ) {
+ this.columnSets = this.columnSets.map(x => {
+ x.field = x.headerName;
+ return x;
+ })
+ this.modals.dismiss(this.columnSets);
+ } else {
+ this.modals.dismiss();
+ }
+ }
+
+ onDeleteClick(i) {
+ const newSets = this.columnSets.filter((x, index) => {
+ return index !== i;
+ });
+ this.columnSets = newSets;
+ }
+
+}
diff --git a/src/app/components/editor/database/database.component.html b/src/app/components/editor/database/database.component.html
new file mode 100644
index 0000000..b8864f6
--- /dev/null
+++ b/src/app/components/editor/database/database.component.html
@@ -0,0 +1,22 @@
+
+
+
+ Manage Columns
+ Insert Row
+ Delete Row
+ Sync Records
+ Drop Database
+
+
+
+
diff --git a/src/app/components/editor/database/database.component.scss b/src/app/components/editor/database/database.component.scss
new file mode 100644
index 0000000..a11b532
--- /dev/null
+++ b/src/app/components/editor/database/database.component.scss
@@ -0,0 +1,4 @@
+div.database-wrapper {
+ border: 2px solid #8c8c8c;
+ border-radius: 3px;
+}
diff --git a/src/app/components/editor/database/database.component.ts b/src/app/components/editor/database/database.component.ts
new file mode 100644
index 0000000..78378d4
--- /dev/null
+++ b/src/app/components/editor/database/database.component.ts
@@ -0,0 +1,219 @@
+import {Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core';
+import HostRecord from '../../../structures/HostRecord';
+import {ApiService} from '../../../service/api.service';
+import {Observable} from 'rxjs';
+import {AlertController, ModalController} from '@ionic/angular';
+import {ColumnsComponent} from './columns/columns.component';
+import {AgGridAngular} from 'ag-grid-angular';
+
+@Component({
+ selector: 'editor-database',
+ templateUrl: './database.component.html',
+ styleUrls: ['./database.component.scss'],
+})
+export class DatabaseComponent implements OnInit {
+ @Input() hostRecord: HostRecord;
+ @Output() hostRecordChange = new EventEmitter();
+ @Output() requestParentSave = new EventEmitter();
+ @Output() requestParentDelete = new EventEmitter();
+ @ViewChild('agGridElement', {static: false}) agGridElement: AgGridAngular;
+
+ public dbRecord: any;
+ public pendingSetup = true;
+ public dirty = false;
+ protected lastClickRow = -1;
+
+ constructor(
+ protected api: ApiService,
+ protected modals: ModalController,
+ protected alerts: AlertController,
+ ) { }
+
+ title = 'app';
+ columnDefs = [];
+ rowData = [];
+
+ ngOnInit() {
+ this.getInitObservable().subscribe(() => {
+ this.getColumnLoadObservable().subscribe(() => {
+ this.getDataLoadObservable().subscribe(() => {
+
+ });
+ });
+ });
+ }
+
+ onCellValueChanged() {
+ this.dirty = true;
+ }
+
+ async onManageColumns() {
+ const modal = await this.modals.create({
+ component: ColumnsComponent,
+ componentProps: {columnSets: this.columnDefs},
+ });
+
+ modal.onDidDismiss().then(result => {
+ if ( result.data ) {
+ this.columnDefs = result.data.map(x => {
+ x.editable = true;
+ if ( x.Type === 'text' ) {
+ x.editor = 'agTextCellEditor';
+ } else if ( x.Type === 'number' ) {
+ x.valueFormatter = (value) => {
+ const num = parseFloat(value.value);
+ if ( !isNaN(num) ) {
+ return num;
+ } else {
+ return '';
+ }
+ };
+ } else if ( x.Type === 'textarea' ) {
+ x.editor = 'agPopupTextCellEditor';
+ }
+ return x;
+ });
+ const endpoint = `/db/${this.hostRecord.PageId}/${this.hostRecord.UUID}/set/${this.hostRecord.Value.Value}/columns`
+ this.api.post(endpoint, {columns: this.columnDefs}).subscribe(res => {
+ this.columnDefs = res.data;
+ });
+ }
+ });
+
+ await modal.present();
+ }
+
+ onInsertRow() {
+ this.rowData.push({});
+ this.agGridElement.api.setRowData(this.rowData);
+ }
+
+ async onRemoveRow() {
+ const alert = await this.alerts.create({
+ header: 'Are you sure?',
+ message: `You are about to delete row ${this.lastClickRow + 1}. This cannot be undone.`,
+ buttons: [
+ {
+ text: 'Keep It',
+ role: 'cancel',
+ },
+ {
+ text: 'Delete It',
+ handler: () => {
+ const newRows = this.rowData.filter((x, i) => {
+ return i !== this.lastClickRow;
+ });
+
+ this.rowData = newRows;
+ this.agGridElement.api.setRowData(this.rowData);
+ this.lastClickRow = -1;
+ },
+ }
+ ],
+ });
+
+ await alert.present();
+ }
+
+ async onDropDatabase() {
+ const alert = await this.alerts.create({
+ header: 'Are you sure?',
+ message: `You are about to delete this database and all its entries. This action cannot be undone.`,
+ buttons: [
+ {
+ text: 'Keep It',
+ role: 'cancel',
+ },
+ {
+ text: 'Delete It',
+ handler: async () => {
+ this.api.post(`/db/${this.hostRecord.PageId}/${this.hostRecord.UUID}/drop/${this.hostRecord.Value.Value}`).subscribe();
+ this.requestParentDelete.emit(this);
+ },
+ },
+ ],
+ });
+
+ await alert.present();
+ }
+
+ onRowClicked($event) {
+ this.lastClickRow = $event.rowIndex;
+ }
+
+ getDataLoadObservable(): Observable {
+ return new Observable(sub => {
+ this.api.get(`/db/${this.hostRecord.PageId}/${this.hostRecord.UUID}/get/${this.hostRecord.Value.Value}/data`).subscribe(res => {
+ this.rowData = res.data.map(x => x.RowData);
+ this.agGridElement.api.setRowData(this.rowData);
+ sub.next();
+ sub.complete();
+ });
+ });
+ }
+
+ onSyncRecords() {
+ this.api.post(`/db/${this.hostRecord.PageId}/${this.hostRecord.UUID}/set/${this.hostRecord.Value.Value}/data`, this.rowData)
+ .subscribe(res => {
+ this.rowData = res.data.map(x => x.RowData);
+ this.agGridElement.api.setRowData(this.rowData);
+ this.dirty = false;
+ });
+ }
+
+ getColumnLoadObservable(): Observable {
+ return new Observable(sub => {
+ this.api.get(`/db/${this.hostRecord.PageId}/${this.hostRecord.UUID}/get/${this.hostRecord.Value.Value}/columns`).subscribe(res => {
+ this.columnDefs = res.data.map(x => {
+ x.editable = true;
+ if ( x.Type === 'text' ) {
+ x.editor = 'agTextCellEditor';
+ } else if ( x.Type === 'number' ) {
+ x.valueFormatter = (value) => {
+ const num = parseFloat(value.value);
+ if ( !isNaN(num) ) {
+ return num;
+ } else {
+ return '';
+ }
+ };
+ } else if ( x.Type === 'textarea' ) {
+ x.editor = 'agPopupTextCellEditor';
+ }
+ return x;
+ });
+ sub.next();
+ sub.complete();
+ });
+ });
+ }
+
+ getInitObservable(): Observable {
+ return new Observable(sub => {
+ if ( this.hostRecord && this.pendingSetup ) {
+ if ( !this.hostRecord.Value.Value ) {
+ this.api.post(`/db/${this.hostRecord.PageId}/${this.hostRecord.UUID}/create`).subscribe(res => {
+ this.dbRecord = res.data;
+ this.hostRecord.Value.Mode = 'database';
+ this.hostRecord.Value.Value = res.data.UUID;
+ this.hostRecord.value = res.data.UUID;
+ this.hostRecordChange.emit(this.hostRecord);
+ this.pendingSetup = false;
+ sub.next(true);
+ sub.complete();
+ });
+ } else {
+ this.api.get(`/db/${this.hostRecord.PageId}/${this.hostRecord.UUID}/get/${this.hostRecord.Value.Value}`).subscribe(res => {
+ this.dbRecord = res.data;
+ this.pendingSetup = false;
+ sub.next(true);
+ sub.complete();
+ });
+ }
+ } else {
+ this.pendingSetup = true;
+ }
+ });
+ }
+
+}
diff --git a/src/app/components/editor/host/host.component.html b/src/app/components/editor/host/host.component.html
index 2f3c160..e7cc0f8 100644
--- a/src/app/components/editor/host/host.component.html
+++ b/src/app/components/editor/host/host.component.html
@@ -23,4 +23,15 @@
[innerHTML]="listLines[i]"
>
+
+
+
diff --git a/src/app/components/editor/host/host.component.spec.ts b/src/app/components/editor/host/host.component.spec.ts
deleted file mode 100644
index 5eb2e63..0000000
--- a/src/app/components/editor/host/host.component.spec.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-import { IonicModule } from '@ionic/angular';
-
-import { HostComponent } from './host.component';
-
-describe('HostComponent', () => {
- let component: HostComponent;
- let fixture: ComponentFixture;
-
- beforeEach(async(() => {
- TestBed.configureTestingModule({
- declarations: [ HostComponent ],
- imports: [IonicModule.forRoot()]
- }).compileComponents();
-
- fixture = TestBed.createComponent(HostComponent);
- component = fixture.componentInstance;
- fixture.detectChanges();
- }));
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-});
diff --git a/src/app/components/editor/host/host.component.ts b/src/app/components/editor/host/host.component.ts
index a6e0e25..9aeb144 100644
--- a/src/app/components/editor/host/host.component.ts
+++ b/src/app/components/editor/host/host.component.ts
@@ -11,6 +11,7 @@ export class HostComponent implements OnInit {
@Output() recordChange = new EventEmitter();
@Output() newHostRequested = new EventEmitter();
@Output() destroyHostRequested = new EventEmitter();
+ @Output() saveHostRequested = new EventEmitter();
@ViewChild('hostContainer', {static: false}) hostContainer: ElementRef;
@ViewChildren('liItems') liItems;
@@ -20,6 +21,11 @@ export class HostComponent implements OnInit {
ngOnInit() {}
+ onRecordChange($event) {
+ console.log({$event});
+ this.recordChange.emit($event);
+ }
+
onKeyUp($event) {
const innerText = this.hostContainer.nativeElement.innerText.trim()
if ( $event.code === 'Enter'
@@ -45,8 +51,8 @@ export class HostComponent implements OnInit {
this.record.type = 'block_code';
} else if ( innerText.startsWith('http') ) {
this.record.type = 'click_link';
- } else if ( innerText.startsWith('-') || innerText.startsWith(' -') ) {
- this.record.type = 'ul';
+ } else if ( false && innerText.startsWith('-') || innerText.startsWith(' -') ) {
+ // this.record.type = 'ul';
this.listLines = [this.record.value];
setTimeout(() => {
const item = this.liItems.toArray()[0].nativeElement;
@@ -57,15 +63,17 @@ export class HostComponent implements OnInit {
s.removeAllRanges();
s.addRange(r);
}, 0);
- } else {
- this.record.type = 'paragraph';
}
}
+ onRequestDelete($event) {
+ this.destroyHostRequested.emit(this);
+ }
+
onLIKeyUp($event, i) {
console.log({$event});
if ( $event.code === 'Enter' ) {
- const newListLines = [];
+ /*const newListLines = [];
this.liItems.forEach((li, index) => {
newListLines.push(li.nativeElement.innerText.trim());
if ( index === i ) {
@@ -73,7 +81,7 @@ export class HostComponent implements OnInit {
}
});
- this.listLines = newListLines;
+ this.listLines = newListLines;*/
// this.listLines[i] = this.liItems[i].innerText.trim()
// const newLines = []
// this.listLines.forEach((rec, x) => {
@@ -98,6 +106,10 @@ export class HostComponent implements OnInit {
}
}
+ onRequestParentSave($event) {
+ this.saveHostRequested.emit(this);
+ }
+
onHostDblClick() {
if ( this.record.type === 'click_link' ) {
window.open(this.record.value.trim(), '_blank');
diff --git a/src/app/components/editor/node-picker/node-picker.component.html b/src/app/components/editor/node-picker/node-picker.component.html
index 8a26120..5a8d183 100644
--- a/src/app/components/editor/node-picker/node-picker.component.html
+++ b/src/app/components/editor/node-picker/node-picker.component.html
@@ -1,33 +1,33 @@
-
+
Paragraph
-
+
Heading 1
-
+
Heading 2
-
+
Heading 3
-
+
Heading 4
-
+
Monospace Block
-
+
Hyperlink
-
+
Database
diff --git a/src/app/components/editor/node-picker/node-picker.component.ts b/src/app/components/editor/node-picker/node-picker.component.ts
index f972b8d..d65146e 100644
--- a/src/app/components/editor/node-picker/node-picker.component.ts
+++ b/src/app/components/editor/node-picker/node-picker.component.ts
@@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core';
+import {PopoverController} from '@ionic/angular';
@Component({
selector: 'editor-node-picker',
@@ -7,8 +8,14 @@ import { Component, OnInit } from '@angular/core';
})
export class NodePickerComponent implements OnInit {
- constructor() { }
+ constructor(
+ private popover: PopoverController,
+ ) { }
ngOnInit() {}
+ onSelect(value: string) {
+ this.popover.dismiss(value).then(() => {});
+ }
+
}
diff --git a/src/app/pages/editor/editor.page.html b/src/app/pages/editor/editor.page.html
index f01e7ba..3e6416a 100644
--- a/src/app/pages/editor/editor.page.html
+++ b/src/app/pages/editor/editor.page.html
@@ -18,14 +18,15 @@
- Add Node
- Save
+ Add Node
+ Save
diff --git a/src/app/pages/editor/editor.page.ts b/src/app/pages/editor/editor.page.ts
index c40ada4..631431b 100644
--- a/src/app/pages/editor/editor.page.ts
+++ b/src/app/pages/editor/editor.page.ts
@@ -28,6 +28,8 @@ export class EditorPage implements OnInit {
this.route.params.subscribe(params => {
this.pageId = params.id;
});
+
+ console.log('editor page', this);
}
ngOnInit() {}
@@ -45,18 +47,9 @@ export class EditorPage implements OnInit {
}
}
- /*onAddClick() {
- this.hostRecords.push(new HostRecord(''));
- setTimeout(() => {
- const host = this.editorHosts.toArray().reverse()[0].hostContainer.nativeElement;
- const s = window.getSelection();
- const r = document.createRange();
- r.setStart(host, 0);
- r.setEnd(host, 0);
- s.removeAllRanges();
- s.addRange(r);
- }, 0);
- }*/
+ onHostRecordChange($event, i) {
+ this.hostRecords[i] = $event;
+ }
async onAddClick($event) {
const popover = await this.popover.create({
@@ -64,9 +57,52 @@ export class EditorPage implements OnInit {
event: $event,
});
+ popover.onDidDismiss().then(arg => {
+ console.log({arg});
+ const defValue = this.getDefaultValue(arg.data);
+ const hostRec = new HostRecord(defValue);
+ console.log({hostRec});
+ hostRec.type = arg.data;
+ hostRec.PageId = this.pageRecord.UUID;
+ this.hostRecords.push(hostRec);
+ if ( hostRec.isNorm() ) {
+ setTimeout(() => {
+ const host = this.editorHosts.toArray().reverse()[0].hostContainer.nativeElement;
+ const s = window.getSelection();
+ const r = document.createRange();
+ r.setStart(host, defValue.length);
+ r.setEnd(host, defValue.length);
+ s.removeAllRanges();
+ s.addRange(r);
+ }, 0);
+ } else {
+ this.onSaveClick();
+ }
+ });
+
await popover.present();
}
+ getDefaultValue(type: string) {
+ if ( type === 'paragraph' ) {
+ return '';
+ } else if ( type === 'header1' ) {
+ return '# ';
+ } else if ( type === 'header2' ) {
+ return '## ';
+ } else if ( type === 'header3' ) {
+ return '### ';
+ } else if ( type === 'header4' ) {
+ return '#### ';
+ } else if ( type === 'block_code' ) {
+ return '```';
+ } else if ( type === 'click_link' ) {
+ return 'https://';
+ } else {
+ return '';
+ }
+ }
+
onNewHostRequested($event) {
const insertAfter = this.getIndexFromRecord($event.record);
const record = new HostRecord('');
diff --git a/src/app/service/page.service.ts b/src/app/service/page.service.ts
index a6580f0..f440d63 100644
--- a/src/app/service/page.service.ts
+++ b/src/app/service/page.service.ts
@@ -52,6 +52,7 @@ export class PageService {
}
save_nodes(page: PageRecord, nodes: Array): Observable> {
+ console.log('save nodes', {nodes})
return new Observable>(sub => {
nodes = nodes.map(x => {
x.PageId = page.UUID;
diff --git a/src/app/structures/HostRecord.ts b/src/app/structures/HostRecord.ts
index 56b6af3..e78eb8b 100644
--- a/src/app/structures/HostRecord.ts
+++ b/src/app/structures/HostRecord.ts
@@ -1,6 +1,6 @@
export default class HostRecord {
public value = '';
- public type: 'paragraph'|'header1'|'header2'|'header3'|'header4'|'block_code'|'click_link'|'ul' = 'paragraph';
+ public type: 'paragraph'|'header1'|'header2'|'header3'|'header4'|'block_code'|'click_link'|'database_ref' = 'paragraph';
public CreatedAt: string;
public PageId: string;
@@ -12,6 +12,10 @@ export default class HostRecord {
this.value = value;
}
+ public isNorm() {
+ return ['paragraph', 'header1', 'header2', 'header3', 'header4', 'block_code', 'click_link'].includes(this.type);
+ }
+
load(data: any) {
this.type = data.Type;