The making of a wallpaper-changing app with electron and vue.js
Purpose and technologies
Purpose
The main purpose of this app is to help users to change their desktop wallpaper and maybe more awesome features such as finding an image, etc. Because there is a vast number of devices and OSs out there so we need to find some frameworks that enable our app to run cross-platform.
Technologies
Enough with the story! Let’s start building the real thing!
So with this app, we are going to use:
- Vue.js — as our front-end framework.
- Vue-loader — as our Vue.js component loader.
- Webpack — as our module bundler.
- Buefy — It contains all the Vue components that we need.
- Electron — This is the framework for building cross-platform apps using web technologies.
- Yarn — as our dependencies manager.
OK, that is all the ingredients that we need.
Setting up the working environment
Code editor
I’m using sublime text 3 with these packages:
Vue.js, vue-cli and generating project folder
I assume that you have already had some basic knowledge about Vue.js and have vue-cli installed. If not, you can learn more about Vue.js at the link on the top section and install vue-cli using:
npm install -g vue-cliORyarn global add vue-cli
After installing vue-cli globally, we can use the vue command to create our new project folder:
vue init webpack-simple [Your project folder name]
In my case, I’m going to name my folder as BGChanger (worst name ever) so I going to type in my command prompt:
vue init webpack-simple BGChanger
After that it will ask some questions about the project in order to generate the package.json file:
Now let’s navigate to our “BGChanger” folder using the command:
cd BGChanger
and then, as you guys have ready guessed:
npm installORyarn install
In this case, I’m going to use yarn to install all my dependencies so I going to use the second command.
After that execute this command
npm run dev
That command helps us to run and check if the project is correctly installed, if it does then it should open up the browser and appear like this:
Buefy
According to Buefy title: “Lightweight UI components for Vue.js based on Bulma” so this is an adequate overview of what Buefy is. Now let’s install it using
yarn add buefyORnpm install buefy
Electron
Now, the next step is to install electron which is the framework that we can use to build cross-platform apps using HTML, CSS and JavaScript.
Note: we have to install both locally and globally in order to use the electron command in the later step.
To install electron locally
yarn add electronORnpm install electron
And globally
yarn global add electronORnpm install -g electron
Electron apps always begin with an entry file. That file will first create the electron browser window and then load a specific HTML file, in this case, our HTML file is auto-generated by vue.js in the previous step. So, to begin I create an entry file a named it as elect.js
and here is the content of it
To test it, we must add our file to the main
property in the package.json
file and also remember to remove the /
before any link in our source folder and index.html
After that open up the terminal or command prompt and type in these 2 commands
webpackelectron .
As a result, a small window pops up with the same output that we have seen in the previous step after we install vue.
Image? Where?
Well, you can’t make a wallpaper-changing app without any image, so where can we find a service that provides free images with decent quality?
Unsplash is your saviour, Unsplash is an image-providing service that contains thousands of creative full HD photos taken by many talented photographers all around the globe. One great thing about Unsplash is that it helps you find images in different categories and in your desired sizes, with a price of $0.
So Unsplash provided us with a link to get a random image with the size that we want and that is where we’re going to get the image from. Here is the link structure
https://source.unsplash.com/random/[width]x[height]
Designing app layout!
Visualizing
This is just a basic app so I’m not going to draw anything, but instead, imagine an app that has a 4 columns grid, each column contain a random image.
Turn it into code!
The src
folder contains 2 files: main.js
and App.vue
.
App.vue
This is the auto-generated file that contains the welcome page in the earlier step that we have seen. Now, we are going to clear the content of this file so that we can fill in our new code later
Everything that is in the template
tag will be rendered so that is where we’re going to put our HTML. The script
tag is where our scripts and definition for the component go. The style
tag will help us to style the component and the lang
attribute is indicating that we will use SCSS
instead of the traditional CSS
that’s why at the first step of generating the project, vue-cli ask if we want to use SASS
.
Main.js
This file contained our main script which will import all other components and we will also use it to attach other libraries to our app. For example, we need to use Buefy for our front-end part, we will import the Buefy dependency that we installed earlier and tell Vue to use it. Here is the content of main.js
after we do it and also don’t forget to add the css
extension to the webpack’s config file.
A grid of image
Buefy is based on Bulma so we can actually read the bulma documentation and implement it using Buefy without any problem. In this case, you can check out the Bulma documentation about their grid system to have a better understand what we’re going to make in the next step.
As I mentioned before, our app will contain a 4 columns grid and each column will contain an image that user can click on and set that image as their desktop wallpaper, so our HTML will look like this:
<template>
<div class="columns is-multiline">
<div class="column is-one-quarter">
<code>is-one-quarter</code>
</div>
<div class="column is-one-quarter">
<code>is-one-quarter</code>
</div>
<div class="column is-one-quarter">
<code>is-one-quarter</code>
</div>
<div class="column is-one-quarter">
<code>is-one-quarter</code>
</div>
<div class="column is-one-quarter">
<code>is-one-quarter</code>
</div>
</div>
</template><script>
export default {
}
</script><style lang="scss"></style>
As you can see, we have 4 columns and each of them is one-quarter wide. I also added the is-multiline
class so that every time a row is filled with 4 columns, the grid will add another row and repeat the process.
Now, let’s replace the code
tag with the img
tag and pass the random image into it.
<template>
<div class="columns is-multiline" id="images">
<div class="column is-one-quarter">
<img src="https://source.unsplash.com/random/1600x900" class="image">
</div>
</div>
</template><script>
export default {
}
</script><style lang="scss">
#images{
padding: 5px;
}
</style>
The code should work fine and this time, we are going to display n
images
<template>
<div class="columns is-multiline" id="images">
<div class="column is-one-quarter" v-for="n in numberOfImages">
<img :src="'https://source.unsplash.com/random/1600x900?sig=' + n" class="image">
</div>
</div>
</template><script>
export default {
data(){
return {
numberOfImages: 10
}
}
}
</script><style lang="scss">
#images{
padding: 5px;
}
</style>
So, by looking at the code, we can realize that we are using the v-for
to loop numberOfImages
times. But wait, what is the ?sig=
doing in the image source? Well, actually the sig
part doesn’t do anything. When you create multiple image tag that shares the same source, the browser will automatically help you to improve the performance by caching the image. As a result, all your image tag will render the same image! So, in order to not let that happens we must make each of our URL unique, therefore, I added the sig
part to the URL. This solution was taken from here.
After implementing the solution, our app displayed as we expected
Great! now we want to be able to control how many images to display. So we can easily do that by creating an input and add the v-model
property to it.
<template>
<div>
<section class="section">
<b-field label="How many images to display">
<b-input v-model.number="numberOfImage" :value="numberOfImage"></b-input>
</b-field>
</section>
<div class="columns is-multiline" id="images">
<div class="column is-one-quarter" v-for="n in numberOfImage">
<img :src="'https://source.unsplash.com/random/1600x900?sig=' + n" class="image">
</div>
</div>
</div>
</template><script>
export default {
data(){
return {
numberOfImage: 10
}
},
}
</script><style lang="scss">
#images{
padding: 5px;
}
.image:hover{
transition: all 0.3s;
box-shadow: 0 0.3125rem 1rem 0 rgba(0,0,0,0.4);
}
</style>
Same code but added a b-field
and b-input
components, these are the components that come from buefy. The b-input
component is the input field that we will type in the number, so we need to use v-model
onto the b-input
but that is not enough. If you only use v-model
it’s going to assign the string to our variable. Because our numberOfImage
is a number, we must use the .number modifier so that everything we type in will be cast into number type.
The result is unbelievable ! It’s like
Anyway here is how it look:
Into the woods
We will now dig deeper into our app’s core. Firstly, we have used the fixed size for all of the images, now we want to be able to find images that will fit our screen. Secondly, how to set an image as our desktop background? Let’s break it down.
Different images with the appropriate size
This process involves 2 steps:
- Detect screen size
- Replace the value of the
src
property of each image
Detecting screen size is pretty simple and straightforward, especially, if you are a web developer this process is too familiar. Anyway, here is how we get the screen width and height
const width = window.screen.width;
const height = window.screen.height;
Moving on assigning the screen size into the src
property. This is similar to the previous process where we assign the number into the seq
query of the src
. First, we create 2 variable width
and height
just like we did in the previous step. Afterwards, we concatenate those 2 variables into one computed variable call size
and then we add the size
variable into the src
value.
<template>
<div>
<section class="section">
<b-field label="How many images to display">
<b-input v-model.number="numberOfImage" :value="numberOfImage"></b-input>
</b-field>
</section>
<div class="columns is-multiline" id="images">
<div class="column is-one-quarter" v-for="n in numberOfImage">
<img :src="'https://source.unsplash.com/random/' + size + '?sig=' + n" class="image">
</div>
</div>
</div>
</template><script>
export default {
data(){
return {
numberOfImage: 10,
width: window.screen.width,
height: window.screen.height
}
},
computed:{
size(){
return this.width + 'x' + this.height;
}
}
}
</script><style lang="scss">
#images{
padding: 5px;
}
.image:hover{
transition: all 0.3s;
box-shadow: 0 0.3125rem 1rem 0 rgba(0,0,0,0.4);
}
</style>
Set an image as wallpaper
This process sounds a little bit confusing as the traditional JavaScript that executes in the browser isn’t allowed to change anything beyond the sandbox. However, this is NodeJs, where you can use JavaScript to do almost anything that you want. After 5 minutes of googling, I came across this amazing module by Sindre Sorhus called wallpaper. So finally, this process takes 2 steps:
- Save and store the image
- Set background image using wallpaper dependency
Saving the image by downloading is not a great idea because when we go to the random image URL it will redirect us to the real image URL but when passing in the URL to the src
attribute of each image, it still keeps the old random URL and not following the redirection. In order to archive this, there is a way that we can try is that to convert that image into base64
using canvas and then write it to the file.
convertToBase64(img){
let canvas = document.createElement("canvas");
canvas.width = this.width;
canvas.height = this.height;
let context = canvas.getContext("2d");
context.drawImage(img, 0, 0);
let dataURL = canvas.toDataURL("image/jpg");
return dataURL.replace(/^data:image\/(png|jpg);base64,/, "");
}
The two variables this.width
and this.height
were declared in the data
function of the Vue instance in the previous step. So, here, we create a new canvas and set it width and height to our screen width and height, after that we draw the image and convert canvas content into dataURL, replacing some unnecessary parts and then return it.
So here is how the set background image look like
setAsBackground(event){
let base64Image = this.convertToBase64(event.target);
let picturePath = path.join(os.homedir(), "/Pictures", "background.jpg");
picturePath = path.normalize(picturePath);
fs.writeFile(picturePath, base64Image, 'base64', (err) => {
wallpaper.set(picturePath, {scale: "stretch"})
.then(() => {
console.log(path.resolve(picturePath));
this.$snackbar.open("Done !");
});
});
}
First, we convert the target image to base64
this function is going to be attached to every image that we display so the event.target
will point to the image that we click on. Then, we write the base64
data to the background.jpg
file. Afterwards, we use the wallpaper library to set the background image.
But there’s a new problem, our app exports everything that it needs to use into the build.js
in the dist
folder, so in order for the wallpaper library to work on windows, we need to change the path to the dist
folder inside the win.js
file in the library which is located in node_modules/wallpaper/lib
. After that, we will have to import the .exe
file in node_modules/wallpaper/lib
to our dist
folder.
End of story!
That is it! That is the whole process of me making this amazing app, it still pretty basic but I’m going to let you improve it with your imagination. If you have any question then comment below. If you want to see the whole source, you can find it here (Yes, I renamed it as “Alluring”, I’m really bad at naming stuff)
Don’t forget to clap as many as you want, it’s free!