// Utility functions define(["webL10n","sugar-web/datastore","FileSaver"], function (l10n, datastore, FileSaver) { var util = {}; 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}]; // Port of timestamp elapsed string from Sugar - timestamp is in milliseconds elapsed since 1er january 1970 util.timestampToElapsedString = function(timestamp, maxlevel, issmall) { var suffix = (issmall ? "_short" : ""); var levels = 0; var time_period = ''; var elapsed_seconds = ((new Date().getTime()) - timestamp)/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?l10n.get(units[i].name+"_one"+suffix):l10n.get(units[i].name+"_other"+suffix)); elapsed_seconds -= elapsed_units * factor; } if (time_period != '') levels += 1; if (levels == maxlevel) break; } if (levels == 0) { return l10n.get("SecondsAgo"+suffix); } return l10n.get("Ago"+suffix, {time:time_period}); } // Port of get_date_range from Sugar util.getDateRange = function(rangetype) { if (rangetype === undefined) { return null; } var now = new Date(); var today = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); now = now.getTime(); var range = null; switch(rangetype) { case 1: range = { min: today, max: now }; // TODAY break; case 2: range = { min: today-86400000, max: now }; // YESTERDAY break; case 3: range = { min: today-604800000, max: now }; // PAST WEEK break; case 4: range = { min: today-2592000000, max: now }; // PAST MONTH break; case 5: range = { min: today-30758400000, max: now }; // PAST YEAR break; } return range; } // Port of get_next/previous_stroke/fill_color from Sugar util.getNextStrokeColor = function(color) { var current = util.getColorIndex(color); if (current == -1) { return color; } var next = nextIndex(current); while (xoPalette.colors[next].stroke != xoPalette.colors[current].stroke) { next = nextIndex(next); } return xoPalette.colors[next]; } util.getPreviousStrokeColor = function(color) { var current = util.getColorIndex(color); if (current == -1) { return color; } var previous = previousIndex(current); while (xoPalette.colors[previous].stroke != xoPalette.colors[current].stroke) { previous = previousIndex(previous); } return xoPalette.colors[previous]; } util.getNextFillColor = function(color) { var current = util.getColorIndex(color); if (current == -1) { return color; } var next = nextIndex(current); while (xoPalette.colors[next].fill != xoPalette.colors[current].fill) { next = nextIndex(next); } return xoPalette.colors[next]; } util.getPreviousFillColor = function(color) { var current = util.getColorIndex(color); if (current == -1) { return color; } var previous = previousIndex(current); while (xoPalette.colors[previous].fill != xoPalette.colors[current].fill) { previous = previousIndex(previous); } return xoPalette.colors[previous]; } util.getColorIndex = function(color) { for (var i = 0 ; i < xoPalette.colors.length ; i++) { if (color.stroke == xoPalette.colors[i].stroke && color.fill == xoPalette.colors[i].fill) { return i; } } return -1; } function nextIndex(index) { var next = index + 1; if (next == xoPalette.colors.length) { next = 0; } return next; } function previousIndex(index) { var previous = index - 1; if (previous < 0) { previous = xoPalette.colors.length - 1; } return previous; } // Get center of screen in canvas util.getCanvasCenter = function() { var canvas = document.getElementById("canvas") || document.getElementById("body"); var canvas_height = canvas.offsetHeight; var canvas_width = canvas.offsetWidth; var canvas_centery = parseFloat(canvas_height)/2.0; var canvas_centerx = parseFloat(canvas_width)/2.0 return { x: canvas_centerx, y: canvas_centery, dx: canvas_width, dy: canvas_height }; } // Compute margin for the element to be centered util.computeMargin = function(size, maxpercent) { var canvas = document.getElementById("canvas"); var canvas_height = canvas.offsetHeight; var canvas_width = canvas.offsetWidth; var size_width = (size.width <= canvas_width ? size.width : maxpercent.width*canvas_width); var size_height = (size.height <= canvas_height ? size.height : maxpercent.height*canvas_height); return { left: parseFloat(canvas_width-size_width)/2.0, top: parseFloat(canvas_height-size_height)/2.0, width: size_width, height: size_height }; } // Test if cursor is inside element util.cursorIsInside = function(ctrl) { var obj = document.getElementById(ctrl.getAttribute("id")); if (obj == null) return false; var p = {}; p.x = obj.offsetLeft; p.y = obj.offsetTop; p.dx = obj.clientWidth; p.dy = obj.clientHeight; while (obj.offsetParent) { p.x = p.x + obj.offsetParent.offsetLeft; p.y = p.y + obj.offsetParent.offsetTop - obj.scrollTop; if (obj == document.getElementsByTagName("body")[0]) { break; } else { obj = obj.offsetParent; } } var isInside = ( mouse.position.x >= p.x && mouse.position.x <= p.x + p.dx && mouse.position.y >= p.y && mouse.position.y <= p.y + p.dy ); return isInside; }; // Show/Hide toolbar util.setToolbar = function(newtoolbar) { if (toolbar == newtoolbar) { return; } toolbar = newtoolbar; newtoolbar.renderInto(document.getElementById("toolbar")); } // Get browser name util.getBrowserName = function() { if (enyo.platform.android) return "Android"; else if (enyo.platform.androidChrome) return "Chrome Android"; else if (enyo.platform.androidFirefox) return "Firefox Android"; else if (enyo.platform.ie) return "IE"; else if (enyo.platform.ios) return "iOS"; else if (enyo.platform.webos) return "webOS"; else if (enyo.platform.blackberry) return "BlackBerry"; else if (enyo.platform.safari) return "Safari"; else if (enyo.platform.chrome) return "Chrome"; else if (enyo.platform.firefox) return "Firefox"; return "Unknown"; } // Get browser version util.getBrowserVersion = function() { if (enyo.platform.android) return enyo.platform.android; else if (enyo.platform.androidChrome) return enyo.platform.androidChrome; else if (enyo.platform.androidFirefox) return enyo.platform.androidFirefox; else if (enyo.platform.ie) return enyo.platform.ie; else if (enyo.platform.ios) return enyo.platform.ios; else if (enyo.platform.webos) return enyo.platform.webos; else if (enyo.platform.blackberry) return enyo.platform.blackberry; else if (enyo.platform.safari) return enyo.platform.safari; else if (enyo.platform.chrome) return enyo.platform.chrome; else if (enyo.platform.firefox) return enyo.platform.firefox; return "?"; } // Get client type util.getClientType = function() { return (document.location.protocol.substr(0,4) == "http" && !constant.noServerMode) ? constant.webAppType : constant.appType; } util.getClientName = function() { return this.getClientType() == constant.webAppType ? "Web App" : "App"; } // URL location util.getCurrentServerUrl = function() { var url = window.location.href; return url.substring(0, url.lastIndexOf('/')); } // Min password size util.getMinPasswordSize = function() { var minSize = constant.minPasswordSize; var info = preferences.getServer(); if (info && info.options && info.options["min-password-size"]) { minSize = info.options["min-password-size"]; } return minSize; } // Restart application util.restartApp = function() { location.reload(); } // Vibrate slightly the device util.vibrate = function() { if ((enyo.platform.android || enyo.platform.androidChrome || enyo.platform.ios) && util.getClientType() == constant.appType && navigator.vibrate) { navigator.vibrate(30); } } // Hide native toolbar util.hideNativeToolbar = function() { if ((enyo.platform.android || enyo.platform.androidChrome) && util.getClientType() == constant.appType && !constant.noServerMode) { AndroidFullScreen.immersiveMode(function() {}, function() {}); } } // Handle volume buttons util.handleVolumeButtons = function() { if ((enyo.platform.android || enyo.platform.androidChrome) && util.getClientType() == constant.appType && !constant.noServerMode) { // HACK: Need only on Android because Cordova intercept volume buttons var emptyf = function() {}; document.addEventListener("volumeupbutton", function() { cordova.plugins.VolumeControl.getVolume(function(value) { var volume = parseInt(value); if (volume < 100) { cordova.plugins.VolumeControl.setVolume((volume+10), emptyf, emptyf); } }, emptyf); }, false); document.addEventListener("volumedownbutton", function() { cordova.plugins.VolumeControl.getVolume(function(value) { var volume = parseInt(value); if (volume > 0) { cordova.plugins.VolumeControl.setVolume((volume-1), emptyf, emptyf); } }, emptyf); }, false); } } // Change browser fav icon and title var buddyIcon='\ \ \ \ \ \ '; util.updateFavicon = function() { var color = preferences.getColor(); var name = preferences.getName(); if (!color) { color = {stroke: "#005FE4", fill: "#FF2B34"}; } document.title = ((name&&name!="")?name+" - ":"")+"Sugarizer"; var icon = buddyIcon.replace(new RegExp("&fill_color;","g"),color.fill).replace(new RegExp("&stroke_color;","g"),color.stroke); var svg_xml = (new XMLSerializer()).serializeToString((new DOMParser()).parseFromString(icon, "text/xml")); var canvas = document.createElement('canvas'); canvas.width = 16; canvas.height = 16; var ctx = canvas.getContext('2d'); var img = new Image(); img.src = "data:image/svg+xml;base64," + btoa(svg_xml); img.onload = function() { ctx.drawImage(img, 0, 0); var link = document.querySelector("link[rel*='icon']") || document.createElement('link'); link.type = 'image/x-icon'; link.rel = 'shortcut icon'; link.href = canvas.toDataURL("image/x-icon"); document.getElementsByTagName('head')[0].appendChild(link); } } // Scan code functions var currentCamera = 0; var qrCancelCallback = null; function closeQR() { QRScanner.cancelScan(function(status){}); if (qrCancelCallback) { qrCancelCallback(); } document.getElementById("toolbar").style.opacity = 1; document.getElementById("canvas").style.opacity = 1; document.getElementById("qrclosebutton").style.visibility = "hidden"; document.getElementById("qrswitchbutton").style.visibility = "hidden"; var qrBackground = document.getElementById("qrbackground"); qrBackground.parentNode.removeChild(qrBackground); } function switchCameraQR() { currentCamera = (currentCamera + 1) % 2; QRScanner.useCamera(currentCamera, function(err, status){}); } util.scanQRCode = function(okCallback, cancelCallback) { currentCamera = 0; qrCancelCallback = cancelCallback; var qrBackground = document.createElement('div'); qrBackground.className = "first-qrbackground"; qrBackground.id = "qrbackground"; document.getElementById("canvas").appendChild(qrBackground); document.getElementById("qrclosebutton").addEventListener('click', closeQR); document.getElementById("qrswitchbutton").addEventListener('click', switchCameraQR); QRScanner.prepare(function(err, status) { document.getElementById("toolbar").style.opacity = 0; document.getElementById("canvas").style.opacity = 0; document.getElementById("qrclosebutton").style.visibility = "visible"; document.getElementById("qrswitchbutton").style.visibility = "visible"; if (err) { console.log("Error "+err); } else { QRScanner.scan(function(err, code) { if (err) { console.log("Error "+err); } else { if (okCallback) { okCallback(code); } } util.cancelScan(); document.getElementById("toolbar").style.opacity = 1; document.getElementById("canvas").style.opacity = 1; document.getElementById("qrclosebutton").style.visibility = "hidden"; document.getElementById("qrswitchbutton").style.visibility = "hidden"; }); QRScanner.show(function(status) {}); } }); } util.cancelScan = function() { QRScanner.cancelScan(function(status){}); qrCancelCallback = null; document.getElementById("qrclosebutton").removeEventListener('click', closeQR); document.getElementById("qrswitchbutton").removeEventListener('click', switchCameraQR); var qrBackground = document.getElementById("qrbackground"); if (qrBackground) qrBackground.parentNode.removeChild(qrBackground); } // Decoding functions taken from // https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding function b64ToUint6(nChr) { return nChr > 64 && nChr < 91 ? nChr - 65 : nChr > 96 && nChr < 123 ? nChr - 71 : nChr > 47 && nChr < 58 ? nChr + 4 : nChr === 43 ? 62 : nChr === 47 ? 63 : 0; } function base64DecToArr(sBase64, nBlocksSize) { var sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length, nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2, taBytes = new Uint8Array(nOutLen); for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) { nMod4 = nInIdx & 3; nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 6 * (3 - nMod4); if (nMod4 === 3 || nInLen - nInIdx === 1) { for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) { taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255; } nUint24 = 0; } } return taBytes; } // Write a new file util.writeFile = function(directory, metadata, content, callback) { var binary = null; var text = null; var extension = "json"; var title = metadata.title; var mimetype = 'application/json'; if (metadata && metadata.mimetype) { mimetype = metadata.mimetype; if (mimetype == "image/jpeg") { extension = "jpg"; } else if (mimetype == "image/png") { extension = "png"; } else if (mimetype == "audio/wav") { extension = "wav"; } else if (mimetype == "video/webm") { extension = "webm"; } else if (mimetype == "audio/mp3"||mimetype == "audio/mpeg") { extension = "mp3"; } else if (mimetype == "video/mp4") { extension = "mp4"; } else if (mimetype == "text/plain") { extension = "txt"; text = JSON.parse(content); } else if (mimetype == "application/pdf") { extension = "pdf"; } else if (mimetype == "application/msword") { extension = "doc"; } else if (mimetype == "application/vnd.oasis.opendocument.text") { extension = "odt"; } else { extension = "bin"; } binary = base64DecToArr(content.substr(content.indexOf('base64,')+7)).buffer; } else { text = JSON.stringify({metadata: metadata, text: content}); } var filename = title; if (filename.indexOf("."+extension)==-1) { filename += "."+extension; } if (util.getClientType() == constant.appType && (enyo.platform.android || enyo.platform.androidChrome || enyo.platform.ios || enyo.platform.electron) && !constant.noServerMode) { if (enyo.platform.android || enyo.platform.androidChrome || enyo.platform.ios) { var cordovaFileStorage = cordova.file.externalRootDirectory; if (enyo.platform.ios) { cordovaFileStorage = cordova.file.documentsDirectory; } window.resolveLocalFileSystemURL(cordovaFileStorage, function(directory) { directory.getFile(filename, {create:true}, function(file) { if (!file) { return; } file.createWriter(function(fileWriter) { fileWriter.seek(fileWriter.length); if (text) { var blob = new Blob([text], {type:mimetype}); fileWriter.write(blob); } else { fileWriter.write(binary); } callback(null, file.fullPath); }, function(evt) { callback(error.code, null); }); }); }); } else { var electron = require("electron"); var ipc = electron.ipcRenderer; ipc.removeAllListeners('save-file-reply'); ipc.send('save-file-dialog', {directory: directory, filename: filename, mimetype: mimetype, extension: extension, text: text, binary: content}); ipc.on('save-file-reply', function(event, arg) { callback(arg.err, arg.filename); }); } } else { var blob = new Blob((text?[text]:[binary]), {type:mimetype}); FileSaver.saveAs(blob, filename); callback(null, filename); } } // Write file content to datastore function writeFileToStore(file, text, callback) { if (file.type == 'application/json') { // Handle JSON file var data = null; try { data = JSON.parse(text); if (!data.metadata) { callback(file.name, -1); return; } } catch(e) { callback(file.name, -1); return; } callback(file.name, 0, data.metadata, data.text); } else { var activity = ""; if (file.type != "text/plain" && file.type != "application/pdf" && file.type != "application/msword" && file.type != "application/vnd.oasis.opendocument.text") { activity = "org.olpcfrance.MediaViewerActivity"; } var metadata = { title: file.name, mimetype: file.type, activity: activity } callback(file.name, 0, metadata, text); } } // Load a file util.loadFile = function(file, callback) { // Use FileReaer object in web if (util.getClientType() == constant.webAppType || constant.noServerMode || (util.getClientType() == constant.appType && !enyo.platform.android && !enyo.platform.androidChrome && !enyo.platform.ios && !enyo.platform.electron)) { // Ensure type is valid var validTypes = ['application/json','image/jpeg','image/png','audio/wav','video/webm','audio/mp3','audio/mpeg','video/mp4','text/plain','application/pdf','application/msword','application/vnd.oasis.opendocument.text']; if (validTypes.indexOf(file.type) == -1) { callback(file.name, -1); return; } var reader = new FileReader(); reader.onload = function() { writeFileToStore(file, reader.result, callback); }; if (file.type == 'application/json') { reader.readAsText(file); } else { reader.readAsDataURL(file); } } } // Ask the user a directory (use to save a set of files) util.askDirectory = function(callback) { if (util.getClientType() != constant.appType || (enyo.platform.android || enyo.platform.androidChrome || enyo.platform.ios)) { return; } var electron = require("electron"); var ipc = electron.ipcRenderer; ipc.removeAllListeners('choose-directory-reply'); ipc.send('choose-directory-dialog'); ipc.on('choose-directory-reply', function(event, arg) { callback(arg); }) } // Ask the user a set files and write it to datastore util.askFiles = function(callback) { if (util.getClientType() != constant.appType) { return; } if (enyo.platform.android || enyo.platform.androidChrome || enyo.platform.ios) { if (!window.cordova && !window.PhoneGap) { return; } var file = { type: "image/jpeg", name: l10n.get("ImageFromDevice") }; var captureSuccess = function(imageData) { var text = "data:image/jpeg;base64," + imageData; writeFileToStore(file, text, callback); } var captureError = function(error) { }; navigator.camera.getPicture(captureSuccess, captureError, { quality: 50, targetWidth: 640, targetHeight: 480, destinationType: Camera.DestinationType.DATA_URL, sourceType: Camera.PictureSourceType.PHOTOLIBRARY }); } else { var electron = require("electron"); var ipc = electron.ipcRenderer; ipc.removeAllListeners('choose-files-reply'); ipc.send('choose-files-dialog'); ipc.on('choose-files-reply', function(event, file, err, text) { if (err) { callback(file.name, -1); } else { writeFileToStore(file, text, callback); } }); } } function base64toBlob(mimetype, base64) { var contentType = mimetype; var byteCharacters = atob(base64.substr(base64.indexOf(';base64,')+8)); var byteArrays = []; for (var offset = 0; offset < byteCharacters.length; offset += 1024) { var slice = byteCharacters.slice(offset, offset + 1024); var byteNumbers = new Array(slice.length); for (var i = 0; i < slice.length; i++) { byteNumbers[i] = slice.charCodeAt(i); } var byteArray = new Uint8Array(byteNumbers); byteArrays.push(byteArray); } var blob = new Blob(byteArrays, {type: contentType}); return blob; } // Open the content as a document in a new Window util.openAsDocument = function(metadata, text) { if (util.getClientType() == constant.webAppType || (util.getClientType() == constant.appType && !enyo.platform.android && !enyo.platform.androidChrome && !enyo.platform.ios && !enyo.platform.electron) || constant.noServerMode) { // Convert blob object URL var blob = base64toBlob(metadata.mimetype, text); var blobUrl = URL.createObjectURL(blob); // Open in a new browser tab window.open(blobUrl, '_blank'); } else if (enyo.platform.android || enyo.platform.androidChrome || enyo.platform.ios) { // Create a temporary file var cordovaFileStorage = cordova.file.externalCacheDirectory; if (enyo.platform.ios) { cordovaFileStorage = cordova.file.documentsDirectory; } window.resolveLocalFileSystemURL(cordovaFileStorage, function(directory) { directory.getFile("sugarizertemp", {create: true, exclusive: false}, function(file) { if (!file) { return; } file.createWriter(function(fileWriter) { fileWriter.seek(fileWriter.length); var blob = base64toBlob(metadata.mimetype, text) fileWriter.write(blob); // Open file in browser if (enyo.platform.ios) { // On iOS should be transfered in Cordova file space first var fileTransfer = new FileTransfer(); var fileURL = "cdvfile://localhost/temporary/sugarizer/sugarizertemp"; fileTransfer.download( cordovaFileStorage+"sugarizertemp", fileURL, function(entry) { cordova.InAppBrowser.open(fileURL, '_blank', 'location=no,closebuttoncaption='+l10n.get("Ok")); }, function(error) { console.log("download error code " + error.code); }, true ); } else { // On Android, just open in the file system var filename = cordovaFileStorage+"sugarizertemp"; cordova.InAppBrowser.open(filename, '_system'); } }, function(evt) { }); }); }); } else { // Save in a temporary file var electron = require("electron"); var shell = electron.shell; var ipc = electron.ipcRenderer; ipc.removeAllListeners('create-tempfile-reply'); ipc.send('create-tempfile', {metadata: metadata, text: text}); ipc.on('create-tempfile-reply', function(event, file) { // Open in a shell shell.openExternal("file://"+file); }); } } // Clean localStorage: datastore and settings util.cleanDatastore = function(full, then) { var results = datastore.find(); var i = 0; var removeOneEntry = function(err) { if (++i < results.length) { datastore.remove(results[i].objectId, removeOneEntry); } else { if (then) { then(); } } }; preferences.reset(full); if (results.length) { datastore.remove(results[i].objectId, removeOneEntry); } else { if (then) { then(); } } } // Compute storage size util.computeDatastoreSize = function(callback) { // Compute local storage size var used = 0; for(var x in localStorage) { var amount = (localStorage[x].length * 2); if (!isNaN(amount)) { used += amount; } } // Compute file size var results = datastore.find(); for (var i = 0 ; i < results.length ; i++) { var entry = results[i]; var textsize = entry.metadata["textsize"]; if (textsize) { used += textsize; } } if (callback) { callback({bytes: used, formatted: util.formatSize(used)}); } } // Format size util.formatSize = function(bytes) { var formatted = ""; if (bytes > 1048576) { formated = (bytes/1024/1024).toFixed()+" "+l10n.get("ShortForMegabytes"); } else if (bytes > 1024) { formated = (bytes/1024).toFixed()+" "+l10n.get("ShortForKilobytes"); } else if (bytes == 0) { formated = "-"; } else { formated = bytes + " " + l10n.get("ShortForBytes"); } return formated; } // Quit application util.quitApp = function() { new Sugar.EE({mode: 2}).renderInto(document.getElementById("body")); window.setTimeout(function() { if (typeof chrome != 'undefined' && chrome.app && chrome.app.runtime) { window.top.postMessage("", '*'); } window.close(); if (!/Edge/.test(navigator.userAgent)) { window.open('', '_self', ''); // HACK: Not allowed on all browsers window.close(); } if (navigator) { if (navigator.app) navigator.app.exitApp(); else if (navigator.device) navigator.device.exitApp(); } }, constant.timerBeforeClose); } return util; });