mirror of https://github.com/espruino/BangleApps
Initial stab at app loading tool
commit
4c20d622ea
|
@ -0,0 +1,24 @@
|
||||||
|
[
|
||||||
|
{ "id": "trex",
|
||||||
|
"name": "T-Rex",
|
||||||
|
"icon": "trex.png",
|
||||||
|
"description": "T-Rex game in the style of Chrome's offline game",
|
||||||
|
"tags": "game",
|
||||||
|
"storage": [
|
||||||
|
{"name":"+trex","file":"trex.json"},
|
||||||
|
{"name":"-trex","file":"trex.js"},
|
||||||
|
{"name":"*trex","file":"trex-icon.js"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "id": "compass",
|
||||||
|
"name": "Compass",
|
||||||
|
"icon": "compass.png",
|
||||||
|
"description": "Simple compass that points North",
|
||||||
|
"tags": "tool,outdoors",
|
||||||
|
"storage": [
|
||||||
|
{"name":"+compass","file":"compass.json"},
|
||||||
|
{"name":"-compass","file":"compass.js"},
|
||||||
|
{"name":"*compass","file":"compass-icon.js"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwghC/AE8IxAAEwAWVDB4WIDBwWJAAIWOwcz///mc4DBhFDwYVBAAYYDJJAWJDAoXKCw//+YXJIwWPCQk/Aof4JBAuHC4v/GBBdHC4nzMIZGHCAIOBC4vz75hDJAgXCCgS9CC4fdAYQXGIwsyCAPyl//nvdVQoXFRofzkYXCCwJGBSIgXFQ4kymcykfdIwZgDC5XzkUyCwJGDC6FNCwPTC5i9FmQXCMgLZFC48zLgMilUv/vdkUjBII9BC6HSC55HD1WiklDNIgXIBok61QYBkSBFC5kqCwMjC6RGB1RcCR4gXIx4MC+Wqkfyl70BEQf4C4+DIwYqBC4XzGAc4C4sISAfz0QDCFgUzRwmAC4wQB+QTCC4f/AYJeCC4hIEPQi9FIwwXDbIzVHC4xICSIYXGRoRGFGAgqFXgouGC4iqDLo4XIJAQYHCwZGHGAgYBXQUzCwYuIDAwAHCxRJEAAxFJDBgWNDBAWPAH4AYA="))
|
|
@ -0,0 +1,34 @@
|
||||||
|
g.clear();
|
||||||
|
g.setColor(0,0.5,1);
|
||||||
|
g.fillCircle(120,130,80,80);
|
||||||
|
g.setColor(0,0,0);
|
||||||
|
g.fillCircle(120,130,70,70);
|
||||||
|
|
||||||
|
function arrow(r,c) {
|
||||||
|
r=-r*Math.PI/180;
|
||||||
|
var p = Math.PI/2;
|
||||||
|
g.setColor(c);
|
||||||
|
g.fillPoly([
|
||||||
|
120+60*Math.sin(r), 130-60*Math.cos(r),
|
||||||
|
120+10*Math.sin(r+p), 130-10*Math.cos(r+p),
|
||||||
|
120+10*Math.sin(r+-p), 130-10*Math.cos(r-p),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldHeading = 0;
|
||||||
|
Bangle.on('mag', function(m) {
|
||||||
|
if (!Bangle.isLCDOn()) return;
|
||||||
|
g.setFont("6x8",3);
|
||||||
|
g.setColor(0);
|
||||||
|
g.fillRect(70,0,170,24);
|
||||||
|
g.setColor(0xffff);
|
||||||
|
g.setFontAlign(0,0);
|
||||||
|
g.drawString((m.heading===undefined)?"---":Math.round(m.heading),120,12);
|
||||||
|
g.setColor(0,0,0);
|
||||||
|
arrow(oldHeading,0);
|
||||||
|
arrow(oldHeading+180,0);
|
||||||
|
arrow(m.heading,0xF800);
|
||||||
|
arrow(m.heading+180,0x001F);
|
||||||
|
oldHeading = m.heading;
|
||||||
|
});
|
||||||
|
Bangle.setCompassPower(1);
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name":"Compass",
|
||||||
|
"icon":"*compass",
|
||||||
|
"src":"-compass"
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwgIXUn//4AFI///+AFC+YFKCIoFeHQYFH/4FGhkAgYKCAo8fApEPGggFG4YFBmAFHAgIdDAqA/BAo38K4gFJWIJ7DAoUB/AqC4EDLwIFCh0GFQPD4EYgP/4YABDwfwSggFFgAFCgOAAoYSDAox4BABQA=="))
|
|
@ -0,0 +1,219 @@
|
||||||
|
greal = g;
|
||||||
|
g.clear();
|
||||||
|
g = Graphics.createArrayBuffer(120,64,1,{msb:true});
|
||||||
|
g.flip = function() {
|
||||||
|
greal.drawImage({
|
||||||
|
width:120,
|
||||||
|
height:64,
|
||||||
|
buffer:g.buffer
|
||||||
|
},0,(240-128)/2,{scale:2});
|
||||||
|
};
|
||||||
|
var W = g.getWidth();
|
||||||
|
var BTNL = BTN4;
|
||||||
|
var BTNR = BTN5;
|
||||||
|
var BTNU = BTN1;
|
||||||
|
|
||||||
|
// Images can be added like this in Espruino v2.00
|
||||||
|
var IMG = {
|
||||||
|
rex: [Graphics.createImage(\`
|
||||||
|
########
|
||||||
|
##########
|
||||||
|
## #######
|
||||||
|
##########
|
||||||
|
##########
|
||||||
|
##########
|
||||||
|
#####
|
||||||
|
########
|
||||||
|
# #####
|
||||||
|
# #######
|
||||||
|
## ##########
|
||||||
|
### ######### #
|
||||||
|
##############
|
||||||
|
##############
|
||||||
|
############
|
||||||
|
###########
|
||||||
|
#########
|
||||||
|
#######
|
||||||
|
### ##
|
||||||
|
## #
|
||||||
|
#
|
||||||
|
##
|
||||||
|
\`),Graphics.createImage(\`
|
||||||
|
########
|
||||||
|
##########
|
||||||
|
## #######
|
||||||
|
##########
|
||||||
|
##########
|
||||||
|
##########
|
||||||
|
#####
|
||||||
|
########
|
||||||
|
# #####
|
||||||
|
# #######
|
||||||
|
## ##########
|
||||||
|
### ######### #
|
||||||
|
##############
|
||||||
|
##############
|
||||||
|
############
|
||||||
|
###########
|
||||||
|
#########
|
||||||
|
#######
|
||||||
|
### ##
|
||||||
|
## ##
|
||||||
|
#
|
||||||
|
##
|
||||||
|
\`),Graphics.createImage(\`
|
||||||
|
########
|
||||||
|
# ######
|
||||||
|
# # ######
|
||||||
|
# ######
|
||||||
|
##########
|
||||||
|
##########
|
||||||
|
#####
|
||||||
|
########
|
||||||
|
# #####
|
||||||
|
# #######
|
||||||
|
## ##########
|
||||||
|
### ######### #
|
||||||
|
##############
|
||||||
|
##############
|
||||||
|
############
|
||||||
|
###########
|
||||||
|
#########
|
||||||
|
#######
|
||||||
|
### ##
|
||||||
|
## #
|
||||||
|
# #
|
||||||
|
## ##
|
||||||
|
\`)],
|
||||||
|
cacti: [Graphics.createImage(\`
|
||||||
|
##
|
||||||
|
####
|
||||||
|
####
|
||||||
|
####
|
||||||
|
####
|
||||||
|
#### #
|
||||||
|
# #### ###
|
||||||
|
### #### ###
|
||||||
|
### #### ###
|
||||||
|
### #### ###
|
||||||
|
### #### ###
|
||||||
|
### #### ###
|
||||||
|
### #### ###
|
||||||
|
### #### ###
|
||||||
|
###########
|
||||||
|
#########
|
||||||
|
####
|
||||||
|
####
|
||||||
|
####
|
||||||
|
####
|
||||||
|
####
|
||||||
|
####
|
||||||
|
####
|
||||||
|
####
|
||||||
|
\`),Graphics.createImage(\`
|
||||||
|
##
|
||||||
|
##
|
||||||
|
# ##
|
||||||
|
## ## #
|
||||||
|
## ## #
|
||||||
|
## ## #
|
||||||
|
## ## #
|
||||||
|
##### #
|
||||||
|
#### #
|
||||||
|
#####
|
||||||
|
####
|
||||||
|
##
|
||||||
|
##
|
||||||
|
##
|
||||||
|
##
|
||||||
|
##
|
||||||
|
##
|
||||||
|
##
|
||||||
|
\`)],
|
||||||
|
};
|
||||||
|
IMG.rex.forEach(i=>i.transparent=0);
|
||||||
|
IMG.cacti.forEach(i=>i.transparent=0);
|
||||||
|
var cacti, rex, frame;
|
||||||
|
|
||||||
|
function gameStart() {
|
||||||
|
rex = {
|
||||||
|
alive : true,
|
||||||
|
img : 0,
|
||||||
|
x : 10, y : 0,
|
||||||
|
vy : 0,
|
||||||
|
score : 0
|
||||||
|
};
|
||||||
|
cacti = [ { x:W, img:1 } ];
|
||||||
|
var random = new Uint8Array(128*3/8);
|
||||||
|
for (var i=0;i<50;i++) {
|
||||||
|
var a = 0|(Math.random()*random.length);
|
||||||
|
var b = 0|(Math.random()*8);
|
||||||
|
random[a]|=1<<b;
|
||||||
|
}
|
||||||
|
IMG.ground = { width: 128, height: 3, bpp : 1, buffer : random.buffer };
|
||||||
|
frame = 0;
|
||||||
|
setInterval(onFrame, 50);
|
||||||
|
}
|
||||||
|
function gameStop() {
|
||||||
|
rex.alive = false;
|
||||||
|
rex.img = 2; // dead
|
||||||
|
clearInterval();
|
||||||
|
setTimeout(function() {
|
||||||
|
setWatch(gameStart, BTNU, {repeat:0,debounce:50,edge:"rising"});
|
||||||
|
}, 1000);
|
||||||
|
setTimeout(onFrame, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFrame() {
|
||||||
|
g.clear();
|
||||||
|
if (rex.alive) {
|
||||||
|
frame++;
|
||||||
|
rex.score++;
|
||||||
|
if (!(frame&3)) rex.img = rex.img?0:1;
|
||||||
|
// move rex
|
||||||
|
if (BTNL.read() && rex.x>0) rex.x--;
|
||||||
|
if (BTNR.read() && rex.x<20) rex.x++;
|
||||||
|
if (BTNU.read() && rex.y==0) rex.vy=4;
|
||||||
|
rex.y += rex.vy;
|
||||||
|
rex.vy -= 0.2;
|
||||||
|
if (rex.y<=0) {rex.y=0; rex.vy=0; }
|
||||||
|
// move cacti
|
||||||
|
var lastCactix = cacti.length?cacti[cacti.length-1].x:W-1;
|
||||||
|
if (lastCactix<W) {
|
||||||
|
cacti.push({
|
||||||
|
x : lastCactix + 24 + Math.random()*W,
|
||||||
|
img : (Math.random()>0.5)?1:0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
cacti.forEach(c=>c.x--);
|
||||||
|
while (cacti.length && cacti[0].x<0) cacti.shift();
|
||||||
|
} else {
|
||||||
|
g.drawString("Game Over!",(W-g.stringWidth("Game Over!"))/2,20);
|
||||||
|
}
|
||||||
|
g.drawLine(0,60,239,60);
|
||||||
|
cacti.forEach(c=>g.drawImage(IMG.cacti[c.img],c.x,60-IMG.cacti[c.img].height));
|
||||||
|
// check against actual pixels
|
||||||
|
var rexx = rex.x;
|
||||||
|
var rexy = 38-rex.y;
|
||||||
|
if (rex.alive &&
|
||||||
|
(g.getPixel(rexx+0, rexy+13) ||
|
||||||
|
g.getPixel(rexx+2, rexy+15) ||
|
||||||
|
g.getPixel(rexx+5, rexy+19) ||
|
||||||
|
g.getPixel(rexx+10, rexy+19) ||
|
||||||
|
g.getPixel(rexx+12, rexy+15) ||
|
||||||
|
g.getPixel(rexx+13, rexy+13) ||
|
||||||
|
g.getPixel(rexx+15, rexy+11) ||
|
||||||
|
g.getPixel(rexx+17, rexy+7) ||
|
||||||
|
g.getPixel(rexx+19, rexy+5) ||
|
||||||
|
g.getPixel(rexx+19, rexy+1))) {
|
||||||
|
return gameStop();
|
||||||
|
}
|
||||||
|
g.drawImage(IMG.rex[rex.img], rexx, rexy);
|
||||||
|
var groundOffset = frame&127;
|
||||||
|
g.drawImage(IMG.ground, -groundOffset, 61);
|
||||||
|
g.drawImage(IMG.ground, 128-groundOffset, 61);
|
||||||
|
g.drawString(rex.score,(W-1)-g.stringWidth(rex.score));
|
||||||
|
g.flip();
|
||||||
|
}
|
||||||
|
|
||||||
|
gameStart();
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name":"T-Rex",
|
||||||
|
"icon":"*trex",
|
||||||
|
"src":"-trex"
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 396 B |
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1,39 @@
|
||||||
|
Puck.debug=3;
|
||||||
|
|
||||||
|
var Comms = {
|
||||||
|
uploadApp : app => {
|
||||||
|
/* eg
|
||||||
|
{ name: "T-Rex",
|
||||||
|
icon: "trex.png",
|
||||||
|
description: "T-Rex game in the style of Chrome's offline game",
|
||||||
|
storage: [
|
||||||
|
{name:"+trex",file:"trex.json"},
|
||||||
|
{name:"-trex",file:"trex.js"},
|
||||||
|
{name:"*trex",file:"trex-icon.js"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
// Load all files
|
||||||
|
Promise.all(app.storage.map(storageFile => httpGet("apps/"+storageFile.file)
|
||||||
|
// map each file to a command to load into storage
|
||||||
|
.then(contents=>`require('Storage').write(${toJS(storageFile.name)},${toJS(contents)});`)))
|
||||||
|
//
|
||||||
|
.then(function(fileContents) {
|
||||||
|
fileContents = fileContents.join("\n");
|
||||||
|
Puck.write(fileContents,function() {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getInstalledApps : () => {
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
Puck.write("\x03",() => {
|
||||||
|
Puck.eval('require("Storage").list().filter(f=>f[0]=="+").map(f=>f.substr(1))', appList => {
|
||||||
|
resolve(appList);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,85 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/spectre.css/dist/spectre.min.css">
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/spectre.css/dist/spectre-exp.min.css">
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/spectre.css/dist/spectre-icons.min.css">
|
||||||
|
<title>Bangle.js loader</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="navbar">
|
||||||
|
<section class="navbar-section">
|
||||||
|
<a href="#" class="navbar-brand mr-2">Bangle.js Loader</a>
|
||||||
|
<!-- <a href="#" class="btn btn-link">...</a> -->
|
||||||
|
</section>
|
||||||
|
<!--<section class="navbar-section">
|
||||||
|
<div class="input-group input-inline">
|
||||||
|
<input class="form-input" type="text" placeholder="search">
|
||||||
|
<button class="btn btn-primary input-group-btn">Search</button>
|
||||||
|
</div>
|
||||||
|
</section>-->
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<ul class="tab tab-block" id="tab-navigate">
|
||||||
|
<li class="tab-item active" id="tab-librarycontainer">
|
||||||
|
<a href="javascript:showTab('librarycontainer')">Library</a>
|
||||||
|
</li>
|
||||||
|
<li class="tab-item" id="tab-myappscontainer">
|
||||||
|
<a href="javascript:showTab('myappscontainer')">My Apps</a>
|
||||||
|
</li>
|
||||||
|
<li class="tab-item" id="tab-aboutcontainer">
|
||||||
|
<a href="javascript:showTab('aboutcontainer')">About</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="container" id="toastcontainer">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container bangle-tab" id="librarycontainer">
|
||||||
|
<!--<div class="filter-nav">
|
||||||
|
<label class="chip" filterid="">All</label>
|
||||||
|
<label class="chip" filterid="clock">Clocks</label>
|
||||||
|
<label class="chip" filterid="game">Games</label>
|
||||||
|
<label class="chip" filterid="tool">Tools</label>
|
||||||
|
<label class="chip" filterid="hardware">Hardware</label>
|
||||||
|
</div>-->
|
||||||
|
<div class="panel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<!-- <div class="input-group">
|
||||||
|
<input class="form-input" type="text" placeholder="Keywords...">
|
||||||
|
<button class="btn btn-primary input-group-btn">Search</button>
|
||||||
|
</div>-->
|
||||||
|
</div>
|
||||||
|
<div class="panel-body"><!-- apps go here --></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container bangle-tab" id="myappscontainer" style="display:none">
|
||||||
|
<div class="panel">
|
||||||
|
<div class="panel-header" style="text-align:right">
|
||||||
|
<button class="btn input-group-btn" id="myappsrefresh">Refresh...</button>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body"><!-- apps go here --></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container bangle-tab" id="aboutcontainer" style="display:none">
|
||||||
|
<div class="hero bg-gray">
|
||||||
|
<div class="hero-body">
|
||||||
|
<h1>About Bangle.js loader</h1>
|
||||||
|
<p>A tool for uploading and removing apps from <a href="https://banglejs.com">Bangle.js Smart Watches</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>Using <a href="https://espruino.com/">Espruino</a>, Icons from <a href="https://icons8.com/">icons8.com</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script src="http://www.puck-js.com/puck.js"></script>
|
||||||
|
<script src="utils.js"></script>
|
||||||
|
<script src="comms.js"></script>
|
||||||
|
<script src="index.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,161 @@
|
||||||
|
var appjson = [];
|
||||||
|
httpGet("apps.json").then(apps=>{
|
||||||
|
appjson = JSON.parse(apps);
|
||||||
|
appjson.sort(appSorter);
|
||||||
|
refreshLibrary();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Status
|
||||||
|
// =========================================== Top Navigation
|
||||||
|
function showToast(message) {
|
||||||
|
// toast-primary, toast-success, toast-warning or toast-error
|
||||||
|
var toastcontainer = document.getElementById("toastcontainer");
|
||||||
|
var msgDiv = htmlElement(`<div class="toast toast-primary"></div>`);
|
||||||
|
msgDiv.innerHTML = message;
|
||||||
|
toastcontainer.append(msgDiv);
|
||||||
|
setTimeout(function() {
|
||||||
|
msgDiv.remove();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
function showPrompt(title, text) {
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
var modal = htmlElement(`<div class="modal active">
|
||||||
|
<!--<a href="#close" class="modal-overlay" aria-label="Close"></a>-->
|
||||||
|
<div class="modal-container">
|
||||||
|
<div class="modal-header">
|
||||||
|
<a href="#close" class="btn btn-clear float-right" aria-label="Close"></a>
|
||||||
|
<div class="modal-title h5">${escapeHtml(title)}</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="content">
|
||||||
|
${escapeHtml(text)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-primary" isyes="1">Yes</button>
|
||||||
|
<button class="btn" isyes="0">No</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`);
|
||||||
|
document.body.append(modal);
|
||||||
|
htmlToArray(modal.getElementsByTagName("button")).forEach(button => {
|
||||||
|
button.addEventListener("click",event => {
|
||||||
|
var isYes = event.target.getAttribute("isyes");
|
||||||
|
if (isYes) resolve();
|
||||||
|
else reject();
|
||||||
|
modal.remove();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// =========================================== Top Navigation
|
||||||
|
function showTab(tabname) {
|
||||||
|
htmlToArray(document.querySelectorAll("#tab-navigate .tab-item")).forEach(tab => {
|
||||||
|
tab.classList.remove("active");
|
||||||
|
});
|
||||||
|
htmlToArray(document.querySelectorAll(".bangle-tab")).forEach(tab => {
|
||||||
|
tab.style.display = "none";
|
||||||
|
});
|
||||||
|
document.getElementById("tab-"+tabname).classList.add("active");
|
||||||
|
document.getElementById(tabname).style.display = "inherit";
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================== Library
|
||||||
|
function refreshLibrary() {
|
||||||
|
var panelbody = document.querySelector("#librarycontainer .panel-body");
|
||||||
|
panelbody.innerHTML = appjson.map((app,idx) => `<div class="tile">
|
||||||
|
<div class="tile-icon">
|
||||||
|
<figure class="avatar"><img src="apps/${app.icon}" alt="${escapeHtml(app.name)}"></figure>
|
||||||
|
</div>
|
||||||
|
<div class="tile-content">
|
||||||
|
<p class="tile-title text-bold">${escapeHtml(app.name)}</p>
|
||||||
|
<p class="tile-subtitle">${escapeHtml(app.description)}</p>
|
||||||
|
</div>
|
||||||
|
<div class="tile-action">
|
||||||
|
<button class="btn btn-link btn-action btn-lg"><i class="icon icon-upload" appid="${app.id}"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
// set badge up top
|
||||||
|
var tab = document.querySelector("#tab-librarycontainer a");
|
||||||
|
tab.classList.add("badge");
|
||||||
|
tab.setAttribute("data-badge", appjson.length);
|
||||||
|
htmlToArray(panelbody.getElementsByTagName("button")).forEach(button => {
|
||||||
|
button.addEventListener("click",event => {
|
||||||
|
var icon = event.target;
|
||||||
|
var appid = icon.getAttribute("appid");
|
||||||
|
var app = appjson.find(app=>app.id==appid);
|
||||||
|
if (!app) return;
|
||||||
|
icon.classList.remove("icon-upload");
|
||||||
|
icon.classList.add("loading");
|
||||||
|
Comms.uploadApp(app).then(() => {
|
||||||
|
showToast(app.name+" Uploaded!");
|
||||||
|
icon.classList.remove("loading");
|
||||||
|
icon.classList.add("icon-delete");
|
||||||
|
}).catch(() => {
|
||||||
|
icon.classList.remove("loading");
|
||||||
|
icon.classList.add("icon-upload");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshLibrary();
|
||||||
|
// =========================================== My Apps
|
||||||
|
|
||||||
|
function appNameToApp(appName) {
|
||||||
|
var app = appjson.find(app=>app.id==appName);
|
||||||
|
if (app) return app;
|
||||||
|
return { id: "appName",
|
||||||
|
name: "Unknown app "+appName,
|
||||||
|
icon: "unknown.png",
|
||||||
|
description: "Unknown app",
|
||||||
|
storage: [],
|
||||||
|
unknown: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshMyApps() {
|
||||||
|
var panelbody = document.querySelector("#myappscontainer .panel-body");
|
||||||
|
var tab = document.querySelector("#tab-myappscontainer a");
|
||||||
|
// set badge up top
|
||||||
|
tab.classList.add("badge");
|
||||||
|
tab.setAttribute("data-badge", "");
|
||||||
|
// Loading indicator
|
||||||
|
panelbody.innerHTML = '<div class="loading loading-lg"></div>';
|
||||||
|
// Get apps
|
||||||
|
Comms.getInstalledApps().then(appIDs => {
|
||||||
|
tab.setAttribute("data-badge", appIDs.length);
|
||||||
|
panelbody.innerHTML = appIDs.map(appNameToApp).sort(appSorter).map(app => `<div class="tile">
|
||||||
|
<div class="tile-icon">
|
||||||
|
<figure class="avatar"><img src="apps/${app.icon}" alt="${escapeHtml(app.name)}"></figure>
|
||||||
|
</div>
|
||||||
|
<div class="tile-content">
|
||||||
|
<p class="tile-title text-bold">${escapeHtml(app.name)}</p>
|
||||||
|
<p class="tile-subtitle">${escapeHtml(app.description)}</p>
|
||||||
|
</div>
|
||||||
|
<div class="tile-action">
|
||||||
|
<button class="btn btn-link btn-action btn-lg"><i class="icon icon-delete" appid="${app.id}"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
htmlToArray(panelbody.getElementsByTagName("button")).forEach(button => {
|
||||||
|
button.addEventListener("click",event => {
|
||||||
|
var icon = event.target;
|
||||||
|
var appid = icon.getAttribute("appid");
|
||||||
|
var app = appNameToApp(appid);
|
||||||
|
showPrompt("Delete","Really remove app '"+appid+"'?").then(() => {
|
||||||
|
// remove app!
|
||||||
|
refreshMyApps();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
document.getElementById("myappsrefresh").addEventListener("click",event=>{
|
||||||
|
refreshMyApps();
|
||||||
|
});
|
|
@ -0,0 +1,37 @@
|
||||||
|
function escapeHtml(text) {
|
||||||
|
var map = {
|
||||||
|
'&': '&',
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
'"': '"',
|
||||||
|
"'": '''
|
||||||
|
};
|
||||||
|
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
|
||||||
|
}
|
||||||
|
function htmlToArray(collection) {
|
||||||
|
return [].slice.call(collection);
|
||||||
|
}
|
||||||
|
function htmlElement(str) {
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.innerHTML = str.trim();
|
||||||
|
return div.firstChild;
|
||||||
|
}
|
||||||
|
function httpGet(url) {
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
var oReq = new XMLHttpRequest();
|
||||||
|
oReq.addEventListener("load", () => resolve(oReq.responseText));
|
||||||
|
oReq.addEventListener("error", () => reject());
|
||||||
|
oReq.addEventListener("abort", () => reject());
|
||||||
|
oReq.open("GET", url);
|
||||||
|
oReq.send();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function toJS(txt) {
|
||||||
|
return JSON.stringify(txt);
|
||||||
|
}
|
||||||
|
// callback for sorting apps
|
||||||
|
function appSorter(a,b) {
|
||||||
|
if (a.unknown || b.unknown)
|
||||||
|
return (a.unknown)? 1 : -1;
|
||||||
|
return (a.name==b.name) ? 0 : ((a.name<b.name) ? -1 : 1);
|
||||||
|
}
|
Loading…
Reference in New Issue