// Journal object chooser dialog define(['picoModal','sugar-web/datastore','sugar-web/graphics/icon','mustache','sugar-web/env','sugar-web/graphics/radiobuttonsgroup'], function(picoModal,datastore,icon,mustache,env,radioButtonsGroup) { var chooser = {}; // Load settings var userSettings = null; var activities = []; env.getEnvironment(function(err, environment) { userSettings = environment.user; for (var i = 0 ; userSettings.activities && i < userSettings.activities.length ; i++) { var activity = userSettings.activities[i]; activities[activity.id] = activity; } }); // Chooser feature to search in the Local Journal var featureLocalJournal = {}; featureLocalJournal.id = "journal-button"; featureLocalJournal.title = "$titleJournal"; featureLocalJournal.placeholder = "$holderSearchJournal"; featureLocalJournal.icon = "lib/sugar-web/graphics/icons/actions/activity-journal.svg"; featureLocalJournal.beforeActivate = function() { featureLocalJournal.isFavorite = false; document.getElementById('favorite-button').style.backgroundImage = "url(lib/sugar-web/graphics/icons/emblems/favorite.svg)"; journalFill.apply(null, featureLocalJournal.filters); }; featureLocalJournal.beforeUnactivate = function() { }; featureLocalJournal.onFavorite = function() { var favorite = document.getElementById('favorite-button'); if (!featureLocalJournal.isFavorite) { icon.colorize(favorite, userSettings.colorvalue, function() {}); } else { favorite.style.backgroundImage = "url(lib/sugar-web/graphics/icons/emblems/favorite.svg)"; } featureLocalJournal.isFavorite = !featureLocalJournal.isFavorite; journalFill.apply(null, featureLocalJournal.filters); }; featureLocalJournal.onScroll = function() {}; featureLocalJournal.onSearch = function() { journalFill.apply(null, featureLocalJournal.filters); }; featureLocalJournal.onCancelSearch = function() { journalFill.apply(null, featureLocalJournal.filters); }; // Chooser feature to search in Abecedarium database var featureAbecedarium = {}; featureAbecedarium.id = "abecedarium-button"; featureAbecedarium.title = "$titleAbecedarium"; featureAbecedarium.placeholder = "$holderSearchAbecedarium"; featureAbecedarium.icon = "lib/sugar-web/graphics/icons/actions/activity-abecedarium.svg"; featureAbecedarium.beforeActivate = function() { document.getElementById('favorite-button').style.visibility = "hidden"; abecedariumInit(abecedariumFill); }; featureAbecedarium.beforeUnactivate = function() { document.getElementById('favorite-button').style.visibility = "visible"; }; featureAbecedarium.onFavorite = function() {}; featureAbecedarium.onScroll = function() { var container = document.getElementById('journal-container'); var scrollValue = (container.scrollTop) / (container.scrollHeight - container.clientHeight); if (scrollValue > 0.90) { featureAbecedarium.resultCount += 30; abecedariumDisplay(featureAbecedarium.results, featureAbecedarium.resultCount); } }; featureAbecedarium.onSearch = function() { abecedariumFill(); }; featureAbecedarium.onCancelSearch = function() { abecedariumFill(); }; // Init feature list: overload it if you want to change the feature list at init var features = []; var currentFeature = -1; chooser.init = function() { features = [featureLocalJournal]; currentFeature = 0; } // Display object chooser dialog with journal content // Each filter has the form = {title: "%hysics", creation_time: "<12222", activity: ["org.olpcfrance.Paint","org.olpcfrance.Record"]} // When more than one filter is provided, an entry is valid if it match one filter or more. Usage examples: // chooser.show(); // chooser.show({title: 'Paint Activity', creation_time: '>'+now.getTime()}); // chooser.show({title: ['Paint Activity', '%hysics']}); // chooser.show({keep: 1}); // chooser.show({activity: 'org.olpcfrance.PaintActivity'}, {mimetype: 'image/png'}) var modal; var result; chooser.show = function(callback, filter1, orFilter2, orFilter3, orFilter4) { result = null; chooser.init(); var imageType = "image/png"; var soundType = /Android/i.test(navigator.userAgent)?"audio/ogg":"audio/mpeg"; var filters = [filter1, orFilter2, orFilter3, orFilter4]; for (var i = 0 ; i < filters.length ; i++) { if (filters[i]) { if (filters[i].mimetype == imageType) { featureAbecedarium.fileformat = ".png"; featureAbecedarium.mimetype = imageType; featureAbecedarium.filelocation = "images/database/"; features.push(featureAbecedarium); break; } else if (filters[i].mimetype == soundType) { featureAbecedarium.fileformat = (soundType == "audio/ogg"?".ogg":".mp3"); featureAbecedarium.mimetype = soundType; featureAbecedarium.filelocation = "audio/{{lang}}/database/"; features.push(featureAbecedarium); break; } } } var contentHeader = "
"; for (var i = 0 ; i < features.length ; i++) { contentHeader += ""; } contentHeader += ""; contentHeader += "
$titleChoose
"; modal = picoModal({ content: doLocalize(contentHeader+"\
\ \ \ \ \ \
\
\
\
$noMatchingEntries
\
\
"), closeButton: false, modalStyles: { backgroundColor: "white", height: "400px", width: "600px", maxWidth: "90%" } }) .afterShow(function(modal) { var color = userSettings.colorvalue; icon.colorize(document.getElementById(features[currentFeature].id), color, function() {}); var radios = []; for (var i = 0 ; i < features.length ; i++) { var radio = document.getElementById(features[i].id); radios.push(radio); radio.addEventListener("click", function(e) { var index = -1; for (var j = 0 ; j < features.length ; j++) { if (features[j].id == e.srcElement.id) { index = j; break; } } if (index != currentFeature) { features[currentFeature].beforeUnactivate(); document.getElementById(features[currentFeature].id).style.backgroundImage = "url("+features[currentFeature].icon+")"; document.getElementById('journal-container').innerHTML = ""; document.getElementById('search-text').value = ''; currentFeature = index; features[currentFeature].filters = [filter1, orFilter2, orFilter3, orFilter4]; features[currentFeature].beforeActivate(); icon.colorize(document.getElementById(features[currentFeature].id), color, function() {}); document.getElementById('search-text').placeholder=doLocalize(features[currentFeature].placeholder); } }) } new radioButtonsGroup.RadioButtonsGroup(radios); var targetHeight = document.getElementById("pictotoolbar").parentNode.offsetHeight - 55*2; document.getElementById('journal-container').style.height = targetHeight + "px"; document.getElementById('journal-container').addEventListener("scroll", function() { features[currentFeature].onScroll(); }); var favorite = document.getElementById('favorite-button'); favorite.addEventListener('click', function() { features[currentFeature].onFavorite(); }); document.getElementById('search-text').addEventListener('keyup', function() { features[currentFeature].onSearch(); }); document.getElementById('cancel-search').addEventListener('click', function() { document.getElementById('search-text').value = ''; features[currentFeature].onCancelSearch(); }); document.getElementById('close-button').addEventListener('click', function() { result = null; modal.close(); }); document.getElementById('search-text').placeholder=doLocalize(features[currentFeature].placeholder); features[currentFeature].filters = [filter1, orFilter2, orFilter3, orFilter4]; features[currentFeature].beforeActivate(); }) .afterClose(function(modal) { modal.destroy(); if (callback) { callback(result); } }) .show(); } // --- Journal feature functions // Get journal entries and fill dialog with entries function journalFill(filter1, orFilter2, orFilter3, orFilter4) { // Get filters var filters = []; if (filter1) { filters.push(filter1); } if (orFilter2) { filters.push(orFilter2); } if (orFilter3) { filters.push(orFilter3); } if (orFilter4) { filters.push(orFilter4); } // Compute filtering var journal = datastore.find(); var rawJournal = []; for (var i = 0 ; i < journal.length ; i++) { var entry = journal[i]; var match = true; if (featureLocalJournal.isFavorite) { match = match && entry.metadata.keep; } var title = document.getElementById('search-text').value; if (title && title.length > 0) { match = match && (entry.metadata.title && (entry.metadata.title.indexOf(title) != -1)); } if (match) { rawJournal.push(entry) } } var length = filters.length; var filteredJournal = rawJournal; if (length > 0) { filteredJournal = []; for(var i = 0 ; i < rawJournal.length ; i++) { var entry = rawJournal[i]; var match = false; for (var j = 0 ; j < length ; j++) { match = match || journalFilterMatch(entry, filters[j]); } if (match) { filteredJournal.push(entry); } } } // Get entries var journal = {entries: filteredJournal.sort(function(e0, e1) { return parseInt(e1.metadata.timestamp) - parseInt(e0.metadata.timestamp); })}; // Add style properties for (var i = 0 ; i < journal.entries.length ; i++) { var entry = journal.entries[i]; var activity = activities[entry.metadata.activity]; entry.imageUrl = "../../" + activity.directory + "/" + activity.icon; entry.index = i; entry.ago = timestampToElapsedString(entry.metadata.creation_time); } // Draw it var template = "\ {{#entries}}\
\
\
{{metadata.title}}
\
{{ago}}
\
\ {{/entries}}\ "; var render = mustache.render(template, journal); document.getElementById('journal-container').innerHTML = render; // Handle click for (var i = 0 ; i < journal.entries.length ; i++) { var entry = document.getElementById('entry_'+i); entry.addEventListener('click', function(e) { var id = e.target.id; id = id.substr(id.indexOf("_")+1); var line = document.getElementById('entry_'+id); line.style.backgroundColor = "#808080"; result = journal.entries[id]; delete result['ago']; delete result['index']; delete result['imageUrl']; window.setTimeout(function() { modal.close(result); }, 200); }); } document.getElementById('journal-empty').style.visibility = (journal.entries.length != 0 ? 'hidden' : 'visible'); } // Test if an entry match a value function journalEntryMatch(entry, name, value) { var fieldValue = entry[name]; if (!fieldValue) { return false; } var firstChar = value[0]; if (firstChar == '%') { return fieldValue.indexOf(value.substr(1)) != -1; } else if (firstChar == '>') { return parseInt(fieldValue) > parseInt(value.substr(1)); } else if (firstChar == '<') { return parseInt(fieldValue) < parseInt(value.substr(1)); } else { return fieldValue == value; } } // Test if an entry match a filter function journalFilterMatch(entry, filter) { var metadata = entry.metadata; var keys = Object.keys(filter); var match = true; for (var j = 0 ; j < keys.length ; j++) { var field = keys[j]; var value = filter[field]; if (value instanceof Array) { var or = false; for (var k = 0 ; k < value.length ; k++) { or = or || journalEntryMatch(metadata, field, value[k]); } match = match && or; } else { match = match && journalEntryMatch(metadata, field, value); } } return match; } // --- Abecedarium feature functions // Load Abecedarium database function abecedariumInit(callback) { featureAbecedarium.database = {}; document.getElementById('journal-empty').style.visibility = 'visible'; featureAbecedarium.baseURL = document.location.href.substr(0, document.location.href.indexOf("/activities/"))+"/activities/Abecedarium.activity/"; featureAbecedarium.lang = (["en","es","fr"].indexOf(userSettings.language)!=-1)?userSettings.language:"en"; featureAbecedarium.filelocation = featureAbecedarium.filelocation.replace("{{lang}}",featureAbecedarium.lang); var count = 0; var loadDatabase = function(file, entry) { var client = new XMLHttpRequest(); var source = featureAbecedarium.baseURL+file; client.onload = function() { if (entry == "ping") { featureAbecedarium.database[entry]=(this.status == 0 || (this.status >= 200 && this.status < 300)); return; } if (this.status == 0 || (this.status >= 200 && this.status < 300)) { featureAbecedarium.database[entry]=JSON.parse(this.responseText); if (++count == 3) { var len = featureAbecedarium.database.meta.length; var entries = []; for (var i = 0 ; i < len; i++) { if (featureAbecedarium.database.meta[i][featureAbecedarium.lang]) { entries.push({"code":featureAbecedarium.database.meta[i]["code"],"text":featureAbecedarium.database.words[featureAbecedarium.database.meta[i]["text"]]}); } } entries.sort(function(a,b) { return a.text.localeCompare(b.text, 'en', {sensitivity: 'base'}); }); var sortedEntries = []; for (var i = 0 ; i < entries.length ; i++) { entries[i].i = sortedEntries.length; sortedEntries.push(entries[i]); } featureAbecedarium.database.content = sortedEntries; delete featureAbecedarium.database.meta; delete featureAbecedarium.database.words; callback(); } } }; client.open("GET", source); client.send(); }; loadDatabase("database/db_url.json", "url"); loadDatabase("database/db_meta.json", "meta"); loadDatabase("database/db_"+featureAbecedarium.lang+".json", "words"); loadDatabase("images/database/_ping.png?"+(new Date()).getTime(), "ping"); } // Fill popup with Abecedarium items function abecedariumFill() { // Find entries matching search field var content = featureAbecedarium.database.content; var title = document.getElementById('search-text').value.toLowerCase(); if (title.length) { content = []; for (var i = 0 ; i < featureAbecedarium.database.content.length ; i++) { if (featureAbecedarium.database.content[i].text.toLowerCase().indexOf(title) != -1) { content.push(featureAbecedarium.database.content[i]); } } } // Display result featureAbecedarium.results = content; featureAbecedarium.resultCount = 30; abecedariumDisplay(content, featureAbecedarium.resultCount); } function abecedariumDisplay(content, count) { // Display entries var template = "\ {{#items}}\
\
\
{{text}}
\
\ {{/items}}\ "; var items = {items: content.slice(0, count)}; var render = mustache.render(template, items); document.getElementById('journal-empty').style.visibility = (content.length != 0 ? 'hidden' : 'visible'); document.getElementById('journal-container').innerHTML = render; // Handle click var len = Math.min(count, content.length); for (var i = 0 ; i < len; i++) { var entry = document.getElementById('entry_'+content[i].i); entry.addEventListener('click', function(e) { var id = e.target.id; id = id.substr(id.indexOf("_")+1); var line = document.getElementById('entry_'+id); line.style.backgroundColor = "#808080"; abecedariumCreateEntry(featureAbecedarium.database.content[id], function() { modal.close(result); }); }); if (featureAbecedarium.mimetype == "image/png") { document.getElementById('eicon_'+content[i].i).style.backgroundImage="url("+(featureAbecedarium.database.ping?featureAbecedarium.baseURL:featureAbecedarium.database.url)+"images/database/"+content[i].code+".png"+")"; } } } // Create a record in Journal for the entry function abecedariumCreateEntry(entry, callback) { var url = featureAbecedarium.database.ping?featureAbecedarium.baseURL:featureAbecedarium.database.url; var mimetype = featureAbecedarium.mimetype; var request = new XMLHttpRequest(); request.open("GET",url+featureAbecedarium.filelocation+entry.code+featureAbecedarium.fileformat,true); request.setRequestHeader("Content-type",mimetype); request.responseType = "arraybuffer"; var that = this; request.onload = function() { if (request.status == 200 || request.status == 0) { var blob = new Uint8Array(this.response); var base64 = "data:"+mimetype+";base64,"+toBase64(blob); var metadata = { mimetype: mimetype, title: entry.text+featureAbecedarium.fileformat, activity: "org.olpcfrance.MediaViewerActivity", timestamp: new Date().getTime(), creation_time: new Date().getTime(), file_size: 0 }; datastore.create(metadata, function(err, objectId) { console.log("Entry '"+entry.text+"' saved in journal."); result = {metadata:metadata, objectId: objectId}; callback(); }, base64); } else { console.log("Error loading entry '"+entry.code+"'."); callback(); } }; request.onerror = function() { console.log("Error loading entry '"+entry.code+"'."); callback(); }; request.send(); } // --- Utility functions // Localize content - currently means only localize in English var l10n = { titleJournal: {en: 'Journal', fr: 'Journal', es: 'Diario', pt: 'Diário'}, titleAbecedarium: {en: 'Abecedarium', fr: 'Abecedarium', es: 'Abecedarium', pt: 'Abecedarium'}, titleClose: {en: 'Cancel', fr: 'Annuler', es: 'Cancelar', pt: 'Cancelar'}, titleChoose: {en: 'Choose an object', fr: 'Choisir un objet', es: 'Elige un objeto', pt: 'Escolher um objeto'}, holderSearchJournal: {en: 'Search in Journal', fr: 'Recherche dans le journal', es: 'Buscar en el diario', pt: 'Pesquisar no diário'}, holderSearchAbecedarium: {en: 'Search in Abecedarium', fr: 'Recherche dans Abecedarium', es: 'Buscar en Abecedarium', pt: 'Pesquisar no Abecedarium'}, noMatchingEntries: {en: 'No matching entries', fr: 'Aucune entrée correspondante', es: 'No hay actividades coincidentes', pt: 'Sem atividades correspondentes'}, SecondsAgo: {en: 'Seconds ago', fr: "A l'instant", es: 'Segundos atrás', pt: 'Segundos atrás'}, Ago: {en: '{{time}} ago', fr: 'il y a {{time}}', es: '{{time}} atrás', pt: '{{time}} atrás'}, Minutes_one: {en: 'minute', fr: 'minute', es: 'minuto', pt: 'minuto'}, Minutes_other: {en: 'minutes', fr: 'minutes', es: 'minutos', pt: 'minutos'}, Hours_one: {en: 'hour', fr: 'heure', es: 'hora', pt: 'hora'}, Hours_other: {en: 'hours', fr: 'heures', es: 'horas', pt: 'horas'}, Days_one: {en: 'day', fr: 'jour', es: 'día', pt: 'dia'}, Days_other: {en: 'days', fr: 'jours', es: 'días', pt: 'dias'}, Weeks_one: {en: 'week', fr: 'semaine', es: 'semana', pt: 'semana'}, Weeks_other: {en: 'weeks', fr: 'semaines', es: 'semanas', pt: 'semanas'}, Months_one: {en: 'month', fr: 'mois', es: 'mes', pt: 'mês'}, Months_other: {en: 'months', fr: 'mois', es: 'meses', pt: 'meses'}, Years_one: {en: 'year', fr: 'année', es: 'año', pt: 'ano'}, Years_other: {en: 'years', fr: 'années', es: 'años', pt: 'anos'}, }; function doLocalize(str, params) { var lang = (["en","fr","es", "pt"].indexOf(userSettings.language)!=-1)?userSettings.language:"en"; var out = str; for(var current in l10n) { out = out.replace('$'+current, l10n[current][lang]); } for(var param in params) { out = out.replace('{{'+param+'}}', params[param]); } return out; } // Compute elapsed time as a string function timestampToElapsedString(timestamp) { var units = [{name:'Years', factor:356 * 24 * 60 * 60}, {name:'Months', factor:30 * 24 * 60 * 60}, {name:'Weeks', factor:7 * 24 * 60 * 60}, {name:'Days', factor:24 * 60 * 60}, {name:'Hours', factor:60 * 60}, {name:'Minutes', factor:60}]; var maxlevel = 1; var levels = 0; var time_period = ''; var time_stamp = (new Date(timestamp).getTime()); var elapsed_seconds = ((new Date().getTime()) - time_stamp)/1000; for (var i = 0; i < units.length ; i++) { var factor = units[i].factor; var elapsed_units = Math.floor(elapsed_seconds / factor); if (elapsed_units > 0) { if (levels > 0) time_period += ','; time_period += ' '+elapsed_units+" "+(elapsed_units==1?doLocalize("$"+units[i].name+"_one"):doLocalize("$"+units[i].name+"_other")); elapsed_seconds -= elapsed_units * factor; } if (time_period != '') levels += 1; if (levels == maxlevel) break; } if (levels == 0) { return doLocalize("$SecondsAgo"); } return doLocalize("$Ago", {time: time_period}); } // Encoding functions taken from // https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding function uint6ToB64(nUint6) { return nUint6 < 26 ? nUint6 + 65 : nUint6 < 52 ? nUint6 + 71 : nUint6 < 62 ? nUint6 - 4 : nUint6 === 62 ? 43 : nUint6 === 63 ? 47 : 65; } function toBase64(aBytes) { var eqLen = (3 - (aBytes.length % 3)) % 3, sB64Enc = ""; for (var nMod3, nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) { nMod3 = nIdx % 3; nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24); if (nMod3 === 2 || aBytes.length - nIdx === 1) { sB64Enc += String.fromCharCode(uint6ToB64(nUint24 >>> 18 & 63), uint6ToB64(nUint24 >>> 12 & 63), uint6ToB64(nUint24 >>> 6 & 63), uint6ToB64(nUint24 & 63)); nUint24 = 0; } } return eqLen === 0 ? sB64Enc : sB64Enc.substring(0, sB64Enc.length - eqLen) + (eqLen === 1 ? "=" : "=="); } return chooser; });