diff --git a/src/app/components/editor/code/code.component.html b/src/app/components/editor/code/code.component.html
index 7db4cd6..d36d71e 100644
--- a/src/app/components/editor/code/code.component.html
+++ b/src/app/components/editor/code/code.component.html
@@ -1,4 +1,4 @@
-
+
Language
@@ -16,3 +16,6 @@
>
+
+ Sorry, this code editor is not available offline yet.
+
diff --git a/src/app/components/editor/code/code.component.scss b/src/app/components/editor/code/code.component.scss
index ef5db85..19ad2d2 100644
--- a/src/app/components/editor/code/code.component.scss
+++ b/src/app/components/editor/code/code.component.scss
@@ -1,4 +1,10 @@
div.code-wrapper {
border: 2px solid #8c8c8c;
border-radius: 3px;
+
+ &.not-offline {
+ text-align: center;
+ padding-top: 100px;
+ color: #595959;
+ }
}
diff --git a/src/app/components/editor/code/code.component.ts b/src/app/components/editor/code/code.component.ts
index 4d559f5..9f7cb3a 100644
--- a/src/app/components/editor/code/code.component.ts
+++ b/src/app/components/editor/code/code.component.ts
@@ -1,6 +1,6 @@
import {Component, Input, OnInit} from '@angular/core';
import {v4} from 'uuid';
-import {ApiService} from '../../../service/api.service';
+import {ApiService, ResourceNotAvailableOfflineError} from '../../../service/api.service';
import {EditorNodeContract} from '../../nodes/EditorNode.contract';
import {EditorService} from '../../../service/editor.service';
@@ -14,6 +14,7 @@ export class CodeComponent extends EditorNodeContract implements OnInit {
public dirty = false;
protected dbRecord: any = {};
protected codeRefId!: string;
+ public notAvailableOffline = false;
public editorOptions = {
language: 'javascript',
@@ -123,34 +124,36 @@ export class CodeComponent extends EditorNodeContract implements OnInit {
}
if ( !this.node.Value.Value && this.editorService.canEdit() ) {
- this.api.post(`/code/${this.page.UUID}/${this.node.UUID}/create`).subscribe({
- next: result => {
- this.dbRecord = result.data;
- this.node.Value.Mode = 'code';
- this.node.Value.Value = result.data.UUID;
- this.node.value = result.data.UUID;
- this.codeRefId = result.data.UUID;
- this.editorOptions.readOnly = this.readonly;
- this.onSelectChange(false);
- this.hadLoad = true;
- res();
- },
- error: rej,
- });
+ this.api.createCodium(this.page.UUID, this.node.UUID).then(data => {
+ this.dbRecord = data;
+ this.node.Value.Mode = 'code';
+ this.node.Value.Value = data.UUID;
+ this.node.value = data.UUID;
+ this.codeRefId = data.UUID;
+ this.editorOptions.readOnly = this.readonly;
+ this.onSelectChange(false);
+ this.hadLoad = true;
+ this.notAvailableOffline = false;
+ res();
+ }).catch(rej);
} else {
- this.api.get(`/code/${this.page.UUID}/${this.node.UUID}/get/${this.node.Value.Value}`).subscribe({
- next: result => {
- this.dbRecord = result.data;
- this.initialValue = this.dbRecord.code;
- this.editorValue = this.dbRecord.code;
- this.editorOptions.language = this.dbRecord.Language;
- this.codeRefId = this.node.Value.Value;
- this.editorOptions.readOnly = this.readonly;
- this.onSelectChange(false);
- this.hadLoad = true;
- res();
- },
- error: rej,
+ this.api.getCodium(this.page.UUID, this.node.UUID, this.node.Value.Value).then(data => {
+ this.dbRecord = data;
+ this.initialValue = this.dbRecord.code;
+ this.editorValue = this.dbRecord.code;
+ this.editorOptions.language = this.dbRecord.Language;
+ this.codeRefId = this.node.Value.Value;
+ this.editorOptions.readOnly = this.readonly;
+ this.onSelectChange(false);
+ this.hadLoad = true;
+ this.notAvailableOffline = false;
+ res();
+ }).catch(e => {
+ if ( e instanceof ResourceNotAvailableOfflineError ) {
+ this.notAvailableOffline = true;
+ } else {
+ rej(e);
+ }
});
}
});
@@ -165,29 +168,18 @@ export class CodeComponent extends EditorNodeContract implements OnInit {
this.dbRecord.code = this.editorValue;
this.dbRecord.Language = this.editorOptions.language;
- this.api.post(`/code/${this.page.UUID}/${this.node.UUID}/set/${this.node.Value.Value}`, this.dbRecord)
- .subscribe({
- next: result => {
- this.dbRecord = result.data;
- this.editorOptions.language = this.dbRecord.Language;
- this.editorValue = this.dbRecord.code;
- this.dirty = false;
- res();
- },
- error: rej,
- });
+ this.api.saveCodium(this.page.UUID, this.node.UUID, this.node.Value.Value, this.dbRecord).then(data => {
+ this.dbRecord = data;
+ this.editorOptions.language = this.dbRecord.Language;
+ this.editorValue = this.dbRecord.code;
+ this.dirty = false;
+ res();
+ }).catch(rej);
});
}
public performDelete(): void | Promise {
- return new Promise((res, rej) => {
- this.api.post(`/code/${this.page.UUID}/${this.node.UUID}/delete/${this.node.Value.Value}`).subscribe({
- next: result => {
- res();
- },
- error: rej,
- });
- });
+ return this.api.deleteCodium(this.page.UUID, this.node.UUID, this.node.Value.Value);
}
ngOnInit() {
diff --git a/src/app/service/api.service.ts b/src/app/service/api.service.ts
index 78f2ca6..8a9536a 100644
--- a/src/app/service/api.service.ts
+++ b/src/app/service/api.service.ts
@@ -6,6 +6,13 @@ import ApiResponse from '../structures/ApiResponse';
import {MenuItem} from './db/MenuItem';
import {DatabaseService} from './db/database.service';
import {ConnectionService} from 'ng-connection-service';
+import {Codium} from './db/Codium';
+
+export class ResourceNotAvailableOfflineError extends Error {
+ constructor(msg = 'This resource is not yet available offline on this device.') {
+ super(msg);
+ }
+}
@Injectable({
providedIn: 'root'
@@ -221,4 +228,163 @@ export class ApiService {
res();
});
}
+
+ public deleteCodium(PageId: string, NodeId: string, CodiumId: string): Promise {
+ return new Promise(async (res, rej) => {
+ const existingLocalCodiums = await this.db.codiums.where({ UUID: CodiumId }).toArray();
+ const existingLocalCodium = existingLocalCodiums.length > 0 ? existingLocalCodiums[0] as Codium : undefined;
+
+ if ( this.isOffline ) {
+ if ( existingLocalCodium ) {
+ existingLocalCodium.deleted = true;
+ existingLocalCodium.needsServerUpdate = true;
+ await existingLocalCodium.save();
+ return res();
+ } else {
+ return rej(new ResourceNotAvailableOfflineError());
+ }
+ }
+
+ this.post(`/code/${PageId}/${NodeId}/delete/${CodiumId}`).subscribe({
+ next: async result => {
+ if ( existingLocalCodium ) {
+ await this.db.codiums.delete(existingLocalCodium.id);
+ }
+ res();
+ },
+ error: rej,
+ });
+ });
+ }
+
+ public saveCodium(PageId: string, NodeId: string, CodiumId: string, data: any): Promise {
+ return new Promise(async (res, rej) => {
+ const existingLocalCodiums = await this.db.codiums.where({ UUID: CodiumId }).toArray();
+ const existingLocalCodium = existingLocalCodiums.length > 0 ? existingLocalCodiums[0] as Codium : undefined;
+
+ // If we're offline, update or create the local record
+ if ( this.isOffline ) {
+ if ( existingLocalCodium ) {
+ existingLocalCodium.fillFromRecord(data);
+ existingLocalCodium.needsServerUpdate = true;
+
+ await existingLocalCodium.save();
+ return res(existingLocalCodium.getSaveRecord());
+ } else {
+ const newLocalCodium = new Codium(
+ data.Language,
+ NodeId,
+ PageId,
+ data.code,
+ data.UUID || Codium.getUUID(),
+ true,
+ );
+
+ await newLocalCodium.save();
+ return res(newLocalCodium.getSaveRecord());
+ }
+ }
+
+ // If we're online, save the data and update our local records
+ this.post(`/code/${PageId}/${NodeId}/set/${CodiumId}`, data).subscribe({
+ next: async result => {
+ if ( existingLocalCodium ) {
+ existingLocalCodium.fillFromRecord(result.data);
+ existingLocalCodium.needsServerUpdate = false;
+
+ await existingLocalCodium.save();
+ return res(result.data);
+ } else {
+ const newLocalCodium = new Codium(
+ result.data.Language,
+ result.data.NodeId,
+ result.data.PageId,
+ result.data.code,
+ result.data.UUID,
+ );
+
+ await newLocalCodium.save();
+ return res(result.data);
+ }
+ },
+ error: rej,
+ });
+ });
+ }
+
+ public createCodium(PageId: string, NodeId: string): Promise {
+ return new Promise(async (res, rej) => {
+ // If offline, create a new local DB record
+ if ( this.isOffline ) {
+ const newLocalCodium = new Codium(
+ 'javascript',
+ NodeId,
+ PageId,
+ '',
+ Codium.getUUID(),
+ true
+ );
+
+ await newLocalCodium.save();
+ return res(newLocalCodium.getSaveRecord());
+ }
+
+ // If online, create a new record on the server and sync it to the local db
+ this.post(`/code/${PageId}/${NodeId}/create`).subscribe({
+ next: async result => {
+ const newLocalCodium = new Codium(
+ result.data.Language,
+ result.data.NodeId,
+ result.data.PageId,
+ result.data.code,
+ result.data.UUID,
+ );
+
+ await newLocalCodium.save();
+ res(result.data);
+ },
+ error: rej,
+ });
+ });
+ }
+
+ public getCodium(PageId: string, NodeId: string, CodiumId: string): Promise {
+ return new Promise(async (res, rej) => {
+ const existingLocalCodiums = await this.db.codiums.where({ UUID: CodiumId }).toArray();
+ const existingLocalCodium = existingLocalCodiums.length > 0 ? existingLocalCodiums[0] as Codium : undefined;
+
+ // If offline, try to load it from the local DB
+ if ( this.isOffline ) {
+ if ( existingLocalCodium ) {
+ return res(existingLocalCodium.getSaveRecord());
+ } else {
+ return rej(new ResourceNotAvailableOfflineError());
+ }
+ }
+
+ // If online, fetch the codium and store/update it locally
+ this.get(`/code/${PageId}/${NodeId}/get/${CodiumId}`).subscribe({
+ next: async result => {
+ if ( existingLocalCodium ) {
+ existingLocalCodium.fillFromRecord(result.data);
+
+ await existingLocalCodium.save();
+ return res(result.data);
+ } else {
+ const newLocalCodium = new Codium(
+ result.data.Language,
+ result.data.NodeId,
+ result.data.PageId,
+ result.data.code,
+ result.data.UUID
+ );
+
+ await newLocalCodium.save();
+ return res(result.data);
+ }
+ },
+ error: rej,
+ });
+ });
+ }
}
diff --git a/src/app/service/db/Codium.ts b/src/app/service/db/Codium.ts
new file mode 100644
index 0000000..f18cc0e
--- /dev/null
+++ b/src/app/service/db/Codium.ts
@@ -0,0 +1,87 @@
+import {Model} from './Model';
+
+export interface ICodium {
+ id?: number;
+ Language: string;
+ NodeId: string;
+ PageId: string;
+ code: string;
+ UUID: string;
+ needsServerUpdate?: boolean;
+ deleted?: boolean;
+}
+
+export class Codium extends Model implements ICodium {
+ id?: number;
+ Language: string;
+ NodeId: string;
+ PageId: string;
+ code: string;
+ UUID: string;
+ needsServerUpdate?: boolean;
+ deleted?: boolean;
+
+ public static getTableName() {
+ return 'codiums';
+ }
+
+ public static getSchema() {
+ return '++id, Language, NodeId, PageId, code, UUID, needsServerUpdate, deleted';
+ }
+
+ constructor(
+ Language: string,
+ NodeId: string,
+ PageId: string,
+ code: string,
+ UUID: string,
+ needsServerUpdate?: boolean,
+ deleted?: boolean,
+ id?: number
+ ) {
+ super();
+
+ this.Language = Language;
+ this.NodeId = NodeId;
+ this.PageId = PageId;
+ this.code = code;
+ 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.Language = record.Language;
+ this.NodeId = record.NodeId;
+ this.PageId = record.PageId;
+ this.code = record.code;
+ this.UUID = record.UUID;
+ }
+
+ public getSaveRecord(): any {
+ return {
+ ...(this.id ? { id: this.id } : {}),
+ Language: this.Language,
+ NodeId: this.NodeId,
+ PageId: this.PageId,
+ code: this.code,
+ 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('codiums') as Dexie.Table;
+ }
+}
diff --git a/src/app/service/db/Model.ts b/src/app/service/db/Model.ts
index f7bbb55..2f1f5fe 100644
--- a/src/app/service/db/Model.ts
+++ b/src/app/service/db/Model.ts
@@ -1,5 +1,6 @@
import Dexie from 'dexie';
import {DatabaseService} from './database.service';
+import {uuid_v4} from '../../utility';
export abstract class Model {
public static dbService?: DatabaseService;
@@ -14,6 +15,10 @@ export abstract class Model {
throw new TypeError('Child class must implement.');
}
+ public static getUUID(): string {
+ return uuid_v4();
+ }
+
public abstract getDatabase(): Dexie.Table;
public abstract getSaveRecord(): any;
diff --git a/src/app/service/db/database.service.ts b/src/app/service/db/database.service.ts
index 8e69950..c26f3ca 100644
--- a/src/app/service/db/database.service.ts
+++ b/src/app/service/db/database.service.ts
@@ -3,17 +3,19 @@ import Dexie from 'dexie';
import {IMigration, Migration} from './Migration';
import {IMenuItem, MenuItem} from './MenuItem';
import {KeyValue, IKeyValue} from './KeyValue';
+import {Codium, ICodium} from './Codium';
@Injectable({
providedIn: 'root'
})
export class DatabaseService extends Dexie {
- protected static registeredModels = [Migration, MenuItem, KeyValue];
+ protected static registeredModels = [Migration, MenuItem, KeyValue, Codium];
protected initialized = false;
migrations!: Dexie.Table;
menuItems!: Dexie.Table;
keyValues!: Dexie.Table;
+ codiums!: Dexie.Table;
constructor(
) {
@@ -46,7 +48,7 @@ export class DatabaseService extends Dexie {
schema[ModelClass.getTableName()] = ModelClass.getSchema();
}
- await this.version(3).stores(schema);
+ await this.version(5).stores(schema);
await this.open();
this.migrations = this.table('migrations');
@@ -58,10 +60,7 @@ export class DatabaseService extends Dexie {
this.keyValues = this.table('keyValues');
this.keyValues.mapToClass(KeyValue);
- // await new Promise(res => {
- // setTimeout(() => {
- // res();
- // }, 1000);
- // });
+ this.codiums = this.table('codiums');
+ this.codiums.mapToClass(Codium);
}
}
diff --git a/src/app/utility.ts b/src/app/utility.ts
new file mode 100644
index 0000000..1d3de37
--- /dev/null
+++ b/src/app/utility.ts
@@ -0,0 +1,8 @@
+
+export function uuid_v4() {
+ // @ts-ignore
+ return ([1e7] + - 1e3 + - 4e3 + - 8e3 + - 1e11).replace(/[018]/g, c =>
+ // tslint:disable-next-line:no-bitwise
+ (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
+ );
+}