#91 - replace sidebar tree component with custom 1st-party implementation
This commit is contained in:
parent
87b99473bd
commit
f3f8578834
@ -22,7 +22,6 @@
|
|||||||
"@angular/pwa": "^0.1001.7",
|
"@angular/pwa": "^0.1001.7",
|
||||||
"@angular/router": "~10.1.5",
|
"@angular/router": "~10.1.5",
|
||||||
"@angular/service-worker": "~10.1.5",
|
"@angular/service-worker": "~10.1.5",
|
||||||
"@circlon/angular-tree-component": "^10.0.0",
|
|
||||||
"@ckeditor/ckeditor5-angular": "^2.0.1",
|
"@ckeditor/ckeditor5-angular": "^2.0.1",
|
||||||
"@ckeditor/ckeditor5-build-decoupled-document": "^27.0.0",
|
"@ckeditor/ckeditor5-build-decoupled-document": "^27.0.0",
|
||||||
"@convergencelabs/monaco-collab-ext": "^0.3.2",
|
"@convergencelabs/monaco-collab-ext": "^0.3.2",
|
||||||
|
@ -7,7 +7,6 @@ dependencies:
|
|||||||
'@angular/pwa': 0.1001.7
|
'@angular/pwa': 0.1001.7
|
||||||
'@angular/router': 10.1.6_2d3b53e7e463c932a6e73e0760b7a0a2
|
'@angular/router': 10.1.6_2d3b53e7e463c932a6e73e0760b7a0a2
|
||||||
'@angular/service-worker': 10.1.6_04f723395c0c28a9d9b8a4ace7178ad2
|
'@angular/service-worker': 10.1.6_04f723395c0c28a9d9b8a4ace7178ad2
|
||||||
'@circlon/angular-tree-component': 10.0.2_04f723395c0c28a9d9b8a4ace7178ad2
|
|
||||||
'@ckeditor/ckeditor5-angular': 2.0.1_11b9a698fa893a2ce33633beb1aae14b
|
'@ckeditor/ckeditor5-angular': 2.0.1_11b9a698fa893a2ce33633beb1aae14b
|
||||||
'@ckeditor/ckeditor5-build-decoupled-document': 27.0.0
|
'@ckeditor/ckeditor5-build-decoupled-document': 27.0.0
|
||||||
'@convergencelabs/monaco-collab-ext': 0.3.2
|
'@convergencelabs/monaco-collab-ext': 0.3.2
|
||||||
@ -1411,19 +1410,6 @@ packages:
|
|||||||
dev: true
|
dev: true
|
||||||
resolution:
|
resolution:
|
||||||
integrity: sha512-tNMDjcv/4DIcHxErTgwB9q2ZcYyN0sUfgGKUK/mm1FJK7Wz+KstoEekxrl/tBiNDgLK1HGi+sppj1An/1DR4fQ==
|
integrity: sha512-tNMDjcv/4DIcHxErTgwB9q2ZcYyN0sUfgGKUK/mm1FJK7Wz+KstoEekxrl/tBiNDgLK1HGi+sppj1An/1DR4fQ==
|
||||||
/@circlon/angular-tree-component/10.0.2_04f723395c0c28a9d9b8a4ace7178ad2:
|
|
||||||
dependencies:
|
|
||||||
'@angular/common': 10.1.6_@angular+core@10.1.6+rxjs@6.6.3
|
|
||||||
'@angular/core': 10.1.6_rxjs@6.6.3+zone.js@0.10.3
|
|
||||||
lodash-es: 4.17.20
|
|
||||||
mobx: 4.14.1
|
|
||||||
tslib: 2.1.0
|
|
||||||
dev: false
|
|
||||||
peerDependencies:
|
|
||||||
'@angular/common': '>=10.0.0 <11.0.0'
|
|
||||||
'@angular/core': '>=10.0.0 <11.0.0'
|
|
||||||
resolution:
|
|
||||||
integrity: sha512-N8KyIQ89fGEO8OKYYgFtY/PbPhHqs4DK5kNPmpJt4KPssvQMF1gP6m+RGVDKlDVH933aOPsrHQax87Fk6dI8gA==
|
|
||||||
/@ckeditor/ckeditor5-adapter-ckfinder/27.0.0:
|
/@ckeditor/ckeditor5-adapter-ckfinder/27.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
ckeditor5: 27.0.0
|
ckeditor5: 27.0.0
|
||||||
@ -7011,10 +6997,6 @@ packages:
|
|||||||
node: '>=8'
|
node: '>=8'
|
||||||
resolution:
|
resolution:
|
||||||
integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
|
integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
|
||||||
/lodash-es/4.17.20:
|
|
||||||
dev: false
|
|
||||||
resolution:
|
|
||||||
integrity: sha512-JD1COMZsq8maT6mnuz1UMV0jvYD0E0aUsSOdrr1/nAG3dhqQXwRRgeW0cSqH1U43INKcqxaiVIQNOUDld7gRDA==
|
|
||||||
/lodash-es/4.17.21:
|
/lodash-es/4.17.21:
|
||||||
dev: false
|
dev: false
|
||||||
resolution:
|
resolution:
|
||||||
@ -7495,10 +7477,6 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
resolution:
|
resolution:
|
||||||
integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||||
/mobx/4.14.1:
|
|
||||||
dev: false
|
|
||||||
resolution:
|
|
||||||
integrity: sha512-Oyg7Sr7r78b+QPYLufJyUmxTWcqeQ96S1nmtyur3QL8SeI6e0TqcKKcxbG+sVJLWANhHQkBW/mDmgG5DDC4fdw==
|
|
||||||
/moment/2.29.1:
|
/moment/2.29.1:
|
||||||
dev: false
|
dev: false
|
||||||
resolution:
|
resolution:
|
||||||
@ -11611,7 +11589,6 @@ specifiers:
|
|||||||
'@angular/pwa': ^0.1001.7
|
'@angular/pwa': ^0.1001.7
|
||||||
'@angular/router': ~10.1.5
|
'@angular/router': ~10.1.5
|
||||||
'@angular/service-worker': ~10.1.5
|
'@angular/service-worker': ~10.1.5
|
||||||
'@circlon/angular-tree-component': ^10.0.0
|
|
||||||
'@ckeditor/ckeditor5-angular': ^2.0.1
|
'@ckeditor/ckeditor5-angular': ^2.0.1
|
||||||
'@ckeditor/ckeditor5-build-decoupled-document': ^27.0.0
|
'@ckeditor/ckeditor5-build-decoupled-document': ^27.0.0
|
||||||
'@convergencelabs/monaco-collab-ext': ^0.3.2
|
'@convergencelabs/monaco-collab-ext': ^0.3.2
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<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 || !menuTarget.data || !menuTarget.data.id">
|
<ion-button fill="outline" color="light" (click)="onNodeMenuClick($event)" [disabled]="!menuTarget || !menuTarget.id">
|
||||||
<i class="fa fa-ellipsis-v" style="color: darkgrey"></i>
|
<i class="fa fa-ellipsis-v" style="color: darkgrey"></i>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-button fill="outline" color="light" (click)="onVirtualRootClear($event)" *ngIf="virtualRootPageId" title="Show entire tree">
|
<ion-button fill="outline" color="light" (click)="onVirtualRootClear($event)" *ngIf="virtualRootPageId" title="Show entire tree">
|
||||||
@ -30,16 +30,20 @@
|
|||||||
</ion-list>
|
</ion-list>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<tree-root style="font-size: 15px;" #menuTree [nodes]="nodes" [options]="options" (moveNode)="onTreeNodeMove($event)">
|
<app-tree-root
|
||||||
<ng-template #treeNodeTemplate let-node let-index="index">
|
#menuTree
|
||||||
<span class="tree-node-container" style="display: flex; padding: 5px; width: 100%;" [ngClass]="node.data.type">
|
[items]="nodes"
|
||||||
<i class="tree-node-icon" [ngClass]="typeIcons[node.data.type]"></i>
|
iconClassField="faIconClass"
|
||||||
<div class="tree-node-name">{{ node.data.name }}</div>
|
(itemSelected)="onMenuItemClick($event)"
|
||||||
</span>
|
(itemActivated)="onMenuItemActivate($event)"
|
||||||
</ng-template>
|
(itemRightClicked)="onMenuItemRightClick($event)"
|
||||||
</tree-root>
|
></app-tree-root>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
<ion-footer>
|
<ion-footer>
|
||||||
|
<ion-searchbar
|
||||||
|
placeholder="Quick filter"
|
||||||
|
(ionChange)="onMenuFilterChange($event)"
|
||||||
|
></ion-searchbar>
|
||||||
<ion-item button lines="full" (click)="showOptions($event)">
|
<ion-item button lines="full" (click)="showOptions($event)">
|
||||||
<ion-icon name="list" slot="start"></ion-icon>
|
<ion-icon name="list" slot="start"></ion-icon>
|
||||||
<ion-label>Menu</ion-label>
|
<ion-label>Menu</ion-label>
|
||||||
|
@ -12,8 +12,7 @@ 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';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import {TREE_ACTIONS, TreeComponent} from '@circlon/angular-tree-component';
|
import {BehaviorSubject, Observable} from 'rxjs';
|
||||||
import {BehaviorSubject, fromEvent, 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 {OptionMenuComponent} from './components/option-menu/option-menu.component';
|
||||||
import {SelectorComponent} from './components/sharing/selector/selector.component';
|
import {SelectorComponent} from './components/sharing/selector/selector.component';
|
||||||
@ -26,6 +25,7 @@ import {EditorService} from './service/editor.service';
|
|||||||
import {debug} from './utility';
|
import {debug} from './utility';
|
||||||
import {AuthService} from './service/auth.service';
|
import {AuthService} from './service/auth.service';
|
||||||
import {OpenerService} from './service/opener.service';
|
import {OpenerService} from './service/opener.service';
|
||||||
|
import {TreeRootComponent} from './components/tree-root/tree-root.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@ -33,82 +33,15 @@ import {OpenerService} from './service/opener.service';
|
|||||||
styleUrls: ['app.component.scss']
|
styleUrls: ['app.component.scss']
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit {
|
export class AppComponent implements OnInit {
|
||||||
@ViewChild('menuTree') menuTree: TreeComponent;
|
@ViewChild('menuTree') menuTree: TreeRootComponent;
|
||||||
public readonly ready$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
public readonly ready$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||||
public addChildTarget: any = false;
|
public addChildTarget: any = false;
|
||||||
public deleteTarget: any = false;
|
public deleteTarget: any = false;
|
||||||
public menuTarget: any = false;
|
public menuTarget: any = false;
|
||||||
public refreshingMenu = false;
|
public refreshingMenu = false;
|
||||||
public lastClickEvent: Array<any> = [];
|
public lastClickEvent?: {event: MouseEvent, item: any};
|
||||||
public nodes = [];
|
public nodes = [];
|
||||||
public currentPageId: string;
|
|
||||||
public virtualRootPageId?: string;
|
public virtualRootPageId?: string;
|
||||||
public options = {
|
|
||||||
isExpandedField: 'expanded',
|
|
||||||
animateExpand: false,
|
|
||||||
scrollOnActivate: false,
|
|
||||||
allowDrag: true,
|
|
||||||
allowDrop: (element, { parent, index }) => {
|
|
||||||
return (
|
|
||||||
!this.api.isOffline
|
|
||||||
&& (element.data.type === 'page' || element.data.type === 'form')
|
|
||||||
&& (parent.data.type === 'page' || parent.data.userRootPage)
|
|
||||||
&& !element.data.userRootPage
|
|
||||||
&& element.data.id !== parent.data.id
|
|
||||||
);
|
|
||||||
},
|
|
||||||
actionMapping: {
|
|
||||||
mouse: {
|
|
||||||
dblClick: (tree, node, $event) => {
|
|
||||||
this.navigateEditorToNode(node);
|
|
||||||
},
|
|
||||||
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];
|
|
||||||
},
|
|
||||||
contextMenu: (tree, node, event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
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];
|
|
||||||
|
|
||||||
this.onNodeMenuClick(event);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public typeIcons = NodeTypeIcons;
|
public typeIcons = NodeTypeIcons;
|
||||||
|
|
||||||
public get appName(): string {
|
public get appName(): string {
|
||||||
@ -121,14 +54,12 @@ export class AppComponent implements OnInit {
|
|||||||
protected versionInterval?: any;
|
protected versionInterval?: any;
|
||||||
protected showedNewVersionAlert = false;
|
protected showedNewVersionAlert = false;
|
||||||
protected showedOfflineAlert = false;
|
protected showedOfflineAlert = false;
|
||||||
protected backbuttonSubscription: any;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private platform: Platform,
|
private platform: Platform,
|
||||||
private splashScreen: SplashScreen,
|
private splashScreen: SplashScreen,
|
||||||
private statusBar: StatusBar,
|
private statusBar: StatusBar,
|
||||||
public readonly api: ApiService,
|
public readonly api: ApiService,
|
||||||
protected navCtrl: NavController,
|
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
protected alerts: AlertController,
|
protected alerts: AlertController,
|
||||||
protected popover: PopoverController,
|
protected popover: PopoverController,
|
||||||
@ -143,6 +74,55 @@ export class AppComponent implements OnInit {
|
|||||||
protected opener: OpenerService,
|
protected opener: OpenerService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
public onMenuItemClick(event: {event: MouseEvent, item: any}) {
|
||||||
|
this.addChildTarget = false;
|
||||||
|
this.deleteTarget = false;
|
||||||
|
this.menuTarget = false;
|
||||||
|
|
||||||
|
if ( !event.item.noChildren && (!event.item.level || event.item.level === 'manage') ) {
|
||||||
|
this.addChildTarget = event.item;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !event.item.noDelete && (!event.item.level || event.item.level === 'manage') ) {
|
||||||
|
this.deleteTarget = event.item;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.menuTarget = event.item;
|
||||||
|
this.lastClickEvent = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public onMenuItemActivate(event: {event: MouseEvent, item: any}) {
|
||||||
|
this.navigateEditorToNode(event.item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onMenuItemRightClick(event: {event: MouseEvent, item: any}) {
|
||||||
|
event.event.preventDefault();
|
||||||
|
event.event.stopPropagation();
|
||||||
|
|
||||||
|
this.addChildTarget = false;
|
||||||
|
this.deleteTarget = false;
|
||||||
|
this.menuTarget = false;
|
||||||
|
|
||||||
|
if ( !event.item.noChildren && (!event.item.level || event.item.level === 'manage') ) {
|
||||||
|
this.addChildTarget = event.item;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !event.item.noDelete && (!event.item.level || event.item.level === 'manage') ) {
|
||||||
|
this.deleteTarget = event.item;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.menuTarget = event.item;
|
||||||
|
this.lastClickEvent = event;
|
||||||
|
|
||||||
|
this.onNodeMenuClick(event.event, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onMenuFilterChange(event) {
|
||||||
|
const filterValue = event?.detail?.value;
|
||||||
|
debug('Filtering tree:', filterValue);
|
||||||
|
this.menuTree?.filterTree(filterValue);
|
||||||
|
}
|
||||||
|
|
||||||
async checkNewVersion() {
|
async checkNewVersion() {
|
||||||
if ( !this.showedNewVersionAlert && await this.session.newVersionAvailable() ) {
|
if ( !this.showedNewVersionAlert && await this.session.newVersionAvailable() ) {
|
||||||
const toast = await this.toasts.create({
|
const toast = await this.toasts.create({
|
||||||
@ -236,15 +216,15 @@ export class AppComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async onNodeMenuClick($event) {
|
async onNodeMenuClick($event, fromContextMenu = false) {
|
||||||
let canManage = this.menuTarget.data.level === 'manage';
|
let canManage = this.menuTarget.level === 'manage';
|
||||||
if ( !canManage ) {
|
if ( !canManage ) {
|
||||||
if ( !this.menuTarget.data.level ) {
|
if ( !this.menuTarget.level ) {
|
||||||
canManage = true;
|
canManage = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !this.menuTarget.data.id ) {
|
if ( !this.menuTarget.id ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,10 +235,12 @@ export class AppComponent implements OnInit {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const manageOptions = [
|
const manageOptions = [
|
||||||
|
...(fromContextMenu ? this.getCreateNodeMenuItems() : []),
|
||||||
{name: 'Share Sub-Tree', icon: 'fa fa-share-alt', value: 'share'},
|
{name: 'Share Sub-Tree', icon: 'fa fa-share-alt', value: 'share'},
|
||||||
|
{name: 'Delete Sub-Tree', icon: 'fa fa-trash noded-danger', value: 'delete'},
|
||||||
];
|
];
|
||||||
|
|
||||||
if ( this.menuTarget.data.bookmark ) {
|
if ( this.menuTarget.bookmark ) {
|
||||||
options.push({name: 'Remove Bookmark', icon: 'fa fa-star', value: 'bookmark_remove'});
|
options.push({name: 'Remove Bookmark', icon: 'fa fa-star', value: 'bookmark_remove'});
|
||||||
} else {
|
} else {
|
||||||
options.push({name: 'Bookmark', icon: 'fa fa-star', value: 'bookmark_add'});
|
options.push({name: 'Bookmark', icon: 'fa fa-star', value: 'bookmark_add'});
|
||||||
@ -280,7 +262,7 @@ export class AppComponent implements OnInit {
|
|||||||
component: SelectorComponent,
|
component: SelectorComponent,
|
||||||
cssClass: 'modal-med',
|
cssClass: 'modal-med',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
node: this.menuTarget.data,
|
node: this.menuTarget,
|
||||||
}
|
}
|
||||||
}).then(modal => {
|
}).then(modal => {
|
||||||
const modalState = {
|
const modalState = {
|
||||||
@ -302,6 +284,14 @@ export class AppComponent implements OnInit {
|
|||||||
this.addBookmark();
|
this.addBookmark();
|
||||||
} else if ( result.data === 'bookmark_remove' ) {
|
} else if ( result.data === 'bookmark_remove' ) {
|
||||||
this.removeBookmark();
|
this.removeBookmark();
|
||||||
|
} else if ( result.data === 'top-level' ) {
|
||||||
|
this.onTopLevelCreate();
|
||||||
|
} else if ( result.data === 'child' ) {
|
||||||
|
this.onChildCreate();
|
||||||
|
} else if ( result.data === 'form' ) {
|
||||||
|
this.onChildCreate('form');
|
||||||
|
} else if ( result.data === 'delete' ) {
|
||||||
|
this.onDeleteClick();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -309,9 +299,9 @@ export class AppComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setVirtualRoot() {
|
async setVirtualRoot() {
|
||||||
if ( this.menuTarget && this.menuTarget.data?.type === 'page' ) {
|
if ( this.menuTarget && this.menuTarget?.type === 'page' ) {
|
||||||
debug('virtual root menu target', this.menuTarget);
|
debug('virtual root menu target', this.menuTarget);
|
||||||
this.virtualRootPageId = this.menuTarget.data.id;
|
this.virtualRootPageId = this.menuTarget.id;
|
||||||
this.reloadMenuItems().subscribe();
|
this.reloadMenuItems().subscribe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -325,7 +315,7 @@ export class AppComponent implements OnInit {
|
|||||||
const exportRecord: any = await new Promise((res, rej) => {
|
const exportRecord: any = await new Promise((res, rej) => {
|
||||||
const reqData = {
|
const reqData = {
|
||||||
format: 'html',
|
format: 'html',
|
||||||
PageId: this.menuTarget.data.id,
|
PageId: this.menuTarget.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.api.post(`/exports/subtree`, reqData).subscribe({
|
this.api.post(`/exports/subtree`, reqData).subscribe({
|
||||||
@ -343,8 +333,8 @@ export class AppComponent implements OnInit {
|
|||||||
addBookmark() {
|
addBookmark() {
|
||||||
const bookmarks = this.session.get('user.preferences.bookmark_page_ids') || [];
|
const bookmarks = this.session.get('user.preferences.bookmark_page_ids') || [];
|
||||||
|
|
||||||
if ( !bookmarks.includes(this.menuTarget.data.id) ) {
|
if ( !bookmarks.includes(this.menuTarget.id) ) {
|
||||||
bookmarks.push(this.menuTarget.data.id);
|
bookmarks.push(this.menuTarget.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.session.set('user.preferences.bookmark_page_ids', bookmarks);
|
this.session.set('user.preferences.bookmark_page_ids', bookmarks);
|
||||||
@ -353,35 +343,39 @@ export class AppComponent implements OnInit {
|
|||||||
|
|
||||||
removeBookmark() {
|
removeBookmark() {
|
||||||
let bookmarks = this.session.get('user.preferences.bookmark_page_ids') || [];
|
let bookmarks = this.session.get('user.preferences.bookmark_page_ids') || [];
|
||||||
bookmarks = bookmarks.filter(x => x !== this.menuTarget.data.id);
|
bookmarks = bookmarks.filter(x => x !== this.menuTarget.id);
|
||||||
|
|
||||||
this.session.set('user.preferences.bookmark_page_ids', bookmarks);
|
this.session.set('user.preferences.bookmark_page_ids', bookmarks);
|
||||||
this.session.save().then(() => this.navService.requestSidebarRefresh({ quiet: true }));
|
this.session.save().then(() => this.navService.requestSidebarRefresh({ quiet: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
async onCreateClick($event: MouseEvent) {
|
getCreateNodeMenuItems() {
|
||||||
const menuItems = [
|
return [
|
||||||
{
|
{
|
||||||
name: 'Top-Level Note',
|
name: 'Create Top-Level Note',
|
||||||
icon: 'fa fa-sticky-note noded-note',
|
icon: 'fa fa-sticky-note noded-note',
|
||||||
value: 'top-level',
|
value: 'top-level',
|
||||||
title: 'Create a new top-level note page',
|
title: 'Create a new top-level note page',
|
||||||
},
|
},
|
||||||
...(this.addChildTarget ? [
|
...(this.addChildTarget ? [
|
||||||
{
|
{
|
||||||
name: 'Child Note',
|
name: 'Create Child Note',
|
||||||
icon: 'fa fa-sticky-note noded-note',
|
icon: 'fa fa-sticky-note noded-note',
|
||||||
value: 'child',
|
value: 'child',
|
||||||
title: 'Create a note page as a child of the given note',
|
title: 'Create a note page as a child of the given note',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Form',
|
name: 'Create Form',
|
||||||
icon: 'fa fa-clipboard-list noded-form',
|
icon: 'fa fa-clipboard-list noded-form',
|
||||||
value: 'form',
|
value: 'form',
|
||||||
title: 'Create a new form page as a child of the given note',
|
title: 'Create a new form page as a child of the given note',
|
||||||
},
|
},
|
||||||
] : []),
|
] : []),
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async onCreateClick($event: MouseEvent) {
|
||||||
|
const menuItems = this.getCreateNodeMenuItems();
|
||||||
|
|
||||||
const popover = await this.popover.create({
|
const popover = await this.popover.create({
|
||||||
event: $event,
|
event: $event,
|
||||||
@ -459,16 +453,11 @@ export class AppComponent implements OnInit {
|
|||||||
handler: async args => {
|
handler: async args => {
|
||||||
args = {
|
args = {
|
||||||
name: args.name,
|
name: args.name,
|
||||||
parentId: this.addChildTarget.data.id,
|
parentId: this.addChildTarget.id,
|
||||||
pageType,
|
pageType,
|
||||||
};
|
};
|
||||||
this.api.post('/page/create-child', args).subscribe(res => {
|
this.api.post('/page/create-child', args).subscribe(res => {
|
||||||
this.reloadMenuItems().subscribe(() => {
|
this.reloadMenuItems().subscribe(() => {
|
||||||
TREE_ACTIONS.EXPAND(
|
|
||||||
this.lastClickEvent[0],
|
|
||||||
this.lastClickEvent[1],
|
|
||||||
this.lastClickEvent[2]
|
|
||||||
);
|
|
||||||
this.router.navigate(['/editor', { id: res.data.UUID }]);
|
this.router.navigate(['/editor', { id: res.data.UUID }]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -484,7 +473,7 @@ export class AppComponent implements OnInit {
|
|||||||
const alert = await this.alerts.create({
|
const alert = await this.alerts.create({
|
||||||
header: 'Delete page?',
|
header: 'Delete page?',
|
||||||
message:
|
message:
|
||||||
'Deleting this page will make its contents and all of its children inaccessible. Are you sure you want to continue?',
|
'Deleting this page will make its contents and all of its children inaccessible. Are you sure you want to continue?',
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
text: 'Keep It',
|
text: 'Keep It',
|
||||||
@ -494,9 +483,9 @@ export class AppComponent implements OnInit {
|
|||||||
text: 'Delete It',
|
text: 'Delete It',
|
||||||
handler: async () => {
|
handler: async () => {
|
||||||
this.api
|
this.api
|
||||||
.post(`/page/delete/${this.deleteTarget.data.id}`)
|
.post(`/page/delete/${this.deleteTarget.id}`)
|
||||||
.subscribe(res => {
|
.subscribe(res => {
|
||||||
if ( this.opener.currentPageId === this.deleteTarget.data.id ) {
|
if ( this.opener.currentPageId === this.deleteTarget.id ) {
|
||||||
this.router.navigate(['/home']);
|
this.router.navigate(['/home']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -717,7 +706,7 @@ export class AppComponent implements OnInit {
|
|||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.loader.dismiss();
|
this.loader.dismiss();
|
||||||
this.menuTree?.treeModel?.expandAll();
|
// this.menuTree?.treeModel?.expandAll();
|
||||||
}, 10);
|
}, 10);
|
||||||
|
|
||||||
if ( !this.versionInterval ) {
|
if ( !this.versionInterval ) {
|
||||||
|
@ -10,7 +10,6 @@ import { AppComponent } from './app.component';
|
|||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
import { ComponentsModule } from './components/components.module';
|
import { ComponentsModule } from './components/components.module';
|
||||||
import { TreeModule } from '@circlon/angular-tree-component';
|
|
||||||
import {AgGridModule} from 'ag-grid-angular';
|
import {AgGridModule} from 'ag-grid-angular';
|
||||||
import {MonacoEditorModule} from 'ngx-monaco-editor';
|
import {MonacoEditorModule} from 'ngx-monaco-editor';
|
||||||
import { APP_BASE_HREF, PlatformLocation } from '@angular/common';
|
import { APP_BASE_HREF, PlatformLocation } from '@angular/common';
|
||||||
@ -47,7 +46,6 @@ export function getBaseHref(platformLocation: PlatformLocation): string {
|
|||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
ComponentsModule,
|
ComponentsModule,
|
||||||
TreeModule,
|
|
||||||
AgGridModule.withComponents([]),
|
AgGridModule.withComponents([]),
|
||||||
MonacoEditorModule.forRoot(),
|
MonacoEditorModule.forRoot(),
|
||||||
HighlightModule,
|
HighlightModule,
|
||||||
|
@ -26,6 +26,7 @@ import {DatetimeRendererComponent} from './editor/database/renderers/datetime-re
|
|||||||
import {CurrencyRendererComponent} from './editor/database/renderers/currency-renderer.component';
|
import {CurrencyRendererComponent} from './editor/database/renderers/currency-renderer.component';
|
||||||
import {BooleanRendererComponent} from './editor/database/renderers/boolean-renderer.component';
|
import {BooleanRendererComponent} from './editor/database/renderers/boolean-renderer.component';
|
||||||
import {SearchComponent} from './search/Search.component';
|
import {SearchComponent} from './search/Search.component';
|
||||||
|
import {TreeRootComponent} from './tree-root/tree-root.component';
|
||||||
|
|
||||||
import {NormComponent} from './nodes/norm/norm.component';
|
import {NormComponent} from './nodes/norm/norm.component';
|
||||||
import {MarkdownComponent as MarkdownEditorComponent} from './nodes/markdown/markdown.component';
|
import {MarkdownComponent as MarkdownEditorComponent} from './nodes/markdown/markdown.component';
|
||||||
@ -74,6 +75,7 @@ import {FileBoxPageComponent} from './nodes/file-box/file-box-page.component';
|
|||||||
CurrencyRendererComponent,
|
CurrencyRendererComponent,
|
||||||
BooleanRendererComponent,
|
BooleanRendererComponent,
|
||||||
SearchComponent,
|
SearchComponent,
|
||||||
|
TreeRootComponent,
|
||||||
|
|
||||||
NormComponent,
|
NormComponent,
|
||||||
MarkdownEditorComponent,
|
MarkdownEditorComponent,
|
||||||
@ -131,6 +133,7 @@ import {FileBoxPageComponent} from './nodes/file-box/file-box-page.component';
|
|||||||
CurrencyRendererComponent,
|
CurrencyRendererComponent,
|
||||||
BooleanRendererComponent,
|
BooleanRendererComponent,
|
||||||
SearchComponent,
|
SearchComponent,
|
||||||
|
TreeRootComponent,
|
||||||
|
|
||||||
NormComponent,
|
NormComponent,
|
||||||
MarkdownEditorComponent,
|
MarkdownEditorComponent,
|
||||||
@ -173,6 +176,7 @@ import {FileBoxPageComponent} from './nodes/file-box/file-box-page.component';
|
|||||||
CurrencyRendererComponent,
|
CurrencyRendererComponent,
|
||||||
BooleanRendererComponent,
|
BooleanRendererComponent,
|
||||||
SearchComponent,
|
SearchComponent,
|
||||||
|
TreeRootComponent,
|
||||||
|
|
||||||
NormComponent,
|
NormComponent,
|
||||||
MarkdownEditorComponent,
|
MarkdownEditorComponent,
|
||||||
|
37
src/app/components/tree-root/tree-root.component.html
Normal file
37
src/app/components/tree-root/tree-root.component.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<div [ngClass]="{container: true, dark: isDark()}">
|
||||||
|
<ng-container *ngFor="let item of items">
|
||||||
|
<div
|
||||||
|
class="item"
|
||||||
|
*ngIf="!item.__app_tree_comp_is_hidden"
|
||||||
|
(click)="onItemClick($event, item)"
|
||||||
|
(dblclick)="onItemDoubleClick($event, item)"
|
||||||
|
(contextmenu)="onItemRightClick($event, item)"
|
||||||
|
[ngClass]="{activated: selectedItem === item}"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
*ngIf="hasNesting(item) && !item.__app_tree_comp_is_collapsed"
|
||||||
|
(click)="onItemHandleClick($event, item)"
|
||||||
|
class="fa fa-chevron-down handle handle-expanded"
|
||||||
|
></i>
|
||||||
|
<i
|
||||||
|
*ngIf="hasNesting(item) && item.__app_tree_comp_is_collapsed"
|
||||||
|
(click)="onItemHandleClick($event, item)"
|
||||||
|
class="fa fa-chevron-right handle handle-collapsed"
|
||||||
|
></i>
|
||||||
|
<i *ngIf="iconClassField" [ngClass]="item[iconClassField]"></i>
|
||||||
|
{{ item[displayField] }}
|
||||||
|
</div>
|
||||||
|
<div class="nested-level" *ngIf="hasNesting(item, true) && !item.__app_tree_comp_is_collapsed">
|
||||||
|
<app-tree-root
|
||||||
|
#childComponents
|
||||||
|
[displayField]="displayField"
|
||||||
|
[childrenField]="childrenField"
|
||||||
|
[nestingLevel]="nestingLevel + 1"
|
||||||
|
[iconClassField]="iconClassField"
|
||||||
|
[items]="item[childrenField]"
|
||||||
|
[parent]="this"
|
||||||
|
[parentItem]="item"
|
||||||
|
></app-tree-root>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
38
src/app/components/tree-root/tree-root.component.scss
Normal file
38
src/app/components/tree-root/tree-root.component.scss
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-level {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
padding: 10px;
|
||||||
|
border-bottom: 1px solid #cccccc;
|
||||||
|
border-left: 1px solid #cccccc;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
i {
|
||||||
|
padding-right: 10px;
|
||||||
|
|
||||||
|
&.handle {
|
||||||
|
color: #888888;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover, &.activated {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container.dark {
|
||||||
|
.item {
|
||||||
|
border-color: #555555;
|
||||||
|
|
||||||
|
&:hover, &.activated {
|
||||||
|
background-color: #333333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
253
src/app/components/tree-root/tree-root.component.ts
Normal file
253
src/app/components/tree-root/tree-root.component.ts
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
import {Component, EventEmitter, Input, OnInit, Output, ViewChildren} from '@angular/core';
|
||||||
|
import {debug} from '../../utility';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A recursive tree listing component.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'app-tree-root',
|
||||||
|
templateUrl: './tree-root.component.html',
|
||||||
|
styleUrls: ['./tree-root.component.scss'],
|
||||||
|
})
|
||||||
|
export class TreeRootComponent implements OnInit {
|
||||||
|
/** The immediate children of this component. */
|
||||||
|
@ViewChildren('childComponents')
|
||||||
|
protected childComponents;
|
||||||
|
|
||||||
|
/** How deeply nested this component is in the tree. */
|
||||||
|
@Input()
|
||||||
|
public nestingLevel = 0;
|
||||||
|
|
||||||
|
/** Field on the listing record with the display string. */
|
||||||
|
@Input()
|
||||||
|
public displayField = 'name';
|
||||||
|
|
||||||
|
/** Field on the listing record that contains the children of an item. */
|
||||||
|
@Input()
|
||||||
|
public childrenField = 'children';
|
||||||
|
|
||||||
|
/** Optionally, field on the listing record that contains the <i> icon class. */
|
||||||
|
@Input()
|
||||||
|
public iconClassField?: string;
|
||||||
|
|
||||||
|
/** The nested listing items. */
|
||||||
|
@Input()
|
||||||
|
public items: any[] = [];
|
||||||
|
|
||||||
|
/** @private Set by the parent component when recursively nested. */
|
||||||
|
@Input()
|
||||||
|
public parent?: TreeRootComponent;
|
||||||
|
|
||||||
|
/** @private Item object that created this listing when recursively nested. */
|
||||||
|
@Input()
|
||||||
|
public parentItem?: any;
|
||||||
|
|
||||||
|
/** Emits the selected item, when top-level tree. */
|
||||||
|
@Output()
|
||||||
|
public itemSelected: EventEmitter<{ event: MouseEvent, item: any }> = new EventEmitter<{event: MouseEvent; item: any}>();
|
||||||
|
|
||||||
|
/** Emits the activated item, when top-level tree. */
|
||||||
|
@Output()
|
||||||
|
public itemActivated: EventEmitter<{ event: MouseEvent, item: any }> = new EventEmitter<{event: MouseEvent; item: any}>();
|
||||||
|
|
||||||
|
/** Emits the right-clicked item, when top-level tree. */
|
||||||
|
@Output()
|
||||||
|
public itemRightClicked: EventEmitter<{ event: MouseEvent, item: any }> = new EventEmitter<{event: MouseEvent; item: any}>();
|
||||||
|
|
||||||
|
/** The selected item record at this level, if one exists. */
|
||||||
|
public selectedItem?: any;
|
||||||
|
|
||||||
|
/** The current tree filter value, if one exists. */
|
||||||
|
protected filterValue?: string;
|
||||||
|
|
||||||
|
/** Called on initialization. */
|
||||||
|
ngOnInit() {
|
||||||
|
if ( !this.parent ) {
|
||||||
|
debug('Top-level tree root component:', this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** True if we're in dark mode. */
|
||||||
|
public isDark() {
|
||||||
|
return document.body.classList.contains('dark');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Recursively filter the tree using the given quick-filter string. */
|
||||||
|
public filterTree(filterValue?: string) {
|
||||||
|
this.setFilterRecursively(filterValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Emit an item selected event, or bubble up to the parent. */
|
||||||
|
public emitItemSelected(event: MouseEvent, item: any) {
|
||||||
|
if ( this.parent ) {
|
||||||
|
return this.parent.emitItemSelected(event, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.itemSelected.emit({
|
||||||
|
event,
|
||||||
|
item,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Emit an item activated event, or bubble up to the parent. */
|
||||||
|
public emitItemActivated(event: MouseEvent, item: any) {
|
||||||
|
if ( this.parent ) {
|
||||||
|
return this.parent.emitItemActivated(event, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.itemActivated.emit({
|
||||||
|
event,
|
||||||
|
item,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Emit an item right-clicked event, or bubble up to the parent. */
|
||||||
|
public emitItemRightClicked(event: MouseEvent, item: any) {
|
||||||
|
if ( this.parent ) {
|
||||||
|
return this.parent.emitItemRightClicked(event, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.itemRightClicked.emit({
|
||||||
|
event,
|
||||||
|
item,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Wrapper for checking arrays for use in NG templates. */
|
||||||
|
public isArray(something: unknown): boolean {
|
||||||
|
return Array.isArray(something);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if an item has nested children.
|
||||||
|
* @param item
|
||||||
|
* @param includeHidden - if true, will include children hidden by the quick-filter
|
||||||
|
*/
|
||||||
|
public hasNesting(item: any, includeHidden = false): boolean {
|
||||||
|
const hasUnfilteredNesting = item && this.isArray(item[this.childrenField]) && item[this.childrenField].length;
|
||||||
|
if ( !this.filterValue || includeHidden ) {
|
||||||
|
return hasUnfilteredNesting;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasUnfilteredNesting && item[this.childrenField].some(child => !child.__app_tree_comp_is_hidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called when an item expand/collapse arrow is clicked. */
|
||||||
|
public onItemHandleClick(event: MouseEvent, item: any) {
|
||||||
|
if ( this.hasNesting(item) ) {
|
||||||
|
this.setExpansionRecursively(item, !item.__app_tree_comp_is_collapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called when an item is clicked. */
|
||||||
|
public onItemClick(event: MouseEvent, item: any) {
|
||||||
|
this.clearSelection();
|
||||||
|
this.selectedItem = item;
|
||||||
|
this.emitItemSelected(event, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called when an item is double-clicked. */
|
||||||
|
public onItemDoubleClick(event: MouseEvent, item: any) {
|
||||||
|
this.emitItemActivated(event, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called when an item is right-clicked. */
|
||||||
|
public onItemRightClick(event: MouseEvent, item: any) {
|
||||||
|
this.clearSelection();
|
||||||
|
this.selectedItem = item;
|
||||||
|
this.emitItemRightClicked(event, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Recursively clear the selection from the entire tree, calling children and bubbling up to parent. */
|
||||||
|
public clearSelection(from?: TreeRootComponent) {
|
||||||
|
this.selectedItem = undefined;
|
||||||
|
|
||||||
|
if ( this.childComponents ) {
|
||||||
|
for ( const child of this.childComponents ) {
|
||||||
|
if ( child === from ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
child.clearSelection(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this.parent ) {
|
||||||
|
if ( this.parent !== from ) {
|
||||||
|
this.parent.clearSelection(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively set the filter on this subtree.
|
||||||
|
* Returns true if any of the children of this level match the filter.
|
||||||
|
* @param filterValue
|
||||||
|
* @param from
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected setFilterRecursively(filterValue?: string, from?: TreeRootComponent): boolean {
|
||||||
|
this.filterValue = filterValue;
|
||||||
|
|
||||||
|
let anyChildMatchesFilter = false;
|
||||||
|
for ( const item of this.items ) {
|
||||||
|
item.__app_tree_comp_is_hidden = this.getFilterHiddenStatusForItem(item);
|
||||||
|
anyChildMatchesFilter = anyChildMatchesFilter || !item.__app_tree_comp_is_hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
return anyChildMatchesFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the given item should be hidden with the current quick-filter.
|
||||||
|
* @param item
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected getFilterHiddenStatusForItem(item: any): boolean {
|
||||||
|
const itemMatchesFilter = (
|
||||||
|
!this.filterValue
|
||||||
|
|| (
|
||||||
|
item[this.displayField]?.trim()?.toLowerCase()?.includes(this.filterValue?.trim()?.toLowerCase())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( !this.hasNesting(item, true) ) {
|
||||||
|
return !itemMatchesFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
let anyChildMatchesFilter = false;
|
||||||
|
const childTree = this.getChildComponentForItem(item);
|
||||||
|
if ( childTree ) {
|
||||||
|
anyChildMatchesFilter = childTree.setFilterRecursively(this.filterValue, this);
|
||||||
|
} else {
|
||||||
|
debug('Unable to find childTree for item:', item[this.displayField]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !(!this.filterValue || itemMatchesFilter || anyChildMatchesFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the TreeRootComponent instance for some item's children, if it exists.
|
||||||
|
* @param item
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected getChildComponentForItem(item: any): TreeRootComponent | undefined {
|
||||||
|
return [...(this.childComponents || [])]?.find(child => {
|
||||||
|
return child.parentItem === item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand or collapse an item recursively.
|
||||||
|
* @param item
|
||||||
|
* @param isCollapsed
|
||||||
|
*/
|
||||||
|
public setExpansionRecursively(item: any, isCollapsed: boolean) {
|
||||||
|
item.__app_tree_comp_is_collapsed = isCollapsed;
|
||||||
|
if ( this.hasNesting(item, true) && isCollapsed ) {
|
||||||
|
for ( const child of item.children ) {
|
||||||
|
this.setExpansionRecursively(child, isCollapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@ import {Page} from './db/Page';
|
|||||||
import {PageNode} from './db/PageNode';
|
import {PageNode} from './db/PageNode';
|
||||||
import {debug} from '../utility';
|
import {debug} from '../utility';
|
||||||
import HostRecord from '../structures/HostRecord';
|
import HostRecord from '../structures/HostRecord';
|
||||||
|
import {NodeTypeIcons} from '../structures/node-types';
|
||||||
|
|
||||||
export class ResourceNotAvailableOfflineError extends Error {
|
export class ResourceNotAvailableOfflineError extends Error {
|
||||||
constructor(msg = 'This resource is not yet available offline on this device.') {
|
constructor(msg = 'This resource is not yet available offline on this device.') {
|
||||||
@ -558,7 +559,7 @@ export class ApiService {
|
|||||||
const tree: any[] = await new Promise(res2 => {
|
const tree: any[] = await new Promise(res2 => {
|
||||||
this.get(loadUrl).subscribe({
|
this.get(loadUrl).subscribe({
|
||||||
next: async result => {
|
next: async result => {
|
||||||
const nodes = result.data as any[];
|
const nodes = this.setMenuItemIconKeys(result.data as any[]);
|
||||||
const items = MenuItem.deflateTree(nodes);
|
const items = MenuItem.deflateTree(nodes);
|
||||||
|
|
||||||
// Update the locally stored nodes
|
// Update the locally stored nodes
|
||||||
@ -577,6 +578,21 @@ export class ApiService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected setMenuItemIconKeys(nodes: any[]): any[] {
|
||||||
|
return nodes.map(node => {
|
||||||
|
node.faIconClass = NodeTypeIcons[node.type];
|
||||||
|
if ( !node.faIconClass ) {
|
||||||
|
debug('Unable to map type icon class for menu item type:', node.faIconClass, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( Array.isArray(node.children) ) {
|
||||||
|
node.children = this.setMenuItemIconKeys(node.children);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public getSessionData(): Promise<any> {
|
public getSessionData(): Promise<any> {
|
||||||
return new Promise(async (res, rej) => {
|
return new Promise(async (res, rej) => {
|
||||||
const sessionKV = await this.db.getKeyValue('session_data');
|
const sessionKV = await this.db.getKeyValue('session_data');
|
||||||
|
@ -13,6 +13,7 @@ export interface IMenuItem {
|
|||||||
shared?: boolean;
|
shared?: boolean;
|
||||||
needsServerUpdate?: boolean;
|
needsServerUpdate?: boolean;
|
||||||
offlineUpdatedAt?: string;
|
offlineUpdatedAt?: string;
|
||||||
|
faIconClass?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MenuItem extends Model<IMenuItem> implements IMenuItem {
|
export class MenuItem extends Model<IMenuItem> implements IMenuItem {
|
||||||
@ -27,13 +28,15 @@ export class MenuItem extends Model<IMenuItem> implements IMenuItem {
|
|||||||
shared?: boolean;
|
shared?: boolean;
|
||||||
needsServerUpdate?: boolean;
|
needsServerUpdate?: boolean;
|
||||||
offlineUpdatedAt?: string;
|
offlineUpdatedAt?: string;
|
||||||
|
faIconClass?: string;
|
||||||
|
|
||||||
public static getTableName() {
|
public static getTableName() {
|
||||||
return 'menuItems';
|
return 'menuItems';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getSchema() {
|
public static getSchema() {
|
||||||
return '++id, serverId, name, childIds, noDelete, noChildren, virtual, type, shared, needsServerUpdate, offlineUpdatedAt';
|
// tslint:disable-next-line:max-line-length
|
||||||
|
return '++id, serverId, name, childIds, noDelete, noChildren, virtual, type, shared, needsServerUpdate, offlineUpdatedAt, faIconClass';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static deflateTree(nodes: any[]): MenuItem[] {
|
public static deflateTree(nodes: any[]): MenuItem[] {
|
||||||
@ -41,7 +44,17 @@ export class MenuItem extends Model<IMenuItem> implements IMenuItem {
|
|||||||
|
|
||||||
for ( const node of nodes ) {
|
for ( const node of nodes ) {
|
||||||
const childIds = node.children ? node.children.map(x => x.id) : [];
|
const childIds = node.children ? node.children.map(x => x.id) : [];
|
||||||
const item = new MenuItem(node.name, node.id, childIds, node.noDelete, node.noChildren, node.virtual, node.type, node.shared);
|
const item = new MenuItem(
|
||||||
|
node.name,
|
||||||
|
node.id,
|
||||||
|
childIds,
|
||||||
|
node.noDelete,
|
||||||
|
node.noChildren,
|
||||||
|
node.virtual,
|
||||||
|
node.type,
|
||||||
|
node.shared,
|
||||||
|
node.faIconClass
|
||||||
|
);
|
||||||
|
|
||||||
items.push(item);
|
items.push(item);
|
||||||
if ( node.children ) {
|
if ( node.children ) {
|
||||||
@ -114,6 +127,7 @@ export class MenuItem extends Model<IMenuItem> implements IMenuItem {
|
|||||||
shared?: boolean,
|
shared?: boolean,
|
||||||
needsServerUpdate?: boolean,
|
needsServerUpdate?: boolean,
|
||||||
offlineUpdatedAt?: string,
|
offlineUpdatedAt?: string,
|
||||||
|
faIconClass?: string,
|
||||||
id?: number
|
id?: number
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
@ -152,6 +166,8 @@ export class MenuItem extends Model<IMenuItem> implements IMenuItem {
|
|||||||
this.offlineUpdatedAt = offlineUpdatedAt;
|
this.offlineUpdatedAt = offlineUpdatedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.faIconClass = faIconClass;
|
||||||
|
|
||||||
if ( id ) {
|
if ( id ) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
@ -174,6 +190,7 @@ export class MenuItem extends Model<IMenuItem> implements IMenuItem {
|
|||||||
...(typeof this.shared !== 'undefined' ? { shared: this.shared } : {}),
|
...(typeof this.shared !== 'undefined' ? { shared: this.shared } : {}),
|
||||||
...(typeof this.needsServerUpdate !== 'undefined' ? { needsServerUpdate: this.needsServerUpdate } : {}),
|
...(typeof this.needsServerUpdate !== 'undefined' ? { needsServerUpdate: this.needsServerUpdate } : {}),
|
||||||
...(typeof this.offlineUpdatedAt !== 'undefined' ? { offlineUpdatedAt: this.offlineUpdatedAt } : {}),
|
...(typeof this.offlineUpdatedAt !== 'undefined' ? { offlineUpdatedAt: this.offlineUpdatedAt } : {}),
|
||||||
|
...(typeof this.faIconClass !== 'undefined' ? { faIconClass: this.faIconClass } : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
@import "~@ionic/angular/css/text-transformation.css";
|
@import "~@ionic/angular/css/text-transformation.css";
|
||||||
@import "~@ionic/angular/css/flex-utils.css";
|
@import "~@ionic/angular/css/flex-utils.css";
|
||||||
|
|
||||||
@import '~@circlon/angular-tree-component/css/angular-tree-component.css';
|
|
||||||
@import "~ag-grid-community/dist/styles/ag-grid.css";
|
@import "~ag-grid-community/dist/styles/ag-grid.css";
|
||||||
@import "~ag-grid-community/dist/styles/ag-theme-balham.css";
|
@import "~ag-grid-community/dist/styles/ag-theme-balham.css";
|
||||||
@import "~ag-grid-community/dist/styles/ag-theme-balham-dark.css";
|
@import "~ag-grid-community/dist/styles/ag-theme-balham-dark.css";
|
||||||
@ -61,6 +60,8 @@
|
|||||||
--noded-background-form: #F2C57C;
|
--noded-background-form: #F2C57C;
|
||||||
--noded-color-form: white;
|
--noded-color-form: white;
|
||||||
--noded-background-form-hover: #F8DEB5;
|
--noded-background-form-hover: #F8DEB5;
|
||||||
|
|
||||||
|
--noded-color-danger: #dd2222
|
||||||
}
|
}
|
||||||
|
|
||||||
.noded-note {
|
.noded-note {
|
||||||
@ -91,6 +92,10 @@
|
|||||||
color: var(--noded-background-form);
|
color: var(--noded-background-form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.noded-danger {
|
||||||
|
color: var(--noded-color-danger);
|
||||||
|
}
|
||||||
|
|
||||||
div.picker-wrapper {
|
div.picker-wrapper {
|
||||||
border: 2px solid lightgrey !important;
|
border: 2px solid lightgrey !important;
|
||||||
border-radius: 7px !important;
|
border-radius: 7px !important;
|
||||||
@ -173,6 +178,13 @@ ionic-selectable-modal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-tall {
|
||||||
|
.modal-wrapper {
|
||||||
|
min-height: calc(100vh - 30px);
|
||||||
|
min-width: calc(100vw - 200px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.modal-med {
|
.modal-med {
|
||||||
.modal-wrapper {
|
.modal-wrapper {
|
||||||
min-height: calc(100vh - 100px);
|
min-height: calc(100vh - 100px);
|
||||||
|
@ -64,3 +64,4 @@ import 'zone.js/dist/zone'; // Included with Angular CLI.
|
|||||||
/***************************************************************************************************
|
/***************************************************************************************************
|
||||||
* APPLICATION IMPORTS
|
* APPLICATION IMPORTS
|
||||||
*/
|
*/
|
||||||
|
(window as any).global = window;
|
||||||
|
Loading…
Reference in New Issue
Block a user