// ==UserScript==
// @name MTurk HIT Database Mk.II
// @author feihtality
// @namespace https://gf.qytechs.cn/en/users/12709
// @version 0.7.492
// @description Keep track of the HITs you've done (and more!)
// @include /^https://www\.mturk\.com/mturk/(dash|view|sort|find|prev|search).*/
// @exclude https://www.mturk.com/mturk/findhits?*hit_scraper
// @grant none
// ==/UserScript==
/**\
**
** This is a complete rewrite of the MTurk HIT Database script from the ground up, which
** eliminates obsolete methods, fixes many bugs, and brings this script up-to-date
** with the current modern browser environment.
**
\**/
/*
* TODO
* projected earnings
* note functionality
* tagging (?)
* searching via R/T buttons
*
*/
const DB_VERSION = 2;
const MTURK_BASE = 'https://www.mturk.com/mturk/';
//const TO_BASE = 'http://turkopticon.ucsd.edu/api/multi-attrs.php';
// polyfill for chrome until v45(?)
if (!NodeList.prototype[Symbol.iterator]) NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
// format leading zeros
Number.prototype.toPadded = function(length) {
'use strict';
length = length || 2;
return ("0000000"+this).substr(-length);
};
// decimal rounding
Math.decRound = function(v, shift) {
'use strict';
v = Math.round(+(v+"e"+shift));
return +(v+"e"+-shift);
};
var qc = { extraDays: !!localStorage.getItem("hitdb_extraDays") || false, seen: {} };
if (localStorage.getItem("hitdb_fetchData"))
qc.fetchData = JSON.parse(localStorage.getItem("hitdb_fetchData"));
else
qc.fetchData = {};
var HITStorage = { //{{{
data: {},
versionChange: function hsversionChange() { //{{{
'use strict';
var db = this.result;
db.onerror = HITStorage.error;
db.onversionchange = function(e) { console.log("detected version change??",console.dir(e)); db.close(); };
this.onsuccess = function() { db.close(); };
var dbo;
console.groupCollapsed("HITStorage.versionChange::onupgradeneeded");
if (!db.objectStoreNames.contains("HIT")) {
console.log("creating HIT OS");
dbo = db.createObjectStore("HIT", { keyPath: "hitId" });
dbo.createIndex("date", "date", { unique: false });
dbo.createIndex("requesterName", "requesterName", { unique: false});
dbo.createIndex("title", "title", { unique: false });
dbo.createIndex("reward", "reward", { unique: false });
dbo.createIndex("status", "status", { unique: false });
dbo.createIndex("requesterId", "requesterId", { unique: false });
localStorage.setItem("hitdb_extraDays", true);
qc.extraDays = true;
}
if (!db.objectStoreNames.contains("STATS")) {
console.log("creating STATS OS");
dbo = db.createObjectStore("STATS", { keyPath: "date" });
}
if (this.transaction.objectStore("STATS").indexNames.length < 5) { // new in v5: schema additions
this.transaction.objectStore("STATS").createIndex("approved", "approved", { unique: false });
this.transaction.objectStore("STATS").createIndex("earnings", "earnings", { unique: false });
this.transaction.objectStore("STATS").createIndex("pending", "pending", { unique: false });
this.transaction.objectStore("STATS").createIndex("rejected", "rejected", { unique: false });
this.transaction.objectStore("STATS").createIndex("submitted", "submitted", { unique: false });
}
(function _updateNotes(dbt) { // new in v5: schema change
if (!db.objectStoreNames.contains("NOTES")) {
console.log("creating NOTES OS");
dbo = db.createObjectStore("NOTES", { keyPath: "id", autoIncrement: true });
dbo.createIndex("hitId", "hitId", { unique: false });
dbo.createIndex("requesterId", "requesterId", { unique: false });
dbo.createIndex("tags", "tags", { unique: false, multiEntry: true });
dbo.createIndex("date", "date", { unique: false });
}
if (db.objectStoreNames.contains("NOTES") && dbt.objectStore("NOTES").indexNames.length < 3) {
_mv(db, dbt, "NOTES", "NOTES", _updateNotes);
}
})(this.transaction);
if (db.objectStoreNames.contains("BLOCKS")) {
console.log("migrating BLOCKS to NOTES");
var temp = [];
this.transaction.objectStore("BLOCKS").openCursor().onsuccess = function() {
var cursor = this.result;
if (cursor) {
temp.push( {
requesterId: cursor.value.requesterId,
tags: "Blocked",
note: "This requester was blocked under the old HitDB. Blocking has been deprecated and removed "+
"from HIT Databse. All blocks have been converted to a Note."
} );
cursor.continue();
} else {
console.log("deleting blocks");
db.deleteObjectStore("BLOCKS");
for (var entry of temp)
this.transaction.objectStore("NOTES").add(entry);
}
};
}
function _mv(db, transaction, source, dest, fn) { //{{{
var _data = [];
transaction.objectStore(source).openCursor().onsuccess = function() {
var cursor = this.result;
if (cursor) {
_data.push(cursor.value);
cursor.continue();
} else {
db.deleteObjectStore(source);
fn(transaction);
if (_data.length)
for (var i=0;i<_data.length;i++)
transaction.objectStore(dest).add(_data[i]);
//console.dir(_data);
}
};
} //}}}
console.groupEnd();
}, // }}} versionChange
error: function(e) { //{{{
'use strict';
if (typeof e === "string")
console.log(e);
else
console.log("Encountered",e.target.error.name,"--",e.target.error.message,e);
}, //}}} onerror
parseDOM: function(doc) {//{{{
'use strict';
var statusLabel = document.querySelector("#hdbStatusText");
statusLabel.style.color = "black";
if (doc.title.search(/Status$/) > 0) // status overview
parseStatus();
else if (doc.querySelector('td[colspan="4"]')) // valid status detail, but no data
parseMisc("next");
else if (doc.title.search(/Status Detail/) > 0) // status detail with data
parseDetail();
else if (doc.querySelector('td[class="error_title"]')) // no more status information
parseMisc("end");
else
throw "ParseError::unhandled document received @"+doc.documentURI;
function parseStatus() {//{{{
HITStorage.data = { HIT: [], STATS: [] };
qc.seen = {};
var _pastDataExists = Boolean(Object.keys(qc.fetchData).length);
var raw = {
day: doc.querySelectorAll(".statusDateColumnValue"),
sub: doc.querySelectorAll(".statusSubmittedColumnValue"),
app: doc.querySelectorAll(".statusApprovedColumnValue"),
rej: doc.querySelectorAll(".statusRejectedColumnValue"),
pen: doc.querySelectorAll(".statusPendingColumnValue"),
pay: doc.querySelectorAll(".statusEarningsColumnValue")
};
var timeout = 0;
for (var i=0;i<raw.day.length;i++) {
var d = {};
var _date = raw.day[i].childNodes[1].href.substr(53);
d.date = HITStorage.ISODate(_date);
d.submitted = +raw.sub[i].textContent;
d.approved = +raw.app[i].textContent;
d.rejected = +raw.rej[i].textContent;
d.pending = +raw.pen[i].textContent;
d.earnings = +raw.pay[i].textContent.substr(1);
HITStorage.data.STATS.push(d);
// check whether or not we need to get status detail pages for date, then
// fetch status detail pages per date in range and slightly slow
// down GET requests to avoid making too many in too short an interval
var payload = { encodedDate: _date, pageNumber: 1, sortType: "All" };
if (_pastDataExists) {
// date not in range but is new date (or old date but we need updates)
// lastDate stored in ISO format, fetchData date keys stored in mturk's URI ecnodedDate format
if ( (d.date > qc.fetchData.lastDate) || ~(Object.keys(qc.fetchData).indexOf(_date)) ) {
setTimeout(HITStorage.fetch, timeout, MTURK_BASE+"statusdetail", payload);
timeout += 250;
qc.fetchData[_date] = { submitted: d.submitted, pending: d.pending };
}
} else { // get everything
setTimeout(HITStorage.fetch, timeout, MTURK_BASE+"statusdetail", payload);
timeout += 250;
qc.fetchData[_date] = { submitted: d.submitted, pending: d.pending };
}
} // for
qc.fetchData.expectedTotal = _calcTotals(qc.fetchData);
// try for extra days
if (qc.extraDays === true) {
localStorage.removeItem("hitdb_extraDays");
d = _decDate(HITStorage.data.STATS[HITStorage.data.STATS.length-1].date);
qc.extraDays = d; // repurpose extraDays for QC
payload = { encodedDate: d, pageNumber: 1, sortType: "All" };
console.log("fetchrequest for", d, "sent by parseStatus");
setTimeout(HITStorage.fetch, 1000, MTURK_BASE+"statusdetail", payload);
}
qc.fetchData.lastDate = HITStorage.data.STATS[0].date; // most recent date seen
}//}}} parseStatus
function parseDetail() {//{{{
var _date = doc.documentURI.replace(/.+(\d{8}).+/, "$1");
var _page = doc.documentURI.replace(/.+ber=(\d+).+/, "$1");
console.log("page:", _page, "date:", _date);
statusLabel.textContent = "Processing "+HITStorage.ISODate(_date)+" page "+_page;
var raw = {
req: doc.querySelectorAll(".statusdetailRequesterColumnValue"),
title: doc.querySelectorAll(".statusdetailTitleColumnValue"),
pay: doc.querySelectorAll(".statusdetailAmountColumnValue"),
status: doc.querySelectorAll(".statusdetailStatusColumnValue"),
feedback: doc.querySelectorAll(".statusdetailRequesterFeedbackColumnValue")
};
for (var i=0;i<raw.req.length;i++) {
var d = {};
d.date = HITStorage.ISODate(_date);
d.feedback = raw.feedback[i].textContent.trim();
d.hitId = raw.req[i].childNodes[1].href.replace(/.+HIT\+(.+)/, "$1");
d.requesterId = raw.req[i].childNodes[1].href.replace(/.+rId=(.+?)&.+/, "$1");
d.requesterName = raw.req[i].textContent.trim().replace(/\|/g,"");
d.reward = +raw.pay[i].textContent.substr(1);
d.status = raw.status[i].textContent;
d.title = raw.title[i].textContent.replace(/\|/g, "");
HITStorage.data.HIT.push(d);
if (!qc.seen[_date]) qc.seen[_date] = {};
qc.seen[_date] = {
submitted: qc.seen[_date].submitted + 1 || 1,
pending: ~d.status.search(/pending/i) ?
(qc.seen[_date].pending + 1 || 1) : (qc.seen[_date].pending || 0)
};
}
// additional pages remain; get them
if (doc.querySelector('img[src="/media/right_dbl_arrow.gif"]')) {
var payload = { encodedDate: _date, pageNumber: +_page+1, sortType: "All" };
setTimeout(HITStorage.fetch, 250, MTURK_BASE+"statusdetail", payload);
return;
}
if (!qc.extraDays) { // not fetching extra days
//no longer any more useful data here, don't need to keep rechecking this date
if (HITStorage.ISODate(_date) !== qc.fetchData.lastDate &&
qc.seen[_date].submitted === qc.fetchData[_date].submitted &&
qc.seen[_date].pending === 0) {
console.log("no more pending hits, removing",_date,"from fetchData");
delete qc.fetchData[_date];
localStorage.setItem("hitdb_fetchData", JSON.stringify(qc.fetchData));
}
// finished scraping; start writing
console.log("totals", _calcTotals(qc.seen), qc.fetchData.expectedTotal);
statusLabel.textContent += " [ "+_calcTotals(qc.seen)+"/"+ qc.fetchData.expectedTotal+" ]";
if (_calcTotals(qc.seen) === qc.fetchData.expectedTotal) {
statusLabel.textContent = "Writing to database...";
HITStorage.write(HITStorage.data, "update");
}
} else if (_date <= qc.extraDays) { // day is older than default range and still fetching extra days
parseMisc("next");
console.log("fetchrequest for", _decDate(HITStorage.ISODate(_date)));
}
}//}}} parseDetail
function parseMisc(type) {//{{{
var d = doc.documentURI.replace(/.+(\d{8}).+/, "$1");
var payload = { encodedDate: _decDate(HITStorage.ISODate(d)), pageNumber: 1, sortType: "All" };
if (type === "next" && +qc.extraDays > 1) {
setTimeout(HITStorage.fetch, 250, MTURK_BASE+"statusdetail", payload);
console.log("going to next page", payload.encodedDate);
} else if (type === "end" && +qc.extraDays > 1) {
statusLabel.textContent = "Writing to database...";
HITStorage.write(HITStorage.data, "update");
} else
throw "Unhandled URL -- how did you end up here??";
}//}}}
function _decDate(date) {//{{{
var y = date.substr(0,4);
var m = date.substr(5,2);
var d = date.substr(8,2);
date = new Date(y,m-1,d-1);
return Number(date.getMonth()+1).toPadded() + Number(date.getDate()).toPadded() + date.getFullYear();
}//}}}
function _calcTotals(obj) {//{{{
var sum = 0;
for (var k in obj){
if (obj.hasOwnProperty(k) && !isNaN(+k))
sum += obj[k].submitted;
}
return sum;
}//}}}
},//}}} parseDOM
ISODate: function(date) { //{{{ MMDDYYYY -> YYYY-MM-DD
'use strict';
return date.substr(4)+"-"+date.substr(0,2)+"-"+date.substr(2,2);
}, //}}} ISODate
fetch: function(url, payload) { //{{{
'use strict';
//format GET request with query payload
if (payload) {
var args = 0;
url += "?";
for (var k in payload) {
if (payload.hasOwnProperty(k)) {
if (args++) url += "&";
url += k + "=" + payload[k];
}
}
}
// defer XHR to a promise
var fetch = new Promise( function(fulfill, deny) {
var urlreq = new XMLHttpRequest();
urlreq.open("GET", url, true);
urlreq.responseType = "document";
urlreq.send();
urlreq.onload = function() {
if (this.status === 200) {
fulfill(this.response);
} else {
deny("Error ".concat(String(this.status)).concat(": "+this.statusText));
}
};
urlreq.onerror = function() { deny("Error ".concat(String(this.status)).concat(": "+this.statusText)); };
urlreq.ontimeout = function() { deny("Error ".concat(String(this.status)).concat(": "+this.statusText)); };
} );
fetch.then( HITStorage.parseDOM, HITStorage.error );
}, //}}} fetch
write: function(input, statusUpdate) { //{{{
'use strict';
var dbh = window.indexedDB.open("HITDB_TESTING");
dbh.onerror = HITStorage.error;
dbh.onsuccess = function() { _write(this.result); };
var counts = { requests: 0, total: 0 };
function _write(db) {
db.onerror = HITStorage.error;
var os = Object.keys(input);
var dbt = db.transaction(os, "readwrite");
var dbo = [];
for (var i=0;i<os.length;i++) { // cycle object stores
dbo[i] = dbt.objectStore(os[i]);
for (var k of input[os[i]]) { // cycle entries to put into object stores
if (statusUpdate && ++counts.requests)
dbo[i].put(k).onsuccess = _statusCallback;
else
dbo[i].put(k);
}
}
db.close();
}
function _statusCallback() {
if (++counts.total === counts.requests) {
var statusLabel = document.querySelector("#hdbStatusText");
statusLabel.style.color = "green";
statusLabel.textContent = statusUpdate === "update" ? "Update Complete!" :
statusUpdate === "restore" ? "Restoring " + counts.total + " entries... Done!" :
"Done!";
document.querySelector("#hdbProgressBar").style.display = "none";
}
}
}, //}}} write
recall: function(store, options) {//{{{
'use strict';
var index = options ? (options.index || null) : null,
range = options ? (options.range || null) : null,
dir = options ? (options.dir || "next") : "next",
fs = options ? (options.filter ? options.filter.status !== "*" ? options.filter.status : false : false) : false,
fq = options ? (options.filter ? options.filter.query !== "*" ? new RegExp(options.filter.query,"i") : false : false) : false,
limit = 0;
if (options && options.progress) {
var progressBar = document.querySelector("#hdbProgressBar");
//statusText = document.querySelector("#hdbStatusText");
progressBar.style.display = "block";
}
var sr = new DatabaseResult();
return new Promise( function(resolve) {
window.indexedDB.open("HITDB_TESTING").onsuccess = function() {
var dbo = this.result.transaction(store, "readonly").objectStore(store), dbq = null;
if (index)
dbq = dbo.index(index).openCursor(range, dir);
else
dbq = dbo.openCursor(range, dir);
dbq.onsuccess = function() {
var c = this.result;
if (c && limit++ < 2000) { // limit to 2000 to save memory usage in large databases
if ( (!fs && !fq) || // no query filter and no status filter OR
(fs && !fq && ~c.value.status.search(fs)) || // status match and no query filter OR
(!fs && fq && // query match and no status filter OR
(~c.value.title.search(fq) || ~c.value.requesterName.search(fq) || ~c.value.hitId.search(fq))) ||
(fs && fq && ~c.value.status.search(fs) && // status match and query match
(~c.value.title.search(fq) || ~c.value.requesterName.search(fq) || ~c.value.hitId.search(fq))) )
sr.include(c.value);
c.continue();
} else
resolve(sr);
};
};
} ); // promise
},//}}} recall
backup: function() {//{{{
'use strict';
var bData = {},
os = ["STATS", "NOTES", "HIT"],
count = 0,
prog = document.querySelector("#hdbProgressBar");
prog.style.display = "block";
window.indexedDB.open("HITDB_TESTING").onsuccess = function() {
for (var store of os) {
this.result.transaction(os, "readonly").objectStore(store).openCursor().onsuccess = populateBackup;
}
};
function populateBackup(e) {
var cursor = e.target.result;
if (cursor) {
if (!bData[cursor.source.name]) bData[cursor.source.name] = [];
bData[cursor.source.name].push(cursor.value);
cursor.continue();
} else
if (++count === 3)
finalizeBackup();
}
function finalizeBackup() {
var backupblob = new Blob([JSON.stringify(bData)], {type:""});
var date = new Date();
var dl = document.createElement("A");
date = date.getFullYear() + Number(date.getMonth()+1).toPadded() + Number(date.getDate()).toPadded();
dl.href = URL.createObjectURL(backupblob);
console.log(dl.href);
dl.download = "hitdb_"+date+".bak";
dl.click();
prog.style.display = "none";
}
}//}}} backup
};//}}} HITStorage
function DatabaseResult() {//{{{
'use strict';
this.results = [];
this.formatHTML = function(type) {
var count = 0, htmlTxt = [], entry = null, _trClass = null;
if (this.results.length < 1) return "<h2>No entries found matching your query.</h2>";
if (type === "daily") {
htmlTxt.push('<tr style="background:#7fb448;font-size:12px;color:white"><th>Date</th><th>Submitted</th>' +
'<th>Approved</th><th>Rejected</th><th>Pending</th><th>Earnings</th></tr>');
for (entry of this.results) {
_trClass = (count++ % 2 === 0) ? 'class="even"' : 'class="odd"';
htmlTxt.push('<tr '+_trClass+' align="center"><td>' + entry.date + '</td><td>' + entry.submitted + '</td>' +
'<td>' + entry.approved + '</td><td>' + entry.rejected + '</td><td>' + entry.pending + '</td>' +
'<td>' + Number(entry.earnings).toFixed(2) + '</td></tr>');
}
} else if (type === "pending" || type === "requester") {
htmlTxt.push('<tr data-sort="99999" style="background:#7fb448;font-size:12px;color:white"><th>Requester ID</th>' +
'<th width="504px">Requester</th><th>' + (type === "pending" ? 'Pending' : 'HITs') + '</th><th>Rewards</th></tr>');
var r = _collate(this.results);
for (var k in r) {
if (r.hasOwnProperty(k)) {
var tr = ['<tr data-hits="'+r[k].length+'"><td>' +
'<span style="cursor:pointer;color:blue;" class="hdbExpandRow" title="Display all pending HITs from this requester">' +
'[+]</span> ' + r[k][0].requesterId + '</td><td>' + r[k][0].requesterName + '</td>' +
'<td>' + r[k].length + '</td><td>' + Number(Math.decRound(r[k].pay,2)).toFixed(2) + '</td></tr>'];
for (var hit of r[k]) { // hits in range per requester id
tr.push('<tr data-rid="'+r[k][0].requesterId+'" style="color:#c60000;display:none;"><td align="right">' + hit.date + '</td>' +
'<td max-width="504px">' + hit.title + '</td><td></td><td align="right">' +
(typeof hit.reward === "object" ? Number(hit.reward.pay).toFixed(2) : Number(hit.reward).toFixed(2)) +
'</td></tr>');
}
htmlTxt.push(tr.join(''));
}
}
htmlTxt.sort(function(a,b) { return +b.substr(15,5).match(/\d+/) - +a.substr(15,5).match(/\d+/); });
} else { // default
htmlTxt.push('<tr style="background:#7FB448;font-size:12px;color:white"><th colspan="3"></th>' +
'<th colspan="2" title="Bonuses must be added in manually.\n\nClick inside' +
'the cell to edit, click out of the cell to save">Reward</th><th colspan="2"></th></tr>'+
'<tr style="background:#7FB448;font-size:12px;color:white">' +
'<th>Date</th><th>Requester</th><th>HIT title</th><th style="font-size:10px;">Pay</th>'+
'<th style="font-size:10px;">Bonus</th><th>Status</th><th>Feedback</th></tr>');
for (entry of this.results) {
_trClass = (count++ % 2 === 0) ? 'class="even"' : 'class="odd"';
var _stColor = ~entry.status.search(/(paid|approved)/i) ? 'style="color:green;"' :
entry.status === "Pending Approval" ? 'style="color:orange;"' : 'style="color:red;"';
htmlTxt.push("<tr "+_trClass+"><td width=\"74px\">" + entry.date + "</td><td style=\"max-width:145px;\">" + entry.requesterName +
"</td><td width='375px' title='HIT ID: "+entry.hitId+"'>" + entry.title + "</td><td>" +
(typeof entry.reward === "object" ? Number(entry.reward.pay).toFixed(2) : Number(entry.reward).toFixed(2)) +
"</td><td width='36px' contenteditable='true' data-hitid='"+entry.hitId+"'>" +
(typeof entry.reward === "object" ? Number(entry.reward.bonus).toFixed(2) : " ") +
"</td><td "+_stColor+">" + entry.status + "</td><td>" + entry.feedback + "</td></tr>");
}
}
return htmlTxt.join('');
}; // formatHTML
this.formatCSV = function(type) {
var csvTxt = [], entry = null;
if (type === "daily") {
csvTxt.push("Date|Submitted|Approved|Rejected|Pending|Earnings\n");
for (entry of this.results) {
csvTxt.push(entry.date+"|"+entry.submitted+"|"+entry.approved+"|"+entry.rejected+
"|"+entry.pending+"|"+Number(entry.earnings).toFixed(2)+"\n");
}
csvToFile(csvTxt, "hitdb_dailyOverview.csv");
} else if (type === "pending" || type === "requester") {
csvTxt.push("RequesterId|Requester|" + (type === "pending" ? "Pending" : "HITs") + "|Rewards\n");
var r = _collate(this.results);
for (var k in r) {
if (r.hasOwnProperty(k))
csvTxt.push(k+"|"+r[k][0].requesterName+"|"+r[k].length+"|"+Number(Math.decRound(r[k].pay,2)).toFixed(2)+"\n");
}
csvToFile(csvTxt, "hitdb_"+type+"Overview.csv");
} else {
csvTxt.push("Date|Requester|Title|Pay|Bonus|Status|Feedback\n");
for (entry of this.results) {
csvTxt.push(entry.date+"|"+entry.requesterName+"|"+entry.title+"|"+
(typeof entry.reward === "object" ? Number(entry.reward.pay).toFixed(2) : Number(entry.reward).toFixed(2))+"|"+
(typeof entry.reward === "object" ? Number(entry.reward.bonus).toFixed(2) : "")+"|"+
entry.status+"|"+entry.feedback+"\n");
}
csvToFile(csvTxt, "hitdb_queryResults.csv");
}
return "<pre>"+csvTxt.join('')+"</pre>";
function csvToFile(csv, filename) {
var blob = new Blob(csv, {type: "text/csv", endings: "native"}),
dl = document.createElement("A");
dl.href = URL.createObjectURL(blob);
dl.download = filename;
dl.click();
return dl;
}
};
this.include = function(value) {
this.results.push(value);
};
function _collate(data) {
var r = {};
for (var e of data) {
if (!r[e.requesterId]) r[e.requesterId] = [];
r[e.requesterId].push(e);
r[e.requesterId].pay = r[e.requesterId].pay ?
typeof e.reward === "object" ? r[e.requesterId].pay + (+e.reward.pay) : r[e.requesterId].pay + (+e.reward) :
typeof e.reward === "object" ? +e.reward.pay : +e.reward;
}
return r;
}
}//}}} databaseresult
/*
*
* Above contains the core functions. Below is the
* main body, interface, and tangential functions.
*
*///{{{
// the Set() constructor is never actually used other than to test for Chrome v38+
if (!("indexedDB" in window && "Set" in window)) alert("HITDB::Your browser is too outdated or otherwise incompatible with this script!");
else {
/*
var tdbh = window.indexedDB.open("HITDB_TESTING");
tdbh.onerror = function(e) { 'use strict'; console.log("[TESTDB]",e.target.error.name+":", e.target.error.message, e); };
tdbh.onsuccess = INFLATEDUMMYVALUES;
tdbh.onupgradeneeded = BLANKSLATE;
var dbh = null;
*/
var dbh = window.indexedDB.open("HITDB_TESTING", DB_VERSION);
dbh.onerror = function(e) { 'use strict'; console.log("[HITDB]",e.target.error.name+":", e.target.error.message, e); };
dbh.onupgradeneeded = HITStorage.versionChange;
if (document.location.pathname.search(/dashboard/) > 0)
dashboardUI();
else
beenThereDoneThat();
}
/*}}}
*
* Above is the main body and core functions. Below
* defines UI layout/appearance and tangential functions.
*
*/
// {{{ css injection
var css = "<style type='text/css'>" +
".hitdbRTButtons {border:1px solid; font-size: 10px; height: 18px; padding-left: 5px; padding-right: 5px; background: pink;}" +
".hitdbRTButtons-green {background: lightgreen;}" +
".hitdbRTButtons-large {width:80px;}" +
".hdbProgressContainer {margin:auto; width:500px; height:6px; position:relative; display:none; border-radius:10px; overflow:hidden; background:#d3d8db;}" +
".hdbProgressInner {width:100%; position:absolute; left:0;top:0;bottom:0; animation: kfpin 1.4s infinite; background:" +
"linear-gradient(262deg, rgba(208,69,247,0), rgba(208,69,247,1), rgba(69,197,247,1), rgba(69,197,247,0)); background-size: 300% 500%;}" +
".hdbProgressOuter {width:30%; position:absolute; left:0;top:0;bottom:0; animation: kfpout 2s cubic-bezier(0,0.55,0.2,1) infinite;}" +
"@keyframes kfpout { 0% {left:-100%;} 70%{left:100%;} 100%{left:100%;} }" +
"@keyframes kfpin { 0%{background-position: 0% 50%} 50%{background-position: 100% 15%} 100%{background-position:0% 30%} }" +
".hdbCalControls {cursor:pointer;} .hdbCalControls:hover {color:c27fcf;}" +
".hdbCalCells {background:#f0f6f9; height:19px}" +
".hdbCalDays {cursor:pointer; text-align:center;} .hdbCalDays:hover {background:#7fb4cf; color:white;}" +
".hdbDayHeader {width:26px; text-align:center; font-weight:bold; font-size:12px; background:#f0f6f9;}" +
".hdbCalHeader {background:#7fb4cf; color:white; font-weight:bold; text-align:center; font-size:11px; padding:3px 0px;}" +
"#hdbCalendarPanel {position:absolute; z-index:10; box-shadow:-2px 3px 5px 0px rgba(0,0,0,0.68);}" +
"</style>";
document.head.innerHTML += css;
// }}}
function beenThereDoneThat() {//{{{
//
// TODO add search on button click
//
'use strict';
var qualNode = document.querySelector('td[colspan="11"]');
if (qualNode) { // we're on the preview page!
var requester = document.querySelector('input[name="requesterId"]').value,
hitId = document.querySelector('input[name="hitId"]').value,
autoApproval = document.querySelector('input[name="hitAutoAppDelayInSeconds"]').value,
hitTitle = document.querySelector('div[style*="ellipsis"]').textContent.trim().replace(/\|/g,""),
insertionNode = qualNode.parentNode.parentNode;
var row = document.createElement("TR"), cellL = document.createElement("TD"), cellR = document.createElement("TD");
cellR.innerHTML = '<span class="capsule_field_title">Auto-Approval:</span> '+_ftime(autoApproval);
var rbutton = document.createElement("BUTTON");
rbutton.classList.add("hitdbRTButtons","hitdbRTButtons-large");
rbutton.textContent = "Requester";
rbutton.onclick = function(e) { e.preventDefault(); };
var tbutton = rbutton.cloneNode(false);
tbutton.textContent = "HIT Title";
tbutton.onclick = function(e) { e.preventDefault(); };
HITStorage.recall("HIT", {index: "requesterId", range: window.IDBKeyRange.only(requester)})
.then(processResults.bind(rbutton));
HITStorage.recall("HIT", {index: "title", range: window.IDBKeyRange.only(hitTitle)})
.then(processResults.bind(tbutton));
row.appendChild(cellL);
row.appendChild(cellR);
cellL.appendChild(rbutton);
cellL.appendChild(tbutton);
cellL.colSpan = "3";
cellR.colSpan = "8";
insertionNode.appendChild(row);
} else { // browsing HITs n sutff
var titleNodes = document.querySelectorAll('a[class="capsulelink"]');
if (titleNodes.length < 1) return; // nothing left to do here!
var requesterNodes = document.querySelectorAll('a[href*="hitgroups&requester"]');
var insertionNodes = [];
for (var i=0;i<titleNodes.length;i++) {
var _title = titleNodes[i].textContent.trim().replace(/\|/g,"");
var _tbutton = document.createElement("BUTTON");
var _id = requesterNodes[i].href.replace(/.+Id=(.+)/, "$1");
var _rbutton = document.createElement("BUTTON");
var _div = document.createElement("DIV"), _tr = document.createElement("TR");
insertionNodes.push(requesterNodes[i].parentNode.parentNode.parentNode);
HITStorage.recall("HIT", {index: "title", range: window.IDBKeyRange.only(_title)} )
.then(processResults.bind(_tbutton));
HITStorage.recall("HIT", {index: "requesterId", range: window.IDBKeyRange.only(_id)} )
.then(processResults.bind(_rbutton));
_tr.appendChild(_div);
_div.id = "hitdbRTInjection-"+i;
_div.appendChild(_rbutton);
_rbutton.textContent = 'R';
_rbutton.classList.add("hitdbRTButtons");
_div.appendChild(_tbutton);
_tbutton.textContent = 'T';
_tbutton.classList.add("hitdbRTButtons");
insertionNodes[i].appendChild(_tr);
}
} // else
function processResults(r) {
/*jshint validthis: true*/
if (r.results.length) this.classList.add("hitdbRTButtons-green");
}
function _ftime(t) {
var d = Math.floor(t/86400);
var h = Math.floor(t%86400/3600);
var m = Math.floor(t%86400%3600/60);
var s = t%86400%3600%60;
return ((d>0) ? d+" day"+(d>1 ? "s " : " ") : "") + ((h>0) ? h+"h " : "") + ((m>0) ? m+"m " : "") + ((s>0) ? s+"s" : "");
}
}//}}} btdt
function dashboardUI() {//{{{
//
// TODO refactor
//
'use strict';
var controlPanel = document.createElement("TABLE");
var insertionNode = document.querySelector(".footer_separator").previousSibling;
document.body.insertBefore(controlPanel, insertionNode);
controlPanel.width = "760";
controlPanel.align = "center";
controlPanel.cellSpacing = "0";
controlPanel.cellPadding = "0";
controlPanel.innerHTML = '<tr height="25px"><td width="10" bgcolor="#7FB448" style="padding-left: 10px;"></td>' +
'<td class="white_text_14_bold" style="padding-left:10px; background-color:#7FB448;">' +
'HIT Database Mk. II <a href="https://gf.qytechs.cn/en/scripts/11733-mturk-hit-database-mk-ii" class="whatis" target="_blank">' +
'(What\'s this?)</a></td></tr>' +
'<tr><td class="container-content" colspan="2">' +
'<div style="text-align:center;" id="hdbDashboardInterface">' +
'<button id="hdbBackup" title="Export your entire database!\nPerfect for moving between computers or as a periodic backup">Create Backup</button>' +
'<button id="hdbRestore" title="Restore database from external backup file" style="margin:5px">Restore</button>' +
'<button id="hdbUpdate" title="Update... the database" style="color:green;">Update Database</button>' +
'<div id="hdbFileSelector" style="display:none"><input id="hdbFileInput" type="file" /></div>' +
'<br>' +
'<button id="hdbPending" title="Summary of all pending HITs\n Can be exported as CSV" style="margin: 0px 5px 5px;">Pending Overview</button>' +
'<button id="hdbRequester" title="Summary of all requesters\n Can be exported as CSV" style="margin: 0px 5px 5px;">Requester Overview</button>' +
'<button id="hdbDaily" title="Summary of each day you\'ve worked\nCan be exported as CSV" style="margin:0px 5px 5px;">Daily Overview</button>' +
'<br>' +
'<label>Find </label>' +
'<select id="hdbStatusSelect"><option value="*">ALL</option><option value="Approval" style="color: orange;">Pending Approval</option>' +
'<option value="Rejected" style="color: red;">Rejected</option><option value="Approved" style="color:green;">Approved - Pending Payment</option>' +
'<option value="(Paid|Approved)" style="color:green;">Paid OR Approved</option></select>' +
'<label> HITs matching: </label><input id="hdbSearchInput" title="Query can be HIT title, HIT ID, or requester name" />' +
'<button id="hdbSearch">Search</button>' +
'<br>' +
'<label>from date </label><input id="hdbMinDate" maxlength="10" size="10" title="Specify a date, or leave blank">' +
'<label> to </label><input id="hdbMaxDate" malength="10" size="10" title="Specify a date, or leave blank">' +
'<label for="hdbCSVInput" title="Export results as CSV file" style="margin-left:50px; vertical-align:middle;">export CSV</label>' +
'<input id="hdbCSVInput" title="Export results as CSV file" type="checkbox" style="vertical-align:middle;">' +
'<br>' +
'<label id="hdbStatusText">placeholder status text</label>' +
'<div id="hdbProgressBar" class="hdbProgressContainer"><div class="hdbProgressOuter"><div class="hdbProgressInner"></div></div></div>' +
'</div></td></tr>';
var updateBtn = document.querySelector("#hdbUpdate"),
backupBtn = document.querySelector("#hdbBackup"),
restoreBtn = document.querySelector("#hdbRestore"),
fileInput = document.querySelector("#hdbFileInput"),
exportCSVInput = document.querySelector("#hdbCSVInput"),
searchBtn = document.querySelector("#hdbSearch"),
searchInput = document.querySelector("#hdbSearchInput"),
pendingBtn = document.querySelector("#hdbPending"),
reqBtn = document.querySelector("#hdbRequester"),
dailyBtn = document.querySelector("#hdbDaily"),
fromdate = document.querySelector("#hdbMinDate"),
todate = document.querySelector("#hdbMaxDate"),
statusSelect = document.querySelector("#hdbStatusSelect"),
progressBar = document.querySelector("#hdbProgressBar");
var searchResults = document.createElement("DIV");
searchResults.align = "center";
searchResults.id = "hdbSearchResults";
searchResults.style.display = "block";
searchResults.innerHTML = '<table cellSpacing="0" cellpadding="2"></table>';
document.body.insertBefore(searchResults, insertionNode);
updateBtn.onclick = function() {
progressBar.style.display = "block";
HITStorage.fetch(MTURK_BASE+"status");
document.querySelector("#hdbStatusText").textContent = "fetching status page....";
};
exportCSVInput.addEventListener("click", function() {
if (exportCSVInput.checked) {
searchBtn.textContent = "Export CSV";
pendingBtn.textContent += " (csv)";
reqBtn.textContent += " (csv)";
dailyBtn.textContent += " (csv)";
}
else {
searchBtn.textContent = "Search";
pendingBtn.textContent = pendingBtn.textContent.replace(" (csv)","");
reqBtn.textContent = reqBtn.textContent.replace(" (csv)","");
dailyBtn.textContent = dailyBtn.textContent.replace(" (csv)", "");
}
});
fromdate.addEventListener("focus", function() {
var offsets = getPosition(this, true);
new Calendar(offsets.x, offsets.y, this).drawCalendar();
});
todate.addEventListener("focus", function() {
var offsets = getPosition(this, true);
new Calendar(offsets.x, offsets.y, this).drawCalendar();
});
backupBtn.onclick = HITStorage.backup;
restoreBtn.onclick = function() { fileInput.click(); };
fileInput.onchange = processFile;
searchBtn.onclick = function() {
var r = getRange();
var _filter = { status: statusSelect.value, query: searchInput.value.trim().length > 0 ? searchInput.value : "*" };
var _opt = { index: "date", range: r.range, dir: r.dir, filter: _filter, progress: true };
HITStorage.recall("HIT", _opt).then(function(r) {
searchResults.firstChild.innerHTML = exportCSVInput.checked ? r.formatCSV() : r.formatHTML();
autoScroll("#hdbSearchResults");
var bonusCells = document.querySelectorAll('td[contenteditable="true"]');
for (var el of bonusCells) {
el.dataset.storedValue = el.textContent;
el.onblur = updateBonus;
el.onkeydown = updateBonus;
}
progressBar.style.display = "none";
});
}; // search button click event
pendingBtn.onclick = function() {
var r = getRange();
var _filter = { status: "Approval", query: searchInput.value.trim().length > 0 ? searchInput.value : "*" },
_opt = { index: "date", dir: "prev", range: r.range, filter: _filter, progress: true };
HITStorage.recall("HIT", _opt).then(function(r) {
searchResults.firstChild.innerHTML = exportCSVInput.checked ? r.formatCSV("pending") : r.formatHTML("pending");
autoScroll("#hdbSearchResults");
var expands = document.querySelectorAll(".hdbExpandRow");
for (var el of expands) {
el.onclick = showHiddenRows;
}
progressBar.style.display = "none";
});
}; //pending overview click event
reqBtn.onclick = function() {
var r = getRange();
var _opt = { index: "date", range: r.range, progress: true };
HITStorage.recall("HIT", _opt).then(function(r) {
searchResults.firstChild.innerHTML = exportCSVInput.checked ? r.formatCSV("requester") : r.formatHTML("requester");
autoScroll("#hdbSearchResults");
var expands = document.querySelectorAll(".hdbExpandRow");
for (var el of expands) {
el.onclick = showHiddenRows;
}
progressBar.style.display = "none";
});
}; //requester overview click event
dailyBtn.onclick = function() {
HITStorage.recall("STATS", { dir: "prev" }).then(function(r) {
searchResults.firstChild.innerHTML = exportCSVInput.checked ? r.formatCSV("daily") : r.formatHTML("daily");
autoScroll("#hdbSearchResults");
});
}; //daily overview click event
function getRange() {
var _min = fromdate.value.length === 10 ? fromdate.value : undefined,
_max = todate.value.length === 10 ? todate.value : undefined;
var _range =
(_min === undefined && _max === undefined) ? null :
(_min === undefined) ? window.IDBKeyRange.upperBound(_max) :
(_max === undefined) ? window.IDBKeyRange.lowerBound(_min) :
(_max < _min) ? window.IDBKeyRange.bound(_max,_min) : window.IDBKeyRange.bound(_min,_max);
return { min: _min, max: _max, range: _range, dir: _max < _min ? "prev" : "next" };
}
function getPosition(element, includeHeight) {
var offsets = { x: 0, y: includeHeight ? element.offsetHeight : 0 };
do {
offsets.x += element.offsetLeft;
offsets.y += element.offsetTop;
element = element.offsetParent;
} while (element);
return offsets;
}
}//}}} dashboard
function showHiddenRows(e) {//{{{
'use strict';
var rid = e.target.parentNode.textContent.substr(4);
var nodes = document.querySelectorAll('tr[data-rid="'+rid+'"]'), el = null;
if (e.target.textContent === "[+]") {
for (el of nodes)
el.style.display="table-row";
e.target.textContent = "[-]";
} else {
for (el of nodes)
el.style.display="none";
e.target.textContent = "[+]";
}
}//}}}
function updateBonus(e) {//{{{
'use strict';
if (e instanceof window.KeyboardEvent && e.keyCode === 13) {
e.target.blur();
return false;
} else if (e instanceof window.FocusEvent) {
var _bonus = +e.target.textContent.replace(/\$/,"");
if (_bonus !== +e.target.dataset.storedValue) {
console.log("updating bonus to",_bonus,"from",e.target.dataset.storedValue,"("+e.target.dataset.hitid+")");
e.target.dataset.storedValue = _bonus;
var _pay = +e.target.previousSibling.textContent,
_range = window.IDBKeyRange.only(e.target.dataset.hitid);
window.indexedDB.open("HITDB_TESTING").onsuccess = function() {
this.result.transaction("HIT", "readwrite").objectStore("HIT").openCursor(_range).onsuccess = function() {
var c = this.result;
if (c) {
var v = c.value;
v.reward = { pay: _pay, bonus: _bonus };
c.update(v);
}
}; // idbcursor
}; // idbopen
} // bonus is new value
} // keycode
} //}}} updateBonus
function processFile(e) {//{{{
'use strict';
var f = e.target.files;
if (f.length && f[0].name.search(/\.bak$/) && ~f[0].type.search(/text/)) {
var reader = new FileReader(), testing = true;
reader.readAsText(f[0].slice(0,10));
reader.onload = function(e) {
if (testing && e.target.result.search(/(STATS|NOTES|HIT)/) < 0) {
return error();
} else if (testing) {
testing = false;
document.querySelector("#hdbProgressBar").style.display = "block";
reader.readAsText(f[0]);
} else {
var data = JSON.parse(e.target.result);
console.log(data);
HITStorage.write(data, "restore");
}
}; // reader.onload
} else {
error();
}
function error() {
var s = document.querySelector("#hdbStatusText"),
e = "Restore::FileReadError : encountered unsupported file";
s.style.color = "red";
s.textContent = e;
throw e;
}
}//}}} processFile
function autoScroll(location, dt) {//{{{
'use strict';
var target = document.querySelector(location).offsetTop,
pos = window.scrollY,
dpos = Math.ceil((target - pos)/3);
dt = dt ? dt-1 : 25; // time step/max recursions
if (target === pos || dpos === 0 || dt === 0) return;
window.scrollBy(0, dpos);
setTimeout(function() { autoScroll(location, dt); }, dt);
}//}}}
function Calendar(offsetX, offsetY, caller) {//{{{
'use strict';
this.date = new Date();
this.offsetX = offsetX;
this.offsetY = offsetY;
this.caller = caller;
this.drawCalendar = function(year,month,day) {//{{{
year = year || this.date.getFullYear();
month = month || this.date.getMonth()+1;
day = day || this.date.getDate();
var longMonths = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
var date = new Date(year,month-1,day);
var anchors = _getAnchors(date);
//make new container if one doesn't already exist
var container = null;
if (document.querySelector("#hdbCalendarPanel")) {
container = document.querySelector("#hdbCalendarPanel");
container.removeChild( container.getElementsByTagName("TABLE")[0] );
}
else {
container = document.createElement("DIV");
container.id = "hdbCalendarPanel";
document.body.appendChild(container);
}
container.style.left = this.offsetX;
container.style.top = this.offsetY;
var cal = document.createElement("TABLE");
cal.cellSpacing = "0";
cal.cellPadding = "0";
cal.border = "0";
container.appendChild(cal);
cal.innerHTML = '<tr>' +
'<th class="hdbCalHeader hdbCalControls" title="Previous month" style="text-align:right;"><span><</span></th>' +
'<th class="hdbCalHeader hdbCalControls" title="Previous year" style="text-align:center;"><span>≪</span></th>' +
'<th colspan="3" id="hdbCalTableTitle" class="hdbCalHeader">'+date.getFullYear()+'<br>'+longMonths[date.getMonth()]+'</th>' +
'<th class="hdbCalHeader hdbCalControls" title="Next year" style="text-align:center;"><span>≫</span></th>' +
'<th class="hdbCalHeader hdbCalControls" title="Next month" style="text-align:left;"><span>></span></th>' +
'</tr><tr><th class="hdbDayHeader" style="color:red;">S</th><th class="hdbDayHeader">M</th>' +
'<th class="hdbDayHeader">T</th><th class="hdbDayHeader">W</th><th class="hdbDayHeader">T</th>' +
'<th class="hdbDayHeader">F</th><th class="hdbDayHeader">S</th></tr>';
document.querySelector('th[title="Previous month"]').addEventListener( "click", function() {
this.drawCalendar(date.getFullYear(), date.getMonth(), 1);
}.bind(this) );
document.querySelector('th[title="Previous year"]').addEventListener( "click", function() {
this.drawCalendar(date.getFullYear()-1, date.getMonth()+1, 1);
}.bind(this) );
document.querySelector('th[title="Next month"]').addEventListener( "click", function() {
this.drawCalendar(date.getFullYear(), date.getMonth()+2, 1);
}.bind(this) );
document.querySelector('th[title="Next year"]').addEventListener( "click", function() {
this.drawCalendar(date.getFullYear()+1, date.getMonth()+1, 1);
}.bind(this) );
var hasDay = false, thisDay = 1;
for (var i=0;i<6;i++) { // cycle weeks
var row = document.createElement("TR");
for (var j=0;j<7;j++) { // cycle days
if (!hasDay && j === anchors.first && thisDay < anchors.total)
hasDay = true;
else if (hasDay && thisDay > anchors.total)
hasDay = false;
var cell = document.createElement("TD");
cell.classList.add("hdbCalCells");
row.appendChild(cell);
if (hasDay) {
cell.classList.add("hdbCalDays");
cell.textContent = thisDay;
cell.addEventListener("click", _clickHandler.bind(this));
cell.dataset.year = date.getFullYear();
cell.dataset.month = date.getMonth()+1;
cell.dataset.day = thisDay++;
}
} // for j
cal.appendChild(row);
} // for i
function _clickHandler(e) {
/*jshint validthis:true*/
var y = e.target.dataset.year;
var m = Number(e.target.dataset.month).toPadded();
var d = Number(e.target.dataset.day).toPadded();
this.caller.value = y+"-"+m+"-"+d;
this.die();
}
function _getAnchors(date) {
var _anchors = {};
date.setMonth(date.getMonth()+1);
date.setDate(0);
_anchors.total = date.getDate();
date.setDate(1);
_anchors.first = date.getDay();
return _anchors;
}
};//}}} drawCalendar
this.die = function() { document.querySelector("#hdbCalendarPanel").remove(); };
}//}}} Calendar
/*
*
*
* * * * * * * * * * * * * TESTING FUNCTIONS -- DELETE BEFORE FINAL RELEASE * * * * * * * * * * *
*
*
*/
function INFLATEDUMMYVALUES() { //{{{
'use strict';
var tdb = this.result;
tdb.onerror = function(e) { console.log("requesterror",e.target.error.name,e.target.error.message,e); };
tdb.onversionchange = function(e) { console.log("tdb received versionchange request", e); tdb.close(); };
//console.log(tdb.transaction("HIT").objectStore("HIT").indexNames.contains("date"));
console.groupCollapsed("Populating test database");
var tdbt = {};
tdbt.trans = tdb.transaction(["HIT", "NOTES", "BLOCKS"], "readwrite");
tdbt.hit = tdbt.trans.objectStore("HIT");
tdbt.notes = tdbt.trans.objectStore("NOTES");
tdbt.blocks= tdbt.trans.objectStore("BLOCKS");
var filler = { notes:[], hit:[], blocks:[]};
for (var n=0;n<100000;n++) {
filler.hit.push({ date: "2015-08-00", requesterName: "tReq"+(n+1), title: "Greatest Title Ever #"+(n+1),
reward: Number((n+1)%(200/n)+(((n+1)%200)/100)).toFixed(2), status: "moo",
requesterId: ("RRRRRRR"+n).substr(-7), hitId: ("HHHHHHH"+n).substr(-7) });
if (n%1000 === 0) {
filler.notes.push({ requesterId: ("RRRRRRR"+n).substr(-7), note: n+1 +
" Proin vel erat commodo mi interdum rhoncus. Sed lobortis porttitor arcu, et tristique ipsum semper a." +
" Donec eget aliquet lectus, vel scelerisque ligula." });
filler.blocks.push({requesterId: ("RRRRRRR"+n).substr(-7)});
}
}
_write(tdbt.hit, filler.hit);
_write(tdbt.notes, filler.notes);
_write(tdbt.blocks, filler.blocks);
function _write(store, obj) {
if (obj.length) {
var t = obj.pop();
store.put(t).onsuccess = function() { _write(store, obj) };
} else {
console.log("population complete");
}
}
console.groupEnd();
dbh = window.indexedDB.open("HITDB_TESTING", DB_VERSION);
dbh.onerror = function(e) { console.log("[HITDB]",e.target.error.name+":", e.target.error.message, e); };
console.log(dbh.readyState, dbh);
dbh.onupgradeneeded = HITStorage.versionChange;
dbh.onblocked = function(e) { console.log("blocked event triggered:", e); };
tdb.close();
}//}}}
function BLANKSLATE() { //{{{ create empty db equivalent to original schema to test upgrade
'use strict';
var tdb = this.result;
if (!tdb.objectStoreNames.contains("HIT")) {
console.log("creating HIT OS");
var dbo = tdb.createObjectStore("HIT", { keyPath: "hitId" });
dbo.createIndex("date", "date", { unique: false });
dbo.createIndex("requesterName", "requesterName", { unique: false});
dbo.createIndex("title", "title", { unique: false });
dbo.createIndex("reward", "reward", { unique: false });
dbo.createIndex("status", "status", { unique: false });
dbo.createIndex("requesterId", "requesterId", { unique: false });
}
if (!tdb.objectStoreNames.contains("STATS")) {
console.log("creating STATS OS");
dbo = tdb.createObjectStore("STATS", { keyPath: "date" });
}
if (!tdb.objectStoreNames.contains("NOTES")) {
console.log("creating NOTES OS");
dbo = tdb.createObjectStore("NOTES", { keyPath: "requesterId" });
}
if (!tdb.objectStoreNames.contains("BLOCKS")) {
console.log("creating BLOCKS OS");
dbo = tdb.createObjectStore("BLOCKS", { keyPath: "id", autoIncrement: true });
dbo.createIndex("requesterId", "requesterId", { unique: false });
}
} //}}}
// vim: ts=2:sw=2:et:fdm=marker:noai