| Line 1: |
Line 1: |
| | // License: GPL | | // License: GPL |
| − | // User Interface Rendering | + | // Indexeddb JavaScript |
| − | console.log('Interface handler loading'); | + | console.log('Indb loading'); |
| | | | |
| − | function InterfaceMediaContainer ( WikibaseProcessor, indexedDBobject ) { | + | function IndexedDBContainer(ww) { |
| − |
| |
| − | this.renderInterface = function( sqitch ) {
| |
| − | switch( sqitch ) {
| |
| − | case 'controller_id':
| |
| − | controller_render();
| |
| − | break;
| |
| − | case 'personal_data_id':
| |
| − | pd_site_render();
| |
| − | break;
| |
| − | case 'interface_button_id':
| |
| − | personal_data_edit_interface_render();
| |
| − | break;
| |
| − | }
| |
| − | }
| |
| | | | |
| − | var button_save = [],
| + | // suppress console.log, switch to true for debug |
| − | button_remove = [],
| + | var Debug_switch = false; |
| − | textInput = [];
| + | // seemingly the Safari engine uses a somewhat different implementation of arguments for db.transaction and index.get |
| | + | // because no browser on any mac / ios is allowed to use a different engine, rather than user agent, platform can be tested |
| | + | var iOS = !!navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform); |
| | + | var Safari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); |
| | + | debuglog("iOS:" + iOS + " & Saf:" + Safari); |
| | | | |
| − | async function controller_render(){
| + | // deterministic hash generator for creating user id#s from usernames, should be replaced by something more functional (guaranteed to be transitively different) |
| − | console.log("this function is done by the current page controller for now");
| + | hashCode = function(s) { |
| − | notify_porting();
| + | if (s == undefined) { |
| − | }
| + | s = '00' |
| | + | } |
| | + | var h = 0, |
| | + | l = s.length, |
| | + | i = 0; |
| | + | if (l > 0) |
| | + | while (i < l) |
| | + | h = (h << 5) - h + s.charCodeAt(i++) | 0; |
| | + | return h; |
| | + | }; |
| | | | |
| − | async function pd_site_render(){
| |
| | | | |
| − | }; | + | if (ww.user != undefined) { |
| | + | // if user exists in object passed to constructor, get username |
| | + | var self = ww.user.getName(); |
| | + | // create userid by hashing username |
| | + | var idgen = hashCode(self); |
| | + | debuglog(idgen); |
| | | | |
| − | async function personal_data_edit_interface_render(){
| + | // Take username for standard databasename format: |
| − | clear_render();
| + | // Our current convention for personal databasename (separate dbname for each user) |
| − |
| + | var PersonalDataDataBaseName = "PD.IO" + idgen + "Database"; |
| − | // Get concerns relation :: Parts of this must be moved upstream, MAIN should know that this is the data the renderer will need and provide it
| + | // Use username in user to get up to date version of self |
| | + | // Blank object for personal data Record with id and username included |
| | + | this.CurrentPerson = { |
| | + | id: idgen, |
| | + | name: { |
| | + | user: self |
| | + | }, |
| | + | age: 0 |
| | + | }; |
| | + | debuglog(PersonalDataDataBaseName); |
| | + | debuglog(this.CurrentPerson); |
| | + | } |
| | | | |
| − | var concern_origin = WikibaseProcessor.checkentity( { this_concerns: [ WBEStructure['concernsRelation'], WBEStructure['IDPropLoc'], WBEStructure['qIDPropName'] ] } )['this_concerns'][0];
| + | // ADD MORE DOC |
| | | | |
| − | // Get defval for entity :: Parts of this must be moved upstream, MAIN should know that this is the data the renderer will need and provide it
| + | // EnqueuePDIO a PDIOLocalDatabase:User database access function with modes |
| | + | // Externally accessible wrapper for Enquire with current user / db preloaded |
| | | | |
| − | var default_value = WikibaseProcessor.checkentity( { default_value: [ WBEStructure['defaultValRelation'], WBEStructure['IDPropLoc'] ] } )['default_value'][0]; //defal
| + | this.EnqueuePDIO = async function(sw, c1, c2, c3) { |
| | + | debuglog("enquing pdio db " + PersonalDataDataBaseName + " with user " + JSON.stringify(this.CurrentPerson)); |
| | + | var k = await Enquire(PersonalDataDataBaseName, this.CurrentPerson, sw, c1, c2, c3); |
| | + | return k; |
| | + | } |
| | | | |
| − | var get_record = indexedDBobject.EnqueuePDIO('update record');
| + | // Enquire, a general purpose indexeddb update function with modes |
| | | | |
| − | get_record.then( function(result) {
| + | // Mode 'aggressive push': |
| | + | // parameter record is pushed to database, overwriting whatever is there |
| | | | |
| − | mw.loader.using( 'oojs-ui-core' ).done( function () {
| + | // Mode 'checkin': |
| | + | // record is retrieved from database, parameter record is pushed if not found |
| | | | |
| − | button_save = [],
| + | // Mode 'get record' |
| − | button_remove = [],
| + | // updates record given as argument |
| − | textInput = [];
| |
| | | | |
| − | var extrafield = ( result[concern_origin] != undefined ) ? Object.keys(result[concern_origin]).length : 0;
| + | // Mode 'update record' field1 field2 field3 |
| | + | // retrieve record, make record.field1.field2 = field3, push back |
| | | | |
| − | button_save[ extrafield ] = new OO.ui.ButtonWidget( { label: "Save", classes: [ 'pduimark' ] } );
| + | // Mode 'update record' field1 field2 |
| − |
| + | // retrieve record, make record.field1 = field2, push back |
| − | textInput[ extrafield ] = new OO.ui.TextInputWidget( {
| |
| − | placeholder: default_value,
| |
| − | classes: [ 'pduimark' ]
| |
| − | } );
| |
| | | | |
| − | button_save[ extrafield ].on( 'click', function () {
| + | // Mode 'remove from record' field1 |
| − | indexedDBobject.EnqueuePDIO('update record', concern_origin, extrafield, textInput[ extrafield ].value );
| + | // retrieve record, splice/delete record.field1, push back |
| − | personal_data_edit_interface_render();
| |
| − | });
| |
| | | | |
| − | $( '#mw-content-text' ).prepend( button_save[ extrafield ].$element );
| + | // Mode 'remove from record' field1 field2 |
| − | $( '#mw-content-text' ).prepend( textInput[ extrafield ].$element );
| + | // retrieve record, splice/delete record.field1.field2, push back |
| | | | |
| − | if ( extrafield > 0) {
| + | // Mode 'import record' field1 |
| − | Object.keys( result[concern_origin] ).forEach( function(data) {
| + | // retrieve record, add all fields from field1, push back |
| − | button_save[data] = new OO.ui.ButtonWidget( { label: "Save", classes: [ 'pduimark' ] } );
| |
| − | button_remove[data] = new OO.ui.ButtonWidget( { label: "Delete", classes: [ 'pduimark' ] } );
| |
| − | textInput[data] = new OO.ui.TextInputWidget( {
| |
| − | value: result[concern_origin][data], ///
| |
| − | placeholder: default_value,
| |
| − | classes: [ 'pduimark' ]
| |
| − | } );
| |
| − | button_save[data].on( 'click', function () {
| |
| | | | |
| − | indexedDBobject.EnqueuePDIO('update record', concern_origin, data, textInput[ data ].value );
| + | async function Enquire(pddbname, record, sqitch, control_1, control_2, control_3) { |
| | + | return new Promise( |
| | + | function(resolve, reject) { |
| | | | |
| − | personal_data_edit_interface_render();
| + | var pddb = window.indexedDB.open(pddbname, 3); |
| − | });
| + | var inp_obj; |
| | | | |
| − | button_remove[data].on( 'click', function () {
| + | pddb.onerror = function() { |
| − | indexedDBobject.EnqueuePDIO('remove from record', concern_origin, data );
| + | console.log("Warning: Access to IndexedDB for application has been rejected."); |
| − | personal_data_edit_interface_render();
| + | }; |
| − | });
| |
| − | $( '#mw-content-text' ).prepend( button_remove[data].$element );
| |
| − | $( '#mw-content-text' ).prepend( button_save[data].$element );
| |
| − | $( '#mw-content-text' ).prepend( textInput[data].$element );
| |
| | | | |
| − | });
| + | // indexeddb internal mechanism for version tracking of schema |
| | + | pddb.onupgradeneeded = function() { |
| | + | console.log('upgrading database'); |
| | + | var db = pddb.result; |
| | + | var store = db.createObjectStore(pddbname, { |
| | + | keyPath: "id" |
| | + | }); |
| | + | var index = store.createIndex("NameIndex", ["id"]); |
| | | | |
| − | }
| + | }; |
| − | });
| + | // Database successfully opened function. |
| − | });
| + | pddb.onsuccess = function() { |
| − | };
| + | debuglog(pddb.result); |
| | + | var db = pddb.result; |
| | + | var tx; |
| | | | |
| − | function clear_render() {
| + | if (iOS || Safari) { |
| − | var ifel = document.getElementsByClassName('pduimark');
| + | tx = db.transaction([pddbname], "readwrite"); |
| − | while( ifel[0] ) {
| + | } else { |
| − | ifel[0].parentNode.removeChild(ifel[0]);
| + | tx = db.transaction(pddbname, "readwrite"); |
| − | };
| + | } |
| − | };
| |
| − |
| |
| − | function notify_porting() {
| |
| | | | |
| − | mw.loader.using( 'oojs-ui-core' ).done( function () {
| + | var store = tx.objectStore(pddbname); |
| | + | var index = store.index("NameIndex"); |
| | | | |
| − | var button_import = new OO.ui.SelectFileWidget( { label: "Import Personal Data", classes: [ 'pduimark' ] } ),
| + | // Get the original record from the db... |
| | + | var getRecord; |
| | + | if (iOS || Safari) { |
| | + | getRecord = index.get(record.id); |
| | + | } else { |
| | + | getRecord = index.get([record.id]); |
| | + | } |
| | | | |
| − | button_export = new OO.ui.ButtonWidget( { label: "Export Personal Data", classes: [ 'pduimark' ] } );
| + | getRecord.onsuccess = function() { |
| | + | debuglog("Record in database: \n <<"); |
| | + | debuglog(getRecord.result); |
| | + | debuglog(">>"); |
| | + | // Essential recordAdd |
| | + | if (sqitch === "aggressive push") { |
| | + | // Mode 'aggressive push': |
| | + | // parameter record is pushed to database, overwriting whatever is there |
| | + | inp_obj = record; |
| | + | store.put(inp_obj); |
| | + | return inp_obj; |
| | + | } else if (sqitch === "checkin") { |
| | + | // Mode 'checkin': |
| | + | // record is retrieved from database, parameter record is pushed if not found |
| | + | if (getRecord.result != undefined) { |
| | + | debuglog("getting from store"); |
| | + | inp_obj = getRecord.result; |
| | + | } else { |
| | + | debuglog("overriding whatever in there"); |
| | + | inp_obj = record; |
| | + | store.put(inp_obj); |
| | + | } |
| | | | |
| − | button_import.on( 'change', function (event) {
| + | } else { |
| | + | // Methods manipulationg the actual record come here |
| | + | // Update & Remove from |
| | + | // If we got the record from store, use that version to be manipulated |
| | + | if (getRecord.result !== undefined) { |
| | + | inp_obj = getRecord.result; |
| | | | |
| − | if (window.webkitURL != null) {
| + | if (sqitch === "get record") { |
| − | console.log("loading file on webkit")
| |
| − | } else {
| |
| − | console.log("loading file non webkit")
| |
| − | }
| |
| | | | |
| − | console.log( event );
| + | debuglog("Getting record"); |
| | + | // we will return later |
| | | | |
| − | });
| + | } |
| | | | |
| − | button_export.on( 'click', function () {
| + | // endof onsuccess |
| | + | } else { |
| | | | |
| − | var get_record = indexedDBobject.EnqueuePDIO('get record');
| + | debuglog("Record not found"); |
| | | | |
| − | get_record.then( function(result) {
| + | if (sqitch === "update record") { |
| − | console.log("happening");
| + | // Update record will use supplied copy to update it |
| − | if ( result === undefined ) { window.alert("No local data to export") } else {
| + | inp_obj = record; |
| | + | } else { |
| | + | if (sqitch === "get record") { |
| | + | inp_obj = undefined; |
| | + | } else if (sqitch === "remove from record") { |
| | + | inp_obj = record; |
| | + | } else { |
| | + | // any other method ...... would quit as record was not even foun |
| | + | return undefined; |
| | + | } |
| | + | } |
| | + | } |
| | + | } |
| | | | |
| − | var filename_tosaveas = "pdiolocalexport_" + result.name.user + "_" + Date.now() + '.json';
| + | if (sqitch === "update record") { // Record Update |
| − |
| + | if (control_1 != undefined) { |
| − | result.id = '';
| + | if (control_2 != undefined) { |
| − | result.name.user = '';
| + | if (control_3 != undefined) { |
| − |
| |
| − | var export_to_text = JSON.stringify(result);
| |
| − | var textFileAsBlob = new Blob([export_to_text], {type:'text/plain'});
| |
| − | var downloadLink = document.createElement("a");
| |
| − | downloadLink.download = filename_tosaveas;
| |
| − | if (window.webkitURL != null)
| |
| − | {
| |
| − | // Chrome allows the link to be clicked
| |
| − | // without actually adding it to the DOM.
| |
| − | downloadLink.href = window.webkitURL.createObjectURL(textFileAsBlob);
| |
| − | }
| |
| − | else
| |
| − | {
| |
| − | // Firefox requires the link to be added to the DOM
| |
| − | // before it can be clicked.
| |
| − | downloadLink.href = window.URL.createObjectURL(textFileAsBlob);
| |
| − | downloadLink.style.display = "none";
| |
| − | document.body.appendChild(downloadLink);
| |
| − | }
| |
| | | | |
| − | downloadLink.click();
| + | // Mode 'update record' field1 field2 field3 |
| | + | // retrieve record, make record.field1.field2 = field3, push back |
| | | | |
| − | }
| + | if (inp_obj[control_1] == undefined) { |
| | + | inp_obj[control_1] = (isNaN(control_2)) ? {} : [] |
| | + | }; |
| | + | inp_obj[control_1][control_2] = control_3; // => "Bob" |
| | + | store.put(inp_obj); |
| | + | } else { |
| | | | |
| − | });
| + | // Mode 'update record' field1 field2 |
| | + | // retrieve record, make record.field1 = field2, push back |
| | | | |
| − | });
| + | inp_obj[control_1] = inp_obj[control_1] || {}; |
| | + | if (Array.isArray(inp_obj[control_1])) { |
| | + | inp_obj[control_1].push(control_2); |
| | + | } else { |
| | + | inp_obj[control_1] = control_2; |
| | + | } |
| | + | store.put(inp_obj); |
| | | | |
| − | mw.notify( $( button_import.$element ), { autoHide: false } );
| + | } |
| − | mw.notify( $( button_export.$element ), { autoHide: false } );
| + | } else { |
| − | });
| + | // Only one control defined. We will return that part of the record, and change nothing |
| − | }
| + | inp_obj = inp_obj[control_1]; |
| | + | } |
| | + | } else { |
| | | | |
| − | } | + | // There's not even control_1, we will just return the record itself |
| | + | |
| | + | } |
| | + | debuglog("return 3") |
| | + | // return inp_obj; |
| | + | |
| | + | }; |
| | + | |
| | + | if (sqitch === "remove from record") { // Record Update |
| | + | if (control_1 != undefined) { |
| | + | if (control_2 != undefined) { |
| | + | |
| | + | // Mode 'remove from record' field1 field2 |
| | + | // retrieve record, splice/delete record.field1.field2, push back |
| | + | |
| | + | if (Array.isArray(inp_obj[control_1]) && !isNaN(control_2)) { |
| | + | inp_obj[control_1].splice(control_2, 1); |
| | + | } else { |
| | + | delete inp_obj[control_1][control_2]; |
| | + | } |
| | + | |
| | + | } else { |
| | + | |
| | + | // Mode 'remove from record' field1 |
| | + | // retrieve record, splice/delete record.field1, push back |
| | + | |
| | + | delete inp_obj[control_1]; |
| | + | } |
| | + | } |
| | + | debuglog("putting object to store:"); |
| | + | debuglog(inp_obj); |
| | + | store.put(inp_obj); |
| | + | } |
| | + | |
| | + | if (sqitch === "import record") { |
| | + | debuglog("import command"); |
| | + | if (control_1 != undefined) { |
| | + | // Only import if control_1 is a non-empty object |
| | + | if ((!Array.isArray(control_1)) && (Object.keys(control_1).length > 0)) { |
| | + | debuglog("command is obj"); |
| | + | Object.keys(control_1).forEach(function(importkey) { |
| | + | debuglog("importing " + importkey); |
| | + | if (control_1.hasOwnProperty(importkey)) { |
| | + | inp_obj[importkey] = control_1[importkey] |
| | + | }; |
| | + | }) |
| | + | store.put(inp_obj); |
| | + | } |
| | + | } |
| | + | } |
| | + | |
| | + | // We are past all methodic code, Async return |
| | + | |
| | + | resolve(inp_obj); |
| | + | |
| | + | }; |
| | + | |
| | + | // Indexeddb internal mechanism, close db |
| | + | |
| | + | tx.oncomplete = function() { |
| | + | debuglog("return 1") |
| | + | db.close(); |
| | + | }; |
| | + | }; |
| | + | debuglog("return 0") |
| | + | |
| | + | // End of promise |
| | + | |
| | + | }).then(function(result) { |
| | + | debuglog("Mark 3"); |
| | + | debuglog(result); |
| | + | return result; |
| | + | }); |
| | + | } |
| | + | |
| | + | function debuglog(l0g) { |
| | + | if (Debug_switch) { |
| | + | console.log(l0g); |
| | + | } |
| | + | } |
| | + | }; |