diff --git a/src/app/components/editor/database/database.component.html b/src/app/components/editor/database/database.component.html
index b22834a..1da1a76 100644
--- a/src/app/components/editor/database/database.component.html
+++ b/src/app/components/editor/database/database.component.html
@@ -1,4 +1,4 @@
-
+
+
+ Sorry, this file group is not available offline yet.
+
diff --git a/src/app/components/editor/files/files.component.scss b/src/app/components/editor/files/files.component.scss
index 2a2d50b..72344ed 100644
--- a/src/app/components/editor/files/files.component.scss
+++ b/src/app/components/editor/files/files.component.scss
@@ -2,4 +2,11 @@ div.files-wrapper {
border: 2px solid #8c8c8c;
border-radius: 3px;
margin-top: 15px;
+
+ &.not-available {
+ height: 150px;
+ text-align: center;
+ padding-top: 40px;
+ color: #494949;
+ }
}
diff --git a/src/app/components/editor/files/files.component.ts b/src/app/components/editor/files/files.component.ts
index fc0ce56..234f724 100644
--- a/src/app/components/editor/files/files.component.ts
+++ b/src/app/components/editor/files/files.component.ts
@@ -1,8 +1,6 @@
-import {Component, ElementRef, EventEmitter, Inject, Input, OnInit, Output, ViewChild} from '@angular/core';
-import HostRecord from '../../../structures/HostRecord';
-import {ApiService} from '../../../service/api.service';
+import {Component, ElementRef, Inject, Input, OnInit, ViewChild} from '@angular/core';
+import {ApiService, ResourceNotAvailableOfflineError} from '../../../service/api.service';
import {AlertController} from '@ionic/angular';
-import {Observable} from 'rxjs';
import { APP_BASE_HREF } from '@angular/common';
import {EditorService} from '../../../service/editor.service';
import {EditorNodeContract} from '../../nodes/EditorNode.contract';
@@ -15,16 +13,12 @@ import {EditorNodeContract} from '../../nodes/EditorNode.contract';
export class FilesComponent extends EditorNodeContract implements OnInit {
@Input() nodeId: string;
@ViewChild('uploadForm') uploadForm: ElementRef;
- // @Input() readonly = false;
- // @Input() hostRecord: HostRecord;
- // @Output() hostRecordChange = new EventEmitter
();
- // @Output() requestParentSave = new EventEmitter();
- // @Output() requestParentDelete = new EventEmitter();
public fileRecords: Array = [];
public pendingSetup = true;
public dbRecord: any = {};
public dirty = false;
+ public notAvailableOffline = false;
public get readonly() {
return !this.node || !this.editorService.canEdit();
@@ -32,7 +26,6 @@ export class FilesComponent extends EditorNodeContract implements OnInit {
constructor(
protected api: ApiService,
- protected alerts: AlertController,
public readonly editorService: EditorService,
@Inject(APP_BASE_HREF) private baseHref: string
) { super(); }
@@ -55,45 +48,31 @@ export class FilesComponent extends EditorNodeContract implements OnInit {
}
if ( !this.node.Value.Value && !this.readonly ) {
- await new Promise((res, rej) => {
- this.api.post(`/files/${this.page.UUID}/${this.node.UUID}/create`).subscribe({
- next: result => {
- this.dbRecord = result.data;
- this.fileRecords = result.data.files;
- this.node.Value.Mode = 'files';
- this.node.Value.Value = result.data.UUID;
- this.node.value = result.data.UUID;
- this.dirty = true;
- res();
- },
- error: rej,
- });
- });
+ this.dbRecord = await this.api.createFileGroup(this.page.UUID, this.node.UUID);
+ this.fileRecords = this.dbRecord.files;
+ this.node.Value.Mode = 'files';
+ this.node.Value.Value = this.dbRecord.UUID;
+ this.node.value = this.dbRecord.UUID;
+ this.dirty = true;
} else {
- await new Promise((res, rej) => {
- this.api.get(`/files/${this.page.UUID}/${this.node.UUID}/get/${this.node.Value.Value}`).subscribe({
- next: result => {
- this.dbRecord = result.data;
- this.fileRecords = result.data.files;
- res();
- },
- error: rej,
- });
- });
+ try {
+ this.dbRecord = await this.api.getFileGroup(this.page.UUID, this.node.UUID, this.node.Value.Value);
+ this.fileRecords = this.dbRecord.files;
+ this.notAvailableOffline = false;
+ } catch (e) {
+ if ( e instanceof ResourceNotAvailableOfflineError ) {
+ this.notAvailableOffline = true;
+ } else {
+ throw e;
+ }
+ }
}
this.pendingSetup = false;
}
public async performDelete(): Promise {
- await new Promise((res, rej) => {
- this.api.post(`/files/${this.page.UUID}/${this.node.UUID}/delete/${this.node.Value.Value}`).subscribe({
- next: result => {
- res();
- },
- error: rej,
- });
- });
+ await this.api.deleteFileGroup(this.page.UUID, this.node.UUID, this.node.Value.Value);
}
ngOnInit() {
diff --git a/src/app/components/option-picker/option-picker.component.ts b/src/app/components/option-picker/option-picker.component.ts
index 0a072d4..179e1ec 100644
--- a/src/app/components/option-picker/option-picker.component.ts
+++ b/src/app/components/option-picker/option-picker.component.ts
@@ -2,6 +2,7 @@ import {Component, Input, OnInit} from '@angular/core';
import {Router} from '@angular/router';
import {ApiService} from '../../service/api.service';
import {PopoverController} from '@ionic/angular';
+import {DatabaseService} from "../../service/db/database.service";
@Component({
selector: 'app-option-picker',
@@ -17,14 +18,16 @@ export class OptionPickerComponent implements OnInit {
protected api: ApiService,
protected router: Router,
protected popover: PopoverController,
+ protected db: DatabaseService,
) { }
ngOnInit() {}
- onSelect(key) {
+ async onSelect(key) {
if ( key === 'html_export' ) {
window.open(this.api._build_url('/data/export/html'), '_blank');
} else if ( key === 'logout' ) {
+ await this.db.purge();
window.location.href = '/auth/logout';
} else if ( key === 'toggle_darkmode' ) {
this.toggleDark();
diff --git a/src/app/service/api.service.ts b/src/app/service/api.service.ts
index 2f89f4c..56e6439 100644
--- a/src/app/service/api.service.ts
+++ b/src/app/service/api.service.ts
@@ -10,6 +10,7 @@ import {Codium} from './db/Codium';
import {Database} from './db/Database';
import {DatabaseColumn} from './db/DatabaseColumn';
import {DatabaseEntry} from './db/DatabaseEntry';
+import {FileGroup} from "./db/FileGroup";
export class ResourceNotAvailableOfflineError extends Error {
constructor(msg = 'This resource is not yet available offline on this device.') {
@@ -703,4 +704,101 @@ export class ApiService {
});
});
}
+
+ public deleteFileGroup(PageId: string, NodeId: string, FileGroupId: string): Promise {
+ return new Promise(async (res, rej) => {
+ const existingFileGroup = await this.db.fileGroups.where({ UUID: FileGroupId }).first() as FileGroup;
+
+ if ( this.isOffline ) {
+ if ( existingFileGroup ) {
+ existingFileGroup.deleted = true;
+ existingFileGroup.needsServerUpdate = true;
+ await existingFileGroup.save();
+ return res();
+ } else {
+ return rej(new ResourceNotAvailableOfflineError());
+ }
+ }
+
+ this.post(`/files/${PageId}/${NodeId}/delete/${FileGroupId}`).subscribe({
+ next: async result => {
+ if ( existingFileGroup ) {
+ await this.db.fileGroups.delete(existingFileGroup.id);
+ res();
+ }
+ },
+ error: rej,
+ });
+ });
+ }
+
+ public createFileGroup(PageId: string, NodeId: string): Promise {
+ return new Promise(async (res, rej) => {
+ if ( this.isOffline ) {
+ const newFileGroup = new FileGroup(
+ NodeId,
+ PageId,
+ [],
+ JSON.stringify([]),
+ FileGroup.getUUID(),
+ true
+ );
+
+ await newFileGroup.save();
+ return res(newFileGroup.inflateToRecord());
+ }
+
+ this.post(`/files/${PageId}/${NodeId}/create`).subscribe({
+ next: async result => {
+ const newFileGroup = new FileGroup(
+ result.data.NodeId,
+ result.data.PageId,
+ result.data.FileIds,
+ JSON.stringify(result.data.files),
+ result.data.UUID
+ );
+
+ await newFileGroup.save();
+ res(result.data);
+ },
+ error: rej,
+ });
+ });
+ }
+
+ public getFileGroup(PageId: string, NodeId: string, FileGroupId: string): Promise {
+ return new Promise(async (res, rej) => {
+ const existingFileGroup = await this.db.fileGroups.where({ UUID: FileGroupId }).first() as FileGroup;
+ if ( this.isOffline ) {
+ if ( existingFileGroup ) {
+ return res(existingFileGroup.inflateToRecord());
+ } else {
+ return rej(new ResourceNotAvailableOfflineError());
+ }
+ }
+
+ this.get(`/files/${PageId}/${NodeId}/get/${FileGroupId}`).subscribe({
+ next: async result => {
+ if ( existingFileGroup ) {
+ existingFileGroup.fillFromRecord(result.data);
+ existingFileGroup.needsServerUpdate = false;
+ await existingFileGroup.save();
+ } else {
+ const newFileGroup = new FileGroup(
+ result.data.NodeId,
+ result.data.PageId,
+ result.data.FileIds,
+ JSON.stringify(result.data.files),
+ result.data.UUID
+ );
+
+ await newFileGroup.save();
+ }
+
+ res(result.data);
+ },
+ error: rej,
+ });
+ });
+ }
}
diff --git a/src/app/service/db/FileGroup.ts b/src/app/service/db/FileGroup.ts
new file mode 100644
index 0000000..e90b87d
--- /dev/null
+++ b/src/app/service/db/FileGroup.ts
@@ -0,0 +1,97 @@
+import {Model} from './Model';
+
+export interface IFileGroup {
+ id?: number;
+ NodeId: string;
+ PageId: string;
+ FileIds: string[];
+ filesJSON: string;
+ UUID: string;
+ needsServerUpdate?: boolean;
+ deleted?: boolean;
+}
+
+export class FileGroup extends Model implements IFileGroup {
+ id?: number;
+ NodeId: string;
+ PageId: string;
+ FileIds: string[];
+ filesJSON: string;
+ UUID: string;
+ needsServerUpdate?: boolean;
+ deleted?: boolean;
+
+ public static getTableName() {
+ return 'fileGroups';
+ }
+
+ public static getSchema() {
+ return '++id, NodeId, PageId, FileIds, filesJSON, UUID, needsServerUpdate, deleted';
+ }
+
+ constructor(
+ NodeId: string,
+ PageId: string,
+ FileIds: string[],
+ filesJSON: string,
+ UUID: string,
+ needsServerUpdate?: boolean,
+ deleted?: boolean,
+ id?: number
+ ) {
+ super();
+
+ this.NodeId = NodeId;
+ this.PageId = PageId;
+ this.FileIds = FileIds;
+ this.filesJSON = filesJSON;
+ this.UUID = UUID;
+
+ if ( typeof needsServerUpdate !== 'undefined' ) {
+ this.needsServerUpdate = needsServerUpdate;
+ }
+
+ if ( typeof deleted !== 'undefined' ) {
+ this.deleted = deleted;
+ }
+
+ if ( id ) {
+ this.id = id;
+ }
+ }
+
+ public fillFromRecord(record: any) {
+ this.NodeId = record.NodeId;
+ this.PageId = record.PageId;
+ this.FileIds = record.FileIds;
+ this.filesJSON = JSON.stringify(record.files);
+ this.UUID = record.UUID;
+ }
+
+ public inflateToRecord() {
+ return {
+ NodeId: this.NodeId,
+ PageId: this.PageId,
+ FileIds: this.FileIds,
+ files: JSON.parse(this.filesJSON),
+ UUID: this.UUID,
+ };
+ }
+
+ public getSaveRecord(): any {
+ return {
+ ...(this.id ? { id: this.id } : {}),
+ NodeId: this.NodeId,
+ PageId: this.PageId,
+ FileIds: this.FileIds,
+ filesJSON: this.filesJSON,
+ UUID: this.UUID,
+ ...(typeof this.needsServerUpdate === 'undefined' ? {} : { needsServerUpdate: this.needsServerUpdate }),
+ ...(typeof this.deleted === 'undefined' ? {} : { deleted: this.deleted }),
+ };
+ }
+
+ public getDatabase(): Dexie.Table {
+ return this.staticClass().dbService.table('fileGroups') as Dexie.Table;
+ }
+}
diff --git a/src/app/service/db/database.service.ts b/src/app/service/db/database.service.ts
index ac3c32e..21552c5 100644
--- a/src/app/service/db/database.service.ts
+++ b/src/app/service/db/database.service.ts
@@ -7,12 +7,13 @@ import {Codium, ICodium} from './Codium';
import {Database, IDatabase} from './Database';
import {DatabaseColumn, IDatabaseColumn} from './DatabaseColumn';
import {DatabaseEntry, IDatabaseEntry} from './DatabaseEntry';
+import {FileGroup, IFileGroup} from './FileGroup';
@Injectable({
providedIn: 'root'
})
export class DatabaseService extends Dexie {
- protected static registeredModels = [Migration, MenuItem, KeyValue, Codium, Database, DatabaseColumn, DatabaseEntry];
+ protected static registeredModels = [Migration, MenuItem, KeyValue, Codium, Database, DatabaseColumn, DatabaseEntry, FileGroup];
protected initialized = false;
migrations!: Dexie.Table;
@@ -22,6 +23,7 @@ export class DatabaseService extends Dexie {
databases!: Dexie.Table;
databaseColumns!: Dexie.Table;
databaseEntries!: Dexie.Table;
+ fileGroups!: Dexie.Table;
constructor(
) {
@@ -54,7 +56,7 @@ export class DatabaseService extends Dexie {
schema[ModelClass.getTableName()] = ModelClass.getSchema();
}
- await this.version(9).stores(schema);
+ await this.version(11).stores(schema);
await this.open();
this.migrations = this.table('migrations');
@@ -77,5 +79,23 @@ export class DatabaseService extends Dexie {
this.databaseEntries = this.table('databaseEntries');
this.databaseEntries.mapToClass(DatabaseEntry);
+
+ this.fileGroups = this.table('fileGroups');
+ this.fileGroups.mapToClass(FileGroup);
+ }
+
+ public async purge() {
+ console.warn('Purging all local data!');
+
+ await Promise.all([
+ this.migrations.clear(),
+ this.menuItems.clear(),
+ this.keyValues.clear(),
+ this.codiums.clear(),
+ this.databases.clear(),
+ this.databaseColumns.clear(),
+ this.databaseEntries.clear(),
+ this.fileGroups.clear(),
+ ]);
}
}