Add offline caching for code editor contents
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing

This commit is contained in:
2020-10-21 21:12:04 -05:00
parent 5737dd23ca
commit 8de9db08a6
8 changed files with 321 additions and 55 deletions

View File

@@ -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<void> {
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<any> {
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<any> {
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<any> {
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,
});
});
}
}

View File

@@ -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<ICodium> 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<ICodium, number> {
return this.staticClass().dbService.table('codiums') as Dexie.Table<ICodium, number>;
}
}

View File

@@ -1,5 +1,6 @@
import Dexie from 'dexie';
import {DatabaseService} from './database.service';
import {uuid_v4} from '../../utility';
export abstract class Model<InterfaceType> {
public static dbService?: DatabaseService;
@@ -14,6 +15,10 @@ export abstract class Model<InterfaceType> {
throw new TypeError('Child class must implement.');
}
public static getUUID(): string {
return uuid_v4();
}
public abstract getDatabase(): Dexie.Table<InterfaceType, number>;
public abstract getSaveRecord(): any;

View File

@@ -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<IMigration, number>;
menuItems!: Dexie.Table<IMenuItem, number>;
keyValues!: Dexie.Table<IKeyValue, number>;
codiums!: Dexie.Table<ICodium, number>;
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);
}
}