Merge pull request #2076 from glemco/master

presentation_timer: added interface for configuration from app loader
pull/2083/head
Gordon Williams 2022-08-09 13:29:56 +01:00 committed by GitHub
commit f3f8313442
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 150 additions and 13 deletions

View File

@ -1 +1,2 @@
0.01: first release
0.02: added interface for configuration from app loader

View File

@ -12,11 +12,10 @@ when the time for the last slide is approaching,
the button becomes red, when it passed,
the time will go on for another half a minute and stop automatically.
The only way to upload personalized timings is
by uploading a CSV to the bangle (i.e. from the IDE),
in the future I'll possibly figure out a better way.
You can set personalized timings from the web interface
by uploading a CSV to the bangle (floppy disk button in the app loader).
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
is supposed to finish and the slide number,
separated by a semicolon.
@ -25,8 +24,7 @@ is lasting until 1 minutes 30 seconds (yes it's decimal),
after another slide will start.
The only requirement is that timings are increasing,
so slides number don't have to be consecutive,
some can be skipped and they can even be short texts
(be careful with that, I didn't test it).
some can be skipped and they can even be short texts.
At the moment the app is just quick and dirty
but it should do its job.

View File

@ -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>

View File

@ -1,15 +1,17 @@
{
"id": "presentation_timer",
"name": "Presentation Timer",
"version": "0.01",
"version": "0.02",
"description": "A touch based presentation timer for Bangle JS 2",
"icon": "presentation_timer.png",
"screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot4.png"}],
"tags": "tools,app",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"interface": "interface.html",
"storage": [
{"name":"presentation_timer.app.js","url":"presentation_timer.app.js"},
{"name":"presentation_timer.img","url":"presentation_timer.icon.js","evaluate":true}
]
],
"data": [{ "name": "presentation_timer.csv" }]
}

View File

@ -19,10 +19,10 @@ const margin = 0.5; //half a minute tolerance
//dummy default values
var slides = [
[0.3, 1],
[0.5, 2],
[0.7, 3],
[1,4]
[1.5, 1],
[2, 2],
[2.5, 3],
[3,4]
];
function log_debug(o) {
@ -267,6 +267,6 @@ g.fillRect(0,0,w,h);
Bangle.loadWidgets();
Bangle.drawWidgets();
readSlides();
draw();
setWatch(() => load(), BTN, { repeat: false, edge: "falling" });
readSlides();