diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a90d431b..e7be6b86c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Chart Activity - CSV support in the Journal - Export CSV instead of TXT in Measure.activity +- Export CSV in Stopwatch activity ### Fixed - Chess multiplayer bug when a third player join #1047 diff --git a/activities/Stopwatch.activity/css/activity.css b/activities/Stopwatch.activity/css/activity.css index 04ecf3dbc..3b1896431 100644 --- a/activities/Stopwatch.activity/css/activity.css +++ b/activities/Stopwatch.activity/css/activity.css @@ -6,6 +6,10 @@ background-image: url(../icons/help.svg); } +#main-toolbar #export-csv-button { + background-image: url(../icons/save-as-csv.svg); +} + /*Add image to fullscreen/unfullscreen button*/ #fullscreen-button { background-image: url(../icons/view-fullscreen.svg); diff --git a/activities/Stopwatch.activity/css/libnotify.css b/activities/Stopwatch.activity/css/libnotify.css new file mode 100644 index 000000000..3a42a59a0 --- /dev/null +++ b/activities/Stopwatch.activity/css/libnotify.css @@ -0,0 +1,92 @@ +html, +body { + min-height: 100%; +} +.humane, +.humane-libnotify { + position: fixed; + -moz-transition: all 0.3s ease-out; + -webkit-transition: all 0.3s ease-out; + -ms-transition: all 0.3s ease-out; + -o-transition: all 0.3s ease-out; + transition: all 0.3s ease-out; + z-index: 100000; + filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); +} +.humane, +.humane-libnotify { + font-family: Ubuntu, Arial, sans-serif; + text-align: center; + font-size: 15px; + bottom: 10px; + right: 10px; + opacity: 0; + width: 150px; + color: #fff; + padding: 10px; + background: #000; + +} +.humane p, +.humane-libnotify p, +.humane ul, +.humane-libnotify ul { + margin: 0; + padding: 0; +} +.humane ul, +.humane-libnotify ul { + list-style: none; +} +.humane.humane-libnotify-info, +.humane-libnotify.humane-libnotify-info { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAABQCAYAAADYxx/bAAAABmJLR0QA/wD/AP+gvaeTAAAAMUlEQVQYlWNgYDB6ysTAwMDAxMDACCcYUFkMDEwMDEwMBNVhkxg65jGhmke6M6hgHgBSdgHnpZwADwAAAABJRU5ErkJggg=='); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(0,0,50,0.9)), color-stop(1, rgba(0,0,100,0.9))) no-repeat; + background: -moz-linear-gradient(top, rgba(0,0,50,0.9) 0%, rgba(0,0,100,0.9) 100%) no-repeat; + background: -webkit-linear-gradient(top, rgba(0,0,50,0.9) 0%, rgba(0,0,100,0.9) 100%) no-repeat; + background: -ms-linear-gradient(top, rgba(0,0,50,0.9) 0%, rgba(0,0,100,0.9) 100%) no-repeat; + background: -o-linear-gradient(top, rgba(0,0,50,0.9) 0%, rgba(0,0,100,0.9) 100%) no-repeat; + background: linear-gradient(top, rgba(0,0,50,0.9) 0%, rgba(0,0,100,0.9) 100%) no-repeat; + *background-color: #030; +} +.humane.humane-libnotify-success, +.humane-libnotify.humane-libnotify-success { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAABQCAYAAADYxx/bAAAABmJLR0QA/wD/AP+gvaeTAAAAMUlEQVQYlWNgMGJ4ysTAwMDAxMAIJxhQWQwMDEwMTKgS2NRhkxg65jGhmke6M6hhHgBS2QHn2LzhygAAAABJRU5ErkJggg=='); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(0,50,0,0.9)), color-stop(1, rgba(0,100,0,0.9))) no-repeat; + background: -moz-linear-gradient(top, rgba(0,50,0,0.9) 0%, rgba(0,100,0,0.9) 100%) no-repeat; + background: -webkit-linear-gradient(top, rgba(0,50,0,0.9) 0%, rgba(0,100,0,0.9) 100%) no-repeat; + background: -ms-linear-gradient(top, rgba(0,50,0,0.9) 0%, rgba(0,100,0,0.9) 100%) no-repeat; + background: -o-linear-gradient(top, rgba(0,50,0,0.9) 0%, rgba(0,100,0,0.9) 100%) no-repeat; + background: linear-gradient(top, rgba(0,50,0,0.9) 0%, rgba(0,100,0,0.9) 100%) no-repeat; + *background-color: #030; +} +.humane.humane-libnotify-error, +.humane-libnotify.humane-libnotify-error { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADICAYAAAAp8ov1AAAABmJLR0QA/wD/AP+gvaeTAAAAPklEQVQokWMwYmB4ysTAwMCATjASFsOmBBvBRJ7x+O0g0wCS7CDTH/RwH7X9MVDuwyaG032D2M2UeIYO7gMAqt8C19Bn7+YAAAAASUVORK5CYII='); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(50,0,0,0.9)), color-stop(1, rgba(100,0,0,0.9))) no-repeat; + background: -moz-linear-gradient(top, rgba(50,0,0,0.9) 0%, rgba(100,0,0,0.9) 100%) no-repeat; + background: -webkit-linear-gradient(top, rgba(50,0,0,0.9) 0%, rgba(100,0,0,0.9) 100%) no-repeat; + background: -ms-linear-gradient(top, rgba(50,0,0,0.9) 0%, rgba(100,0,0,0.9) 100%) no-repeat; + background: -o-linear-gradient(top, rgba(50,0,0,0.9) 0%, rgba(100,0,0,0.9) 100%) no-repeat; + background: linear-gradient(top, rgba(50,0,0,0.9) 0%, rgba(100,0,0,0.9) 100%) no-repeat; + *background-color: #300; +} +.humane.humane-animate, +.humane-libnotify.humane-libnotify-animate { + opacity: 1; + +} +.humane.humane-animate:hover, +.humane-libnotify.humane-libnotify-animate:hover { + opacity: 0.2; +} +.humane.humane-animate, +.humane-libnotify.humane-libnotify-js-animate { + opacity: 1; + +} +.humane.humane-animate:hover, +.humane-libnotify.humane-libnotify-js-animate:hover { + opacity: 0.2; + filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=20); +} diff --git a/activities/Stopwatch.activity/icons/save-as-csv.svg b/activities/Stopwatch.activity/icons/save-as-csv.svg new file mode 100644 index 000000000..027eaa9bc --- /dev/null +++ b/activities/Stopwatch.activity/icons/save-as-csv.svg @@ -0,0 +1,141 @@ + + + +image/svg+xml + + + + + + + + CSV + \ No newline at end of file diff --git a/activities/Stopwatch.activity/index.html b/activities/Stopwatch.activity/index.html index 242afae01..aa0b110b6 100644 --- a/activities/Stopwatch.activity/index.html +++ b/activities/Stopwatch.activity/index.html @@ -12,6 +12,7 @@ href="lib/sugar-web/graphics/css/sugar-200dpi.css"> + @@ -25,6 +26,8 @@
+
+ diff --git a/activities/Stopwatch.activity/js/activity.js b/activities/Stopwatch.activity/js/activity.js index 8b34d655e..88bc1661c 100644 --- a/activities/Stopwatch.activity/js/activity.js +++ b/activities/Stopwatch.activity/js/activity.js @@ -1,4 +1,5 @@ define(["sugar-web/activity/activity","mustache", "sugar-web/env", "tutorial", "webL10n"], function (activity,mustache,env, tutorial, webL10n) { + // Manipulate the DOM only when it is ready. requirejs(['domReady!'], function (doc) { @@ -275,4 +276,40 @@ define(["sugar-web/activity/activity","mustache", "sugar-web/env", "tutorial", " tutorial.start(); }); + document.getElementById("export-csv-button").addEventListener('click', function() { + var stopwatchData = document.getElementById("stopwatch-list").getElementsByTagName("li"); + var csvContent = "Marks No.,Time (s),Marks,Counter\n"; + var marksSet = []; + + for (i = 0; i < stopwatchData.length; i++) { + var marks = stopwatchData[i].getElementsByClassName("marks")[0].innerHTML.split(" - "); + for (var j = 0; j < marks.length; j++) { + if (marks.length === 1 && !marks[0]) break; + var mark = marks[j]; + + var minutes = parseInt(mark.split(":")[0]); + var sec = parseInt(mark.split(":")[1]); + var tenthsOfSec = parseInt(mark.split(":")[2]); + var time = (minutes * 60) + sec + (tenthsOfSec / 10); + + if (!marksSet.includes(mark)) marksSet.push(mark); + var markNo = marksSet.indexOf(mark) + 1; + csvContent += `${markNo},${time || ""},${mark},${i+1}\n`; + }; + } + var metadata = { + mimetype: "text/csv", + title: "Stopwatch export.csv", + activity: "org.sugarlabs.ChartActivity", + timestamp: new Date().getTime(), + creation_time: new Date().getTime(), + file_size: 0 + }; + requirejs(["sugar-web/datastore", "humane"], function (datastore, humane) { + datastore.create(metadata, function () { + humane.log(webL10n.get("exportAsCSV")); + }, csvContent); + }); + }); + }); \ No newline at end of file diff --git a/activities/Stopwatch.activity/lib/humane.js b/activities/Stopwatch.activity/lib/humane.js new file mode 100644 index 000000000..0764b4a4c --- /dev/null +++ b/activities/Stopwatch.activity/lib/humane.js @@ -0,0 +1,240 @@ +/** + * humane.js + * Humanized Messages for Notifications + * @author Marc Harter (@wavded) + * @example + * humane.log('hello world'); + * @license MIT + * See more usage examples at: http://wavded.github.com/humane-js/ + */ + +;!function (name, context, definition) { + // HACK: Force loading with define to avoid loading issue in Electron + //if (typeof module !== 'undefined') module.exports = definition(name, context) + //else if (typeof define === 'function' && typeof define.amd === 'object') define(definition) + //else context[name] = definition(name, context) + define(definition); +}('humane', this, function (name, context) { + var win = window + var doc = document + + var ENV = { + on: function (el, type, cb) { + 'addEventListener' in win ? el.addEventListener(type,cb,false) : el.attachEvent('on'+type,cb) + }, + off: function (el, type, cb) { + 'removeEventListener' in win ? el.removeEventListener(type,cb,false) : el.detachEvent('on'+type,cb) + }, + bind: function (fn, ctx) { + return function () { fn.apply(ctx,arguments) } + }, + isArray: Array.isArray || function (obj) { return Object.prototype.toString.call(obj) === '[object Array]' }, + config: function (preferred, fallback) { + return preferred != null ? preferred : fallback + }, + transSupport: false, + useFilter: /msie [678]/i.test(navigator.userAgent), // sniff, sniff + _checkTransition: function () { + var el = doc.createElement('div') + var vendors = { webkit: 'webkit', Moz: '', O: 'o', ms: 'MS' } + + for (var vendor in vendors) + if (vendor + 'Transition' in el.style) { + this.vendorPrefix = vendors[vendor] + this.transSupport = true + } + } + } + ENV._checkTransition() + + var Humane = function (o) { + o || (o = {}) + this.queue = [] + this.baseCls = o.baseCls || 'humane' + this.addnCls = o.addnCls || '' + this.timeout = 'timeout' in o ? o.timeout : 2500 + this.waitForMove = o.waitForMove || false + this.clickToClose = o.clickToClose || false + this.timeoutAfterMove = o.timeoutAfterMove || false + this.container = o.container + + try { this._setupEl() } // attempt to setup elements + catch (e) { + ENV.on(win,'load',ENV.bind(this._setupEl, this)) // dom wasn't ready, wait till ready + } + } + + Humane.prototype = { + constructor: Humane, + _setupEl: function () { + var el = doc.createElement('div') + el.style.display = 'none' + if (!this.container){ + if(doc.body) this.container = doc.body; + else throw 'document.body is null' + } + this.container.appendChild(el) + this.el = el + this.removeEvent = ENV.bind(function(){ + var timeoutAfterMove = ENV.config(this.currentMsg.timeoutAfterMove,this.timeoutAfterMove) + if (!timeoutAfterMove){ + this.remove() + } else { + setTimeout(ENV.bind(this.remove,this),timeoutAfterMove) + } + },this) + + this.transEvent = ENV.bind(this._afterAnimation,this) + this._run() + }, + _afterTimeout: function () { + if (!ENV.config(this.currentMsg.waitForMove,this.waitForMove)) this.remove() + + else if (!this.removeEventsSet) { + ENV.on(doc.body,'mousemove',this.removeEvent) + ENV.on(doc.body,'click',this.removeEvent) + ENV.on(doc.body,'keypress',this.removeEvent) + ENV.on(doc.body,'touchstart',this.removeEvent) + this.removeEventsSet = true + } + }, + _run: function () { + if (this._animating || !this.queue.length || !this.el) return + + this._animating = true + if (this.currentTimer) { + clearTimeout(this.currentTimer) + this.currentTimer = null + } + + var msg = this.queue.shift() + var clickToClose = ENV.config(msg.clickToClose,this.clickToClose) + + if (clickToClose) { + ENV.on(this.el,'click',this.removeEvent) + ENV.on(this.el,'touchstart',this.removeEvent) + } + + var timeout = ENV.config(msg.timeout,this.timeout) + + if (timeout > 0) + this.currentTimer = setTimeout(ENV.bind(this._afterTimeout,this), timeout) + + if (ENV.isArray(msg.html)) msg.html = '' + + this.el.innerHTML = msg.html + this.currentMsg = msg + this.el.className = this.baseCls + if (ENV.transSupport) { + this.el.style.display = 'block' + setTimeout(ENV.bind(this._showMsg,this),50) + } else { + this._showMsg() + } + + }, + _setOpacity: function (opacity) { + if (ENV.useFilter){ + try{ + this.el.filters.item('DXImageTransform.Microsoft.Alpha').Opacity = opacity*100 + } catch(err){} + } else { + this.el.style.opacity = String(opacity) + } + }, + _showMsg: function () { + var addnCls = ENV.config(this.currentMsg.addnCls,this.addnCls) + if (ENV.transSupport) { + this.el.className = this.baseCls+' '+addnCls+' '+this.baseCls+'-animate' + } + else { + var opacity = 0 + this.el.className = this.baseCls+' '+addnCls+' '+this.baseCls+'-js-animate' + this._setOpacity(0) // reset value so hover states work + this.el.style.display = 'block' + + var self = this + var interval = setInterval(function(){ + if (opacity < 1) { + opacity += 0.1 + if (opacity > 1) opacity = 1 + self._setOpacity(opacity) + } + else clearInterval(interval) + }, 30) + } + }, + _hideMsg: function () { + var addnCls = ENV.config(this.currentMsg.addnCls,this.addnCls) + if (ENV.transSupport) { + this.el.className = this.baseCls+' '+addnCls + ENV.on(this.el,ENV.vendorPrefix ? ENV.vendorPrefix+'TransitionEnd' : 'transitionend',this.transEvent) + } + else { + var opacity = 1 + var self = this + var interval = setInterval(function(){ + if(opacity > 0) { + opacity -= 0.1 + if (opacity < 0) opacity = 0 + self._setOpacity(opacity); + } + else { + self.el.className = self.baseCls+' '+addnCls + clearInterval(interval) + self._afterAnimation() + } + }, 30) + } + }, + _afterAnimation: function () { + if (ENV.transSupport) ENV.off(this.el,ENV.vendorPrefix ? ENV.vendorPrefix+'TransitionEnd' : 'transitionend',this.transEvent) + + if (this.currentMsg.cb) this.currentMsg.cb() + this.el.style.display = 'none' + + this._animating = false + this._run() + }, + remove: function (e) { + var cb = typeof e == 'function' ? e : null + + ENV.off(doc.body,'mousemove',this.removeEvent) + ENV.off(doc.body,'click',this.removeEvent) + ENV.off(doc.body,'keypress',this.removeEvent) + ENV.off(doc.body,'touchstart',this.removeEvent) + ENV.off(this.el,'click',this.removeEvent) + ENV.off(this.el,'touchstart',this.removeEvent) + this.removeEventsSet = false + + if (cb && this.currentMsg) this.currentMsg.cb = cb + if (this._animating) this._hideMsg() + else if (cb) cb() + }, + log: function (html, o, cb, defaults) { + var msg = {} + if (defaults) + for (var opt in defaults) + msg[opt] = defaults[opt] + + if (typeof o == 'function') cb = o + else if (o) + for (var opt in o) msg[opt] = o[opt] + + msg.html = html + if (cb) msg.cb = cb + this.queue.push(msg) + this._run() + return this + }, + spawn: function (defaults) { + var self = this + return function (html, o, cb) { + self.log.call(self,html,o,cb,defaults) + return self + } + }, + create: function (o) { return new Humane(o) } + } + return new Humane() +}); diff --git a/activities/Stopwatch.activity/lib/tutorial.js b/activities/Stopwatch.activity/lib/tutorial.js index c3da252e4..a10cbc85e 100644 --- a/activities/Stopwatch.activity/lib/tutorial.js +++ b/activities/Stopwatch.activity/lib/tutorial.js @@ -43,7 +43,13 @@ define(["webL10n"], function (l10n) { position: "left", title: l10n.get("TutoRemoveTitle"), intro: l10n.get("TutoRemoveContent") - } + }, + { + element: "#export-csv-button", + position: "bottom", + title: l10n.get("exportAsCSV"), + intro: l10n.get("TutoCsvButton") + }, ]; steps = steps.filter(function (obj) { diff --git a/activities/Stopwatch.activity/locale.ini b/activities/Stopwatch.activity/locale.ini index f992d4eb6..c0198dadb 100644 --- a/activities/Stopwatch.activity/locale.ini +++ b/activities/Stopwatch.activity/locale.ini @@ -17,6 +17,8 @@ TutoMarkTitle=Lap TutoMarkContent=Press this icon to add an intermediate time lap TutoRemoveTitle=Remove TutoRemoveContent=Press this icon to remove this Stopwatch from list +TutoCsvButton=This button generates a CSV file that contains all the stopwatch marks with their corresponding counter number and time of marks in seconds. +exportAsCSV=Export as csv [en] TutoPrev=Prev @@ -36,6 +38,8 @@ TutoMarkTitle=Lap TutoMarkContent=Press this icon to add an intermediate time lap TutoRemoveTitle=Remove TutoRemoveContent=Press this icon to remove this Stopwatch from list +TutoCsvButton=This button generates a CSV file that contains all the stopwatch marks with their corresponding counter number and time of marks in seconds. +exportAsCSV=Export as csv [fr] TutoPrev=Préc @@ -55,6 +59,8 @@ TutoMarkTitle=Intermédiaire TutoMarkContent=Cliquez sur cet icône pour créer un temps intermédiaire TutoRemoveTitle=Supprimer TutoRemoveContent=Cliquez sur cet icône pour supprimer ce chronomètre de la liste +TutoCsvButton=Ce bouton génère un fichier CSV qui contient toutes les marques du chronomètre avec leur numéro de compteur correspondant et le temps des marques en secondes. +exportAsCSV=Exporter en CSV [pl] TutoPrev=Prev @@ -74,6 +80,8 @@ TutoMarkTitle=Lap TutoMarkContent=Press this icon to add an intermediate time lap TutoRemoveTitle=Remove TutoRemoveContent=Press this icon to remove this Stopwatch from list +TutoCsvButton=Ten przycisk generuje plik CSV zawierający wszystkie oznaczenia stopera wraz z odpowiadającym im numerem licznika i czasem oznaczeń w sekundach. +exportAsCSV=Eksportuj jako CSV [pt] TutoPrev=Anterior @@ -93,3 +101,5 @@ TutoMarkTitle=Lap TutoMarkContent=Press this icon to add an intermediate time lap TutoRemoveTitle=Remover TutoRemoveContent=Clique sobre o ícone para remover este cronômetro da lista +TutoCsvButton=Este botão gera um arquivo CSV que contém todas as marcas do cronômetro com seu respectivo número do contador e tempo das marcas em segundos. +exportAsCSV=Exportar como CSV