mirror of https://github.com/espruino/BangleApps
Merge pull request #2076 from glemco/master
presentation_timer: added interface for configuration from app loaderpull/2083/head
commit
f3f8313442
|
@ -1 +1,2 @@
|
||||||
0.01: first release
|
0.01: first release
|
||||||
|
0.02: added interface for configuration from app loader
|
||||||
|
|
|
@ -12,11 +12,10 @@ when the time for the last slide is approaching,
|
||||||
the button becomes red, when it passed,
|
the button becomes red, when it passed,
|
||||||
the time will go on for another half a minute and stop automatically.
|
the time will go on for another half a minute and stop automatically.
|
||||||
|
|
||||||
The only way to upload personalized timings is
|
You can set personalized timings from the web interface
|
||||||
by uploading a CSV to the bangle (i.e. from the IDE),
|
by uploading a CSV to the bangle (floppy disk button in the app loader).
|
||||||
in the future I'll possibly figure out a better way.
|
|
||||||
|
|
||||||
Each line in the file (which must be called `presentation_timer.csv`)
|
Each line in the file (`presentation_timer.csv`)
|
||||||
contains the time in minutes at which the slide
|
contains the time in minutes at which the slide
|
||||||
is supposed to finish and the slide number,
|
is supposed to finish and the slide number,
|
||||||
separated by a semicolon.
|
separated by a semicolon.
|
||||||
|
@ -25,8 +24,7 @@ is lasting until 1 minutes 30 seconds (yes it's decimal),
|
||||||
after another slide will start.
|
after another slide will start.
|
||||||
The only requirement is that timings are increasing,
|
The only requirement is that timings are increasing,
|
||||||
so slides number don't have to be consecutive,
|
so slides number don't have to be consecutive,
|
||||||
some can be skipped and they can even be short texts
|
some can be skipped and they can even be short texts.
|
||||||
(be careful with that, I didn't test it).
|
|
||||||
|
|
||||||
At the moment the app is just quick and dirty
|
At the moment the app is just quick and dirty
|
||||||
but it should do its job.
|
but it should do its job.
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="../../css/spectre.min.css" />
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
.alert {
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f44336; /* Red */
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="info"></div>
|
||||||
|
|
||||||
|
<button id="btnReload" class="btn btn-primary">Reload from watch</button>
|
||||||
|
<button id="btnUpload" class="btn btn-primary">Upload to watch</button>
|
||||||
|
<button id="btnDownload" class="btn btn-primary">Download</button>
|
||||||
|
|
||||||
|
<pre id="presentation_timer" contenteditable></pre>
|
||||||
|
|
||||||
|
<script src="../../core/lib/interface.js"></script>
|
||||||
|
<script>
|
||||||
|
const filePresentationTimer = "presentation_timer.csv";
|
||||||
|
const defaultValue="1.5;1\n2;2\n2.5;3\n3;4\n"
|
||||||
|
|
||||||
|
function errorFormat() {
|
||||||
|
var date = new Date();
|
||||||
|
var error =
|
||||||
|
'<p class="alert">' +
|
||||||
|
date.toUTCString() +
|
||||||
|
" : Wrong format, it should be CSV, refer to the README" +
|
||||||
|
"</p>";
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEditableContent() {
|
||||||
|
//transform any tag in an EOL (should be <br> or <div>), ignore ending tags
|
||||||
|
return document.getElementById("presentation_timer")
|
||||||
|
.innerHTML.replace(/<[^>]*>/g,"\n").replace(/\n\n+/g, '\n').replace(/^\n|\n$/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
const re = new RegExp("^([0-9]*[.])?[0-9]+;[A-z0-9]+$");
|
||||||
|
function isCorrectCsvString(str) {
|
||||||
|
let wronglines = str.split("\n").filter(e=>e && !e.match(re));
|
||||||
|
//TODO check for increasing numbers
|
||||||
|
if(wronglines.length) {
|
||||||
|
console.log(wronglines.join("\n"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadFile(fileid, contents) {
|
||||||
|
Puck.write(
|
||||||
|
`\x10(function() {
|
||||||
|
require("Storage").write("${fileid}",\`${contents}\`);
|
||||||
|
Bluetooth.print("OK");
|
||||||
|
})()\n`,
|
||||||
|
(ret) => {
|
||||||
|
console.log("uploadFile", ret);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Load settings CSV file from the watch.
|
||||||
|
*/
|
||||||
|
function loadTimes() {
|
||||||
|
document.getElementById("info").innerHTML = "";
|
||||||
|
Util.showModal("Loading...");
|
||||||
|
Puck.eval(`require('Storage').read("${filePresentationTimer}")`, (data) => {
|
||||||
|
document.getElementById("presentation_timer").innerHTML = data;
|
||||||
|
Util.hideModal();
|
||||||
|
if(!data) {
|
||||||
|
document.getElementById("presentation_timer").innerHTML = defaultValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/* Save settings as a CSV file on the watch.
|
||||||
|
*/
|
||||||
|
function uploadTimes() {
|
||||||
|
document.getElementById("info").innerHTML = "";
|
||||||
|
Util.showModal("Uploading...");
|
||||||
|
let csvTimes = getEditableContent();
|
||||||
|
if (isCorrectCsvString(csvTimes)) {
|
||||||
|
uploadFile(filePresentationTimer, csvTimes);
|
||||||
|
} else {
|
||||||
|
document.getElementById("info").innerHTML = errorFormat();
|
||||||
|
}
|
||||||
|
Util.hideModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadTimes() {
|
||||||
|
document.getElementById("info").innerHTML = "";
|
||||||
|
Util.showModal("Downloading...");
|
||||||
|
let csvTimes = getEditableContent();
|
||||||
|
if (isCorrectCsvString(csvTimes)) {
|
||||||
|
var a = document.createElement("a"),
|
||||||
|
file = new Blob([csvTimes], { type: "text/csv" });
|
||||||
|
var url = URL.createObjectURL(file);
|
||||||
|
a.href = url;
|
||||||
|
a.download = filePresentationTimer;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
setTimeout(function () {
|
||||||
|
document.body.removeChild(a);
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
}, 0);
|
||||||
|
} else {
|
||||||
|
document.getElementById("info").innerHTML = errorFormat();
|
||||||
|
}
|
||||||
|
Util.hideModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("btnUpload")
|
||||||
|
.addEventListener("click", function () {
|
||||||
|
uploadTimes();
|
||||||
|
});
|
||||||
|
document
|
||||||
|
.getElementById("btnDownload")
|
||||||
|
.addEventListener("click", function () {
|
||||||
|
downloadTimes();
|
||||||
|
});
|
||||||
|
document
|
||||||
|
.getElementById("btnReload")
|
||||||
|
.addEventListener("click", function () {
|
||||||
|
loadTimes();
|
||||||
|
});
|
||||||
|
function onInit() {
|
||||||
|
loadTimes();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,15 +1,17 @@
|
||||||
{
|
{
|
||||||
"id": "presentation_timer",
|
"id": "presentation_timer",
|
||||||
"name": "Presentation Timer",
|
"name": "Presentation Timer",
|
||||||
"version": "0.01",
|
"version": "0.02",
|
||||||
"description": "A touch based presentation timer for Bangle JS 2",
|
"description": "A touch based presentation timer for Bangle JS 2",
|
||||||
"icon": "presentation_timer.png",
|
"icon": "presentation_timer.png",
|
||||||
"screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot4.png"}],
|
"screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot4.png"}],
|
||||||
"tags": "tools,app",
|
"tags": "tools,app",
|
||||||
"supports": ["BANGLEJS2"],
|
"supports": ["BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
|
"interface": "interface.html",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"presentation_timer.app.js","url":"presentation_timer.app.js"},
|
{"name":"presentation_timer.app.js","url":"presentation_timer.app.js"},
|
||||||
{"name":"presentation_timer.img","url":"presentation_timer.icon.js","evaluate":true}
|
{"name":"presentation_timer.img","url":"presentation_timer.icon.js","evaluate":true}
|
||||||
]
|
],
|
||||||
|
"data": [{ "name": "presentation_timer.csv" }]
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,10 @@ const margin = 0.5; //half a minute tolerance
|
||||||
|
|
||||||
//dummy default values
|
//dummy default values
|
||||||
var slides = [
|
var slides = [
|
||||||
[0.3, 1],
|
[1.5, 1],
|
||||||
[0.5, 2],
|
[2, 2],
|
||||||
[0.7, 3],
|
[2.5, 3],
|
||||||
[1,4]
|
[3,4]
|
||||||
];
|
];
|
||||||
|
|
||||||
function log_debug(o) {
|
function log_debug(o) {
|
||||||
|
@ -267,6 +267,6 @@ g.fillRect(0,0,w,h);
|
||||||
|
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
|
readSlides();
|
||||||
draw();
|
draw();
|
||||||
setWatch(() => load(), BTN, { repeat: false, edge: "falling" });
|
setWatch(() => load(), BTN, { repeat: false, edge: "falling" });
|
||||||
readSlides();
|
|
||||||
|
|
Loading…
Reference in New Issue