BangleApps/apps/heatsuite/custom.html

614 lines
28 KiB
HTML

<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<style>
.jsoneditor-container {
height: 500px;
margin-bottom: 30px;
border: 1px solid #ccc;
}
.editor-section {
margin-bottom: 40px;
}
</style>
</head>
<body>
<ul class="tab tab-block" id="tab-navigate">
<li class="tab-item active" id="tab-settingsContainer">
<a href="javascript:showTab('settingsContainer')">Settings</a>
</li>
<li class="tab-item" id="tab-tasksContainer">
<a href="javascript:showTab('tasksContainer')">Tasks</a>
</li>
<li class="tab-item" id="tab-surveyContainer">
<a href="javascript:showTab('surveyContainer')">EMA</a>
</li>
<li class="tab-item" id="tab-downloadData">
<a href="javascript:showTab('downloadData')">
<div id="downloadData-tab-label" class="" data-badge="0">Download
</div>
</a>
</li>
</ul>
<div class="container apploader-tab" id="settingsContainer">
<form id="heatsuiteSettings">
<div class="row pt-2"><strong>Recorder Options:</strong>
<br>Select what sensor/data you want to record and average each minute:
</div>
<div class="form-group">
<label class="form-switch">
<input type="checkbox" name="record" value="steps">
<i class="form-icon"></i> Steps
</label>
<label class="form-switch">
<input type="checkbox" name="record" value="hrm">
<i class="form-icon"></i> Optical Heart Rate
</label>
<label class="form-switch">
<input type="checkbox" name="record" value="acc">
<i class="form-icon"></i> Accelerometry (per minute magnitude)
</label>
<label class="form-switch">
<input type="checkbox" name="record" value="bat">
<i class="form-icon"></i> Battery
</label>
<label class="form-switch">
<input type="checkbox" name="record" value="movement">
<i class="form-icon"></i> Movement
</label>
<label class="form-switch">
<input type="checkbox" name="record" value="baro">
<i class="form-icon"></i> Temperature/Pressure
</label>
<label class="form-switch">
<input type="checkbox" name="record" value="bthrm">
<i class="form-icon"></i> Bluetooth HRM (Uses BTHRM app/module)
</label>
<label class="form-switch">
<input type="checkbox" name="record" value="CORESensor">
<i class="form-icon"></i> CORE Sensor (Uses coretemp app/module)
</label>
</div>
<div class="row pt-2"><strong>High Resolution Accelerometer Data:</strong>
<br>If you want to record high temporal resolution accelerometer data (magnitude of x,y,z; can store up to 4 days of data at 1 second, will rollover).
<div class="form-group">
<label class="form-switch">
<input type="checkbox" name="highAcc">
<i class="form-icon"></i> High Temporal Accelerometry &radic;(x&sup2; + y&sup2; + z&sup2;)
</label>
<label class="form-label" for="input-AccLogInt">Interval (seconds)</label>
<input class="form-input" type="number" name="AccLogInt" id="input-AccLogInt" value=1 min=1>
<p class="form-input-hint mb-0">Interval for logging averaged magnitude and sum from accelerometer - default is 1 second
</p>
</div>
</div>
<div class="row pt-2"><strong>GPS PSMOO Options:</strong>
<br>Option to have GPS work in PSMOO (uses GPSSetup app/module)
</div>
<div class="form-group">
<label class="form-switch">
<input type="checkbox" name="GPS">
<i class="form-icon"></i> Turn GPS On
</label>
<label class="form-label" for="input-GPSScanTime">Scan Time (mins)</label>
<input class="form-input" type="number" name="GPSScanTime" id="input-GPSScanTime" value=2 min=0>
<p class="form-input-hint mb-0">The time spent scanning for a GPS signal.</strong></p>
<label class="form-label" for="input-GPSInterval">Scan Interval (mins)</label>
<input class="form-input" type="number" name="GPSInterval" id="input-GPSInterval" value=10 min=0>
<p class="form-input-hint mb-0">The time between scans when a <strong>signal is not acquired.</strong></p>
<label class="form-label" for="input-GPSAdaptiveTime">Adaptive Time (mins)</label>
<input class="form-input" type="number" name="GPSAdaptiveTime" id="input-GPSAdaptiveTime" value=2 min=0>
<p class="form-input-hint mb-0">The time between scans when a <strong>signal is acquired.</strong></p>
</div>
<div class="row pt-2"><strong>Extras:</strong>
<label class="form-switch">
<input type="checkbox" name="fallDetect">
<i class="form-icon"></i> Fall Detection (beta)
</label>
<p class="form-input-hint mb-0">Will detect falls.</p>
<label class="form-switch">
<input type="checkbox" name="surveyRandomize">
<i class="form-icon"></i> Randomize EMA questions
</label>
<p class="form-input-hint mb-0">Enable this if you want your questions to be shuffled at random each time.</p>
<label class="form-switch">
<input type="checkbox" name="swipeOpen" checked>
<i class="form-icon"></i> Swipe to Launch HeatSuite App
</label>
<p class="form-input-hint mb-0">On by default. Will enable quick access to the HeatSuite app for participants by simply swiping right on the screen.</p>
<label class="form-label" for="input-studyID">Study ID:</label>
<input class="form-input" type="text" name="studyID" id="input-studyID" value="" minlength="1" maxlength="4">
<p class="form-input-hint mb-0">For communicating with HeatSuite Nodes. Maximum of 4 (no special) characters.
</p>
<label class="form-label" for="input-filePrefix">File Prefix</label>
<input class="form-input" type="text" name="filePrefix" id="input-filePrefix" value="htst" minlength="1"
maxlength="5">
<p class="form-input-hint mb-0">ONLY CHANGE IF YOU KNOW WHAT YOU ARE DOING.</p>
</div>
</form>
</div>
<div class="container apploader-tab pb-2" id="tasksContainer" style="display:none;">
<div class="row pt-2"><strong>Edit your Task JSON File Editor:</strong>
<br><a href="https://heatsuitelabs.github.io/HeatSuiteDocs/" target="_blank">Read the Docs</a> on how to properly format the JSON file. GUI coming soon to BangleApps.
</div>
<div id="heatsuite_taskFile_editor" class="jsoneditor-container"></div>
<button class="btn btn-primary" id="heatsuite_taskFile_resetBtn">Restore Default</button>
</div>
<div class="container apploader-tab pb-2" id="surveyContainer" style="display:none;">
<div class="row pt-2"><strong>Ecological Momentary Assessment (EMA) JSON File Editor:</strong>
<br><a href="https://heatsuitelabs.github.io/HeatSuiteDocs/" target="_blank">Read the Docs</a> on how to properly format the JSON file. GUI coming soon to BangleApps.
</div>
<div id="heatsuite_surveyFile_editor" class="jsoneditor-container"></div>
<button class="btn btn-primary" id="heatsuite_surveyFile_resetBtn">Restore Default</button>
</div>
<div class="container apploader-tab pb-2" id="downloadData" style="display:none;">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>File name</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="downloadData-tab-content">
</tbody>
<div id="progressElementHeatSuite" style="margin-top:10px; font-weight:bold;"></div>
</table>
</div>
<p class="p-2 m-2"><button id="upload" class="btn btn-success">Upload</button></p>
<script src="../../core/lib/customize.js"></script>
<link href="https://cdn.jsdelivr.net/npm/jsoneditor@latest/dist/jsoneditor.min.css" rel="stylesheet" type="text/css">
<script src="https://cdn.jsdelivr.net/npm/jsoneditor@latest/dist/jsoneditor.min.js"></script>
<script>
let HeatSuiteFileList = [];
//default Schema
let heatsuite__taskFile_defaultSchema = [
{
"id": "survey",
"icon": 'require("heatshrink").decompress(atob("lEo4kA///6H7BIP2m9hjEpyQLBxeq0UAp3js1Z5F59GB9nChvL+83E4cCkQABkA6NgUq293rVCChsq6MRAAOcoQmM4ISCAAPqFBclCQkRjJpBExOxCYsR8goJkmRB4XuAYUVCZMsJYdEFgUZoATIpwTC1XnooFC2QTIoIhCC4OqCYVyMRGhBoXhjNLAoXSCZkR2mpAgXilGIwRnFCYlKPgd40czmeoCglJCYd5AgdYn////zCgknBwfZAgeqCQIUCwQTClQOD3oDCj1DCYf/nATHAAcawYTE/Q8CkoTHvE/+c6mY8FMYgACi9Kn84xAqDmSfC7euCYnE0f/1CKBPQRQBgWqkUk3zeCvWKBwJ4BnATFKQUipWq0koBoSyBxATDZYsCkUAOgk4AoYTGAAUjCYgmCUAgAFlQTDRQQTGgUowQuBJ4f/nQEDRYIiD81u0UAhAODmYECeAkCq1ms1nkECbIoACD4ImCCQIAB2QGBFAYADHYchs3nv1msguBY4IAD+bhBCYVGs261YTCMovz1GKnSfCCYIACtRXCxTHBeAJYBxGACYek8wnDSYWIxAjCdocls0eE4PiXhAAEhEWCQNhoATNgFOCYPkCRxABpdqIQQUP2gSQCYMiCaMAHRw="))',
"cbBtn": "Bangle.load('heatsuite.survey.js');",
"tod": [
900,
1200,
1500,
1800
],
"debounce": 300
},
{
"id": "bloodPressure",
"icon": 'require("heatshrink").decompress(atob("lEo4kA///zND//3BYOe98ggHGhEllM533ssW5odBhFOud642DhE10omCnczHSO3s3jmATP3MRjNrCh+2s0ZzNrCJkDn1mv1382X8YlMyMRi1xy1hzwSKhdxs12CYIWBu5RKm8WtMeCYfp8ATJ2MRiITDjPpsCUCu4ADv2w24TBjITCiOZE4MLCQgABve+MQMXCYeW2ATBv2ZAAeeve28w7Ey3mE4dCkQACx172INBtd+s1m81xJ4ITBpH/wn//oTB29pjIRBs1nAgITD7l3zGXv4nDsJRBiORz1hCYdCogBCCYWhiNusNutIWByA7EKIXXCYOq1QPBAAIZBiA7DvITCp4TDAAYTBHYp3CpgTNxvdAAX+CZl+9wAECZjDBbIQEBCZwlC9ITOy98zOcFAITOzl+vgTQvJSCJ6QWBCYMaCZED852CAAXj21pCgkRswTBgEzmc78/rAgMwm1usLvDz1m2ATBAAMD5+TAodpiIVBt3njN+CYvn8YGDnwnEu18uAMDgHpDQkAnfms1mtc+/4TFvngAwgxCAQV3EAt8AwoWFvIMF5gTKgF/HYv5mATKnPLAoc3+4TLh3J8ayCvieDKBN85LVBznJJxaSCvPM5nJuYSMCgW73ezBIoA="))',
"tod": [
830,
1330,
1930
],
"debounce": 3600,
"btPair": true,
"btInfo": {
"service": "1810",
"supported": [
"A&D_UA-651"
]
}
},
{
"id": "coreTemperature",
"icon": 'require("heatshrink").decompress(atob("lEo4kA///A4Pf99fx3YtnjjEkkN/7+X0ujoFK/VusdBhH0ykIoXY2ozPjnBIyEBr2piATPiuq1WREx9aCYIoPjmaCYIoOgNo9ITC1gTMi3yvwoC9gmNw8nFAVRExmL5dyFAOlMZcBt+2223FAImN+9Emlru/cExlo6kzme8x9hJptkmYnBwwmN21DmdG3Am/TZF28YmPgvyl9rEx0AjsikX28wmNgFnkVyFAImNgHnl7pBvAmNgOyk/3u/2ExsM6UiEx8Ajd3u9/6omNgO3u/b8wSNgEeu974IRNEwPvu+xCRzrB+/+HBwABrF/8ISPhnXYBwAC5t9qImQ2/cCR8Ajm32ASPgPr5YmQj3eMCEB9qHQEycA5wmROoImRgAA=="))',
"tod": [
830,
1330,
1930
],
"TaskDesc": "coreTemperature_TaskDesc",
"debounce": 3600,
"btPair": true,
"btInfo": {
"service": "1809",
"supported": [
"BLEThermistorPod"
]
}
},
{
"id": "bodyMass",
"icon": "require('heatshrink').decompress(atob('lEo4kB3//AAIJB7//wUQjnn485t97vvvoMIhlj9dasspptbz33kshiIApkUpylP/nGswAB5n/pOSkQSEl+3utVAAOI93uxAGCq96+QSCil1gczABdXyITBo4SDmt6ut61Wq09TBQU+ukRiVwDgcK5t1vvd7vc0ALD88hjgbDmcLvxOBAAOH2ALDgvBk5EEq1+wpfBrGHsoLDh1yyouEtN+q3M5llw+WvwMCgF5CYMFu9V3l2E4tn5dVu9Qh1ZCYN0/9E2s65phBMYWjrdE/9HCYOXmv2u9kqaLBRIKLDmtLu9vqF5y83o930lzWA83+4MCvMnmtKCZYgB2lQuQTBtmr5ijEAAc15mr+1euUqTYNESwgAFBgUO0Urgc+uwmIFAVn8fu2UW8czu2DCZM4s8z9FhCYVcE5fFmflsMcCAITP4MfCaPxigQB8yeIUAVumHloMZCYMLCZewCYORCYU32YTJndzCYUSEgM34p7BAA0+rgTBg8hCYM+ulE2oTHrdEo/g84TCmvLvdHgYSFmF009kuYTEu93tiNGDwN3vm+CYWjm9nCZvqkMR2E3pV6pQ7H1932lw9cRiNonGk+llMY9Wp9KwFWCYNFZgN3HQflrw8Du9emFUCYMqG41cdIIAEh2yCYMUbI1VqpSF89BCYMRo7FIAAc4ugSCiMZ2tY9wAGn3uwt7yITDiMp+1mAAtqrVmt+SCQgAl'))",
"tod": [
800
],
"debounce": 3600,
"btPair": false,
"btInfo": {
"service": "181b",
"supported": [
"MIBFS"
]
}
},
{
"id": "urine",
"icon": "require('heatshrink').decompress(atob('lEo4kA///7/3oMInGVtdC//3rvn5lS7/Gx1rksZDYIXB7edkAFB4wqGhcukXgHp0Lkkz6eaCheykWq9Ux5nM42elWilwjH0c97VG5lVqtcjOp7s3JwIAEgU4xF6tgSBAAPBzXf/FACYsK7GD1vFCYdcjuIx+bCY/azgSDAANRyePzwTHzQ6EAAWax87CY2Z1I6EHgVqvNyMY0q1ATGqsX1XrRg1JoITH4dJRY0Ao5iGAAPN1MkogVFogTI42p1uIUItEngTJ0lE0AnQle7J59d1JjHoemO4/JoYTHpGjWZGTCY9BzWMCYvBzVjCZFn1tc5gADtOtqewCY2py/qvOZAAeu1OUCY0LSYN3ogAEo90Yo0AgU4xAAJYooTBzvdABLFFAAMq1QAJMY+0ExMyCQ0AgkRjiKEAAQTIhVhirbGrmeCY8C6MRCY1VvYTHhwTI4x2HAANhCY/Ndw4AB0YTG5mERQ5QCods5nFrnM4Np1YSIAAOy1PWiMRtE31wSKgEL0mq0lEAQJNJHgc96c5zM9xFLCZcE7GPAAP4x7FIAAe5xGP/4AC7QTLlMzAAk0RRIAB8UiAAkuCYwA=='))",
"cbBtn": "Bangle.load('heatsuite.urine.js');",
"debounce": 0,
"tod": [],
"btPair": false,
"btInfo": null,
"notify": false
},
{
"id": "sleep",
"icon": "require('heatshrink').decompress(atob('lEo4kA///v3nBIPDjE968CqOzxlX/1m0VBhGBkHb61qpV4/0978Y2xZ1gQTmkQ9VkA9llJKMJZMjmRADlM85gAC4eSBYUCCIOW23Mmc281v30RAAXr+wLBmfMt9py0Rj27CAgAEBYIMCtMsB5AAE///33u4UD2ISLjfMnNu9cwhPxE5nr5m+9+QgVhBYhpBAAVrA4Mcn/ui0gCYsWnVVAANa0YLBsfxCZFjrGIAAWFm0fRQP+CY0fmoSDAANT/dmswTCMYkc1ATFxXBUIPuMYMDCYfsquqAAOlxGqqs7CYcwT4mzqfGGgNmu9onQTB93uT4ITFzHdAAd3xM/CYo7D2eWCYtpCYY7BMYgTNY4vzCY1jz4TCT4r5Bs92v996/3AoPM2J3BCYsfm/duvDtvWnk3u9z+ITIm3d63IE4Nss48BCYhjE5l9J4t35jHFT4fzr/dJoJRBu9lO4ITBT4MsNAPhHgPM7vfnnD/t3wf+8ITB93Cy0R9e//exspNBtGGHQQLC30RtOW+3Mmcz5/ztpOC692ngLC5lvtMjmUggAABhh5BWQciBYUCCIIQCAAcjs4SBuwMHAA+V42MrISOAAMikQSQAD4='))",
"cbBtn": 'modHS.saveDataToFile("sleep", "event", {"marker":"sleep"});Bangle.showClock();',
"debounce": 0,
"tod": [],
"btPair": false,
"btInfo": null,
"notify": false
}
];
let heatsuite__surveyFile_defaultSchema = {
"supported":{
"en_GB":"English (GB)",
"fr_CA":"Francais (CA)"
},
"questions":[{
"key":"comfort",
"text": {
"en_GB":"Thermal comfort?",
"fr_CA":"Confort thermique?"
},
"tod":[[0,2359]],
"oncePerDay": true,
"orderFix":false,
"options": [{
"text":{
"en_GB":"Comfortable",
"fr_CA":"Confortable"
},
"value":0,
"color":"#ffffff",
"btnColor":"#38ed35"
},{
"text":{
"en_GB":"Uncomfortable",
"fr_CA": "Inconfortable"
},
"value":1,
"color":"#ffffff",
"btnColor":"#ff0019"
}]
}]
};
//function from original index.js of BangleApps - required to bring here as the custom.html is loaded in an iframe
function showTab(tabname) {
document.querySelectorAll("#tab-navigate .tab-item").forEach(tab => {
tab.classList.remove("active");
});
document.querySelectorAll(".apploader-tab").forEach(tab => {
tab.style.display = "none";
});
document.getElementById("tab-" + tabname).classList.add("active");
document.getElementById(tabname).style.display = "inherit";
}
function formDataToJson(form) {
const formData = new FormData(form);
const data = {};
const forceArrayFields = ['record']; // force these to always be arrays
const checkboxHandled = new Set();
for (let [name, value] of formData.entries()) {
const field = form.querySelector(`[name="${name}"]`);
if (field && field.type === 'checkbox') {
if (checkboxHandled.has(name)) continue;
checkboxHandled.add(name);
const checkboxes = form.querySelectorAll(`input[type="checkbox"][name="${name}"]`);
const values = Array.from(checkboxes)
.filter(cb => cb.checked)
.map(cb => cb.hasAttribute('value') ? cb.value : true);
data[name] = forceArrayFields.includes(name) ? values : (values.length ? values[0] : false);
} else {
if (data.hasOwnProperty(name)) {
if (!Array.isArray(data[name])) {
data[name] = [data[name]];
}
data[name].push(value);
} else {
if (name === "studyID" && !value.length) continue;
data[name] = value;
}
}
}
forceArrayFields.forEach(field => {
if (!Array.isArray(data[field])) {
data[field] = data[field] !== undefined ? [data[field]] : [];
}
});
return data;
}
function fillFormFromJson(form, data) {
for (let [name, value] of Object.entries(data)) {
const fields = form.querySelectorAll(`[name="${name}"]`);
if (!fields.length) continue;
fields.forEach(field => {
if (field.type === 'checkbox') {
if (Array.isArray(value)) {
field.checked = value.includes(field.value);
} else {
field.checked = value === true || value === field.value || value === "true";
}
} else if (field.type === 'radio') {
field.checked = (field.value === value);
} else {
field.value = value;
}
});
}
}
//autosave form data to localstorage
function autosaveSettings(){
const form = document.getElementById('heatsuiteSettings');
const formjson = formDataToJson(form);
localStorage.setItem("heatuite__settings", JSON.stringify(formjson));
}
function readStorageJSONAsync(filename) {
return new Promise((resolve, reject) => {
Util.readStorageJSON(filename, function (data) {
try {
resolve(data || {});
} catch (err) {
reject(err);
}
});
});
}
function readStorageAsync(filename) {
return new Promise((resolve, reject) => {
Util.readStorageFile(filename, function (data) {
try {
resolve(data);
} catch (err) {
reject(err);
}
});
});
}
function downloadSingleFile(filename, callback) {
Util.readStorageFile(filename, (c) => {
let url;
let blob;
const a = document.createElement('a');
let fnArr = filename.split('_');
if (fnArr[1] !== 'accel') {
blob = new Blob([c], { type: 'text/plain' });
url = URL.createObjectURL(blob);
if (callback) return callback(filename, blob); // Call with original blob
} else {
function secondsToClock(seconds) {
let h = Math.floor(seconds / 3600);
let m = Math.floor((seconds % 3600) / 60);
let s = seconds % 60;
return [h, m, s].map(v => v.toString().padStart(2, '0')).join(':');
}
let csv = "time,seconds,mag_avg,mag_sum\n";
let lines = c.trim().split("\n");
for (let line of lines) {
let parts = line.split(",").map(v => parseInt(v, 10));
if (parts.length !== 3) continue;
let seconds = parts[0];
let avg = parts[1] / 8192;
let sum = parts[2] / 1024;
let time = secondsToClock(seconds);
csv += `${time},${seconds},${avg},${sum}\n`;
}
blob = new Blob([csv], { type: 'text/csv' });
}
if (callback) return callback(filename, blob);
url = URL.createObjectURL(blob);
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
});
}
function downloadAllFiles() {
console.log("Downloading all files", HeatSuiteFileList);
const zip = new JSZip();
let index = 0;
const progressEl = document.getElementById("progressElementHeatSuite");
if (progressEl) progressEl.textContent = "Preparing to download...";
function processNext() {
if (index >= HeatSuiteFileList.length) {
if (progressEl) progressEl.textContent = "Creating ZIP...";
zip.generateAsync({ type: "blob" }).then(content => {
const a = document.createElement('a');
const url = URL.createObjectURL(content);
a.href = url;
a.download = "all_files.zip";
a.click();
URL.revokeObjectURL(url);
if (progressEl) progressEl.textContent = "Download complete!";
});
return;
}
const filename = HeatSuiteFileList[index];
if (progressEl) progressEl.textContent = `Downloading file ${index + 1} of ${HeatSuiteFileList.length}: ${filename}`;
downloadSingleFile(filename, (name, blob) => {
zip.file(name, blob);
index++;
processNext();
});
}
processNext();
}
function deleteFile(filename){
if (confirm(`Are you sure you want to delete ${filename}?`)) {
Util.eraseStorageFile(filename,(c) =>{
var filePrefix = settings.filePrefix || 'htst';
return Puck.eval('require("Storage").list(/^'+filePrefix+'/)',renderDownloadTab);
});
}
}
function deleteAllFiles() {
if (!confirm("Are you sure you want to delete all files?")) return;
console.log("Deleting all files", HeatSuiteFileList);
let index = 0;
const progressEl = document.getElementById("progressElementHeatSuite");
if (progressEl) progressEl.textContent = "Preparing to delete files...";
function processNext() {
if (index >= HeatSuiteFileList.length) {
if (progressEl) progressEl.textContent = "All files deleted.";
// Refresh the list via Puck/E.show
const filePrefix = settings.filePrefix || 'htst';
Puck.eval('require("Storage").list(/^' + filePrefix + '/)', renderDownloadTab);
return;
}
const filename = HeatSuiteFileList[index];
if (progressEl) progressEl.textContent = `Deleting file ${index + 1} of ${HeatSuiteFileList.length}: ${filename}`;
Util.eraseStorageFile(filename, () => {
index++;
processNext();
});
}
processNext(); // Start the chain
}
function renderDownloadTabLogs(e){
var element = document.getElementById("downloadData-tab-content");
if(e.length > 0){
}else{
e.innerHTML = "No files to download.";
}
}
function renderDownloadTab(e){
HeatSuiteFileList = e.map(file => file.replace(/\x01$/, '')); //save it globally for later use...safer
var element = document.getElementById("downloadData-tab-content");
var tab_badge = document.getElementById("downloadData-tab-label");
if(HeatSuiteFileList.length > 0){
tab_badge.classList.add("badge");
tab_badge.dataset.badge = HeatSuiteFileList.length;
element.innerHTML = "";
HeatSuiteFileList.forEach((filename)=>{
element.insertAdjacentHTML('beforeend', `<tr id="file-${filename}"><td>${filename}</td><td><i class="icon icon-download" onclick="downloadSingleFile('${filename}');"></i>&nbsp; <i class="icon icon-delete" onclick="deleteFile('${filename}');"></i></td></tr>`);
});
element.insertAdjacentHTML('beforeend',`<tr id="end" class="text-bold"><td><button class="btn" onclick="downloadAllFiles();">Download All <i class="icon icon-download" ></i></button></td><td><button class="btn" onclick="">Delete All <i class="icon icon-delete" ></i></button> </td></tr>`);
}else{
tab_badge.classList.remove("badge");
tab_badge.dataset.badge = 0;
element.innerHTML = '<tr class="active"><td>No Files</td></tr>';
}
}
function onInit(device) {
let settings = {};
let promise = Promise.resolve();
promise = promise.then(() => {
return readStorageJSONAsync("heatsuite.default.json");
});
promise = promise.then(defaults => {
return readStorageJSONAsync("heatsuite.settings.json").then(user => {
settings = Object.assign({}, defaults, user);
});
});
promise = promise.then(() => {
const settingsForm = document.getElementById('heatsuiteSettings');
fillFormFromJson(settingsForm, settings);
});
promise = promise.then(() =>{
document.getElementById("downloadData-tab-content").innerHTML = '<div class="loading"></div>';;
var filePrefix = settings.filePrefix || 'htst';
return Puck.eval('require("Storage").list(/^'+filePrefix+'/)',renderDownloadTab);
});
//promise = promise.then(()=>{
// return Puck.eval('require("Storage").list(heatsuite.log)',renderDownloadTabLogs);
//});
promise.catch(error => {
console.error("Error loading settings:", error);
});
}
const editors = {};
function initJsonEditor({ elementId, storageKey, defaultSchema, resetBtnId }) {
const container = document.getElementById(elementId);
const savedContent = localStorage.getItem(storageKey);
const initialContent = savedContent ? JSON.parse(savedContent) : defaultSchema;
const options = {
modes: ['tree', 'form', 'code', 'text'],
mode: 'code',
onChange: function () {
try {
const currentContent = editor.get();
localStorage.setItem(storageKey, JSON.stringify(currentContent));
} catch (err) {
console.error(`[${storageKey}] Invalid JSON not saved.`);
}
}
};
const editor = new JSONEditor(container, options);
editor.set(initialContent);
editors[storageKey] = { editor, defaultSchema };
if (resetBtnId) {
const resetBtn = document.getElementById(resetBtnId);
resetBtn.addEventListener('click', () => {
editor.set(defaultSchema);
localStorage.setItem(storageKey, JSON.stringify(defaultSchema));
});
}
}
window.onload = function () {
const studyIDInput = document.getElementById('input-studyID');
studyIDInput.addEventListener("input", () => {
studyIDInput.value = studyIDInput.value.replace(/[^a-zA-Z0-9]/g, '');
});
const filePrefixInput = document.getElementById('input-filePrefix');
filePrefixInput.addEventListener("input", () => {
filePrefixInput.value = filePrefixInput.value.replace(/[^a-zA-Z0-9]/g, '');
});
const settingsForm = document.getElementById('heatsuiteSettings');
settingsForm.addEventListener('input', autosaveSettings);
settingsForm.addEventListener('change', autosaveSettings);
const storedSettings = localStorage.getItem("heatuite__settings");
if (storedSettings) { //lets fill form:
fillFormFromJson(settingsForm, JSON.parse(storedSettings));
}
//initialize both JsonEditors for tasks and surveys
initJsonEditor({
elementId: 'heatsuite_taskFile_editor',
storageKey: 'heatsuite__taskFile',
defaultSchema: heatsuite__taskFile_defaultSchema,
resetBtnId: 'heatsuite_taskFile_resetBtn'
});
initJsonEditor({
elementId: 'heatsuite_surveyFile_editor',
storageKey: 'heatsuite__surveyFile',
defaultSchema: heatsuite__surveyFile_defaultSchema,
resetBtnId: 'heatsuite_surveyFile_resetBtn'
});
};
// When the 'upload' button is clicked...
document.getElementById("upload").addEventListener("click", function () {
const form = document.getElementById('heatsuiteSettings');
//TO DO VALIDATE jsonEditors!
sendCustomizedApp({
storage: [
{ 'name': "heatsuite.settings.json", content: JSON.stringify(formDataToJson(form)) },
{ "name": "heatsuite.tasks.json", content: JSON.stringify(editors['heatsuite__taskFile'].editor.get()) },
{ "name": "heatsuite.survey.json", content: JSON.stringify(editors['heatsuite__surveyFile'].editor.get()) },
]
});
});
</script>
</body>
</html>