You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
825 lines
23 KiB
825 lines
23 KiB
import {Component, OnInit, ViewChild, HostListener} from '@angular/core';
|
|
|
|
import {
|
|
AlertController,
|
|
ModalController,
|
|
Platform,
|
|
PopoverController,
|
|
LoadingController,
|
|
ToastController, NavController
|
|
} 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, fromEvent, 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';
|
|
import {NodeTypeIcons} from './structures/node-types';
|
|
import {NavigationService} from './service/navigation.service';
|
|
import {DatabaseService} from './service/db/database.service';
|
|
import {EditorService} from './service/editor.service';
|
|
import {debug} from './utility';
|
|
import {AuthService} from './service/auth.service';
|
|
import {OpenerService} from './service/opener.service';
|
|
|
|
@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 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 get appName(): string {
|
|
return this.session.appName || 'Noded';
|
|
}
|
|
|
|
public darkMode = false;
|
|
protected loader?: any;
|
|
protected hasSearchOpen = false;
|
|
protected versionInterval?: any;
|
|
protected showedNewVersionAlert = false;
|
|
protected showedOfflineAlert = false;
|
|
protected backbuttonSubscription: any;
|
|
|
|
constructor(
|
|
private platform: Platform,
|
|
private splashScreen: SplashScreen,
|
|
private statusBar: StatusBar,
|
|
public readonly api: ApiService,
|
|
protected navCtrl: NavController,
|
|
protected router: Router,
|
|
protected alerts: AlertController,
|
|
protected popover: PopoverController,
|
|
protected modal: ModalController,
|
|
protected session: SessionService,
|
|
protected loading: LoadingController,
|
|
protected navService: NavigationService,
|
|
protected toasts: ToastController,
|
|
protected db: DatabaseService,
|
|
protected editor: EditorService,
|
|
protected auth: AuthService,
|
|
protected opener: OpenerService,
|
|
) { }
|
|
|
|
async checkNewVersion() {
|
|
if ( !this.showedNewVersionAlert && await this.session.newVersionAvailable() ) {
|
|
const toast = await this.toasts.create({
|
|
cssClass: 'compat-toast-container',
|
|
header: 'Update Available',
|
|
message: `A new version of ${this.appName} is available. Please refresh to update.`,
|
|
buttons: [
|
|
{
|
|
side: 'end',
|
|
text: 'Refresh',
|
|
handler: () => {
|
|
window.location.reload();
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
this.showedNewVersionAlert = true;
|
|
await toast.present();
|
|
}
|
|
}
|
|
|
|
ngOnInit() {
|
|
debug('Initializing application.');
|
|
this.initializeApp();
|
|
}
|
|
|
|
@HostListener('window:popstate', ['$event'])
|
|
dismissModal(event) {
|
|
const modal = this.modal.getTop();
|
|
|
|
if ( modal ) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
this.modal.dismiss();
|
|
}
|
|
}
|
|
|
|
showOptions($event) {
|
|
this.popover.create({
|
|
event: $event,
|
|
component: OptionPickerComponent,
|
|
componentProps: {
|
|
toggleDark: () => this.toggleDark(),
|
|
isDark: () => this.isDark(),
|
|
showSearch: () => this.handleKeyboardEvent(),
|
|
isPrefetch: () => this.isPrefetch(),
|
|
togglePrefetch: () => this.togglePrefetch(),
|
|
doPrefetch: () => this.doPrefetch(),
|
|
}
|
|
}).then(popover => popover.present());
|
|
}
|
|
|
|
@HostListener('document:keyup.control./', ['$event'])
|
|
async handleKeyboardEvent() {
|
|
if ( this.hasSearchOpen ) {
|
|
return;
|
|
}
|
|
|
|
const modal = await this.modal.create({
|
|
component: SearchComponent,
|
|
cssClass: 'modal-med',
|
|
});
|
|
|
|
const modalState = {
|
|
modal : true,
|
|
desc : 'Search everything'
|
|
};
|
|
|
|
history.pushState(modalState, null);
|
|
|
|
this.hasSearchOpen = true;
|
|
await modal.present();
|
|
|
|
await modal.onDidDismiss();
|
|
this.hasSearchOpen = false;
|
|
}
|
|
|
|
public navigateEditorToNode(node: any) {
|
|
if ( !node.data ) {
|
|
node = { data: node };
|
|
}
|
|
|
|
const id = node.data.id;
|
|
const nodeId = node.data.node_id;
|
|
if ( !node.data.virtual ) {
|
|
debug('Navigating editor to node:', {id, nodeId});
|
|
|
|
this.opener.currentPageId = id;
|
|
this.opener.openTarget(id, nodeId);
|
|
}
|
|
}
|
|
|
|
async onNodeMenuClick($event) {
|
|
let canManage = this.menuTarget.data.level === 'manage';
|
|
if ( !canManage ) {
|
|
if ( !this.menuTarget.data.level ) {
|
|
canManage = true;
|
|
}
|
|
}
|
|
|
|
if ( !this.menuTarget.data.id ) {
|
|
return;
|
|
}
|
|
|
|
const options = [
|
|
{name: 'Make Virtual Root', icon: 'fa fa-search-plus', value: 'virtual_root'},
|
|
{name: 'Export to HTML', icon: 'fa fa-file-export', value: 'export_html'},
|
|
// {name: 'Export as PDF', icon: 'fa fa-file-export', value: 'export_pdf'},
|
|
];
|
|
|
|
const manageOptions = [
|
|
{name: 'Share Sub-Tree', icon: 'fa fa-share-alt', value: 'share'},
|
|
];
|
|
|
|
if ( this.menuTarget.data.bookmark ) {
|
|
options.push({name: 'Remove Bookmark', icon: 'fa fa-star', value: 'bookmark_remove'});
|
|
} else {
|
|
options.push({name: 'Bookmark', icon: 'fa fa-star', value: 'bookmark_add'});
|
|
}
|
|
|
|
const popover = await this.popover.create({
|
|
component: OptionMenuComponent,
|
|
componentProps: {
|
|
menuItems: [
|
|
...(!canManage ? options : [...options, ...manageOptions]),
|
|
],
|
|
},
|
|
event: $event,
|
|
});
|
|
|
|
popover.onDidDismiss().then((result) => {
|
|
if ( result.data === 'share' ) {
|
|
this.modal.create({
|
|
component: SelectorComponent,
|
|
cssClass: 'modal-med',
|
|
componentProps: {
|
|
node: this.menuTarget.data,
|
|
}
|
|
}).then(modal => {
|
|
const modalState = {
|
|
modal : true,
|
|
desc : 'Share page'
|
|
};
|
|
|
|
history.pushState(modalState, null);
|
|
|
|
modal.present();
|
|
});
|
|
} else if ( result.data === 'export_html' ) {
|
|
this.exportTargetAsHTML();
|
|
} else if ( result.data === 'export_pdf' ) {
|
|
// this.exportTargetAsPDF();
|
|
} else if ( result.data === 'virtual_root' ) {
|
|
this.setVirtualRoot();
|
|
} else if ( result.data === 'bookmark_add' ) {
|
|
this.addBookmark();
|
|
} else if ( result.data === 'bookmark_remove' ) {
|
|
this.removeBookmark();
|
|
}
|
|
});
|
|
|
|
await popover.present();
|
|
}
|
|
|
|
async setVirtualRoot() {
|
|
if ( this.menuTarget && this.menuTarget.data?.type === 'page' ) {
|
|
debug('virtual root menu target', this.menuTarget);
|
|
this.virtualRootPageId = this.menuTarget.data.id;
|
|
this.reloadMenuItems().subscribe();
|
|
}
|
|
}
|
|
|
|
onVirtualRootClear(event) {
|
|
delete this.virtualRootPageId;
|
|
this.reloadMenuItems().subscribe();
|
|
}
|
|
|
|
async exportTargetAsHTML() {
|
|
const exportRecord: any = await new Promise((res, rej) => {
|
|
const reqData = {
|
|
format: 'html',
|
|
PageId: this.menuTarget.data.id,
|
|
};
|
|
|
|
this.api.post(`/exports/subtree`, reqData).subscribe({
|
|
next: (result) => {
|
|
res(result.data);
|
|
},
|
|
error: rej
|
|
});
|
|
});
|
|
|
|
const dlUrl = this.api._build_url(`/exports/${exportRecord.UUID}/download`);
|
|
window.open(dlUrl, '_blank');
|
|
}
|
|
|
|
addBookmark() {
|
|
const bookmarks = this.session.get('user.preferences.bookmark_page_ids') || [];
|
|
|
|
if ( !bookmarks.includes(this.menuTarget.data.id) ) {
|
|
bookmarks.push(this.menuTarget.data.id);
|
|
}
|
|
|
|
this.session.set('user.preferences.bookmark_page_ids', bookmarks);
|
|
this.session.save().then(() => this.navService.requestSidebarRefresh({ quiet: true }));
|
|
}
|
|
|
|
removeBookmark() {
|
|
let bookmarks = this.session.get('user.preferences.bookmark_page_ids') || [];
|
|
bookmarks = bookmarks.filter(x => x !== this.menuTarget.data.id);
|
|
|
|
this.session.set('user.preferences.bookmark_page_ids', bookmarks);
|
|
this.session.save().then(() => this.navService.requestSidebarRefresh({ quiet: true }));
|
|
}
|
|
|
|
async onCreateClick($event: MouseEvent) {
|
|
const menuItems = [
|
|
{
|
|
name: 'Top-Level Note',
|
|
icon: 'fa fa-sticky-note noded-note',
|
|
value: 'top-level',
|
|
title: 'Create a new top-level note page',
|
|
},
|
|
...(this.addChildTarget ? [
|
|
{
|
|
name: 'Child Note',
|
|
icon: 'fa fa-sticky-note noded-note',
|
|
value: 'child',
|
|
title: 'Create a note page as a child of the given note',
|
|
},
|
|
{
|
|
name: 'Form',
|
|
icon: 'fa fa-clipboard-list noded-form',
|
|
value: 'form',
|
|
title: 'Create a new form page as a child of the given note',
|
|
},
|
|
] : []),
|
|
];
|
|
|
|
const popover = await this.popover.create({
|
|
event: $event,
|
|
component: OptionMenuComponent,
|
|
componentProps: {
|
|
menuItems,
|
|
},
|
|
});
|
|
|
|
popover.onDidDismiss().then(({ data: value }) => {
|
|
if ( value === 'top-level' ) {
|
|
this.onTopLevelCreate();
|
|
} else if ( value === 'child' ) {
|
|
this.onChildCreate();
|
|
} else if ( value === 'form' ) {
|
|
this.onChildCreate('form');
|
|
}
|
|
});
|
|
|
|
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 => {
|
|
const page = await this.editor.createPage(args.name);
|
|
this.reloadMenuItems().subscribe();
|
|
await this.router.navigate(['/editor', { id: page.UUID }]);
|
|
}
|
|
}
|
|
]
|
|
});
|
|
|
|
await alert.present();
|
|
}
|
|
|
|
async onChildCreate(pageType?: string) {
|
|
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,
|
|
pageType,
|
|
};
|
|
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.opener.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(quiet = false) {
|
|
if ( !quiet ) {
|
|
this.refreshingMenu = true;
|
|
}
|
|
|
|
this.reloadMenuItems().subscribe();
|
|
|
|
setTimeout(() => {
|
|
if ( !quiet ) {
|
|
this.refreshingMenu = false;
|
|
}
|
|
}, 2000);
|
|
}
|
|
|
|
reloadMenuItems() {
|
|
return new Observable(sub => {
|
|
this.api.getMenuItems(false, this.virtualRootPageId).then(nodes => {
|
|
this.nodes = nodes;
|
|
sub.next();
|
|
sub.complete();
|
|
});
|
|
});
|
|
}
|
|
|
|
async initializeApp() {
|
|
const initializedOnce = this.navService.initialized$.getValue();
|
|
|
|
debug('app', this);
|
|
this.loader = await this.loading.create({
|
|
message: 'Setting things up...',
|
|
cssClass: 'noded-loading-mask',
|
|
showBackdrop: true,
|
|
});
|
|
|
|
debug('Initializing platform and database...');
|
|
await this.loader.present();
|
|
await this.platform.ready();
|
|
await this.db.createSchemata();
|
|
|
|
let toast: any;
|
|
|
|
if ( !initializedOnce ) {
|
|
debug('Subscribing to offline changes...');
|
|
this.api.offline$.subscribe(async isOffline => {
|
|
if ( isOffline && !this.showedOfflineAlert ) {
|
|
debug('Application went offline!');
|
|
toast = await this.toasts.create({
|
|
cssClass: 'compat-toast-container',
|
|
message: 'Uh, oh! It looks like you\'re offline. Some features might not work as expected...',
|
|
});
|
|
|
|
this.showedOfflineAlert = true;
|
|
await toast.present();
|
|
} else if ( !isOffline && this.showedOfflineAlert ) {
|
|
debug('Appliation went online!');
|
|
await toast.dismiss();
|
|
this.showedOfflineAlert = false;
|
|
await this.api.syncOfflineData();
|
|
}
|
|
});
|
|
}
|
|
|
|
debug('Getting initial status...');
|
|
let stat: any = await this.session.stat();
|
|
debug('Got stat:', stat);
|
|
|
|
this.api.isPublicUser = !!stat.public_user;
|
|
this.api.isAuthenticated = !!stat.authenticated_user;
|
|
this.api.systemBase = stat.system_base;
|
|
|
|
if ( !this.api.isAuthenticated || this.api.isPublicUser ) {
|
|
debug('Unauthenticated or public user...');
|
|
if ( !this.api.isOffline ) {
|
|
debug('Trying to resume session...');
|
|
await this.api.resumeSession();
|
|
debug('Checking new status...');
|
|
stat = await this.session.stat();
|
|
debug('Got session resume stat:', stat);
|
|
|
|
this.api.isAuthenticated = stat.authenticated_user;
|
|
this.api.isPublicUser = stat.public_user;
|
|
|
|
if ( !stat.authenticated_user ) {
|
|
debug('Not authenticated! Redirecting.');
|
|
window.location.href = `${stat.system_base}start`;
|
|
return;
|
|
}
|
|
} else {
|
|
debug('Unauthenticated offline user. Purging local data!');
|
|
await this.db.purge();
|
|
window.location.href = `${stat.system_base}start`;
|
|
return;
|
|
}
|
|
}
|
|
|
|
debug('Set app name and system base:', stat.app_name, stat.system_base);
|
|
this.session.appName = stat.app_name;
|
|
this.session.systemBase = stat.system_base;
|
|
|
|
debug('Initializing session...');
|
|
await this.session.initialize();
|
|
|
|
if ( this.session.get('user.preferences.dark_mode') && !this.darkMode ) {
|
|
this.toggleDark();
|
|
}
|
|
|
|
debug('Hiding native splash screen & setting status bar styles...');
|
|
await this.statusBar.styleDefault();
|
|
await this.splashScreen.hide();
|
|
|
|
// If we went online after being offline, sync the local data
|
|
if ( !this.api.isOffline && await this.api.needsSync() ) {
|
|
this.loader.message = 'Syncing data...';
|
|
try {
|
|
await this.api.syncOfflineData();
|
|
} catch (e) {
|
|
this.toasts.create({
|
|
cssClass: 'compat-toast-container',
|
|
message: 'An error occurred while syncing offline data. Not all data was saved.',
|
|
buttons: [
|
|
'Okay'
|
|
],
|
|
}).then(tst => {
|
|
tst.present();
|
|
});
|
|
}
|
|
}
|
|
|
|
if ( this.isPrefetch() && !this.api.isPublicUser ) {
|
|
debug('Pre-fetching offline data...');
|
|
this.loader.message = 'Downloading data...';
|
|
try {
|
|
await this.api.prefetchOfflineData();
|
|
} catch (e) {
|
|
debug('Pre-fetch error:', e);
|
|
this.toasts.create({
|
|
cssClass: 'compat-toast-container',
|
|
message: 'An error occurred while pre-fetching offline data. Not all data was saved.',
|
|
buttons: [
|
|
'Okay'
|
|
],
|
|
}).then(tst => {
|
|
tst.present();
|
|
});
|
|
}
|
|
}
|
|
|
|
this.navService.initialized$.next(true);
|
|
|
|
if ( !this.api.isPublicUser && this.session.get('user.preferences.default_page') ) {
|
|
debug('Navigating to default page!');
|
|
const id = this.session.get('user.preferences.default_page');
|
|
const node = this.findNode(id);
|
|
if ( node ) {
|
|
this.navigateEditorToNode(node);
|
|
} else if ( this.auth.authInProgress ) {
|
|
await this.router.navigate(['/home']);
|
|
}
|
|
} else if ( this.auth.authInProgress ) {
|
|
await this.router.navigate(['/home']);
|
|
}
|
|
|
|
if ( !initializedOnce ) {
|
|
debug('Creating menu subscription...');
|
|
this.navService.sidebarRefresh$.subscribe(([_, quiet]) => {
|
|
this.onMenuRefresh(quiet);
|
|
});
|
|
|
|
this.navService.navigationRequest$.subscribe(pageId => {
|
|
debug('Page navigation request: ', {pageId});
|
|
if ( !pageId ) {
|
|
debug('Empty page ID. Will not navigate.');
|
|
return;
|
|
}
|
|
|
|
this.opener.currentPageId = pageId;
|
|
this.router.navigate(['/editor', { id: pageId }]);
|
|
});
|
|
|
|
this.navService.initializationRequest$.subscribe((count) => {
|
|
if ( count === 0 ) {
|
|
return;
|
|
}
|
|
|
|
this.initializeApp();
|
|
});
|
|
}
|
|
|
|
debug('Reloading menu items...');
|
|
this.reloadMenuItems().subscribe(() => {
|
|
debug('Reloaded menu items. Displaying interface.');
|
|
this.ready$.next(true);
|
|
|
|
setTimeout(() => {
|
|
this.loader.dismiss();
|
|
this.menuTree?.treeModel?.expandAll();
|
|
}, 10);
|
|
|
|
if ( !this.versionInterval ) {
|
|
this.versionInterval = setInterval(() => {
|
|
debug('Checking for new application version.');
|
|
this.checkNewVersion();
|
|
}, 1000 * 60 * 5); // Check for new version every 5 mins
|
|
}
|
|
});
|
|
|
|
this.auth.authInProgress = false;
|
|
}
|
|
|
|
async doPrefetch() {
|
|
if ( this.api.isOffline ) {
|
|
return;
|
|
}
|
|
|
|
this.loader = await this.loading.create({
|
|
message: 'Pre-fetching data...',
|
|
cssClass: 'noded-loading-mask',
|
|
showBackdrop: true,
|
|
});
|
|
|
|
await new Promise(res => setTimeout(res, 2000));
|
|
|
|
await this.loader.present();
|
|
|
|
try {
|
|
if (await this.api.needsSync()) {
|
|
this.loader.message = 'Syncing data...';
|
|
await this.api.syncOfflineData();
|
|
}
|
|
|
|
this.loader.message = 'Downloading data...';
|
|
await this.api.prefetchOfflineData();
|
|
} catch (e) {
|
|
const msg = await this.alerts.create({
|
|
header: 'Uh, oh!',
|
|
message: 'An unexpected error occurred while trying to sync offline data, and we were unable to continue.',
|
|
buttons: [
|
|
'OK',
|
|
],
|
|
});
|
|
|
|
await msg.present();
|
|
}
|
|
|
|
this.loader.dismiss();
|
|
}
|
|
|
|
toggleDark() {
|
|
// const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
|
|
this.darkMode = !this.darkMode;
|
|
this.session.set('user.preferences.dark_mode', this.darkMode);
|
|
document.body.classList.toggle('dark', this.darkMode);
|
|
}
|
|
|
|
togglePrefetch() {
|
|
this.session.set('user.preferences.auto_prefetch', !this.isPrefetch());
|
|
}
|
|
|
|
findNode(id: string, nodes = this.nodes) {
|
|
for ( const node of nodes ) {
|
|
if ( node.id === id ) {
|
|
return node;
|
|
}
|
|
|
|
if ( node.children ) {
|
|
const foundNode = this.findNode(id, node.children);
|
|
if ( foundNode ) {
|
|
return foundNode;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
isDark() {
|
|
return !!this.darkMode;
|
|
}
|
|
|
|
isPrefetch() {
|
|
return !!this.session.get('user.preferences.auto_prefetch');
|
|
}
|
|
|
|
async onTreeNodeMove({ node, to }) {
|
|
if ( this.api.isOffline ) {
|
|
debug('Cannot move node. API is offline.');
|
|
return;
|
|
}
|
|
|
|
const { parent } = to;
|
|
debug('Moving node:', { node, parent });
|
|
|
|
try {
|
|
await this.api.moveMenuNode(node.id, to.parent.id);
|
|
} catch (error) {
|
|
console.error('Error moving tree node:', error);
|
|
this.alerts.create({
|
|
header: 'Error Moving Node',
|
|
message: error.message,
|
|
buttons: [
|
|
{
|
|
text: 'Okay',
|
|
role: 'cancel',
|
|
},
|
|
],
|
|
}).then(x => x.present());
|
|
}
|
|
|
|
await this.reloadMenuItems().toPromise();
|
|
}
|
|
}
|