|
|
|
import {AfterViewInit, Component, ElementRef, OnInit, ViewChild, HostListener, Host} from '@angular/core';
|
|
|
|
|
|
|
|
import {AlertController, ModalController, Platform, PopoverController, LoadingController} from '@ionic/angular';
|
|
|
|
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
|
|
|
|
import { StatusBar } from '@ionic-native/status-bar/ngx';
|
|
|
|
import { ApiService } from './service/api.service';
|
|
|
|
import { Router } from '@angular/router';
|
|
|
|
import {TREE_ACTIONS, TreeComponent} from '@circlon/angular-tree-component';
|
|
|
|
import {BehaviorSubject, Observable} from 'rxjs';
|
|
|
|
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';
|
|
|
|
import {SessionService} from './service/session.service';
|
|
|
|
import {SearchComponent} from "./components/search/Search.component";
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
selector: 'app-root',
|
|
|
|
templateUrl: 'app.component.html',
|
|
|
|
styleUrls: ['app.component.scss']
|
|
|
|
})
|
|
|
|
export class AppComponent implements OnInit {
|
|
|
|
@ViewChild('menuTree') menuTree: TreeComponent;
|
|
|
|
public readonly ready$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
|
|
|
public addChildTarget: any = false;
|
|
|
|
public deleteTarget: any = false;
|
|
|
|
public menuTarget: any = false;
|
|
|
|
public refreshingMenu = false;
|
|
|
|
public lastClickEvent: Array<any> = [];
|
|
|
|
public nodes = [];
|
|
|
|
public currentPageId: string;
|
|
|
|
public options = {
|
|
|
|
isExpandedField: 'expanded',
|
|
|
|
animateExpand: true,
|
|
|
|
actionMapping: {
|
|
|
|
mouse: {
|
|
|
|
dblClick: (tree, node, $event) => {
|
|
|
|
const id = node.data.id;
|
|
|
|
if ( !node.data.virtual ) {
|
|
|
|
this.currentPageId = id;
|
|
|
|
this.router.navigate(['/editor', { id }]);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
click: (tree, node, $event) => {
|
|
|
|
TREE_ACTIONS.FOCUS(tree, node, $event);
|
|
|
|
TREE_ACTIONS.EXPAND(tree, node, $event);
|
|
|
|
|
|
|
|
this.addChildTarget = false;
|
|
|
|
this.deleteTarget = false;
|
|
|
|
this.menuTarget = false;
|
|
|
|
|
|
|
|
if ( !node.data.noChildren && (!node.data.level || node.data.level === 'manage') ) {
|
|
|
|
this.addChildTarget = node;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !node.data.noDelete && (!node.data.level || node.data.level === 'manage') ) {
|
|
|
|
this.deleteTarget = node;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.menuTarget = node;
|
|
|
|
this.lastClickEvent = [tree, node, $event];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
public get appName(): string {
|
|
|
|
return this.session.appName || 'Noded';
|
|
|
|
}
|
|
|
|
|
|
|
|
public darkMode = false;
|
|
|
|
protected loader?: any;
|
|
|
|
protected hasSearchOpen = false;
|
|
|
|
|
|
|
|
protected initialized$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
private platform: Platform,
|
|
|
|
private splashScreen: SplashScreen,
|
|
|
|
private statusBar: StatusBar,
|
|
|
|
private api: ApiService,
|
|
|
|
protected router: Router,
|
|
|
|
protected alerts: AlertController,
|
|
|
|
protected popover: PopoverController,
|
|
|
|
protected modal: ModalController,
|
|
|
|
protected session: SessionService,
|
|
|
|
protected loading: LoadingController,
|
|
|
|
) {
|
|
|
|
this.initializeApp();
|
|
|
|
}
|
|
|
|
|
|
|
|
_doInit() {
|
|
|
|
this.reloadMenuItems().subscribe(() => {
|
|
|
|
this.ready$.next(true);
|
|
|
|
setTimeout(() => {
|
|
|
|
this.loader.dismiss();
|
|
|
|
this.menuTree.treeModel.expandAll();
|
|
|
|
}, 10);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
ngOnInit() {
|
|
|
|
if ( !this.initialized$.getValue() ) {
|
|
|
|
const sub = this.initialized$.subscribe((didInit) => {
|
|
|
|
if (didInit) {
|
|
|
|
this._doInit();
|
|
|
|
sub.unsubscribe();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
this._doInit();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
showOptions($event) {
|
|
|
|
this.popover.create({
|
|
|
|
event: $event,
|
|
|
|
component: OptionPickerComponent,
|
|
|
|
componentProps: {
|
|
|
|
toggleDark: () => this.toggleDark(),
|
|
|
|
isDark: () => this.isDark(),
|
|
|
|
showSearch: () => this.handleKeyboardEvent(),
|
|
|
|
}
|
|
|
|
}).then(popover => popover.present());
|
|
|
|
}
|
|
|
|
|
|
|
|
onFilterChange($event) {
|
|
|
|
const query = $event.detail.value.toLowerCase();
|
|
|
|
this.menuTree.treeModel.clearFilter();
|
|
|
|
if ( query ) {
|
|
|
|
this.menuTree.treeModel.filterNodes(node => {
|
|
|
|
if ( node.data.virtual ) {
|
|
|
|
// "Virtual" tree nodes should always be shown
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return node.data.name.toLowerCase().indexOf(query) >= 0;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@HostListener('document:keyup.control./', ['$event'])
|
|
|
|
async handleKeyboardEvent() {
|
|
|
|
if ( this.hasSearchOpen ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log('firing search');
|
|
|
|
const modal = await this.modal.create({
|
|
|
|
component: SearchComponent,
|
|
|
|
});
|
|
|
|
|
|
|
|
this.hasSearchOpen = true;
|
|
|
|
await modal.present();
|
|
|
|
|
|
|
|
await modal.onDidDismiss();
|
|
|
|
this.hasSearchOpen = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
async onNodeMenuClick($event) {
|
|
|
|
let canManage = this.menuTarget.data.level === 'manage';
|
|
|
|
if ( !canManage ) {
|
|
|
|
if ( !this.menuTarget.data.level ) {
|
|
|
|
canManage = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const popover = await this.popover.create({
|
|
|
|
component: OptionMenuComponent,
|
|
|
|
componentProps: {
|
|
|
|
menuItems: [
|
|
|
|
...(!canManage ? [] : [{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() {
|
|
|
|
const alert = await this.alerts.create({
|
|
|
|
header: 'Create Page',
|
|
|
|
message: 'Please enter a new name for the page:',
|
|
|
|
cssClass: 'page-prompt',
|
|
|
|
inputs: [
|
|
|
|
{
|
|
|
|
name: 'name',
|
|
|
|
type: 'text',
|
|
|
|
placeholder: 'My Awesome Page'
|
|
|
|
}
|
|
|
|
],
|
|
|
|
buttons: [
|
|
|
|
{
|
|
|
|
text: 'Cancel',
|
|
|
|
role: 'cancel',
|
|
|
|
cssClass: 'secondary'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text: 'Create',
|
|
|
|
handler: async args => {
|
|
|
|
this.api.post('/page/create', args).subscribe(res => {
|
|
|
|
this.router.navigate(['/editor', { id: res.data.UUID }]);
|
|
|
|
this.reloadMenuItems().subscribe();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
});
|
|
|
|
|
|
|
|
await alert.present();
|
|
|
|
}
|
|
|
|
|
|
|
|
async onChildCreate() {
|
|
|
|
const alert = await this.alerts.create({
|
|
|
|
header: 'Create Sub-Page',
|
|
|
|
message: 'Please enter a new name for the page:',
|
|
|
|
cssClass: 'page-prompt',
|
|
|
|
inputs: [
|
|
|
|
{
|
|
|
|
name: 'name',
|
|
|
|
type: 'text',
|
|
|
|
placeholder: 'My Awesome Page'
|
|
|
|
}
|
|
|
|
],
|
|
|
|
buttons: [
|
|
|
|
{
|
|
|
|
text: 'Cancel',
|
|
|
|
role: 'cancel',
|
|
|
|
cssClass: 'secondary'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text: 'Create',
|
|
|
|
handler: async args => {
|
|
|
|
args = {
|
|
|
|
name: args.name,
|
|
|
|
parentId: this.addChildTarget.data.id
|
|
|
|
};
|
|
|
|
this.api.post('/page/create-child', args).subscribe(res => {
|
|
|
|
this.reloadMenuItems().subscribe(() => {
|
|
|
|
TREE_ACTIONS.EXPAND(
|
|
|
|
this.lastClickEvent[0],
|
|
|
|
this.lastClickEvent[1],
|
|
|
|
this.lastClickEvent[2]
|
|
|
|
);
|
|
|
|
this.router.navigate(['/editor', { id: res.data.UUID }]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
});
|
|
|
|
|
|
|
|
await alert.present();
|
|
|
|
}
|
|
|
|
|
|
|
|
async onDeleteClick() {
|
|
|
|
const alert = await this.alerts.create({
|
|
|
|
header: 'Delete page?',
|
|
|
|
message:
|
|
|
|
'Deleting this page will make its contents and all of its children inaccessible. Are you sure you want to continue?',
|
|
|
|
buttons: [
|
|
|
|
{
|
|
|
|
text: 'Keep It',
|
|
|
|
role: 'cancel'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text: 'Delete It',
|
|
|
|
handler: async () => {
|
|
|
|
this.api
|
|
|
|
.post(`/page/delete/${this.deleteTarget.data.id}`)
|
|
|
|
.subscribe(res => {
|
|
|
|
if ( this.currentPageId === this.deleteTarget.data.id ) {
|
|
|
|
this.router.navigate(['/home']);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.reloadMenuItems().subscribe();
|
|
|
|
this.deleteTarget = false;
|
|
|
|
this.addChildTarget = false;
|
|
|
|
this.menuTarget = false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
});
|
|
|
|
|
|
|
|
await alert.present();
|
|
|
|
}
|
|
|
|
|
|
|
|
onMenuRefresh() {
|
|
|
|
this.refreshingMenu = true;
|
|
|
|
this.reloadMenuItems().subscribe();
|
|
|
|
setTimeout(() => {
|
|
|
|
this.refreshingMenu = false;
|
|
|
|
}, 2000);
|
|
|
|
}
|
|
|
|
|
|
|
|
reloadMenuItems() {
|
|
|
|
return new Observable(sub => {
|
|
|
|
this.api.get('/menu/items').subscribe(result => {
|
|
|
|
this.nodes = result.data;
|
|
|
|
setTimeout(() => {
|
|
|
|
sub.next();
|
|
|
|
sub.complete();
|
|
|
|
}, 0);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async initializeApp() {
|
|
|
|
this.loader = await this.loading.create({
|
|
|
|
message: 'Starting up...',
|
|
|
|
cssClass: 'noded-loading-mask',
|
|
|
|
showBackdrop: true,
|
|
|
|
});
|
|
|
|
|
|
|
|
await this.loader.present();
|
|
|
|
|
|
|
|
await this.platform.ready();
|
|
|
|
const stat: any = await this.session.stat();
|
|
|
|
|
|
|
|
if ( !stat.authenticated_user ) {
|
|
|
|
window.location.href = `${stat.system_base}start`;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.session.appName = stat.app_name;
|
|
|
|
this.session.systemBase = stat.system_base;
|
|
|
|
|
|
|
|
await this.session.initialize();
|
|
|
|
await this.statusBar.styleDefault();
|
|
|
|
await this.splashScreen.hide();
|
|
|
|
this.initialized$.next(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
toggleDark() {
|
|
|
|
// const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
|
|
|
|
this.darkMode = !this.darkMode;
|
|
|
|
document.body.classList.toggle('dark', this.darkMode);
|
|
|
|
}
|
|
|
|
|
|
|
|
isDark() {
|
|
|
|
return !!this.darkMode;
|
|
|
|
}
|
|
|
|
}
|