+
+
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 = '
'+msg.html.join('
')+'
'
+
+ 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