forked from FOSS/BangleApps
add favourite functionality
* library select/unselect apps/widgets * as library section * as upload * deny unselect for boot and settingmaster
parent
1689973abc
commit
0fa4b44990
|
@ -7,3 +7,4 @@ Changed for individual apps are listed in `apps/appname/ChangeLog`
|
||||||
* Added optional `README.md` file for apps
|
* Added optional `README.md` file for apps
|
||||||
* Remove 2v04 version warning, add links in About to official/developer versions
|
* Remove 2v04 version warning, add links in About to official/developer versions
|
||||||
* Fix issue removing an app that was just installed (Fix #253)
|
* Fix issue removing an app that was just installed (Fix #253)
|
||||||
|
* Add `Favourite` functionality
|
||||||
|
|
|
@ -96,6 +96,7 @@
|
||||||
<label class="chip" filterid="widget">Widgets</label>
|
<label class="chip" filterid="widget">Widgets</label>
|
||||||
<label class="chip" filterid="bluetooth">Bluetooth</label>
|
<label class="chip" filterid="bluetooth">Bluetooth</label>
|
||||||
<label class="chip" filterid="outdoors">Outdoors</label>
|
<label class="chip" filterid="outdoors">Outdoors</label>
|
||||||
|
<label class="chip" filterid="favourites">Favourites</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
|
@ -134,7 +135,8 @@
|
||||||
<h3>Utilities</h3>
|
<h3>Utilities</h3>
|
||||||
<p><button class="btn" id="settime">Set Bangle.js Time</button>
|
<p><button class="btn" id="settime">Set Bangle.js Time</button>
|
||||||
<button class="btn" id="removeall">Remove all Apps</button>
|
<button class="btn" id="removeall">Remove all Apps</button>
|
||||||
<button class="btn" id="installdefault">Install default apps</button></p>
|
<button class="btn" id="installdefault">Install default apps</button>
|
||||||
|
<button class="btn" id="installfavourite">Install favourite apps</button></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
115
js/index.js
115
js/index.js
|
@ -1,6 +1,8 @@
|
||||||
var appJSON = []; // List of apps and info from apps.json
|
var appJSON = []; // List of apps and info from apps.json
|
||||||
var appsInstalled = []; // list of app JSON
|
var appsInstalled = []; // list of app JSON
|
||||||
var files = []; // list of files on Bangle
|
var files = []; // list of files on Bangle
|
||||||
|
var favourites = []; // list of user favourite app
|
||||||
|
const FAVOURITE = "favouriteapps.json";
|
||||||
|
|
||||||
httpGet("apps.json").then(apps=>{
|
httpGet("apps.json").then(apps=>{
|
||||||
try {
|
try {
|
||||||
|
@ -18,7 +20,7 @@ httpGet("apps.json").then(apps=>{
|
||||||
function showChangeLog(appid) {
|
function showChangeLog(appid) {
|
||||||
var app = appNameToApp(appid);
|
var app = appNameToApp(appid);
|
||||||
function show(contents) {
|
function show(contents) {
|
||||||
showPrompt(app.name+" Change Log",contents,{ok:true}).catch(()=>{});;
|
showPrompt(app.name+" Change Log",contents,{ok:true}).catch(()=>{});
|
||||||
}
|
}
|
||||||
httpGet(`apps/${appid}/ChangeLog`).
|
httpGet(`apps/${appid}/ChangeLog`).
|
||||||
then(show).catch(()=>show("No Change Log available"));
|
then(show).catch(()=>show("No Change Log available"));
|
||||||
|
@ -142,6 +144,20 @@ function handleAppInterface(app) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleAppFavourite(favourite, app){
|
||||||
|
if (favourite) {
|
||||||
|
favourites = favourites.concat([app.id]);
|
||||||
|
} else {
|
||||||
|
if ([ "boot","setting"].includes(app.id)) {
|
||||||
|
showToast(app.name + ' is required, can\'t remove it' , 'warning');
|
||||||
|
}else {
|
||||||
|
favourites = favourites.filter(e => e != app.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
localStorage.setItem("favouriteapps.json", JSON.stringify(favourites));
|
||||||
|
refreshLibrary();
|
||||||
|
}
|
||||||
|
|
||||||
// =========================================== Top Navigation
|
// =========================================== Top Navigation
|
||||||
function showTab(tabname) {
|
function showTab(tabname) {
|
||||||
htmlToArray(document.querySelectorAll("#tab-navigate .tab-item")).forEach(tab => {
|
htmlToArray(document.querySelectorAll("#tab-navigate .tab-item")).forEach(tab => {
|
||||||
|
@ -156,7 +172,7 @@ function showTab(tabname) {
|
||||||
|
|
||||||
// =========================================== Library
|
// =========================================== Library
|
||||||
|
|
||||||
var chips = Array.from(document.querySelectorAll('.chip')).map(chip => chip.attributes.filterid.value)
|
var chips = Array.from(document.querySelectorAll('.chip')).map(chip => chip.attributes.filterid.value);
|
||||||
var hash = window.location.hash ? window.location.hash.slice(1) : '';
|
var hash = window.location.hash ? window.location.hash.slice(1) : '';
|
||||||
|
|
||||||
var activeFilter = !!~chips.indexOf(hash) ? hash : '';
|
var activeFilter = !!~chips.indexOf(hash) ? hash : '';
|
||||||
|
@ -165,27 +181,34 @@ var currentSearch = '';
|
||||||
function refreshFilter(){
|
function refreshFilter(){
|
||||||
var filtersContainer = document.querySelector("#librarycontainer .filter-nav");
|
var filtersContainer = document.querySelector("#librarycontainer .filter-nav");
|
||||||
filtersContainer.querySelector('.active').classList.remove('active');
|
filtersContainer.querySelector('.active').classList.remove('active');
|
||||||
if(activeFilter) filtersContainer.querySelector('.chip[filterid="'+activeFilter+'"]').classList.add('active')
|
if(activeFilter) filtersContainer.querySelector('.chip[filterid="'+activeFilter+'"]').classList.add('active');
|
||||||
else filtersContainer.querySelector('.chip[filterid]').classList.add('active')
|
else filtersContainer.querySelector('.chip[filterid]').classList.add('active');
|
||||||
}
|
}
|
||||||
function refreshLibrary() {
|
function refreshLibrary() {
|
||||||
var panelbody = document.querySelector("#librarycontainer .panel-body");
|
var panelbody = document.querySelector("#librarycontainer .panel-body");
|
||||||
var visibleApps = appJSON;
|
var visibleApps = appJSON;
|
||||||
|
|
||||||
if (activeFilter) {
|
if (activeFilter) {
|
||||||
visibleApps = visibleApps.filter(app => app.tags && app.tags.split(',').includes(activeFilter));
|
if ( activeFilter == "favourites" ) {
|
||||||
|
visibleApps = visibleApps.filter(app => app.id && (favourites.filter( e => e == app.id).length));
|
||||||
|
}else{
|
||||||
|
visibleApps = visibleApps.filter(app => app.tags && app.tags.split(',').includes(activeFilter));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentSearch) {
|
if (currentSearch) {
|
||||||
visibleApps = visibleApps.filter(app => app.name.toLowerCase().includes(currentSearch) || app.tags.includes(currentSearch));
|
visibleApps = visibleApps.filter(app => app.name.toLowerCase().includes(currentSearch) || app.tags.includes(currentSearch));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
favourites = (localStorage.getItem(FAVOURITE)) === null ? JSON.parse('["boot","launch","setting"]') : JSON.parse(localStorage.getItem("favouriteapps.json"));
|
||||||
|
|
||||||
panelbody.innerHTML = visibleApps.map((app,idx) => {
|
panelbody.innerHTML = visibleApps.map((app,idx) => {
|
||||||
var appInstalled = appsInstalled.find(a=>a.id==app.id);
|
var appInstalled = appsInstalled.find(a=>a.id==app.id);
|
||||||
var version = getVersionInfo(app, appInstalled);
|
var version = getVersionInfo(app, appInstalled);
|
||||||
var versionInfo = version.text;
|
var versionInfo = version.text;
|
||||||
if (versionInfo) versionInfo = " <small>("+versionInfo+")</small>";
|
if (versionInfo) versionInfo = " <small>("+versionInfo+")</small>";
|
||||||
var readme = `<a href="#" onclick="showReadme('${app.id}')">Read more...</a>`;
|
var readme = `<a href="#" onclick="showReadme('${app.id}')">Read more...</a>`;
|
||||||
|
var favourite = favourites.find(e => e == app.id);
|
||||||
return `<div class="tile column col-6 col-sm-12 col-xs-12">
|
return `<div class="tile column col-6 col-sm-12 col-xs-12">
|
||||||
<div class="tile-icon">
|
<div class="tile-icon">
|
||||||
<figure class="avatar"><img src="apps/${app.icon?`${app.id}/${app.icon}`:"unknown.png"}" alt="${escapeHtml(app.name)}"></figure><br/>
|
<figure class="avatar"><img src="apps/${app.icon?`${app.id}/${app.icon}`:"unknown.png"}" alt="${escapeHtml(app.name)}"></figure><br/>
|
||||||
|
@ -196,6 +219,7 @@ function refreshLibrary() {
|
||||||
<a href="https://github.com/espruino/BangleApps/tree/master/apps/${app.id}" target="_blank" class="link-github"><img src="img/github-icon-sml.png" alt="See the code on GitHub"/></a>
|
<a href="https://github.com/espruino/BangleApps/tree/master/apps/${app.id}" target="_blank" class="link-github"><img src="img/github-icon-sml.png" alt="See the code on GitHub"/></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="tile-action">
|
<div class="tile-action">
|
||||||
|
<button class="btn btn-link btn-action btn-lg ${!app.custom?"text-error":"d-hide"}" appid="${app.id}" title="Favorite"><i class="icon"></i>${favourite?"♥":"♡"}</button>
|
||||||
<button class="btn btn-link btn-action btn-lg ${(appInstalled&&app.interface)?"":"d-hide"}" appid="${app.id}" title="Download data from app"><i class="icon icon-download"></i></button>
|
<button class="btn btn-link btn-action btn-lg ${(appInstalled&&app.interface)?"":"d-hide"}" appid="${app.id}" title="Download data from app"><i class="icon icon-download"></i></button>
|
||||||
<button class="btn btn-link btn-action btn-lg ${app.allow_emulator?"":"d-hide"}" appid="${app.id}" title="Try in Emulator"><i class="icon icon-share"></i></button>
|
<button class="btn btn-link btn-action btn-lg ${app.allow_emulator?"":"d-hide"}" appid="${app.id}" title="Try in Emulator"><i class="icon icon-share"></i></button>
|
||||||
<button class="btn btn-link btn-action btn-lg ${version.canUpdate?"":"d-hide"}" appid="${app.id}" title="Update App"><i class="icon icon-refresh"></i></button>
|
<button class="btn btn-link btn-action btn-lg ${version.canUpdate?"":"d-hide"}" appid="${app.id}" title="Update App"><i class="icon icon-refresh"></i></button>
|
||||||
|
@ -232,7 +256,7 @@ function refreshLibrary() {
|
||||||
// upload
|
// upload
|
||||||
icon.classList.remove("icon-upload");
|
icon.classList.remove("icon-upload");
|
||||||
icon.classList.add("loading");
|
icon.classList.add("loading");
|
||||||
uploadApp(app)
|
uploadApp(app);
|
||||||
} else if (icon.classList.contains("icon-menu")) {
|
} else if (icon.classList.contains("icon-menu")) {
|
||||||
// custom HTML update
|
// custom HTML update
|
||||||
icon.classList.remove("icon-menu");
|
icon.classList.remove("icon-menu");
|
||||||
|
@ -250,6 +274,10 @@ function refreshLibrary() {
|
||||||
updateApp(app);
|
updateApp(app);
|
||||||
} else if (icon.classList.contains("icon-download")) {
|
} else if (icon.classList.contains("icon-download")) {
|
||||||
handleAppInterface(app);
|
handleAppInterface(app);
|
||||||
|
} else if ( button.innerText == String.fromCharCode(0x2661)) {
|
||||||
|
handleAppFavourite(true, app);
|
||||||
|
} else if ( button.innerText == String.fromCharCode(0x2665) ) {
|
||||||
|
handleAppFavourite(false, app);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -262,17 +290,17 @@ refreshLibrary();
|
||||||
function uploadApp(app) {
|
function uploadApp(app) {
|
||||||
return getInstalledApps().then(()=>{
|
return getInstalledApps().then(()=>{
|
||||||
if (appsInstalled.some(i => i.id === app.id)) {
|
if (appsInstalled.some(i => i.id === app.id)) {
|
||||||
return updateApp(app)
|
return updateApp(app);
|
||||||
}
|
}
|
||||||
Comms.uploadApp(app).then((appJSON) => {
|
Comms.uploadApp(app).then((appJSON) => {
|
||||||
Progress.hide({ sticky: true })
|
Progress.hide({ sticky: true });
|
||||||
if (appJSON) {
|
if (appJSON) {
|
||||||
appsInstalled.push(appJSON)
|
appsInstalled.push(appJSON);
|
||||||
}
|
}
|
||||||
showToast(app.name + ' Uploaded!', 'success')
|
showToast(app.name + ' Uploaded!', 'success');
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
Progress.hide({ sticky: true })
|
Progress.hide({ sticky: true });
|
||||||
showToast('Upload failed, ' + err, 'error')
|
showToast('Upload failed, ' + err, 'error');
|
||||||
}).finally(()=>{
|
}).finally(()=>{
|
||||||
refreshMyApps();
|
refreshMyApps();
|
||||||
refreshLibrary();
|
refreshLibrary();
|
||||||
|
@ -286,8 +314,8 @@ function removeApp(app) {
|
||||||
return showPrompt("Delete","Really remove '"+app.name+"'?").then(() => {
|
return showPrompt("Delete","Really remove '"+app.name+"'?").then(() => {
|
||||||
return getInstalledApps().then(()=>{
|
return getInstalledApps().then(()=>{
|
||||||
// a = from appid.info, app = from apps.json
|
// a = from appid.info, app = from apps.json
|
||||||
return Comms.removeApp(appsInstalled.find(a => a.id === app.id))
|
return Comms.removeApp(appsInstalled.find(a => a.id === app.id));
|
||||||
})
|
});
|
||||||
}).then(()=>{
|
}).then(()=>{
|
||||||
appsInstalled = appsInstalled.filter(a=>a.id!=app.id);
|
appsInstalled = appsInstalled.filter(a=>a.id!=app.id);
|
||||||
showToast(app.name+" removed successfully","success");
|
showToast(app.name+" removed successfully","success");
|
||||||
|
@ -315,13 +343,13 @@ function updateApp(app) {
|
||||||
if (app.custom) return customApp(app);
|
if (app.custom) return customApp(app);
|
||||||
return getInstalledApps().then(() => {
|
return getInstalledApps().then(() => {
|
||||||
// a = from appid.info, app = from apps.json
|
// a = from appid.info, app = from apps.json
|
||||||
let remove = appsInstalled.find(a => a.id === app.id)
|
let remove = appsInstalled.find(a => a.id === app.id);
|
||||||
// no need to remove files which will be overwritten anyway
|
// no need to remove files which will be overwritten anyway
|
||||||
remove.files = remove.files.split(',')
|
remove.files = remove.files.split(',')
|
||||||
.filter(f => f !== app.id + '.info')
|
.filter(f => f !== app.id + '.info')
|
||||||
.filter(f => !app.storage.some(s => s.name === f))
|
.filter(f => !app.storage.some(s => s.name === f))
|
||||||
.join(',')
|
.join(',');
|
||||||
return Comms.removeApp(remove)
|
return Comms.removeApp(remove);
|
||||||
}).then(()=>{
|
}).then(()=>{
|
||||||
showToast(`Updating ${app.name}...`);
|
showToast(`Updating ${app.name}...`);
|
||||||
appsInstalled = appsInstalled.filter(a=>a.id!=app.id);
|
appsInstalled = appsInstalled.filter(a=>a.id!=app.id);
|
||||||
|
@ -397,7 +425,7 @@ return `<div class="tile column col-6 col-sm-12 col-xs-12">
|
||||||
// check icon to figure out what we should do
|
// check icon to figure out what we should do
|
||||||
if (icon.classList.contains("icon-delete")) removeApp(app);
|
if (icon.classList.contains("icon-delete")) removeApp(app);
|
||||||
if (icon.classList.contains("icon-refresh")) updateApp(app);
|
if (icon.classList.contains("icon-refresh")) updateApp(app);
|
||||||
if (icon.classList.contains("icon-download")) handleAppInterface(app)
|
if (icon.classList.contains("icon-download")) handleAppInterface(app);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -405,7 +433,7 @@ return `<div class="tile column col-6 col-sm-12 col-xs-12">
|
||||||
let haveInstalledApps = false;
|
let haveInstalledApps = false;
|
||||||
function getInstalledApps(refresh) {
|
function getInstalledApps(refresh) {
|
||||||
if (haveInstalledApps && !refresh) {
|
if (haveInstalledApps && !refresh) {
|
||||||
return Promise.resolve(appsInstalled)
|
return Promise.resolve(appsInstalled);
|
||||||
}
|
}
|
||||||
showLoadingIndicator("myappscontainer");
|
showLoadingIndicator("myappscontainer");
|
||||||
// Get apps and files
|
// Get apps and files
|
||||||
|
@ -453,7 +481,7 @@ filtersContainer.addEventListener('click', ({ target }) => {
|
||||||
activeFilter = target.getAttribute('filterid') || '';
|
activeFilter = target.getAttribute('filterid') || '';
|
||||||
refreshFilter();
|
refreshFilter();
|
||||||
refreshLibrary();
|
refreshLibrary();
|
||||||
window.location.hash = activeFilter
|
window.location.hash = activeFilter;
|
||||||
});
|
});
|
||||||
|
|
||||||
var librarySearchInput = document.querySelector("#searchform input");
|
var librarySearchInput = document.querySelector("#searchform input");
|
||||||
|
@ -526,7 +554,7 @@ document.getElementById("installdefault").addEventListener("click",event=>{
|
||||||
upload();
|
upload();
|
||||||
}).catch(function() {
|
}).catch(function() {
|
||||||
Progress.hide({sticky:true});
|
Progress.hide({sticky:true});
|
||||||
reject()
|
reject();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
upload();
|
upload();
|
||||||
|
@ -541,3 +569,48 @@ document.getElementById("installdefault").addEventListener("click",event=>{
|
||||||
showToast("App Install failed, "+err,"error");
|
showToast("App Install failed, "+err,"error");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Install all favoutrie apps in one go
|
||||||
|
document.getElementById("installfavourite").addEventListener("click",event=>{
|
||||||
|
var defaultApps, appCount;
|
||||||
|
asyncLocalStorage.getItem(FAVOURITE).then(json=>{
|
||||||
|
defaultApps = JSON.parse(json);
|
||||||
|
defaultApps = defaultApps.map( appid => appJSON.find(app=>app.id==appid) );
|
||||||
|
if (defaultApps.some(x=>x===undefined))
|
||||||
|
throw "Not all apps found";
|
||||||
|
appCount = defaultApps.length;
|
||||||
|
return showPrompt("Install Defaults","Remove everything and install favourite apps?");
|
||||||
|
}).then(() => {
|
||||||
|
return Comms.removeAllApps();
|
||||||
|
}).then(()=>{
|
||||||
|
Progress.hide({sticky:true});
|
||||||
|
appsInstalled = [];
|
||||||
|
showToast(`Existing apps removed. Installing ${appCount} apps...`);
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
function upload() {
|
||||||
|
var app = defaultApps.shift();
|
||||||
|
if (app===undefined) return resolve();
|
||||||
|
Progress.show({title:`${app.name} (${appCount-defaultApps.length}/${appCount})`,sticky:true});
|
||||||
|
Comms.uploadApp(app,"skip_reset").then((appJSON) => {
|
||||||
|
Progress.hide({sticky:true});
|
||||||
|
if (appJSON) appsInstalled.push(appJSON);
|
||||||
|
showToast(`(${appCount-defaultApps.length}/${appCount}) ${app.name} Uploaded`);
|
||||||
|
upload();
|
||||||
|
}).catch(function() {
|
||||||
|
Progress.hide({sticky:true});
|
||||||
|
reject();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
upload();
|
||||||
|
});
|
||||||
|
}).then(()=>{
|
||||||
|
return Comms.setTime();
|
||||||
|
}).then(()=>{
|
||||||
|
showToast("Favourites apps successfully installed!","success");
|
||||||
|
return getInstalledApps(true);
|
||||||
|
}).catch(err=>{
|
||||||
|
Progress.hide({sticky:true});
|
||||||
|
showToast("App Install failed, "+err,"error");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
1
js/ui.js
1
js/ui.js
|
@ -86,6 +86,7 @@ function showToast(message, type) {
|
||||||
var style = "toast-primary";
|
var style = "toast-primary";
|
||||||
if (type=="success") style = "toast-success";
|
if (type=="success") style = "toast-success";
|
||||||
else if (type=="error") style = "toast-error";
|
else if (type=="error") style = "toast-error";
|
||||||
|
else if (type=="warning") style = "toast-warning";
|
||||||
else if (type!==undefined) console.log("showToast: unknown toast "+type);
|
else if (type!==undefined) console.log("showToast: unknown toast "+type);
|
||||||
var toastcontainer = document.getElementById("toastcontainer");
|
var toastcontainer = document.getElementById("toastcontainer");
|
||||||
var msgDiv = htmlElement(`<div class="toast ${style}"></div>`);
|
var msgDiv = htmlElement(`<div class="toast ${style}"></div>`);
|
||||||
|
|
13
js/utils.js
13
js/utils.js
|
@ -67,3 +67,16 @@ function getVersionInfo(appListing, appInstalled) {
|
||||||
canUpdate : canUpdate
|
canUpdate : canUpdate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const asyncLocalStorage = {
|
||||||
|
setItem: function (key, value) {
|
||||||
|
return Promise.resolve().then(function () {
|
||||||
|
localStorage.setItem(key, value);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getItem: function (key) {
|
||||||
|
return Promise.resolve().then(function () {
|
||||||
|
return localStorage.getItem(key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue