Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://gitlab.com/TheJaredWilcurt/battery-app-workshop

Instructions and code samples for a Desktop App workshop.
https://gitlab.com/TheJaredWilcurt/battery-app-workshop

Last synced: 3 months ago
JSON representation

Instructions and code samples for a Desktop App workshop.

Awesome Lists containing this project

README

        

screenshot of finished app running

# Introduction

This repo contains step-by-step instructions for creating your first desktop app. It was created for a live workshop I gave. It is split into 3 sections with a TLDR style summary at the end of each section.

If you'd like to see the finished project checkout the `finished-app` branch.

In this workshop you will learn the **basics** of:
* Node.js
* NPM
* NW.js
* Vue.js
* Packaging a desktop app for distribution

**Prerequisites for this workshop:**
* A laptop (any of these should work)
* A Windows laptop (32-Bit or 64-Bit) with admin rights.
* An Apple laptop (64-Bit) with access to "sudo" (though we probably won't need it)
* A laptop with Ubuntu, Debian, Zorin, or other Debian-based version of Linux (32-Bit or 64-Bit) with access to "sudo".
* **Note:** If you are using a laptop with an ARM processor (like a Chromebook), you will not be able to participate, but you can still watch and take notes.
* A Text Editor like Sublime Text, VS Code, Brackets, or Atom.
* Basic knowledge of HTML/CSS/JS
* Basic usage of the command prompt/terminal
* Install Node.js before you arrive unless you need help installing it.

# Setting up the project

**Note:** Whenever this tutorial says `Run` that means you should run the command in the command line or Terminal.

1. Install Node and NPM
* http://nodejs.org
* NPM comes with Node on Windows and OSX.
* If you are on linux, you will need to google how to install NPM as instructions vary for different distros.
1. Run `node -v` and `npm -v` to ensure they are installed
1. If you have a folder where you keep all your projects, create a new folder in there called "battery", otherwise just make it on your desktop.
* From this point on, assume all files referenced will be inside this "battery" folder
1. Navigate to the "battery" folder in the command line/terminal
1. Run `npm init`
* Keep the defaults for everything except the `entry point`, set that to `index.html`
1. Run `npm install --save-dev nw@sdk`
1. Edit the `package.json` file in your text editor. In the `scripts` section. Add this script:
* `"start": "nw ."`
1. Create an `index.html` file in your "battery" folder
1. Edit the `index.html` file and put this inside it:

```html


Hello World


Hello World


```
10\. Run `npm start`
* A window with our "Hello World" message should have launched. You can close it now, that means it worked!
* At any step from now on, you can run `npm start` to launch the app and see your changes.
* Once open try one of these commands to open the Developer Tools, some commands only work on some OS's.
* `F12`
* `Fn + F12`
* `Ctrl + Shift + I`
* `Ctrl + Shift + J`
* `Cmd + Shift + I`
* `Cmd + Shift + J`

11\. To stop running your app you can close its window or click on your command prompt/teminal and run one of these commands (depending on OS)
* `Ctrl + C`
* `Ctrl + Z`

* * *

The steps in the above section are the same for basically any desktop app you want to create.

**Section Summary**:

1. `npm init` (set entry to `index.html`)
1. `npm install --save-dev nw@sdk`
1. Edit the package.json to have `"nw ."` for the `"start"` script
1. Create an empty `index.html` file
1. `npm start`

* * *

# Building the app

1. For this project we will be using the Vue.js framework to make instructions simpler and the Node module "SystemInformation" to give use easy access to information about the computer's battery.
* Run `npm install --save vue systeminformation`
1. Create a `scripts` folder and a `styles` folder in your "battery" project folder.
1. In the `scripts` folder create a file called `main.js`
1. In the `styles` folder create a file called `style.css`
1. Replace the code in your `index.html` file with this:

```html


Battery Meter




Battery Meter




```

6\. Now we want to start adding in some dynamic functionality, so we're going to create a `

` with an `id` of "app" under the `h2` to put all the dynamic stuff in, so Vue can handle it for us.

```html

Battery Meter




```

7\. In our `scripts\main.js` file, lets set up Vue so it knows the basics.
* The data will will be what we use in the HTML to display the correct content. Updating the values in our JS will also update the values in the DOM for us

```js
const app = new Vue({
el: '#app',
data: {
percent: 0,
cycles: 0,
isCharging: false,
updatedInfo: false
}
});
```

8\. Download a battery icon image from [IconArchive.com](http://iconarchive.com).
* Download a `.PNG` image, and if you are on Windows, also get the `.ico` file, if you are on OSX also get the `.icns` files.
* Save the images as `icon.png`, `icon.ico`, and `app.icns` so we can reference them later.

9\. In the `index.html` let's add some content into our `

` and have it reference those pieces of data from Vue.
* There's a lot here, so lets break it up into each line
* The `meter` will create a bar on the page for us to set a value for. The `:value="percent"` will set it to whatever the `data.percent` value is.
* We're going to put in an image and a big text of the current battery percent. The `{{ percent }}` will be set by whatever `data.percent` is.
* The next two blocks have a `v-if` and `v-else`. Based on if `isCharging` is `true` or `false`, it will only show one of the two blocks.
* Not all batteries keep track of a Cycle count. So we only show the amount of cycles if it's greater than 0.
* We want to show when the app checked for battery, so we are going to set its class to `updated` if `updatedInfo` is `true`

```html



Battery Icon{{ percent }}%

Charging

Not charging

Cycle count: {{ cycles }}

Updated


```

10\. At this point we have some content in the app, but it isn't very pretty and it isn't using real data. So let's fix that.
11\. In your `styles\style.css` file add in this:

```css
body {
font-family: tahoma, sans-serif;
text-align: center;
}
h2 {
margin: 20px 0px;
font-size: 40px;
}
meter {
width: 270px;
height: 30px;
}
h1 {
margin: 20px 0px;
color: #030;
font-size: 60px;
font-weight: bold;
}
img,
.percent {
max-height: 60px;
margin: 0px 10px;
vertical-align: middle;
}
.charging {
color: #797;
font-style: italic;
}
.charging,
.cycle {
margin-bottom: 10px;
}
.updated {
opacity: 0.0;
transition: 2s ease all;
}
```

12\. That's all just plain CSS, so not a lot to explain. One item worth noting is that the `.updated` class has a 2 second animation that will fade it out. This will come in handy later.
13\. Now let's add some live data to the app using the `systeminformation` Node module.
14\. Add this to the bottom of your `main.js` file.

```js
// Import the systeminformation module and store it in the si variable
const si = require('systeminformation');

// Create a function that can be continuosly called to get updated battery info
function updateData () {
// Access the battery API of si and pass in a callback function
si.battery(function (data) {
// Force the value of updatedInfo in our Vue app to be false,
// this will show the text "Updated" on the page.
app.updatedInfo = false;
// Update the data in our Vue app with the data from si
app.percent = data.percent;
app.isCharging = data.ischarging;
app.cycles = data.cyclecount;
// To force a CSS animation to occur we wait 1 second
setTimeout(function () {
// Then re-add the class of "updated" on the page so the text fades away
app.updatedInfo = true;
}, 1000);
});
}

// Run the function once when the app first loads.
updateData();

// Update the battery data every 10 seconds.
setInterval(updateData, (10 * 1000));
```

15\. Find the `"main"` section in your `package.json` add this `"window"` section right after it:

```js
"main": "index.html",
"window": {
"width": 430,
"height": 350,
"icon": "icon.png"
},
```

16\. Close your app and relaunch it to test the icon and window size.
* Icon may not show up during development on OSX, but should on Windows and most Ubuntu.

* * *

**Section summary:**

Here's everything in one `index.html` file (though you're better off making seperate `.js`, `.css`, `.html`).

```html


Battery Meter


body {
font-family: tahoma, sans-serif;
text-align: center;
}
h2 {
margin: 20px 0px;
font-size: 40px;
}
meter {
width: 270px;
height: 30px;
}
h1 {
margin: 20px 0px;
color: #030;
font-size: 60px;
font-weight: bold;
}
img,
.percent {
max-height: 60px;
margin: 0px 10px;
vertical-align: middle;
}
.charging {
color: #797;
font-style: italic;
}
.cycle,
.charging {
margin-bottom: 10px;
}
.updated {
transition: 2s ease all;
opacity: 0.0;
}




Battery Meter





Battery Icon{{ percent }}%


Charging

Not charging

Cycle count: {{ cycles }}

Updated



var app = new Vue({
el: '#app',
data: {
percent: 0,
isCharging: false,
cycles: 0,
updatedInfo: false
}
});
var si = require('systeminformation');
function updateData () {
si.battery(function (data) {
app.updatedInfo = false;
app.percent = data.percent;
app.isCharging = data.ischarging;
app.cycles = data.cyclecount;
setTimeout(function () {
app.updatedInfo = true;
}, 1000);
});
}
updateData();
setInterval(updateData, 10000);

```

Also download an image and set it in your `package.json` like so:
```js
{
"name": "battery",
"version": "1.0.0",
"author": "",
"description": "",
"main": "index.html",
"scripts": {
"start": "nw ."
},
"dependencies": {
"systeminformation": "^3.23.6",
"vue": "2.x.x"
},
"devDependencies": {
"nw": "sdk"
},
"window": {
"width": 430,
"height": 350,
"icon": "icon.png"
},
"license": "MIT"
}
```

**Note:** If your version numbers for your `dependencies` and `devDependencies` are different, that's fine.

* * *

# Packaging your app for distribution

**All OS's**:

1. Create an empty folder called `package.nw` on your desktop.
* On OSX call it `app.nw` instead
1. Copy all of your project files/folders into it, **except** the `node_modules` folder.
1. Edit the `package.nw/package.json` file and delete the `devDependencies` section. Save.
1. In the commandline `cd` into `package.nw` (or `app.nw`) and run `npm install`. Now your `node_modules` folder in the `package.nw` folder only has your dependencies for your app, and none of the devDependencies.
1. Next download the **normal** (not SDK) version of NW.js for your platform:
* [https://nwjs.io/downloads](https://nwjs.io/downloads)
1. Open the download and extract the `nwjs-v0.xx.x-yyy-zzz` folder to your desktop and rename it to `battery-1.0.0`

**Windows/Linux**:

1. Move the `package.nw` folder inside the `battery-1.0.0` folder
1. Open the `battery-1.0.0` folder and rename the `nw.exe` file to `BatteryMeter.exe`
* On Windows you can download [Resource Hacker](http://www.angusj.com/resourcehacker/), and open your `BatteryMeter.exe` in it to change the icon and meta data.
1. Double-Click `BatteryMeter.exe` to see your app launch from the folder.
* Optionally you can try removing files from your `battery-1.0.0` folder that come with NW.js and see if your app still runs and doesn't produce a debug log. Some are only used when you are doing specific tasks, like viewing PDF's, audio, or video. This can lower your distribution size some (~10MB), but will cause your app to crash if it needs to call upon that file at some point. So if you do this, be sure to test your app thoroughly.
1. Zip up the `battery-1.0.0` folder. Make sure to use a `.zip` file (not `.7z` or `.rar`) since all OS's can open them natively.
* You can use 7-Zip with options of deflate and Ultra to get a smaller filesize. (only use deflate, other compression types are not supported on Windows natively)

**OSX**:

1. Open the `battery-1.0.0` folder on the desktop
1. Right-click the `nwjs.app` file and open its contents
1. Go to the `nwjs.app/Contents/Resources` folder and move the `app.nw` folder from your Desktop to here.
1. Go back to the `nwjs.app` file and double-click it
* If you get a security warning, click OK and go to System Preferences > Security & Privacy > Lock icon in corner > Anywhere
1. Close the app.
1. In Finder in the `nwjs.app/Contents/Resources` folder, replace the `app.icns` and `document.icns` file with copies of your `app.icns` icon.
1. Right-Click `nwjs.app` > Get Info > Drag your `app.icns` file to the icon in the top corner of the info window. Close the info window.
1. Edit the `nwjs.app/Contents/Info.plist` file in your text editor.
1. Change these line:

```html
CFBundleDisplayName
nwjs
```
to
```html
CFBundleDisplayName
BatteryMeter
```
and
```html
CFBundleName
Chromium
```
to
```html
CFBundleName
BatteryMeter
```

10\. Rename `nwjs.app` to `BatteryMeter.app`

11\. Navigate to the `battery-1.0.0` folder in terminal and run `zip -r -9 "battery-1.0.0.zip" "BatteryMeter.app"`

* * *

Wow, manually packaging your app for distribution is a lot of weird tedius work. Fortunately, there are tools to automate the process, but I wanted you to understand what those tools are doing for you behind the scenes. The most popular option is [nw-builder-phoenix](https://github.com/evshiron/nwjs-builder-phoenix#readme).

* [More information](http://docs.nwjs.io/en/latest/For%20Users/Package%20and%20Distribute/)

* * *

## Additional Resources

* [NW.js Official Website](https://nwjs.io)
* [NW.js Community resources](https://nwutils.io)