Implement sub-tree sharing; read-only pages
This commit is contained in:
parent
1eda3d0b30
commit
9f361896ee
@ -10,7 +10,7 @@
|
|||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-list-header>
|
<ion-list-header>
|
||||||
<ion-buttons class="ion-padding-start">
|
<ion-buttons>
|
||||||
<ion-button fill="outline" [color]="refreshingMenu ? 'success' : 'light'" (click)="onMenuRefresh()">
|
<ion-button fill="outline" [color]="refreshingMenu ? 'success' : 'light'" (click)="onMenuRefresh()">
|
||||||
<ion-icon color="tertiary" name="refresh"></ion-icon>
|
<ion-icon color="tertiary" name="refresh"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
@ -23,6 +23,9 @@
|
|||||||
<ion-button fill="outline" color="light" (click)="onDeleteClick()" [disabled]="!deleteTarget">
|
<ion-button fill="outline" color="light" (click)="onDeleteClick()" [disabled]="!deleteTarget">
|
||||||
<ion-icon color="danger" name="trash"></ion-icon>
|
<ion-icon color="danger" name="trash"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
<ion-button fill="outline" color="light" (click)="onNodeMenuClick($event)" [disabled]="!menuTarget">
|
||||||
|
<ion-icon color="dark" name="more"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
</ion-list-header>
|
</ion-list-header>
|
||||||
|
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
||||||
import { TestBed, async } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { Platform } from '@ionic/angular';
|
|
||||||
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
|
|
||||||
import { StatusBar } from '@ionic-native/status-bar/ngx';
|
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
|
||||||
|
|
||||||
describe('AppComponent', () => {
|
|
||||||
|
|
||||||
let statusBarSpy, splashScreenSpy, platformReadySpy, platformSpy;
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
|
||||||
statusBarSpy = jasmine.createSpyObj('StatusBar', ['styleDefault']);
|
|
||||||
splashScreenSpy = jasmine.createSpyObj('SplashScreen', ['hide']);
|
|
||||||
platformReadySpy = Promise.resolve();
|
|
||||||
platformSpy = jasmine.createSpyObj('Platform', { ready: platformReadySpy });
|
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
declarations: [AppComponent],
|
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
||||||
providers: [
|
|
||||||
{ provide: StatusBar, useValue: statusBarSpy },
|
|
||||||
{ provide: SplashScreen, useValue: splashScreenSpy },
|
|
||||||
{ provide: Platform, useValue: platformSpy },
|
|
||||||
],
|
|
||||||
imports: [ RouterTestingModule.withRoutes([])],
|
|
||||||
}).compileComponents();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should create the app', async () => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
const app = fixture.debugElement.componentInstance;
|
|
||||||
expect(app).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should initialize the app', async () => {
|
|
||||||
TestBed.createComponent(AppComponent);
|
|
||||||
expect(platformSpy.ready).toHaveBeenCalled();
|
|
||||||
await platformReadySpy;
|
|
||||||
expect(statusBarSpy.styleDefault).toHaveBeenCalled();
|
|
||||||
expect(splashScreenSpy.hide).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have menu labels', async () => {
|
|
||||||
const fixture = await TestBed.createComponent(AppComponent);
|
|
||||||
await fixture.detectChanges();
|
|
||||||
const app = fixture.nativeElement;
|
|
||||||
const menuItems = app.querySelectorAll('ion-label');
|
|
||||||
expect(menuItems.length).toEqual(2);
|
|
||||||
expect(menuItems[0].textContent).toContain('Home');
|
|
||||||
expect(menuItems[1].textContent).toContain('List');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have urls', async () => {
|
|
||||||
const fixture = await TestBed.createComponent(AppComponent);
|
|
||||||
await fixture.detectChanges();
|
|
||||||
const app = fixture.nativeElement;
|
|
||||||
const menuItems = app.querySelectorAll('ion-item');
|
|
||||||
expect(menuItems.length).toEqual(2);
|
|
||||||
expect(menuItems[0].getAttribute('ng-reflect-router-link')).toEqual('/home');
|
|
||||||
expect(menuItems[1].getAttribute('ng-reflect-router-link')).toEqual('/list');
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
@ -1,6 +1,6 @@
|
|||||||
import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';
|
import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';
|
||||||
|
|
||||||
import {AlertController, Platform, PopoverController} from '@ionic/angular';
|
import {AlertController, ModalController, Platform, PopoverController} from '@ionic/angular';
|
||||||
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
|
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
|
||||||
import { StatusBar } from '@ionic-native/status-bar/ngx';
|
import { StatusBar } from '@ionic-native/status-bar/ngx';
|
||||||
import { ApiService } from './service/api.service';
|
import { ApiService } from './service/api.service';
|
||||||
@ -8,6 +8,8 @@ import { Router } from '@angular/router';
|
|||||||
import {TREE_ACTIONS, TreeComponent} from 'angular-tree-component';
|
import {TREE_ACTIONS, TreeComponent} from 'angular-tree-component';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import {OptionPickerComponent} from './components/option-picker/option-picker.component';
|
import {OptionPickerComponent} from './components/option-picker/option-picker.component';
|
||||||
|
import {OptionMenuComponent} from './components/option-menu/option-menu.component';
|
||||||
|
import {SelectorComponent} from './components/sharing/selector/selector.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@ -18,6 +20,7 @@ export class AppComponent implements OnInit {
|
|||||||
@ViewChild('menuTree', {static: false}) menuTree: TreeComponent;
|
@ViewChild('menuTree', {static: false}) menuTree: TreeComponent;
|
||||||
public addChildTarget: any = false;
|
public addChildTarget: any = false;
|
||||||
public deleteTarget: any = false;
|
public deleteTarget: any = false;
|
||||||
|
public menuTarget: any = false;
|
||||||
public refreshingMenu = false;
|
public refreshingMenu = false;
|
||||||
public lastClickEvent: Array<any> = [];
|
public lastClickEvent: Array<any> = [];
|
||||||
public nodes = [];
|
public nodes = [];
|
||||||
@ -37,13 +40,20 @@ export class AppComponent implements OnInit {
|
|||||||
click: (tree, node, $event) => {
|
click: (tree, node, $event) => {
|
||||||
TREE_ACTIONS.FOCUS(tree, node, $event);
|
TREE_ACTIONS.FOCUS(tree, node, $event);
|
||||||
TREE_ACTIONS.EXPAND(tree, node, $event);
|
TREE_ACTIONS.EXPAND(tree, node, $event);
|
||||||
if ( !node.data.noChildren ) {
|
|
||||||
|
this.addChildTarget = false;
|
||||||
|
this.deleteTarget = false;
|
||||||
|
this.menuTarget = false;
|
||||||
|
|
||||||
|
if ( !node.data.noChildren && (!node.data.level || node.data.level === 'manage') ) {
|
||||||
this.addChildTarget = node;
|
this.addChildTarget = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !node.data.noDelete ) {
|
if ( !node.data.noDelete && (!node.data.level || node.data.level === 'manage') ) {
|
||||||
this.deleteTarget = node;
|
this.deleteTarget = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.menuTarget = node;
|
||||||
this.lastClickEvent = [tree, node, $event];
|
this.lastClickEvent = [tree, node, $event];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,6 +70,7 @@ export class AppComponent implements OnInit {
|
|||||||
protected router: Router,
|
protected router: Router,
|
||||||
protected alerts: AlertController,
|
protected alerts: AlertController,
|
||||||
protected popover: PopoverController,
|
protected popover: PopoverController,
|
||||||
|
protected modal: ModalController,
|
||||||
) {
|
) {
|
||||||
this.initializeApp();
|
this.initializeApp();
|
||||||
}
|
}
|
||||||
@ -86,6 +97,33 @@ export class AppComponent implements OnInit {
|
|||||||
this.menuTree.treeModel.filterNodes(value, true);
|
this.menuTree.treeModel.filterNodes(value, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async onNodeMenuClick($event) {
|
||||||
|
const popover = await this.popover.create({
|
||||||
|
component: OptionMenuComponent,
|
||||||
|
componentProps: {
|
||||||
|
menuItems: [
|
||||||
|
...(this.menuTarget.data.level === 'view' ? [] : [{name: 'Share Sub-Tree', icon: 'person-add', value: 'share'}]),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
event: $event,
|
||||||
|
});
|
||||||
|
|
||||||
|
popover.onDidDismiss().then((result) => {
|
||||||
|
if ( result.data === 'share' ) {
|
||||||
|
this.modal.create({
|
||||||
|
component: SelectorComponent,
|
||||||
|
componentProps: {
|
||||||
|
node: this.menuTarget.data,
|
||||||
|
}
|
||||||
|
}).then(modal => {
|
||||||
|
modal.present();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await popover.present();
|
||||||
|
}
|
||||||
|
|
||||||
async onTopLevelCreate() {
|
async onTopLevelCreate() {
|
||||||
const alert = await this.alerts.create({
|
const alert = await this.alerts.create({
|
||||||
header: 'Create Page',
|
header: 'Create Page',
|
||||||
@ -185,6 +223,7 @@ export class AppComponent implements OnInit {
|
|||||||
this.reloadMenuItems().subscribe();
|
this.reloadMenuItems().subscribe();
|
||||||
this.deleteTarget = false;
|
this.deleteTarget = false;
|
||||||
this.addChildTarget = false;
|
this.addChildTarget = false;
|
||||||
|
this.menuTarget = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,8 @@ import {MonacoEditorModule} from 'ngx-monaco-editor';
|
|||||||
import {FilesComponent} from './editor/files/files.component';
|
import {FilesComponent} from './editor/files/files.component';
|
||||||
import {OptionPickerComponent} from './option-picker/option-picker.component';
|
import {OptionPickerComponent} from './option-picker/option-picker.component';
|
||||||
import {HostOptionsComponent} from './editor/host-options/host-options.component';
|
import {HostOptionsComponent} from './editor/host-options/host-options.component';
|
||||||
|
import {OptionMenuComponent} from './option-menu/option-menu.component';
|
||||||
|
import {SelectorComponent} from './sharing/selector/selector.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -22,7 +24,9 @@ import {HostOptionsComponent} from './editor/host-options/host-options.component
|
|||||||
CodeComponent,
|
CodeComponent,
|
||||||
FilesComponent,
|
FilesComponent,
|
||||||
OptionPickerComponent,
|
OptionPickerComponent,
|
||||||
HostOptionsComponent
|
HostOptionsComponent,
|
||||||
|
OptionMenuComponent,
|
||||||
|
SelectorComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@ -39,7 +43,9 @@ import {HostOptionsComponent} from './editor/host-options/host-options.component
|
|||||||
CodeComponent,
|
CodeComponent,
|
||||||
FilesComponent,
|
FilesComponent,
|
||||||
OptionPickerComponent,
|
OptionPickerComponent,
|
||||||
HostOptionsComponent
|
HostOptionsComponent,
|
||||||
|
OptionMenuComponent,
|
||||||
|
SelectorComponent,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
HostComponent,
|
HostComponent,
|
||||||
@ -49,7 +55,9 @@ import {HostOptionsComponent} from './editor/host-options/host-options.component
|
|||||||
CodeComponent,
|
CodeComponent,
|
||||||
FilesComponent,
|
FilesComponent,
|
||||||
OptionPickerComponent,
|
OptionPickerComponent,
|
||||||
HostOptionsComponent
|
HostOptionsComponent,
|
||||||
|
OptionMenuComponent,
|
||||||
|
SelectorComponent,
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class ComponentsModule {}
|
export class ComponentsModule {}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label position="floating">Language</ion-label>
|
<ion-label position="floating">Language</ion-label>
|
||||||
<ion-select style="min-width: 40px;" [(ngModel)]="editorOptions.language" (ionChange)="onSelectChange()">
|
<ion-select style="min-width: 40px;" [(ngModel)]="editorOptions.language" (ionChange)="onSelectChange()" [disabled]="readonly">
|
||||||
<ion-select-option *ngFor="let lang of languageOptions" [value]="lang.toLowerCase()">{{lang}}</ion-select-option>
|
<ion-select-option *ngFor="let lang of languageOptions" [value]="lang.toLowerCase()">{{lang}}</ion-select-option>
|
||||||
</ion-select>
|
</ion-select>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
@ -15,7 +15,7 @@
|
|||||||
#theEditor
|
#theEditor
|
||||||
></ngx-monaco-editor>
|
></ngx-monaco-editor>
|
||||||
</div>
|
</div>
|
||||||
<ion-toolbar>
|
<ion-toolbar *ngIf="!readonly">
|
||||||
<ion-buttons slot="end">
|
<ion-buttons slot="end">
|
||||||
<ion-button (click)="onDropClick()"><ion-icon name="alert" color="danger"></ion-icon> Drop Editor</ion-button>
|
<ion-button (click)="onDropClick()"><ion-icon name="alert" color="danger"></ion-icon> Drop Editor</ion-button>
|
||||||
<ion-button (click)="onSaveClick()"><ion-icon name="save" [color]="dirty ? 'warning' : 'success'"></ion-icon> Save Changes</ion-button>
|
<ion-button (click)="onSaveClick()"><ion-icon name="save" [color]="dirty ? 'warning' : 'success'"></ion-icon> Save Changes</ion-button>
|
||||||
|
@ -11,6 +11,7 @@ import {AlertController, LoadingController} from '@ionic/angular';
|
|||||||
styleUrls: ['./code.component.scss'],
|
styleUrls: ['./code.component.scss'],
|
||||||
})
|
})
|
||||||
export class CodeComponent implements OnInit {
|
export class CodeComponent implements OnInit {
|
||||||
|
@Input() readonly = false;
|
||||||
@Input() hostRecord: HostRecord;
|
@Input() hostRecord: HostRecord;
|
||||||
@Output() hostRecordChange = new EventEmitter<HostRecord>();
|
@Output() hostRecordChange = new EventEmitter<HostRecord>();
|
||||||
@Output() requestParentSave = new EventEmitter<CodeComponent>();
|
@Output() requestParentSave = new EventEmitter<CodeComponent>();
|
||||||
@ -90,6 +91,7 @@ export class CodeComponent implements OnInit {
|
|||||||
public editorOptions = {
|
public editorOptions = {
|
||||||
language: 'javascript',
|
language: 'javascript',
|
||||||
uri: v4(),
|
uri: v4(),
|
||||||
|
readOnly: this.readonly,
|
||||||
};
|
};
|
||||||
public editorValue = '';
|
public editorValue = '';
|
||||||
|
|
||||||
@ -104,6 +106,7 @@ export class CodeComponent implements OnInit {
|
|||||||
loader.present().then(() => {
|
loader.present().then(() => {
|
||||||
this.getInitObservable().subscribe(() => {
|
this.getInitObservable().subscribe(() => {
|
||||||
this.editorOptions.language = this.dbRecord.language;
|
this.editorOptions.language = this.dbRecord.language;
|
||||||
|
this.editorOptions.readOnly = this.readonly;
|
||||||
this.onSelectChange(false);
|
this.onSelectChange(false);
|
||||||
loader.dismiss();
|
loader.dismiss();
|
||||||
});
|
});
|
||||||
@ -118,7 +121,7 @@ export class CodeComponent implements OnInit {
|
|||||||
this.hostRecord.Value = {};
|
this.hostRecord.Value = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !this.hostRecord.Value.Value ) {
|
if ( !this.hostRecord.Value.Value && !this.readonly ) {
|
||||||
this.api.post(`/code/${this.hostRecord.PageId}/${this.hostRecord.UUID}/create`).subscribe(res => {
|
this.api.post(`/code/${this.hostRecord.PageId}/${this.hostRecord.UUID}/create`).subscribe(res => {
|
||||||
this.dbRecord = res.data;
|
this.dbRecord = res.data;
|
||||||
this.hostRecord.Value.Mode = 'code';
|
this.hostRecord.Value.Mode = 'code';
|
||||||
@ -146,6 +149,10 @@ export class CodeComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSaveClick() {
|
onSaveClick() {
|
||||||
|
if ( this.readonly ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.dbRecord.code = this.editorValue;
|
this.dbRecord.code = this.editorValue;
|
||||||
this.dbRecord.language = this.editorOptions.language;
|
this.dbRecord.language = this.editorOptions.language;
|
||||||
this.api.post(`/code/${this.hostRecord.PageId}/${this.hostRecord.UUID}/set/${this.hostRecord.Value.Value}`, this.dbRecord)
|
this.api.post(`/code/${this.hostRecord.PageId}/${this.hostRecord.UUID}/set/${this.hostRecord.Value.Value}`, this.dbRecord)
|
||||||
@ -158,6 +165,10 @@ export class CodeComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onDropClick() {
|
async onDropClick() {
|
||||||
|
if ( this.readonly ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const alert = await this.alerts.create({
|
const alert = await this.alerts.create({
|
||||||
header: 'Are you sure?',
|
header: 'Are you sure?',
|
||||||
message: `You are about to delete this code. This action cannot be undone.`,
|
message: `You are about to delete this code. This action cannot be undone.`,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<div class="database-wrapper">
|
<div class="database-wrapper">
|
||||||
<ion-toolbar>
|
<ion-toolbar *ngIf="!readonly">
|
||||||
<ion-buttons>
|
<ion-buttons>
|
||||||
<ion-button (click)="onManageColumns()"><ion-icon name="build" color="primary"></ion-icon> Manage Columns</ion-button>
|
<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)="onInsertRow()"><ion-icon name="add-circle" color="success"></ion-icon> Insert Row</ion-button>
|
||||||
@ -14,6 +14,7 @@
|
|||||||
class="ag-theme-balham"
|
class="ag-theme-balham"
|
||||||
[rowData]="rowData"
|
[rowData]="rowData"
|
||||||
[columnDefs]="columnDefs"
|
[columnDefs]="columnDefs"
|
||||||
|
suppressMovableColumns="true"
|
||||||
(rowClicked)="onRowClicked($event)"
|
(rowClicked)="onRowClicked($event)"
|
||||||
(cellValueChanged)="onCellValueChanged()"
|
(cellValueChanged)="onCellValueChanged()"
|
||||||
#agGridElement
|
#agGridElement
|
||||||
|
@ -13,6 +13,7 @@ import {AgGridAngular} from 'ag-grid-angular';
|
|||||||
})
|
})
|
||||||
export class DatabaseComponent implements OnInit {
|
export class DatabaseComponent implements OnInit {
|
||||||
@Input() hostRecord: HostRecord;
|
@Input() hostRecord: HostRecord;
|
||||||
|
@Input() readonly = false;
|
||||||
@Output() hostRecordChange = new EventEmitter<HostRecord>();
|
@Output() hostRecordChange = new EventEmitter<HostRecord>();
|
||||||
@Output() requestParentSave = new EventEmitter<DatabaseComponent>();
|
@Output() requestParentSave = new EventEmitter<DatabaseComponent>();
|
||||||
@Output() requestParentDelete = new EventEmitter<DatabaseComponent>();
|
@Output() requestParentDelete = new EventEmitter<DatabaseComponent>();
|
||||||
@ -55,6 +56,10 @@ export class DatabaseComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onManageColumns() {
|
async onManageColumns() {
|
||||||
|
if ( this.readonly ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const modal = await this.modals.create({
|
const modal = await this.modals.create({
|
||||||
component: ColumnsComponent,
|
component: ColumnsComponent,
|
||||||
componentProps: {columnSets: this.columnDefs},
|
componentProps: {columnSets: this.columnDefs},
|
||||||
@ -91,12 +96,20 @@ export class DatabaseComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onInsertRow() {
|
onInsertRow() {
|
||||||
|
if ( this.readonly ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.rowData.push({});
|
this.rowData.push({});
|
||||||
this.agGridElement.api.setRowData(this.rowData);
|
this.agGridElement.api.setRowData(this.rowData);
|
||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async onRemoveRow() {
|
async onRemoveRow() {
|
||||||
|
if ( this.readonly ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const alert = await this.alerts.create({
|
const alert = await this.alerts.create({
|
||||||
header: 'Are you sure?',
|
header: 'Are you sure?',
|
||||||
message: `You are about to delete row ${this.lastClickRow + 1}. This cannot be undone.`,
|
message: `You are about to delete row ${this.lastClickRow + 1}. This cannot be undone.`,
|
||||||
@ -125,6 +138,10 @@ export class DatabaseComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onDropDatabase() {
|
async onDropDatabase() {
|
||||||
|
if ( this.readonly ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const alert = await this.alerts.create({
|
const alert = await this.alerts.create({
|
||||||
header: 'Are you sure?',
|
header: 'Are you sure?',
|
||||||
message: `You are about to delete this database and all its entries. This action cannot be undone.`,
|
message: `You are about to delete this database and all its entries. This action cannot be undone.`,
|
||||||
@ -162,6 +179,10 @@ export class DatabaseComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSyncRecords() {
|
onSyncRecords() {
|
||||||
|
if ( this.readonly ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.loader.create({message: 'Syncing the database...'}).then(loader => {
|
this.loader.create({message: 'Syncing the database...'}).then(loader => {
|
||||||
loader.present().then(() => {
|
loader.present().then(() => {
|
||||||
this.api.post(`/db/${this.hostRecord.PageId}/${this.hostRecord.UUID}/set/${this.hostRecord.Value.Value}/data`, this.rowData)
|
this.api.post(`/db/${this.hostRecord.PageId}/${this.hostRecord.UUID}/set/${this.hostRecord.Value.Value}/data`, this.rowData)
|
||||||
@ -179,7 +200,7 @@ export class DatabaseComponent implements OnInit {
|
|||||||
return new Observable<any>(sub => {
|
return new Observable<any>(sub => {
|
||||||
this.api.get(`/db/${this.hostRecord.PageId}/${this.hostRecord.UUID}/get/${this.hostRecord.Value.Value}/columns`).subscribe(res => {
|
this.api.get(`/db/${this.hostRecord.PageId}/${this.hostRecord.UUID}/get/${this.hostRecord.Value.Value}/columns`).subscribe(res => {
|
||||||
this.columnDefs = res.data.map(x => {
|
this.columnDefs = res.data.map(x => {
|
||||||
x.editable = true;
|
x.editable = !this.readonly;
|
||||||
if ( x.Type === 'text' ) {
|
if ( x.Type === 'text' ) {
|
||||||
x.editor = 'agTextCellEditor';
|
x.editor = 'agTextCellEditor';
|
||||||
} else if ( x.Type === 'number' ) {
|
} else if ( x.Type === 'number' ) {
|
||||||
@ -208,7 +229,7 @@ export class DatabaseComponent implements OnInit {
|
|||||||
if ( !this.hostRecord.Value ) {
|
if ( !this.hostRecord.Value ) {
|
||||||
this.hostRecord.Value = {};
|
this.hostRecord.Value = {};
|
||||||
}
|
}
|
||||||
if ( !this.hostRecord.Value.Value ) {
|
if ( !this.hostRecord.Value.Value && !this.readonly ) {
|
||||||
this.api.post(`/db/${this.hostRecord.PageId}/${this.hostRecord.UUID}/create`).subscribe(res => {
|
this.api.post(`/db/${this.hostRecord.PageId}/${this.hostRecord.UUID}/create`).subscribe(res => {
|
||||||
this.dbRecord = res.data;
|
this.dbRecord = res.data;
|
||||||
this.hostRecord.Value.Mode = 'database';
|
this.hostRecord.Value.Mode = 'database';
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<div class="files-wrapper">
|
<div class="files-wrapper">
|
||||||
<div class="new-uploads" style="margin: 20px;">
|
<div class="new-uploads" style="margin: 20px;" *ngIf="!readonly">
|
||||||
<form #uploadForm [action]="getApiSubmit()" enctype="multipart/form-data" method="post">
|
<form #uploadForm [action]="getApiSubmit()" enctype="multipart/form-data" method="post">
|
||||||
<input style="margin-top: 10px;" type="file" id="file" name="uploaded_file">
|
<input style="margin-top: 10px;" type="file" id="file" name="uploaded_file">
|
||||||
<input type="hidden" name="redirectTo" [value]="getReturnUrl()">
|
<input type="hidden" name="redirectTo" [value]="getReturnUrl()">
|
||||||
|
@ -11,6 +11,7 @@ import { APP_BASE_HREF } from '@angular/common';
|
|||||||
styleUrls: ['./files.component.scss'],
|
styleUrls: ['./files.component.scss'],
|
||||||
})
|
})
|
||||||
export class FilesComponent implements OnInit {
|
export class FilesComponent implements OnInit {
|
||||||
|
@Input() readonly = false;
|
||||||
@Input() hostRecord: HostRecord;
|
@Input() hostRecord: HostRecord;
|
||||||
@Output() hostRecordChange = new EventEmitter<HostRecord>();
|
@Output() hostRecordChange = new EventEmitter<HostRecord>();
|
||||||
@Output() requestParentSave = new EventEmitter<FilesComponent>();
|
@Output() requestParentSave = new EventEmitter<FilesComponent>();
|
||||||
@ -38,6 +39,10 @@ export class FilesComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSubmitClick() {
|
onSubmitClick() {
|
||||||
|
if ( this.readonly ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.uploadForm.nativeElement.submit();
|
this.uploadForm.nativeElement.submit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,6 +56,10 @@ export class FilesComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onDestroyClick() {
|
async onDestroyClick() {
|
||||||
|
if ( this.readonly ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const alert = await this.alerts.create({
|
const alert = await this.alerts.create({
|
||||||
header: 'Are you sure?',
|
header: 'Are you sure?',
|
||||||
message: 'You are about to delete these files. This action cannot be undone.',
|
message: 'You are about to delete these files. This action cannot be undone.',
|
||||||
@ -81,7 +90,7 @@ export class FilesComponent implements OnInit {
|
|||||||
this.hostRecord.Value = {};
|
this.hostRecord.Value = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !this.hostRecord.Value.Value ) {
|
if ( !this.hostRecord.Value.Value && !this.readonly ) {
|
||||||
this.api.post(`/files/${this.hostRecord.PageId}/${this.hostRecord.UUID}/create`).subscribe(res => {
|
this.api.post(`/files/${this.hostRecord.PageId}/${this.hostRecord.UUID}/create`).subscribe(res => {
|
||||||
this.dbRecord = res.data;
|
this.dbRecord = res.data;
|
||||||
this.fileRecords = res.data.files;
|
this.fileRecords = res.data.files;
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
<ng-container>
|
<ng-container>
|
||||||
<div
|
<div
|
||||||
*ngIf="record.type === 'paragraph' || record.type === 'header1' || record.type === 'header2' || record.type === 'header3' || record.type === 'header4' || record.type === 'block_code' || record.type === 'click_link'"
|
*ngIf="!page.isViewOnly() && ( record.type === 'paragraph'
|
||||||
|
|| record.type === 'header1'
|
||||||
|
|| record.type === 'header2'
|
||||||
|
|| record.type === 'header3'
|
||||||
|
|| record.type === 'header4'
|
||||||
|
|| record.type === 'block_code'
|
||||||
|
|| record.type === 'click_link' )"
|
||||||
class="host-host ion-padding"
|
class="host-host ion-padding"
|
||||||
contenteditable="true"
|
contenteditable="true"
|
||||||
(keyup)="onKeyUp($event)"
|
(keyup)="onKeyUp($event)"
|
||||||
@ -10,8 +16,22 @@
|
|||||||
[ngClass]="{'paragraph': record.type === 'paragraph', 'header1': record.type === 'header1', 'header2': record.type === 'header2', 'header3': record.type === 'header3', 'header4': record.type === 'header4', 'block_code': record.type === 'block_code', 'click_link': record.type === 'click_link'}"
|
[ngClass]="{'paragraph': record.type === 'paragraph', 'header1': record.type === 'header1', 'header2': record.type === 'header2', 'header3': record.type === 'header3', 'header4': record.type === 'header4', 'block_code': record.type === 'block_code', 'click_link': record.type === 'click_link'}"
|
||||||
[innerHTML]="record.value.replace('\n', '<br>')"
|
[innerHTML]="record.value.replace('\n', '<br>')"
|
||||||
></div>
|
></div>
|
||||||
|
<div
|
||||||
|
*ngIf="page.isViewOnly() && ( record.type === 'paragraph'
|
||||||
|
|| record.type === 'header1'
|
||||||
|
|| record.type === 'header2'
|
||||||
|
|| record.type === 'header3'
|
||||||
|
|| record.type === 'header4'
|
||||||
|
|| record.type === 'block_code'
|
||||||
|
|| record.type === 'click_link' )"
|
||||||
|
(click)="onHostDblClick()"
|
||||||
|
class="host-host ion-padding"
|
||||||
|
#hostContainer
|
||||||
|
[ngClass]="{'paragraph': record.type === 'paragraph', 'header1': record.type === 'header1', 'header2': record.type === 'header2', 'header3': record.type === 'header3', 'header4': record.type === 'header4', 'block_code': record.type === 'block_code', 'click_link': record.type === 'click_link'}"
|
||||||
|
[innerHTML]="record.value.replace('\n', '<br>')"
|
||||||
|
></div>
|
||||||
<ul
|
<ul
|
||||||
*ngIf="record.type === 'ul'"
|
*ngIf="record.type === 'ul' && !page.isViewOnly()"
|
||||||
class="host-host ion-padding"
|
class="host-host ion-padding"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
@ -23,6 +43,16 @@
|
|||||||
[innerHTML]="listLines[i]"
|
[innerHTML]="listLines[i]"
|
||||||
></li>
|
></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<ul
|
||||||
|
*ngIf="record.type === 'ul' && page.isViewOnly()"
|
||||||
|
class="host-host ion-padding"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
#liItems
|
||||||
|
*ngFor="let line of listLines; let i = index"
|
||||||
|
[innerHTML]="listLines[i]"
|
||||||
|
></li>
|
||||||
|
</ul>
|
||||||
<div *ngIf="record.type === 'page_sep'" class="hr-wrapper">
|
<div *ngIf="record.type === 'page_sep'" class="hr-wrapper">
|
||||||
<hr>
|
<hr>
|
||||||
</div>
|
</div>
|
||||||
@ -31,6 +61,7 @@
|
|||||||
class="db-wrapper"
|
class="db-wrapper"
|
||||||
>
|
>
|
||||||
<editor-database
|
<editor-database
|
||||||
|
[readonly]="page.isViewOnly()"
|
||||||
[hostRecord]="record"
|
[hostRecord]="record"
|
||||||
(hostRecordChange)="onRecordChange($event)"
|
(hostRecordChange)="onRecordChange($event)"
|
||||||
(requestParentSave)="onRequestParentSave($event)"
|
(requestParentSave)="onRequestParentSave($event)"
|
||||||
@ -39,6 +70,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="code-wrapper" *ngIf="record.type === 'code_ref'">
|
<div class="code-wrapper" *ngIf="record.type === 'code_ref'">
|
||||||
<editor-code
|
<editor-code
|
||||||
|
[readonly]="page.isViewOnly()"
|
||||||
[hostRecord]="record"
|
[hostRecord]="record"
|
||||||
(hostRecordChange)="onRecordChange($event)"
|
(hostRecordChange)="onRecordChange($event)"
|
||||||
(requestParentSave)="onRequestParentSave($event)"
|
(requestParentSave)="onRequestParentSave($event)"
|
||||||
@ -47,6 +79,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="files-wrapper" *ngIf="record.type === 'file_ref'">
|
<div class="files-wrapper" *ngIf="record.type === 'file_ref'">
|
||||||
<editor-files
|
<editor-files
|
||||||
|
[readonly]="page.isViewOnly()"
|
||||||
[hostRecord]="record"
|
[hostRecord]="record"
|
||||||
(hostRecordChange)="onRecordChange($event)"
|
(hostRecordChange)="onRecordChange($event)"
|
||||||
(requestParentSave)="onRequestParentSave($event)"
|
(requestParentSave)="onRequestParentSave($event)"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import {Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild, ViewChildren} from '@angular/core';
|
import {Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild, ViewChildren} from '@angular/core';
|
||||||
import HostRecord from '../../../structures/HostRecord';
|
import HostRecord from '../../../structures/HostRecord';
|
||||||
|
import PageRecord from '../../../structures/PageRecord';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'editor-host',
|
selector: 'editor-host',
|
||||||
@ -7,6 +8,7 @@ import HostRecord from '../../../structures/HostRecord';
|
|||||||
styleUrls: ['./host.component.scss'],
|
styleUrls: ['./host.component.scss'],
|
||||||
})
|
})
|
||||||
export class HostComponent implements OnInit {
|
export class HostComponent implements OnInit {
|
||||||
|
@Input() page: PageRecord;
|
||||||
@Input() record: HostRecord;
|
@Input() record: HostRecord;
|
||||||
@Output() recordChange = new EventEmitter<HostRecord>();
|
@Output() recordChange = new EventEmitter<HostRecord>();
|
||||||
@Output() newHostRequested = new EventEmitter<HostComponent>();
|
@Output() newHostRequested = new EventEmitter<HostComponent>();
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
<ion-list>
|
||||||
|
<ion-item *ngFor="let menuItem of menuItems; let i = index" button (click)="onSelect(menuItems[i].value)">
|
||||||
|
<ion-icon slot="start" [name]="menuItems[i].icon"></ion-icon>
|
||||||
|
<ion-label>{{ menuItems[i].name }}</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
22
src/app/components/option-menu/option-menu.component.ts
Normal file
22
src/app/components/option-menu/option-menu.component.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import {Component, Input, OnInit} from '@angular/core';
|
||||||
|
import {PopoverController} from '@ionic/angular';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'common-option-menu',
|
||||||
|
templateUrl: './option-menu.component.html',
|
||||||
|
styleUrls: ['./option-menu.component.scss'],
|
||||||
|
})
|
||||||
|
export class OptionMenuComponent implements OnInit {
|
||||||
|
@Input() menuItems: Array<{name: string, icon: string, value: string, type?: string}> = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected popover: PopoverController,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit() {}
|
||||||
|
|
||||||
|
async onSelect(value) {
|
||||||
|
await this.popover.dismiss(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
98
src/app/components/sharing/selector/selector.component.html
Normal file
98
src/app/components/sharing/selector/selector.component.html
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>{{ title }}</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 class="ion-padding">
|
||||||
|
<ion-grid>
|
||||||
|
<ion-row>
|
||||||
|
<ion-col size="12">
|
||||||
|
<h4>Create Sharing Link</h4>
|
||||||
|
<p>You can share this sub-tree of your notes by generating a sharing link. You can choose to allow the recipient to have view, edit, or manage access to this sub-tree. Sharing links are one time use only and require a Noded account.</p>
|
||||||
|
<ion-buttons>
|
||||||
|
<ion-button fill="outline" color="medium" (click)="getShareLink('view')">
|
||||||
|
<ion-icon name="link" color="dark"></ion-icon> View
|
||||||
|
</ion-button>
|
||||||
|
<ion-button fill="outline" color="medium" (click)="getShareLink('update')">
|
||||||
|
<ion-icon name="link" color="dark"></ion-icon> Edit
|
||||||
|
</ion-button>
|
||||||
|
<ion-button fill="outline" color="medium" (click)="getShareLink('manage')">
|
||||||
|
<ion-icon name="link" color="dark"></ion-icon> Manage
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
<ion-row *ngIf="generatedLink">
|
||||||
|
<ion-col size="12">
|
||||||
|
<ion-item>
|
||||||
|
<ion-label position="floating">Share this link to give access:</ion-label>
|
||||||
|
<ion-input [(ngModel)]="generatedLink" [readonly]="true"></ion-input>
|
||||||
|
</ion-item>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
<ion-row *ngIf="isShared">
|
||||||
|
<ion-col size="12">
|
||||||
|
<h4>Shared With</h4>
|
||||||
|
<ion-list>
|
||||||
|
<ion-item *ngFor="let group of sharingInfo.view; let i = index">
|
||||||
|
<ion-icon slot="start" name="contact"></ion-icon>
|
||||||
|
<ion-label>
|
||||||
|
{{ sharingInfo.view[i].username }} <span class="share-token">VIEW</span>
|
||||||
|
</ion-label>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button fill="invisible" (click)="setShareLevel(sharingInfo.view[i], 'update')">
|
||||||
|
<ion-icon name="create" color="medium"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
<ion-button fill="invisible" (click)="setShareLevel(sharingInfo.view[i], 'manage')">
|
||||||
|
<ion-icon name="build" color="medium"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
<ion-button fill="invisible" (click)="unsharePage(sharingInfo.view[i])">
|
||||||
|
<ion-icon name="close" color="medium"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item *ngFor="let group of sharingInfo.update; let i = index">
|
||||||
|
<ion-icon slot="start" name="contact"></ion-icon>
|
||||||
|
<ion-label>
|
||||||
|
{{ sharingInfo.update[i].username }} <span class="share-token">VIEW</span> <span class="share-token">EDIT</span>
|
||||||
|
</ion-label>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button fill="invisible" (click)="setShareLevel(sharingInfo.update[i], 'view')">
|
||||||
|
<ion-icon name="eye" color="medium"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
<ion-button fill="invisible" (click)="setShareLevel(sharingInfo.update[i], 'manage')">
|
||||||
|
<ion-icon name="build" color="medium"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
<ion-button fill="invisible" (click)="unsharePage(sharingInfo.update[i])">
|
||||||
|
<ion-icon name="close" color="medium"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item *ngFor="let group of sharingInfo.manage; let i = index">
|
||||||
|
<ion-icon slot="start" name="contact"></ion-icon>
|
||||||
|
<ion-label>
|
||||||
|
{{ sharingInfo.manage[i].username }} <span class="share-token">VIEW</span> <span class="share-token">EDIT</span> <span class="share-token">MANAGE</span>
|
||||||
|
</ion-label>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button fill="invisible" (click)="setShareLevel(sharingInfo.manage[i], 'view')">
|
||||||
|
<ion-icon name="eye" color="medium"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
<ion-button fill="invisible" (click)="setShareLevel(sharingInfo.manage[i], 'update')">
|
||||||
|
<ion-icon name="create" color="medium"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
<ion-button fill="invisible" (click)="unsharePage(sharingInfo.manage[i])">
|
||||||
|
<ion-icon name="close" color="medium"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-grid>
|
||||||
|
</ion-content>
|
@ -0,0 +1,8 @@
|
|||||||
|
.share-token {
|
||||||
|
background-color: #dedede;
|
||||||
|
color: #555;
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 10pt;
|
||||||
|
font-style: italic;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
77
src/app/components/sharing/selector/selector.component.ts
Normal file
77
src/app/components/sharing/selector/selector.component.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import {Component, Input, OnInit} from '@angular/core';
|
||||||
|
import {ModalController} from '@ionic/angular';
|
||||||
|
import {ApiService} from '../../../service/api.service';
|
||||||
|
import {Observable} from 'rxjs';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-selector',
|
||||||
|
templateUrl: './selector.component.html',
|
||||||
|
styleUrls: ['./selector.component.scss'],
|
||||||
|
})
|
||||||
|
export class SelectorComponent implements OnInit {
|
||||||
|
@Input() node: any;
|
||||||
|
public sharingInfo: {
|
||||||
|
view: Array<{username: string, id: string, level: 'view'|'update'|'manage'}>,
|
||||||
|
update: Array<{username: string, id: string, level: 'view'|'update'|'manage'}>,
|
||||||
|
manage: Array<{username: string, id: string, level: 'view'|'update'|'manage'}>,
|
||||||
|
} = {view: [], update: [], manage: []};
|
||||||
|
public generatedLink = '';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected modals: ModalController,
|
||||||
|
protected api: ApiService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public get title() {
|
||||||
|
return this.node ? `Share ${this.node.name}` : 'Manage Sharing';
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isShared() {
|
||||||
|
return (this.sharingInfo.view.length > 0) || (this.sharingInfo.update.length > 0) || (this.sharingInfo.manage.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.loadShareInfo().subscribe(data => {
|
||||||
|
this.sharingInfo = data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dismissModal(success: boolean) {
|
||||||
|
this.modals.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadShareInfo(): Observable<{
|
||||||
|
view: Array<{username: string, id: string, level: 'view'|'update'|'manage'}>,
|
||||||
|
update: Array<{username: string, id: string, level: 'view'|'update'|'manage'}>,
|
||||||
|
manage: Array<{username: string, id: string, level: 'view'|'update'|'manage'}>,
|
||||||
|
}> {
|
||||||
|
return new Observable(sub => {
|
||||||
|
this.api.get(`/share/page/${this.node.id}/info`).subscribe(result => {
|
||||||
|
sub.next(result.data);
|
||||||
|
sub.complete();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setShareLevel(group, level) {
|
||||||
|
this.api.post(`/share/page/${this.node.id}/share`, { user_id: group.id, level }).subscribe(result => {
|
||||||
|
this.loadShareInfo().subscribe(data => {
|
||||||
|
this.sharingInfo = data;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
unsharePage(group) {
|
||||||
|
this.api.post(`/share/page/${this.node.id}/revoke`, { user_id: group.id }).subscribe(result => {
|
||||||
|
this.loadShareInfo().subscribe(data => {
|
||||||
|
this.sharingInfo = data;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getShareLink(level) {
|
||||||
|
this.api.get(`/share/page/${this.node.id}/link/${level}`).subscribe(result => {
|
||||||
|
this.generatedLink = result.data.link;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -23,7 +23,7 @@
|
|||||||
(mouseenter)="makeVisible(i)"
|
(mouseenter)="makeVisible(i)"
|
||||||
(mouseleave)="makeInvisible(i)"
|
(mouseleave)="makeInvisible(i)"
|
||||||
>
|
>
|
||||||
<ion-button fill="invisible" color="primary" (click)="onOptionsClick($event, i)">
|
<ion-button fill="invisible" color="primary" (click)="onOptionsClick($event, i)" *ngIf="pageRecord.level !== 'view'">
|
||||||
<ion-icon
|
<ion-icon
|
||||||
name="options"
|
name="options"
|
||||||
color="medium"
|
color="medium"
|
||||||
@ -33,6 +33,7 @@
|
|||||||
<editor-host
|
<editor-host
|
||||||
style="width: 100%;"
|
style="width: 100%;"
|
||||||
#editorHosts
|
#editorHosts
|
||||||
|
[page]="pageRecord"
|
||||||
[record]="hostRecords[i]"
|
[record]="hostRecords[i]"
|
||||||
(recordChange)="onHostRecordChange($event, i)"
|
(recordChange)="onHostRecordChange($event, i)"
|
||||||
(newHostRequested)="onNewHostRequested($event)"
|
(newHostRequested)="onNewHostRequested($event)"
|
||||||
@ -41,7 +42,7 @@
|
|||||||
</editor-host>
|
</editor-host>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="editor-buttons" style="margin-bottom: 50px;">
|
<div class="editor-buttons" style="margin-bottom: 50px;" *ngIf="pageRecord.level !== 'view'">
|
||||||
<ion-button (click)="onAddClick($event)" class="ion-padding ion-margin-start" 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" fill="outline" color="medium">Save</ion-button>
|
<ion-button (click)="onSaveClick()" class="ion-padding" fill="outline" color="medium">Save</ion-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -55,7 +55,9 @@ export class EditorPage implements OnInit {
|
|||||||
this.pageRecord = pageRecord;
|
this.pageRecord = pageRecord;
|
||||||
this.pages.get_nodes(pageRecord).subscribe((hosts: Array<HostRecord>) => {
|
this.pages.get_nodes(pageRecord).subscribe((hosts: Array<HostRecord>) => {
|
||||||
this.hostRecords = hosts;
|
this.hostRecords = hosts;
|
||||||
|
if ( !pageRecord.isViewOnly() ) {
|
||||||
this.onSaveClick();
|
this.onSaveClick();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -64,10 +66,16 @@ export class EditorPage implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onHostRecordChange($event, i) {
|
onHostRecordChange($event, i) {
|
||||||
|
if ( !this.pageRecord.isViewOnly() ) {
|
||||||
this.hostRecords[i] = $event;
|
this.hostRecords[i] = $event;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async onAddClick($event) {
|
async onAddClick($event) {
|
||||||
|
if ( this.pageRecord.isViewOnly() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const popover = await this.popover.create({
|
const popover = await this.popover.create({
|
||||||
component: NodePickerComponent,
|
component: NodePickerComponent,
|
||||||
event: $event,
|
event: $event,
|
||||||
@ -119,6 +127,10 @@ export class EditorPage implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onNewHostRequested($event) {
|
onNewHostRequested($event) {
|
||||||
|
if ( this.pageRecord.isViewOnly() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const insertAfter = this.getIndexFromRecord($event.record);
|
const insertAfter = this.getIndexFromRecord($event.record);
|
||||||
const record = new HostRecord('');
|
const record = new HostRecord('');
|
||||||
const newHosts = []
|
const newHosts = []
|
||||||
@ -137,6 +149,10 @@ export class EditorPage implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onDestroyHostRequested($event) {
|
onDestroyHostRequested($event) {
|
||||||
|
if ( this.pageRecord.isViewOnly() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let removedIndex = 0;
|
let removedIndex = 0;
|
||||||
const newHostRecords = this.editorHosts.filter((host, i) => {
|
const newHostRecords = this.editorHosts.filter((host, i) => {
|
||||||
if ( $event.record === host.record ) {
|
if ( $event.record === host.record ) {
|
||||||
@ -158,8 +174,6 @@ export class EditorPage implements OnInit {
|
|||||||
focusIndex = removedIndex - 1;
|
focusIndex = removedIndex - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log({removedIndex, focusIndex, edHArr: this.editorHosts.toArray()});
|
|
||||||
|
|
||||||
if ( focusIndex >= 0 ) {
|
if ( focusIndex >= 0 ) {
|
||||||
this.editorHosts.toArray()[focusIndex].takeFocus(false);
|
this.editorHosts.toArray()[focusIndex].takeFocus(false);
|
||||||
}
|
}
|
||||||
@ -177,6 +191,10 @@ export class EditorPage implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSaveClick() {
|
onSaveClick() {
|
||||||
|
if ( this.pageRecord.isViewOnly() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.loader.create({message: 'Saving changes...'}).then(loader => {
|
this.loader.create({message: 'Saving changes...'}).then(loader => {
|
||||||
loader.present().then(() => {
|
loader.present().then(() => {
|
||||||
this.pageRecord.Name = this.titleBar.el.innerText.trim();
|
this.pageRecord.Name = this.titleBar.el.innerText.trim();
|
||||||
@ -196,6 +214,10 @@ export class EditorPage implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onOptionsClick($event, i) {
|
async onOptionsClick($event, i) {
|
||||||
|
if ( this.pageRecord.isViewOnly() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const popover = await this.popover.create({
|
const popover = await this.popover.create({
|
||||||
component: HostOptionsComponent,
|
component: HostOptionsComponent,
|
||||||
event: $event,
|
event: $event,
|
||||||
@ -208,7 +230,6 @@ export class EditorPage implements OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
popover.onDidDismiss().then((result) => {
|
popover.onDidDismiss().then((result) => {
|
||||||
console.log({result});
|
|
||||||
if ( result.data === 'delete_node' ) {
|
if ( result.data === 'delete_node' ) {
|
||||||
$event.record = this.hostRecords[i];
|
$event.record = this.hostRecords[i];
|
||||||
this.onDestroyHostRequested($event);
|
this.onDestroyHostRequested($event);
|
||||||
|
@ -11,8 +11,9 @@ export default class PageRecord {
|
|||||||
public CreatedUserId: string;
|
public CreatedUserId: string;
|
||||||
public UpdateUserId: string;
|
public UpdateUserId: string;
|
||||||
public ChildPageIds: Array<string>;
|
public ChildPageIds: Array<string>;
|
||||||
|
public level: 'view'|'manage'|'update'|false;
|
||||||
|
|
||||||
constructor(data: any = {Name: 'Click to edit title...'}) {
|
constructor(data: any = {Name: 'Click to edit...'}) {
|
||||||
[
|
[
|
||||||
'UUID',
|
'UUID',
|
||||||
'Name',
|
'Name',
|
||||||
@ -25,7 +26,8 @@ export default class PageRecord {
|
|||||||
'UpdatedAt',
|
'UpdatedAt',
|
||||||
'CreatedUserId',
|
'CreatedUserId',
|
||||||
'UpdateUserId',
|
'UpdateUserId',
|
||||||
'ChildPageIds'
|
'ChildPageIds',
|
||||||
|
'level'
|
||||||
].forEach(field => {
|
].forEach(field => {
|
||||||
if ( field in data ) {
|
if ( field in data ) {
|
||||||
this[field] = data[field];
|
this[field] = data[field];
|
||||||
@ -55,4 +57,8 @@ export default class PageRecord {
|
|||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isViewOnly() {
|
||||||
|
return this.level === 'view';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user