espruino / BangleApps

Bangle.js App Loader (and Apps)
https://banglejs.com/apps
MIT License
489 stars 1.15k forks source link

What about a "Install My default Apps" feature? #212

Closed MaBecker closed 4 years ago

MaBecker commented 4 years ago

The App Loader has a function in section About to "Install default Apps"

What about a feature like this, if possible?

brainfart-be commented 4 years ago

That's a nice idea. I had to reinstall everything 2 or 3 times already and it's a bit annoying to do.

MaBecker commented 4 years ago

Or have in section My Apps the feature to store this set of installed apps as "My default Apps".

MaBecker commented 4 years ago

this is how the defaultapps.json look like:

["boot","launch","mclock","setting","about","alarm","widbat","widbt","welcome"]

It's an array of app id's

gfwilliams commented 4 years ago

Sounds good. Maybe have a little heart you can click to favourite an app. It could even drag them to the top of the apps list.

MaBecker commented 4 years ago

Ok, so lets work on this one ;-)

MaBecker commented 4 years ago

Please check the way I am planing to implement the favorite functionality

What do you think about this approach?

gfwilliams commented 4 years ago

add empty and filled heart image (16x16) for possible apps

You could maybe just use a unicode heart character? https://www.fileformat.info/info/unicode/char/2661/index.htm and https://www.fileformat.info/info/unicode/char/2665/index.htm ?

But yeah, sounds great to me! As an optional thing you could add a 'favourite' tag to the listed tags there, so only the favourites get shown?

MaBecker commented 4 years ago

Ok, got a set of default favorite apps

favorites = (localStorage.getItem("favoriteapps.json")) == null ? JSON.parse('["boot","launch","setting"]') : JSON.parse(localStorage.getItem("favoriteapps.json"));

know how to determine if favorite

var favorite = favorites.find(e => e == app.id);

can add red heart conture or filled to output

      <button class="btn btn-link btn-action btn-lg ${!app.custom?"text-error":"d-hide"}" appid="${app.id}" title="Favorite"><i class="icon"></i>${favorite?"&#x2665;":"&#x2661;"}</button>

can handle click-on

      else if ( button.innerText =  "&#x2661" ||  button.innerText == "&#x2665") {
        if ( button.innerText == "&#x2661") // empty heart, add to list
          handleAppFavorite(true, app);
        if (button.innerText == "&#x2665")  // full heart, remove from list 
          handleAppFavorite(false, app);
      }

created function handleAppFavorite()

function handleAppFavorite(favorite , app){
  if (favorite) {
    console.log("remove from favorites")
    favorites = favorites.filter(e =>e != app.id);
  } else {
    console.log("add to favorites")
    favorites = favorites.concat([app.id]);
  }
  localStorage.setItem("favoriteapps.json", JSON.stringify(favorites));
}

Rendered page

Bildschirmfoto 2020-04-10 um 21 11 51

Page after a click on heart

Bildschirmfoto 2020-04-10 um 21 12 35

No clue why the <i class="icon"></i>got lost.

Any hint how to get this fixed?

brainfart-be commented 4 years ago

Do you have a "live" version of this running somewhere ?
Or is your fork contain this code? I can take a look at this

EDIT: I don't see the part where you replace the icon in the code sample you put there.

First thing that got to my mind is since you are doing some checking on button.innerText, i assume that you are editing the icon doing something like button.innerText = "&#x2661" If that's the case, the <i></i> is in that button and is overwrite by the button.innerText = "&#x2661" And a simple fix would be to do button.innerHTML = "<i class='icon'></i>&#x2661;" Or to put the ♡ in a span and edit that span

MaBecker commented 4 years ago

Wow, you named and yes there is a singe = where a double should be ...

     else if ( button.innerText =  "&#x2661" ||  button.innerText == "&#x2665") {
brainfart-be commented 4 years ago

I literally copy/pasted the error without noticing :D (And i didn't expected that at all haha)

MaBecker commented 4 years ago

Any idea how to make this special filter work?

favourites =  ["boot", "launch", "setting"];
activeFilter = "favourites";

filter section

function refreshLibrary() {
  var panelbody = document.querySelector("#librarycontainer .panel-body");
  var visibleApps = appJSON;

  if (activeFilter) {

    visibleApps = visibleApps.filter(app => app.tags && app.tags.split(',').includes(activeFilter));

    // special filter 
    if ( activeFilter == "favourites" ) {
      console.log(activeFilter,visibleApps);
 ->>     visibleApps = visibleApps.filter(app => app.id && (favourites.filter( e => e == app.id)));
    }
  }
......

visibleApps should contain the apps with id's from favourites.

brainfart-be commented 4 years ago

I don't know which part will fix it, but i did two thing I add .length in the filter function you wrote I put the "active filter" in the else

if (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));
    }

  }
MaBecker commented 4 years ago

Bildschirmfoto 2020-04-11 um 22 56 48

MaBecker commented 4 years ago

Should favourite function deny to remove bootloader and setting from the list - I would say yes.

What about launch?

brainfart-be commented 4 years ago

I surely wouldn't put launcher in, I don't use it and i don't want my favourites list to contains something i don't want.

Obviously bootloader and setting are different because it's "required" in order to have a fully functional watch

MaBecker commented 4 years ago

Now it show's a warning toast for app.id boot and setting if trying to remove

Bildschirmfoto 2020-04-12 um 12 14 28

MaBecker commented 4 years ago

Is there a shorter way to code this?

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);
        });
    }
};
// Install all favoutrie apps in one go
document.getElementById("installfavourite").addEventListener("click",event=>{
  var defaultApps, appCount;

  asyncLocalStorage.getItem(FAVOURITE).then(json=>{
........
brainfart-be commented 4 years ago

What is the point of making it use a Promise ?

localStorage (as sessionStorage) is synchronous in the sense that it is a virtually blocking API. When an instruction saving data in localStorage is executed, the next instruction in the script will only be executed once the data is (somehow) saved

I think a shorter way to write this would be to remove the idea of wanting a Promise there

MaBecker commented 4 years ago

What is the point of making it use a Promise ?

Well, I just wanted to use the same code default install is using:

https://github.com/espruino/BangleApps/blob/1689973abc8a51511bfea54bddcbb1bc34a9b871/js/index.js#L502-L504

MaBecker commented 4 years ago

Just created pull request #292.

gfwilliams commented 4 years ago

Just merged! Actually, just a thought about the above:

I just wanted to use the same code

Maybe we could have a installApps function that took a list of app names? That way we could share the same code between installDefaultApps and installFavouriteApps?

MaBecker commented 4 years ago

Maybe we could have a installApps function that took a list of app names

Nice, like that. So let me see what I can achieve.

MaBecker commented 4 years ago

@brainfart-be anything you observed to be added or changed?

gfwilliams commented 4 years ago

Just did the installApps function - think this is done :)

MaBecker commented 4 years ago

Thanks