MOBILE-3323 editor: Create offline provider for editor

main
Dani Palou 2020-01-28 17:57:41 +01:00
parent 96c8622608
commit d2f4df452e
2 changed files with 268 additions and 3 deletions

View File

@ -14,14 +14,25 @@
import { NgModule } from '@angular/core';
import { CoreEditorComponentsModule } from './components/components.module';
import { CoreEditorOfflineProvider } from './providers/editor-offline';
// List of providers (without handlers).
export const CORE_GRADES_PROVIDERS: any[] = [
CoreEditorOfflineProvider,
];
@NgModule({
declarations: [
],
imports: [
CoreEditorComponentsModule
CoreEditorComponentsModule,
],
providers: [
]
CoreEditorOfflineProvider,
],
})
export class CoreEditorModule {}
export class CoreEditorModule {
constructor(editorOffline: CoreEditorOfflineProvider) {
// Inject the helper even if it isn't used here it's instantiated.
}
}

View File

@ -0,0 +1,254 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreUtilsProvider } from '@providers/utils/utils';
/**
* Service with features regarding rich text editor in offline.
*/
@Injectable()
export class CoreEditorOfflineProvider {
protected DRAFT_TABLE = 'editor_draft';
protected logger;
protected siteSchema: CoreSiteSchema = {
name: 'CoreEditorProvider',
version: 1,
tables: [
{
name: this.DRAFT_TABLE,
columns: [
{
name: 'contextlevel',
type: 'TEXT',
},
{
name: 'contextinstanceid',
type: 'INTEGER',
},
{
name: 'elementid',
type: 'TEXT',
},
{
name: 'extraparams', // Moodle web uses a page hash built with URL. App will use some params stringified.
type: 'TEXT',
},
{
name: 'drafttext',
type: 'TEXT',
notNull: true
},
{
name: 'pageinstance',
type: 'TEXT',
notNull: true
},
{
name: 'timecreated',
type: 'INTEGER',
notNull: true
},
{
name: 'timemodified',
type: 'INTEGER',
notNull: true
},
],
primaryKeys: ['contextlevel', 'contextinstanceid', 'elementid', 'extraparams']
},
],
};
constructor(
logger: CoreLoggerProvider,
protected sitesProvider: CoreSitesProvider,
protected textUtils: CoreTextUtilsProvider,
protected utils: CoreUtilsProvider) {
this.logger = logger.getInstance('CoreEditorProvider');
this.sitesProvider.registerSiteSchema(this.siteSchema);
}
/**
* Delete a draft from DB.
*
* @param contextLevel Context level.
* @param contextInstanceId The instance ID related to the context.
* @param elementId Element ID.
* @param extraParams Object with extra params to identify the draft.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done.
*/
async deleteDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any},
siteId?: string): Promise<void> {
try {
const db = await this.sitesProvider.getSiteDb(siteId);
const params = this.fixDraftPrimaryData(contextLevel, contextInstanceId, elementId, extraParams);
return db.deleteRecords(this.DRAFT_TABLE, params);
} catch (error) {
// Ignore errors, probably no draft stored.
}
}
/**
* Return an object with the draft primary data converted to the right format.
*
* @param contextLevel Context level.
* @param contextInstanceId The instance ID related to the context.
* @param elementId Element ID.
* @param extraParams Object with extra params to identify the draft.
* @return Object with the fixed primary data.
*/
protected fixDraftPrimaryData(contextLevel: string, contextInstanceId: number, elementId: string,
extraParams: {[name: string]: any}): CoreEditorDraftPrimaryData {
return {
contextlevel: contextLevel,
contextinstanceid: contextInstanceId,
elementid: elementId,
extraparams: this.utils.sortAndStringify(extraParams || {}),
};
}
/**
* Get a draft from DB.
*
* @param contextLevel Context level.
* @param contextInstanceId The instance ID related to the context.
* @param elementId Element ID.
* @param extraParams Object with extra params to identify the draft.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the draft data. Undefined if no draft stored.
*/
async getDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any},
siteId?: string): Promise<CoreEditorDraft> {
const db = await this.sitesProvider.getSiteDb(siteId);
const params = this.fixDraftPrimaryData(contextLevel, contextInstanceId, elementId, extraParams);
return db.getRecord(this.DRAFT_TABLE, params);
}
/**
* Get draft to resume it.
*
* @param contextLevel Context level.
* @param contextInstanceId The instance ID related to the context.
* @param elementId Element ID.
* @param extraParams Object with extra params to identify the draft.
* @param pageInstance Unique identifier to prevent storing data from several sources at the same time.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the draft text. Undefined if no draft stored.
*/
async resumeDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any},
pageInstance: string, siteId?: string): Promise<string> {
try {
// Check if there is a draft stored.
const entry = await this.getDraft(contextLevel, contextInstanceId, elementId, extraParams, siteId);
// There is a draft stored. Update its page instance.
try {
const db = await this.sitesProvider.getSiteDb(siteId);
entry.pageinstance = pageInstance;
entry.timemodified = Date.now();
await db.insertRecord(this.DRAFT_TABLE, entry);
} catch (error) {
// Ignore errors saving the draft. It shouldn't happen.
}
return entry.drafttext;
} catch (error) {
// No draft stored. Store an empty draft to save the pageinstance.
await this.saveDraft(contextLevel, contextInstanceId, elementId, extraParams, pageInstance, '', siteId);
}
}
/**
* Save a draft in DB.
*
* @param contextLevel Context level.
* @param contextInstanceId The instance ID related to the context.
* @param elementId Element ID.
* @param extraParams Object with extra params to identify the draft.
* @param pageInstance Unique identifier to prevent storing data from several sources at the same time.
* @param draftText The text to store.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done.
*/
async saveDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any},
pageInstance: string, draftText: string, siteId?: string): Promise<void> {
let timecreated = Date.now();
let entry: CoreEditorDraft;
// Check if there is a draft already stored.
try {
entry = await this.getDraft(contextLevel, contextInstanceId, elementId, extraParams, siteId);
timecreated = entry.timecreated;
} catch (error) {
// No draft already stored.
}
if (entry && entry.pageinstance != pageInstance) {
this.logger.warning(`Discarding draft because of pageinstance. Context '${contextLevel}' '${contextInstanceId}', ` +
`element '${elementId}'`);
throw null;
}
const db = await this.sitesProvider.getSiteDb(siteId);
const data: CoreEditorDraft = this.fixDraftPrimaryData(contextLevel, contextInstanceId, elementId, extraParams);
data.drafttext = (draftText || '').trim();
data.pageinstance = pageInstance;
data.timecreated = timecreated;
data.timemodified = Date.now();
await db.insertRecord(this.DRAFT_TABLE, data);
}
}
/**
* Primary data to identify a stored draft.
*/
type CoreEditorDraftPrimaryData = {
contextlevel: string; // Context level.
contextinstanceid: number; // The instance ID related to the context.
elementid: string; // Element ID.
extraparams: string; // Extra params stringified.
};
/**
* Draft data stored.
*/
type CoreEditorDraft = CoreEditorDraftPrimaryData & {
drafttext?: string; // Draft text stored.
pageinstance?: string; // Unique identifier to prevent storing data from several sources at the same time.
timecreated?: number; // Time created.
timemodified?: number; // Time modified.
};