finish database implementation
This commit is contained in:
parent
e2dd56ab72
commit
e97c19f19d
@ -3,11 +3,15 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { HostComponent } from './editor/host/host.component';
|
import { HostComponent } from './editor/host/host.component';
|
||||||
import {NodePickerComponent} from './editor/node-picker/node-picker.component';
|
import {NodePickerComponent} from './editor/node-picker/node-picker.component';
|
||||||
import {IonicModule} from '@ionic/angular';
|
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({
|
@NgModule({
|
||||||
declarations: [HostComponent, NodePickerComponent],
|
declarations: [HostComponent, NodePickerComponent, DatabaseComponent, ColumnsComponent],
|
||||||
imports: [CommonModule, IonicModule],
|
imports: [CommonModule, IonicModule, AgGridModule, FormsModule],
|
||||||
entryComponents: [HostComponent, NodePickerComponent],
|
entryComponents: [HostComponent, NodePickerComponent, DatabaseComponent, ColumnsComponent],
|
||||||
exports: [HostComponent, NodePickerComponent]
|
exports: [HostComponent, NodePickerComponent, DatabaseComponent, ColumnsComponent]
|
||||||
})
|
})
|
||||||
export class ComponentsModule {}
|
export class ComponentsModule {}
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Manage Database Columns</ion-title>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button (click)="dismissModal(false)">
|
||||||
|
<ion-icon name="close"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content>
|
||||||
|
<ion-grid>
|
||||||
|
<ion-row>
|
||||||
|
<ion-col size="12">
|
||||||
|
<ion-button (click)="onAddColumnClick()" fill="outline">Add Column</ion-button>
|
||||||
|
<ion-button (click)="dismissModal(true)" color="success" fill="outline">Save</ion-button>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
<ion-row
|
||||||
|
*ngFor="let colSet of columnSets; let i = index"
|
||||||
|
>
|
||||||
|
<ion-col size="5">
|
||||||
|
<ion-item>
|
||||||
|
<ion-label position="floating">Field Label</ion-label>
|
||||||
|
<ion-input type="text" required [(ngModel)]="columnSets[i].headerName"></ion-input>
|
||||||
|
</ion-item>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col size="5">
|
||||||
|
<ion-item>
|
||||||
|
<ion-label position="floating">Data Type</ion-label>
|
||||||
|
<ion-select interface="popover" [(ngModel)]="columnSets[i].Type">
|
||||||
|
<ion-select-option value="text">Text</ion-select-option>
|
||||||
|
<ion-select-option value="number">Number</ion-select-option>
|
||||||
|
<ion-select-option value="textarea">Text-Area</ion-select-option>
|
||||||
|
</ion-select>
|
||||||
|
</ion-item>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col size="2" align-items-center>
|
||||||
|
<ion-button fill="outline" color="light" (click)="onDeleteClick(i)">
|
||||||
|
<ion-icon color="danger" name="trash"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-grid>
|
||||||
|
</ion-content>
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
22
src/app/components/editor/database/database.component.html
Normal file
22
src/app/components/editor/database/database.component.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<div class="database-wrapper">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons>
|
||||||
|
<ion-button (click)="onManageColumns()"><ion-icon name="build" color="primary"></ion-icon> Manage Columns</ion-button>
|
||||||
|
<ion-button (click)="onInsertRow()"><ion-icon name="add-circle" color="success"></ion-icon> Insert Row</ion-button>
|
||||||
|
<ion-button (click)="onRemoveRow()" [disabled]="lastClickRow < 0"><ion-icon name="remove-circle" color="danger"></ion-icon> Delete Row</ion-button>
|
||||||
|
<ion-button (click)="onSyncRecords()"><ion-icon name="save" [color]="dirty ? 'warning' : 'success'"></ion-icon> Sync Records</ion-button>
|
||||||
|
<ion-button (click)="onDropDatabase()"><ion-icon name="alert" color="danger"></ion-icon> Drop Database</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
<div class="grid-wrapper">
|
||||||
|
<ag-grid-angular
|
||||||
|
style="width: 100%; height: 500px;"
|
||||||
|
class="ag-theme-balham"
|
||||||
|
[rowData]="rowData"
|
||||||
|
[columnDefs]="columnDefs"
|
||||||
|
(rowClicked)="onRowClicked($event)"
|
||||||
|
(cellValueChanged)="onCellValueChanged()"
|
||||||
|
#agGridElement
|
||||||
|
></ag-grid-angular>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,4 @@
|
|||||||
|
div.database-wrapper {
|
||||||
|
border: 2px solid #8c8c8c;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
219
src/app/components/editor/database/database.component.ts
Normal file
219
src/app/components/editor/database/database.component.ts
Normal file
@ -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<HostRecord>();
|
||||||
|
@Output() requestParentSave = new EventEmitter<DatabaseComponent>();
|
||||||
|
@Output() requestParentDelete = new EventEmitter<DatabaseComponent>();
|
||||||
|
@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<any> {
|
||||||
|
return new Observable<any>(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<any> {
|
||||||
|
return new Observable<any>(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<any> {
|
||||||
|
return new Observable<any>(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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -23,4 +23,15 @@
|
|||||||
[innerHTML]="listLines[i]"
|
[innerHTML]="listLines[i]"
|
||||||
></li>
|
></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<div
|
||||||
|
*ngIf="record.type === 'database_ref'"
|
||||||
|
class="db-wrapper"
|
||||||
|
>
|
||||||
|
<editor-database
|
||||||
|
[hostRecord]="record"
|
||||||
|
(hostRecordChange)="onRecordChange($event)"
|
||||||
|
(requestParentSave)="onRequestParentSave($event)"
|
||||||
|
(requestParentDelete)="onRequestDelete($event)"
|
||||||
|
></editor-database>
|
||||||
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -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<HostComponent>;
|
|
||||||
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
});
|
|
@ -11,6 +11,7 @@ export class HostComponent implements OnInit {
|
|||||||
@Output() recordChange = new EventEmitter<HostRecord>();
|
@Output() recordChange = new EventEmitter<HostRecord>();
|
||||||
@Output() newHostRequested = new EventEmitter<HostComponent>();
|
@Output() newHostRequested = new EventEmitter<HostComponent>();
|
||||||
@Output() destroyHostRequested = new EventEmitter<HostComponent>();
|
@Output() destroyHostRequested = new EventEmitter<HostComponent>();
|
||||||
|
@Output() saveHostRequested = new EventEmitter<HostComponent>();
|
||||||
@ViewChild('hostContainer', {static: false}) hostContainer: ElementRef;
|
@ViewChild('hostContainer', {static: false}) hostContainer: ElementRef;
|
||||||
@ViewChildren('liItems') liItems;
|
@ViewChildren('liItems') liItems;
|
||||||
|
|
||||||
@ -20,6 +21,11 @@ export class HostComponent implements OnInit {
|
|||||||
|
|
||||||
ngOnInit() {}
|
ngOnInit() {}
|
||||||
|
|
||||||
|
onRecordChange($event) {
|
||||||
|
console.log({$event});
|
||||||
|
this.recordChange.emit($event);
|
||||||
|
}
|
||||||
|
|
||||||
onKeyUp($event) {
|
onKeyUp($event) {
|
||||||
const innerText = this.hostContainer.nativeElement.innerText.trim()
|
const innerText = this.hostContainer.nativeElement.innerText.trim()
|
||||||
if ( $event.code === 'Enter'
|
if ( $event.code === 'Enter'
|
||||||
@ -45,8 +51,8 @@ export class HostComponent implements OnInit {
|
|||||||
this.record.type = 'block_code';
|
this.record.type = 'block_code';
|
||||||
} else if ( innerText.startsWith('http') ) {
|
} else if ( innerText.startsWith('http') ) {
|
||||||
this.record.type = 'click_link';
|
this.record.type = 'click_link';
|
||||||
} else if ( innerText.startsWith('-') || innerText.startsWith(' -') ) {
|
} else if ( false && innerText.startsWith('-') || innerText.startsWith(' -') ) {
|
||||||
this.record.type = 'ul';
|
// this.record.type = 'ul';
|
||||||
this.listLines = [this.record.value];
|
this.listLines = [this.record.value];
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const item = this.liItems.toArray()[0].nativeElement;
|
const item = this.liItems.toArray()[0].nativeElement;
|
||||||
@ -57,15 +63,17 @@ export class HostComponent implements OnInit {
|
|||||||
s.removeAllRanges();
|
s.removeAllRanges();
|
||||||
s.addRange(r);
|
s.addRange(r);
|
||||||
}, 0);
|
}, 0);
|
||||||
} else {
|
|
||||||
this.record.type = 'paragraph';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onRequestDelete($event) {
|
||||||
|
this.destroyHostRequested.emit(this);
|
||||||
|
}
|
||||||
|
|
||||||
onLIKeyUp($event, i) {
|
onLIKeyUp($event, i) {
|
||||||
console.log({$event});
|
console.log({$event});
|
||||||
if ( $event.code === 'Enter' ) {
|
if ( $event.code === 'Enter' ) {
|
||||||
const newListLines = [];
|
/*const newListLines = [];
|
||||||
this.liItems.forEach((li, index) => {
|
this.liItems.forEach((li, index) => {
|
||||||
newListLines.push(li.nativeElement.innerText.trim());
|
newListLines.push(li.nativeElement.innerText.trim());
|
||||||
if ( index === i ) {
|
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()
|
// this.listLines[i] = this.liItems[i].innerText.trim()
|
||||||
// const newLines = []
|
// const newLines = []
|
||||||
// this.listLines.forEach((rec, x) => {
|
// this.listLines.forEach((rec, x) => {
|
||||||
@ -98,6 +106,10 @@ export class HostComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onRequestParentSave($event) {
|
||||||
|
this.saveHostRequested.emit(this);
|
||||||
|
}
|
||||||
|
|
||||||
onHostDblClick() {
|
onHostDblClick() {
|
||||||
if ( this.record.type === 'click_link' ) {
|
if ( this.record.type === 'click_link' ) {
|
||||||
window.open(this.record.value.trim(), '_blank');
|
window.open(this.record.value.trim(), '_blank');
|
||||||
|
@ -1,33 +1,33 @@
|
|||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-item>
|
<ion-item button (click)="onSelect('paragraph')">
|
||||||
<ion-icon slot="start" name="menu"></ion-icon>
|
<ion-icon slot="start" name="menu"></ion-icon>
|
||||||
<ion-label>Paragraph</ion-label>
|
<ion-label>Paragraph</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item button (click)="onSelect('header1')">
|
||||||
<ion-icon slot="start" name="alert"></ion-icon>
|
<ion-icon slot="start" name="alert"></ion-icon>
|
||||||
<ion-label>Heading 1</ion-label>
|
<ion-label>Heading 1</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item button (click)="onSelect('header2')">
|
||||||
<ion-icon slot="start" name="alert"></ion-icon>
|
<ion-icon slot="start" name="alert"></ion-icon>
|
||||||
<ion-label>Heading 2</ion-label>
|
<ion-label>Heading 2</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item button (click)="onSelect('header3')">
|
||||||
<ion-icon slot="start" name="alert"></ion-icon>
|
<ion-icon slot="start" name="alert"></ion-icon>
|
||||||
<ion-label>Heading 3</ion-label>
|
<ion-label>Heading 3</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item button (click)="onSelect('header4')">
|
||||||
<ion-icon slot="start" name="alert"></ion-icon>
|
<ion-icon slot="start" name="alert"></ion-icon>
|
||||||
<ion-label>Heading 4</ion-label>
|
<ion-label>Heading 4</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item button (click)="onSelect('block_code')">
|
||||||
<ion-icon slot="start" name="code"></ion-icon>
|
<ion-icon slot="start" name="code"></ion-icon>
|
||||||
<ion-label>Monospace Block</ion-label>
|
<ion-label>Monospace Block</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item button (click)="onSelect('click_link')">
|
||||||
<ion-icon slot="start" name="link"></ion-icon>
|
<ion-icon slot="start" name="link"></ion-icon>
|
||||||
<ion-label>Hyperlink</ion-label>
|
<ion-label>Hyperlink</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item button (click)="onSelect('database_ref')">
|
||||||
<ion-icon slot="start" name="analytics"></ion-icon>
|
<ion-icon slot="start" name="analytics"></ion-icon>
|
||||||
<ion-label>Database</ion-label>
|
<ion-label>Database</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import {PopoverController} from '@ionic/angular';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'editor-node-picker',
|
selector: 'editor-node-picker',
|
||||||
@ -7,8 +8,14 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
})
|
})
|
||||||
export class NodePickerComponent implements OnInit {
|
export class NodePickerComponent implements OnInit {
|
||||||
|
|
||||||
constructor() { }
|
constructor(
|
||||||
|
private popover: PopoverController,
|
||||||
|
) { }
|
||||||
|
|
||||||
ngOnInit() {}
|
ngOnInit() {}
|
||||||
|
|
||||||
|
onSelect(value: string) {
|
||||||
|
this.popover.dismiss(value).then(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,14 +18,15 @@
|
|||||||
<ng-container>
|
<ng-container>
|
||||||
<div class="editor-root ion-padding">
|
<div class="editor-root ion-padding">
|
||||||
<div class="host-container ion-padding">
|
<div class="host-container ion-padding">
|
||||||
<editor-host #editorHosts *ngFor="let record of hostRecords; let i = index" [(record)]="hostRecords[i]"
|
<editor-host #editorHosts *ngFor="let record of hostRecords; let i = index" [record]="hostRecords[i]" (recordChange)="onHostRecordChange($event, i)"
|
||||||
(newHostRequested)="onNewHostRequested($event)" (destroyHostRequested)="onDestroyHostRequested($event)">
|
(newHostRequested)="onNewHostRequested($event)" (destroyHostRequested)="onDestroyHostRequested($event)"
|
||||||
|
(saveHostRequested)="onSaveClick()">
|
||||||
</editor-host>
|
</editor-host>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="editor-buttons">
|
<div class="editor-buttons">
|
||||||
<ion-button (click)="onAddClick($event)" class="ion-padding ion-margin" fill="outline" color="medium">Add Node</ion-button>
|
<ion-button (click)="onAddClick($event)" class="ion-padding ion-margin-start" fill="outline" color="medium">Add Node</ion-button>
|
||||||
<ion-button (click)="onSaveClick()" class="ion-padding ion-margin" fill="outline" color="medium">Save</ion-button>
|
<ion-button (click)="onSaveClick()" class="ion-padding" fill="outline" color="medium">Save</ion-button>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
@ -28,6 +28,8 @@ export class EditorPage implements OnInit {
|
|||||||
this.route.params.subscribe(params => {
|
this.route.params.subscribe(params => {
|
||||||
this.pageId = params.id;
|
this.pageId = params.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('editor page', this);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {}
|
ngOnInit() {}
|
||||||
@ -45,18 +47,9 @@ export class EditorPage implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*onAddClick() {
|
onHostRecordChange($event, i) {
|
||||||
this.hostRecords.push(new HostRecord(''));
|
this.hostRecords[i] = $event;
|
||||||
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);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
async onAddClick($event) {
|
async onAddClick($event) {
|
||||||
const popover = await this.popover.create({
|
const popover = await this.popover.create({
|
||||||
@ -64,9 +57,52 @@ export class EditorPage implements OnInit {
|
|||||||
event: $event,
|
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();
|
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) {
|
onNewHostRequested($event) {
|
||||||
const insertAfter = this.getIndexFromRecord($event.record);
|
const insertAfter = this.getIndexFromRecord($event.record);
|
||||||
const record = new HostRecord('');
|
const record = new HostRecord('');
|
||||||
|
@ -52,6 +52,7 @@ export class PageService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
save_nodes(page: PageRecord, nodes: Array<HostRecord>): Observable<Array<HostRecord>> {
|
save_nodes(page: PageRecord, nodes: Array<HostRecord>): Observable<Array<HostRecord>> {
|
||||||
|
console.log('save nodes', {nodes})
|
||||||
return new Observable<Array<HostRecord>>(sub => {
|
return new Observable<Array<HostRecord>>(sub => {
|
||||||
nodes = nodes.map(x => {
|
nodes = nodes.map(x => {
|
||||||
x.PageId = page.UUID;
|
x.PageId = page.UUID;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
export default class HostRecord {
|
export default class HostRecord {
|
||||||
public value = '';
|
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 CreatedAt: string;
|
||||||
public PageId: string;
|
public PageId: string;
|
||||||
@ -12,6 +12,10 @@ export default class HostRecord {
|
|||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isNorm() {
|
||||||
|
return ['paragraph', 'header1', 'header2', 'header3', 'header4', 'block_code', 'click_link'].includes(this.type);
|
||||||
|
}
|
||||||
|
|
||||||
load(data: any) {
|
load(data: any) {
|
||||||
this.type = data.Type;
|
this.type = data.Type;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user