// Czysto hobbistyczny blog na temat WebDev, WordPress i Internetu

Kurs Electron – Głupiutki Kalkulator z HTML, CSS i JS

📆 Dodane 17 dni temu [23-09-2019] // Mateusz Mikos // 👁 +500 // 🖊 Brak komentarzy

Kursy (5) Poradniki (17) Programowanie (7)

#aplikacja okienkowa (1) #electron (1) #kurs electron (1) #kursy (2)

Kurs Electron – Głupiutki Kalkulator z HTML, CSS i JS

Przedstawiam Wam Kurs Electron. Jest to pierwsza część, w której postaramy się zrobić naszą pierwszą, okienkową, multiplatformową aplikację.

Co to Electron?

Electron to świetna biblioteka, która pozwala na tworzenie aplikacji okienkowych dla Windows, Mac, czy Linux i to za pomocą HTML, CSS i Javascript. Każdy Web Developer może spróbować swoich sił tworząc coś nowego lub przenosząc swoją aplikację z przeglądarki do “okienka”.

Electron został stworzony w oparciu o Chromium (otwartą wersję przeglądarki Chrome), dlatego nic nie stoi na przeszkodzie, abyś użył np. Reacta! Oprócz tego, Electron na start pozwala na raportowanie błędów, czy automatyczne aktualizacje. Czego chcieć więcej?

Aplikacje zbudowane na Electronie

Długo nie trzeba szukać – z pomocą Electronu powstało wiele aplikacji, z których na pewno korzystacie:

  • Discord;
  • Skype;
  • Visual Studio Code;
  • GitKraken;
  • Atom;
  • Github Desktop;

Więcej takich przykładów znajdziecie tutaj.

Przygotujmy się do pracy

Pierwsze, co musimy sprawdzić, to czy mamy zainstalowany Nodejs. Otwieramy wiersz poleceń / terminal (w moim przypadku Git Bash) i wpisujemy dwie komendy: node -v i npm -v. Jeżeli w odpowiedzi zobaczycie wersję Nodejs i wersję NPM – możemy iść dalej. Jeżeli jednak coś jest nie tak zapraszam Was do wpisu: Jak zainstalować Nodejs i NPM?

Kurs Electron – Sprawdzenie Nodejs i NPM

Tworzenie folderu projektu

Możecie to zrobić gdzie chcecie. Ja utworzę folder D:/electron-projects/calculator. Za pomocą komendy cd d: przejdę na dysk D. Następnie za pomocą mkdir electron-projects stworzę nowy folder i przejdę do niego cd electron-projects. W tym właśnie folderze będę trzymał projekty stworzone z Electronem. Na koniec tworzę folder docelowego projektu mkdir calculator i przechodzę do niego cd calculator.

Kurs Electron – Tworzenie Folderu Projektu

Inicjacja NPM

Teraz zainicjujemy nasz projekt i stworzymy plik package.json, w którym będą zapisane wszystkie zależności. Użyjemy do tego komendy npm init -y. Użyłem tutaj flagi -y, żeby pominąć wszystkie pytania na temat projektu i zastosować domyślne ustawienia (y oznacza yes).

Kurs Electron – Inicjacja Projektu

Instalacja Electrona

Teraz czas na zainstalowanie Electrona. Aby to zrobić skorzystamy także z NPM. Użyjemy do tego komendy npm install electron --save-dev, czyli jako zależność dewelperska. Instalacja może trochę potrwać. Do pobrania jest (w moim przypadku) 61.34MB i trwało to (także w moim przypadku) 93s.

Instalacja Electrona

Otwórz w edytorze

Na koniec otwieramy folder calculator w wybranym edytorze. W moim przypadku jest to Visual Studio Code. Mogę go otworzyć za pomocą komendy: code .. Jak wcześniej wspomniałem – Visual Studio Code jest stworzony właśnie na Electronie.

Struktura naszego projektu

Jak na razie w folderze calculator mamy dwa pliki i jeden folder:

Struktura projektu

Potrzebujemy jeszcze dwóch plików – main.js i index.html.

main.js to główny plik, który Electron wykona po uruchomieniu, natomiast index.html to plik, który zostanie wyrenderowany. Stwórzmy więc oba pliki i zostawmy je na razie puste.

Pamiętacie jak przy inicjacji projektu dodaliśmy flagę -y? Teraz przejdźmy do pliku package.json i ręcznie wprowadźmy zmiany. Aktualnie wygląda on tak:

{
  "name": "calculator",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "electron": "^6.0.10"
  }
}

Najpierw dodajmy sobie jakiś opis naszej aplikacji. W trzeciej linijce możemy napisać coś takiego:

"description": "Puerile Calculator - My first Electron App",

Dalej zmieńmy main. U nas głównym plikiem jest main.js:

"main": "main.js",

Możemy też przy okazji ustawić autora:

"author": "Mateusz Mikos",

I – na sam koniec – aby nasza aplikacja stała się aplikacją Electrona musimy zmienić 7 linijkę:

"start": "electron ."

main.js

Będzie tu dużo niezrozumiałego kodu – nie przejmujcie się tym! Postaram się wszystko opisywać, dodatkowo po drodze wszystko powinno się wyjaśnić.

Moduł electron

main.js zaczniemy od załadowania sobie dwóch klas z modułu electron:

const { app, BrowserWindow } = require('electron');

Dzięki temu będziemy mogli się odwoływać do app i BrowserWindow po prostu korzystając z tych zmiennych, bez załadowywania całego modułu electron.

app – jest to klasa, która zajmuje się cyklem życia naszej aplikacji
BrowserWindow – jest to klasa, która zajmuje się oknem naszej aplikacji

Obiekt okna

Teraz zapiszemy sobie zmienną, w której zachowamy odwołanie do obiektu okna naszej aplikacji. Robi się to po to, by nasza aplikacja nie zamknęła się automatycznie po wykonaniu roboty:

let win

Tworzenie okna przeglądarki

Kolejnym krokiem jest napisanie funkcji tworzenia okna przeglądarki. Brzmi to dosyć skomplikowanie, ale takie nie jest:

function createWindow() {
    win = new BrowserWindow({
        width: 800,
        height: 600,
        backgroundColor: "#223",
        webPreferences: {
            nodeIntegration: true
        }
    })
}

Tutaj kod w sumie jest self explanatory. Do zmiennej win przypisujemy nowy obiekt BrowserWindow, jako parametry dla konstruktora podajemy width, czyli szerokość okna, height, czyli wysokość okna, backgroundColor, czyli kolor tła i webPreferences, czyli funkcje przeglądarki, które chcemy aktywować. Na pewno wrócimy tutaj później Poniżej podam Wam szerszą listę dostępnych parametrów:

  • x i y – przesunięcie okna względem lewej i górnej krawędzi ekranu;
  • useContentSize – wielkość okna dostosowana do zawartości;
  • center  – wyśrodkowanie okna;
  • minWidth  / minHeight – minimalna szerokość i minimalna wysokość
  • maxWidth  / maxHeight – maksymalna szerokość i maksymalna wysokość okna;
  • resizable  – użytkownik może zmieniać rozmiar okna;
  • movable  – okno może być przesuwane przez użytkownika;
  • minimizable  / maximizable  – okno może być minimalizowane / maksymalizowane;
  • alwaysOnTop  – okno zawsze na wierzchu;
  • itd.

Ładowanie strony do okna

Teraz czas załadować nasz index.html do stworzonego okna. Aby to zrobić, do funkcji createWindow dopisujemy taką linijkę:

function createWindow() {
    win = new BrowserWindow({
        width: 800,
        height: 600,
        backgroundColor: "#223",
        webPreferences: {
            nodeIntegration: true
        }
    })
    win.loadFile('index.html')
}

Włączanie narzędzi deweloperskich

Dodatkowo, włączmy sobie narzędzia deweloperskie, znane z chyba każdej przeglądarki, mogą nam się przydać w późniejszym szukaniu problemów. Tę linijkę także dopisujemy do funkcji createWindow:

function createWindow() {
    win = new BrowserWindow({
        width: 800,
        height: 600,
        backgroundColor: "#223",
        webPreferences: {
            nodeIntegration: true
        }
    })
    win.loadFile('index.html')
    win.webContents.openDevTools()
}

Zamykanie okna

Oczywiście oprócz stworzenia okna, warto też zadbać o jego poprawne zamknięcie. Do dyspozycji mamy listener .on('closed'). Oczywiście umieszczamy to w funkcji createWindow:

function createWindow() {
    win = new BrowserWindow({
        width: 800,
        height: 600,
        backgroundColor: "#223",
        webPreferences: {
            nodeIntegration: true
        }
    })
    win.loadFile('index.html')
    win.webContents.openDevTools()
    win.on('closed', () => {
        win = null
    })
}

Te trzy linijki po zamknięciu okna do zmiennej win przypiszą null, czyli usną obiekt BrowserWindow.

Wywołanie funkcji createWindow

Teraz, gdy mamy już całą funkcję createWindow musimy ją wywołać w odpowiednim momencie – gdy aplikacja będzie gotowa:

app.on('ready', createWindow)

Gdy aplikacja będzie gotowa, zostanie wywołana nasza funkcja.

Zamknięcie aplikacji

Pomyślicie – o co mu chodzi? Przecież zamknęliśmy okno. – Tak zamknęliśmy okno, ale okno, to tylko “karta” naszej przeglądarki. Czas zadbać o zamknięcie przeglądarki. Jako, że w macOS zazwyczaj aplikacje zostają w “górnym pasku” musimy pominąć właśnie te systemy:

app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        app.quit()
    }
})

Wyjaśniając: gdy wszystkie okna aplikacji są zamknięte sprawdź czy system to nie macOS ( process.platform !== 'darwin' ) i zamknij aplikację.

Ponowne tworzenie okna (dla macOS)

W macOS mamy sytuację – aplikacja jest otwarta, nie ma żadnych okien i zostaje wywołana z docka. Właśnie dla takiej sytuacji potrzebujemy stworzyć ponownie okno. Dopiszmy więc na końcu main.js taki kod:

app.on('activate', () => {
    if (win === null) {
        createWindow()
    }
})

Znowu tłumacząc na nasz język: gdy aplikacja jest aktywowana sprawdź czy nasze okno jest stworzone. Jeżeli okno nie jest stworzone wywołaj funkcję, która je tworzy – createWindow.

Cała zawartość pliku main.js

Ok, jeżeli ktoś się zgubił – nic nie szkodzi. Poniżej znajdziecie cały plik main.js wraz z komentarzami:

// Z modułu electron wyciągamy klasy app i BrowserWindow
const { app, BrowserWindow } = require('electron')

// Tworzymy zmienną, która będzie przechowywać obiekt BrowserWindow
let win

// Funkcja tworząca okno (obiekt BrowserWindow)
function createWindow () {
    
    // Tworzenie obiektu
    win = new BrowserWindow({
        width: 800, // szerokość okna
        height: 600, // wysokość okna
        backgroundColor: "#223", // kolor tła
        webPreferences: { // funkcje przeglądarki
            nodeIntegration: true // integracja z Nodejs
        }
    })

    // Ładowanie pliku index.html do okna
    win.loadFile('index.html')

    // Otwarcie narzędzi deweloperskich
    win.webContents.openDevTools()

    // Gdy okno zostanie zamknięte
    win.on('closed', () => {
        // Przypisz zmiennej null
        win = null
    })
}


// Wykonaj funkcję createWindow, gdy inicjalizacja Electrona będzie zakończona
app.on('ready', createWindow)

// Gdy wszyskie okna są zamknięte
app.on('window-all-closed', () => {
    // Jeżeli system operacyjny != macOS
    if (process.platform !== 'darwin') {
        // Zamknij aplikację
        app.quit()
    }
})

// Gdy aplikacja aktywowana (dla macOS)
app.on('activate', () => {
    // Jeżeli okno nie istnieje (zostało zamknięte)
    if (win === null) {
        // Stwórz okno
        createWindow()
    }
})

Mamy 53 linijki bardzo standardowego pliku main.js. Mam nadzieję, że Wasz kod nie wygląda tak samo – np. zmieniliście rozmiary okna, dodaliście inny kolor tła lub Wasza aplikacja otwiera się w pełnym oknie. Nie warto ślepo przepisywać kodu! 😉

Pierwsze uruchomienie

Nie ma co, zanim skończymy projekt trzeba chociaż raz uruchomić aplikację! Aby to zrobić w wierszu poleceń wpisujemy komendę npm start.

Kurs Electron – Pierwsze uruchomienie pustej aplikacji

Uff, zadziałało! Chociaż w sumie nasza aplikacja nie ma jeszcze nic ciekawego do zaoferowania to wiemy jedno – działa! Nie musimy już zamykać naszej aplikacji – możemy ją odświeżać za pomocą skrótu CTRL+R.

index.html

Ok, zajmijmy się teraz samym kalkulatorem. Tutaj liczę na Waszą inwencję twórczą. Oczywiście poniżej znajdziecie listę kroków, którą wykonałem😊. Mój kalkulator będzie głupiutki, w miarę responsywny i mało zaawansowany, nie chcę, żeby tworzenie samego kalkulatora zajęło nam więcej czasu niż Electron.

HTML, jQuery, Style

Pierwsze, co robię to stworzenie sobie podstawowego HTML-a. W Visual Studio Code (i w większości edytorów) możemy to zrobić wpisując ! (wykrzyknik) i klikając enter:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    
</body>
</html>

Przy okazji zmieńmy tag title. Ta wartość będzie wyświetlana na pasku naszej aplikacji:

<title>Puerile Calculator</title>

Tytuł to nie bez powodu głupiutki kalkulator. Końcowy efekt mojej aplikacji ani nie będzie dokładny, ani też wolny od błędów. Jeżeli ktoś chce się potrenować i doprowadzić go do w 100% działającego kalkulatora – zapraszam do sklonowania repozytorium i miłego kodowania! 😉

Dodajmy też style.css. Ja skorzystam z Sass – Wy nie musicie, podam także kod pliku style.css. Jeżeli chcecie się dowiedzieć co to właściwie Sass zapraszam do innego wpisu – Darmowy Kurs Sass. W <head> dodaję:

<link rel="stylesheet" href="css/style.css">

A w samym projekcie tworzę folder css. W folderze tworzę plik style.scss (jeżeli nie korzystasz z Sass to oczywiście stwórz style.css).

Dodatkowo na samym dole index.html dodaję jQuery i plik z własnymi skryptami:

<script>let $ = require('jquery');</script>
<script src="js/scripts.js"></script>

Oczywiście tworzę folder js, a w nim pliki scripts.js.

Mamy już jQuery przypisane do znaku dolara. Musimy jeszcze zainstalować jQuery z NPM: npm install jquery --save

Teraz czas umieścić w <body> trochę HTML-a. Zrobimy jeden container, w którym znajdzie się #heading, czyli miejsce na “wyświetlacz” i przycisk czyszczenia i #keyboard, czyli klawiaturę.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Puerile Calculator</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <div class="container">
        <div id="heading">
            
        </div>
        <div id="keyboard">
            
        </div>
    </div>

    <script>let $ = require('jquery');</script>
    <script src="js/scripts.js"></script>
</body>
</html>

Jako “wyświetlacz” wykorzystamy input. Dodamy jeszcze dodatkowy output dla naszego kalkulatora (coś na wzór “poprzednich obliczeń”). Div #heading wygląda teraz tak:

<div id="heading">
    <div id="add-output"></div>
    <div id="screen-grid">
        <div id="clear"><div>C</div></div>
        <input type="text" id="input-output">            
     </div>
</div>

Teraz czas na klawiaturę, tutaj nie ma co kombinować:

<div id="keyboard">
    <div class="key"><div>1</div></div>
    <div class="key"><div>2</div></div>
    <div class="key"><div>3</div></div>
    <div class="key"><div>+</div></div>
    <div class="key"><div>4</div></div>
    <div class="key"><div>5</div></div>
    <div class="key"><div>6</div></div>
    <div class="key"><div>-</div></div>
    <div class="key"><div>7</div></div>
    <div class="key"><div>8</div></div>
    <div class="key"><div>9</div></div>
    <div class="key"><div>*</div></div>
    <div class="key"><div>.</div></div>
    <div class="key"><div>0</div></div>
    <div class="key"><div>=</div></div>
    <div class="key"><div>/</div></div>
</div>

To już cały kod HTML, możemy uruchomić aplikację i zobaczyć jak się prezentuje:

Kurs Electron – Aplikacja po dodaniu kodu HTML

Jeżeli ktoś chciałby sobie skopiować cały index.html to proszę:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Puerile Calculator</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <div class="container">
        <div id="heading">
            <div id="add-output"></div>
            <div id="screen-grid">
                <div id="clear"><div>C</div></div>
                <input type="text" id="input-output">            
            </div>
        </div>
        <div id="keyboard">
            <div class="key"><div>1</div></div>
            <div class="key"><div>2</div></div>
            <div class="key"><div>3</div></div>
            <div class="key"><div>+</div></div>
            <div class="key"><div>4</div></div>
            <div class="key"><div>5</div></div>
            <div class="key"><div>6</div></div>
            <div class="key"><div>-</div></div>
            <div class="key"><div>7</div></div>
            <div class="key"><div>8</div></div>
            <div class="key"><div>9</div></div>
            <div class="key"><div>*</div></div>
            <div class="key"><div>.</div></div>
            <div class="key"><div>0</div></div>
            <div class="key"><div>=</div></div>
            <div class="key"><div>/</div></div>
        </div>
    </div>

    <script>let $ = require('jquery');</script>
    <script src="js/scripts.js"></script>
</body>
</html>

Style.css / Style.scss

Teraz czas na trochę wyglądu. Tak jak wspomniałem wcześniej, będę korzystał z Sass, więc ponownie polecam zajrzenie do wpisu Darmowy Kurs Sass. Dla tych, których nie interesuje Sass dodam cały plik CSS pod instrukcjami. Najpierw zajmijmy się kolorami:

$light-color: #ccc;
$dark-color: #223;
$fill-color: darken($dark-color, 5%);
$border-color: darken($light-color, 60%);

Jedną z trudności na początku tworzenia stylów dla aplikacji Electron może być to, że height: 100vh; potrafi być większe od okna. Dlatego pierwsze, co napiszę to:

* {
    padding: 0;
    margin: 0;
    box-sizing: border-box;
    font-family: Arial Black;
    color: $light-color;
}

Teraz dodajmy szerokość i wysokość do body:

body {
    width: 100vw;
    height: 100vh;
    overflow: hidden;
}

Pierwszym elementem w naszym index.html jest .container. Dam mu 100% wysokości, szerokości i padding 5px. Przygotuję też od razu selektory dla klawiszy, klawiatury i “wyświetlacza”:

.container {
    width: 100%;    
    height: 100%;
    padding: 5px;
    
    #heading {
        
    }

    #keyboard {
        
    }

    .key, #clear {
        
    }
}

Ok, w #heading dodam teraz wysokość, style dla #add-output, czyli dodatkowego wyjścia, #screen-grid, czyli siatki dla przycisku i “wyświetlacza”, no i dla samego wyświetlacza:

#heading {
    height: 15%;

    #add-output {
        text-align: right;
        font-size: 0.9em;
        height: 30%;
    }

    #screen-grid {
        height: 70%;
        display: grid;
        grid-template-columns: 20% 80%;

        #input-output {
            width: 100%;
            height: 100%;
            color: $light-color;
            border: 1px solid $border-color;
            background-color: $fill-color;
            text-align: right;
            font-size: 2em;

            &:hover {
                background-color: lighten($fill-color, 5%);
            }
        }
    }
}

Tutaj chyba nie muszę dużo tłumaczyć – tekst do prawej strony, dodatkowe wyjście ma mniejszą wysokość, sam input jest dosyć duży. Nie dodałem tutaj stylów dla przycisku C. One zostaną dodane później, razem ze stylami innych klawiszy.

Teraz zajmuję się samą klawiaturą, tutaj do pisania nie ma dużo. 10px paddingu od góry, też będzie wykorzystany display: grid;. Siatka będzie miała 4×4. Co oznacza, że jedna komórka będzie miała 1/4 dostępnej wysokości i 1/4 dostępnej szerokości:

#keyboard {
    padding-top: 10px;
    display: grid;
    height: 85%;
    grid-template-columns: 1fr 1fr 1fr 1fr;
    grid-template-rows: 1fr 1fr 1fr 1fr;
}

Zbliżamy się do końca stylów – zostały mi już tylko klawisze:

.key, #clear {
    background-color: $fill-color;
    border: 1px solid $border-color;
    font-size: 1.5em;
    cursor: pointer;
    display: table;
    height: 100%;

    div {
        display: table-cell;
        vertical-align: middle;
        text-align: center;
    }

    &:hover {
        background-color: lighten($fill-color, 5%);
    }
}

Mamy tutaj kolor tła, zmianę koloru tła po najechaniu na przycisk, obramowanie. display: table i display: table-cell użyłem, aby wyśrodkować tekst w przyciskach w pionie. Dlatego właśnie w HTML było coś takiego: <div class="key"><div>/</div></div>.

Tak wygląda cały kod style.scss:

$light-color: #ccc;
$dark-color: #223;
$fill-color: darken($dark-color, 5%);
$border-color: darken($light-color, 60%);

* {
    padding: 0;
    margin: 0;
    box-sizing: border-box;
    font-family: Arial Black;
    color: $light-color;
}

body {
    width: 100vw;
    height: 100vh;
    overflow: hidden;
}

.container {
    width: 100%;    
    height: 100%;
    padding: 5px;
    
    #heading {
        height: 15%;
        #add-output {
            text-align: right;
            font-size: 0.9em;
            height: 30%;
        }

        #screen-grid {
            height: 70%;
            display: grid;
            grid-template-columns: 20% 80%;

            #input-output {
                width: 100%;
                height: 100%;
                color: $light-color;
                border: 1px solid $border-color;
                background-color: $fill-color;
                text-align: right;
                font-size: 2em;
    
                &:hover {
                    background-color: lighten($fill-color, 5%);
                }
            }
        }
    }

    #keyboard {
        padding-top: 10px;
        display: grid;
        height: 85%;
        grid-template-columns: 1fr 1fr 1fr 1fr;
        grid-template-rows: 1fr 1fr 1fr 1fr;
    }

    .key, #clear {
        background-color: $fill-color;
        border: 1px solid $border-color;
        font-size: 1.5em;
        cursor: pointer;
        display: table;
        height: 100%;

        div {
            display: table-cell;
            vertical-align: middle;
            text-align: center;
        }

        &:hover {
            background-color: lighten($fill-color, 5%);
        }
    }
}

Tak wygląda cały wygenerowany kod style.css:

* {
  padding: 0;
  margin: 0;
  -webkit-box-sizing: border-box;
          box-sizing: border-box;
  font-family: Arial Black;
  color: #ccc;
}

body {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}

.container {
  width: 100%;
  height: 100%;
  padding: 5px;
}

.container #heading {
  height: 15%;
}

.container #heading #add-output {
  text-align: right;
  font-size: 0.9em;
  height: 30%;
}

.container #heading #screen-grid {
  height: 70%;
  display: -ms-grid;
  display: grid;
  -ms-grid-columns: 20% 80%;
      grid-template-columns: 20% 80%;
}

.container #heading #screen-grid #input-output {
  width: 100%;
  height: 100%;
  color: #ccc;
  border: 1px solid #333333;
  background-color: #181824;
  text-align: right;
  font-size: 2em;
}

.container #heading #screen-grid #input-output:hover {
  background-color: #222233;
}

.container #keyboard {
  padding-top: 10px;
  display: -ms-grid;
  display: grid;
  height: 85%;
  -ms-grid-columns: 1fr 1fr 1fr 1fr;
      grid-template-columns: 1fr 1fr 1fr 1fr;
  -ms-grid-rows: 1fr 1fr 1fr 1fr;
      grid-template-rows: 1fr 1fr 1fr 1fr;
}

.container .key, .container #clear {
  background-color: #181824;
  border: 1px solid #333333;
  font-size: 1.5em;
  cursor: pointer;
  display: table;
  height: 100%;
}

.container .key div, .container #clear div {
  display: table-cell;
  vertical-align: middle;
  text-align: center;
}

.container .key:hover, .container #clear:hover {
  background-color: #222233;
}

A tak wygląda moja interpretacja kalkulatora:

Kurs Electron – Kalkulator po dodaniu stylów

Nie jest najpiękniejszy, ale wydaje mi się, że coś mogłoby z niego kiedyś być 😎.

scripts.js

No i przyszedł czas na dodanie temu kalkulatorowi jakichś funkcji. Po pierwsze, stworzę mu trzy zmienne, wydaje mi się, że mogą być później przydatne:

var result = 0;
var last_char = '+';
var first_char = '';

Mamy tutaj wynik, ostatni znak i pierwszy znak. Nie chcę tutaj zastanawiać się długo nad wadami i zaletami rozwiązań, chcę, żeby kalkulator chociaż coś dodał:

function clear_input_output() {
    $('#input-output').val('');
}

function clear_add_output() {
    $('#add-output').text('');
}

function clear_all() {
    clear_input_output();
    clear_add_output();
    result = 0;
    last_char = '+';
    first_char = '';
}

Dodam trzy funkcje, które też mogą się później przydać – clear_input_output() wyczyści nam “wyświetlacz”, clear_add_output() wyczyści nam dodatkowe wyjście, a clear_all() wyczyści nam dwa poprzednie, wynik, pierwszy znak i ostatni znak. Dodatkowo, przyda mi się jeszcze:

function filterOutput(input) {
    return input.replace(/[^0-9.]+/g, '');
}

function calc_it(o)
{
    if (o.includes('.')) o = parseFloat(o);
    else o = parseInt(o, 10);
    
    switch(last_char) {
        case '+':
            result += o;
            break;
        case '-':
            result -= o;
            break;
        case '/':
            result /= o;
            break;
        case '*':
            result *= o;
            break;
    }
}

Funkcja filterOutput() odfiltruje nam litery i wszystkie inne znaki oprócz cyfr i kropki. Funkcja calc_it() zamieni string na liczbę całkowitą lub zmiennoprzecinkową (zależnie od tego, czy string posiada kropkę). Następnie do wyniku doda, odejmie, pomnoży lub podzieli go przez stworzoną liczbę i przypisze wynik do zmiennej result.

Teraz czas na główną funkcję, która to wszystko odpowiednio obsłuży:

function input_output(e = null) {
    if (e !== null) {
        if (e.which == 13) {
            $('#input-output').val($('#input-output').val() + '=');
        } else if (e.which == 127 || e.which == 27) {
            clear_all();
        }
    }
    
    let input = $('#input-output').val();
    input = input.replace(',', '.');
    let output = filterOutput(input);
    let length = input.length;
    let last = input.charAt(length-1);
    let first = input.charAt(0);
    let add_output = $('#add-output').text();

    $('#input-output').val(output);


    if (length == 1) {
        if (first == '+' || first == '/' || first == '*') {
            clear_input_output();
        } else if (first == '-') {
            clear_input_output();
            last_char = '-';
            $('#add-output').text('-');
        } else if (first == '.') {
            $('#input-output').val('0.');
        } else if (first == ',') {
            $('#input-output').val('0.');
        }
    } else {
        if (last == '+' || last == '-' || last == '/' || last == '*' || last == '=') {
            calc_it(output);
            clear_input_output();
            if (last == '=') {
                $('#add-output').text(add_output + input + result + '; ');
                $('#input-output').val(result);
                result = 0;
            } else {
                last_char = last;
                $('#add-output').text(add_output + output + last_char);
            }
        }
    }
}

Na początku funkcji, jeżeli mamy podane w parametrze zdarzenie, a klikniętym klawiszem jest enter, wtedy dodajemy do wyświetlacza znak równości. Jeżeli natomiast kliknięty klawisz to Delete lub Escape, wtedy czyścimy wszystkie wyjścia. Dalej tworzymy kilka zmiennych – input jest to czyste wejście ze zmienionymi jedynie przecinkami na kropki, output to input tylko z usuniętymi znakami innymi niż 0-9 i kropka, length to ilość wprowadzonych znaków, last i first to pierwszy i ostatni znak, a add_output to aktualnie wyświetlana treść w dodatkowym wyjściu. Ta linijka $('#input-output').val(output); odpowiada za wyświetlenie wyczyszczonego wejścia. Dalej zaczynają się instrukcje warunkowe – jeżeli długość wejścia jest równa 1, wtedy sprawdzamy czy pierwszy znak to + lub * lub /, jeżeli tak to czyścimy “wyświetlacz”, natomiast jeżeli pierwszym znakiem jest -, to czyścimy wyświetlacz i wrzucamy znak na górę, do dodatkowego wyjścia. Przy okazji zmieniamy ostatni znak na -. Dalej mamy rozwiązany problem z przecinkiem i kropką, jeżeli pierwsza jest kropka lub przecinek, zamieniamy output na 0.. Dalej mamy else, przypadek, w którym długość wejścia jest różna od 1. Tutaj oczywiście pozwalamy na wprowadzanie cyfr aż do wprowadzenia jakiegoś znaku. Po wprowadzeniu znaku, wynik zostaje obliczony jeszcze raz (razem z nową liczbą), całe wejście trafi do dodatkowego wyjścia, a jeżeli znakiem było = to zobaczymy wynik działania. Wiem, nazewnictwo nie jest zbytnio spektakularne, szczególnie zmienna output (oczyszczony input) może być myląca, ale to tylko dla testów i nauki 😊.

Moim ostatnim krokiem w scripts.js było dodanie listenerów. Mamy tutaj kliknięcie przycisku, kliknięcie przycisku C i wprowadzanie danych do “wyświetlacza” z klawiatury:

$('.key').on('click', function() {
    var key = $(this).text();
    $('#input-output').val($('#input-output').val() + key);
    input_output();
});

$('#clear').on('click', function() {
    clear_all();
});

$('#input-output').on('keyup', function(e) {
    input_output(e);
});

W pierwszym przypadku wyciągam sobie kliknięty myszką przycisk – robię to chyba najbardziej prymitywnym możliwym sposobem. Informację o klikniętym przycisku na koniec wyświetlacza i wywołuję input_output();. Po kliknięciu myszką w przycisk C czyszczę wszystkie wyjścia. Natomiast po puszczeniu klawisza na klawiaturze, podczas wprowadzania wejścia do pola “łapię” event (e) i razem z tym parametrem wywołuję input_output(e);. Poniżej wrzucam cały kod z scripts.js:

var result = 0;
var last_char = '+';
var first_char = '';

function clear_input_output() {
    $('#input-output').val('');
}

function clear_add_output() {
    $('#add-output').text('');
}

function clear_all() {
    clear_input_output();
    clear_add_output();
    result = 0;
    last_char = '+';
    first_char = '';
}

function filterOutput(input) {
    return input.replace(/[^0-9.]+/g, '');
}

function calc_it(o)
{
    if (o.includes('.')) o = parseFloat(o);
    else o = parseInt(o, 10);
    
    switch(last_char) {
        case '+':
            result += o;
            break;
        case '-':
            result -= o;
            break;
        case '/':
            result /= o;
            break;
        case '*':
            result *= o;
            break;
    }
}

function input_output(e = null) {
    if (e !== null) {
        if (e.which == 13) {
            $('#input-output').val($('#input-output').val() + '=');
        } else if (e.which == 127 || e.which == 27) {
            clear_all();
        }
    }
    
    let input = $('#input-output').val();
    input = input.replace(',', '.');
    let output = filterOutput(input);
    let length = input.length;
    let last = input.charAt(length-1);
    let first = input.charAt(0);
    let add_output = $('#add-output').text();

    $('#input-output').val(output);


    if (length == 1) {
        if (first == '+' || first == '/' || first == '*') {
            clear_input_output();
        } else if (first == '-') {
            clear_input_output();
            last_char = '-';
            $('#add-output').text('-');
        } else if (first == '.') {
            $('#input-output').val('0.');
        } else if (first == ',') {
            $('#input-output').val('0.');
        }
    } else {
        if (last == '+' || last == '-' || last == '/' || last == '*' || last == '=') {
            calc_it(output);
            clear_input_output();
            if (last == '=') {
                $('#add-output').text(add_output + input + result + '; ');
                $('#input-output').val(result);
                result = 0;
            } else {
                last_char = last;
                $('#add-output').text(add_output + output + last_char);
            }
        }
    }
}


$('.key').on('click', function() {
    var key = $(this).text();
    $('#input-output').val($('#input-output').val() + key);
    input_output();
});

$('#clear').on('click', function() {
    clear_all();
});

$('#input-output').on('keyup', function(e) {
    input_output(e);
});

Żeby kalkulator działał prawidłowo trzeba zmienić kilka rzeczy, ale to już nie w tym kursie. Poniżej możecie zobaczyć jak głupiutki kalkulator liczy:

Oczywiście, czasem kalkulator też mnoży i dzieli:

Wracamy do Electrona

To kurs Electron, więc wróćmy jeszcze do niego. To co mi się nie podoba w kalkulatorze to menu. Czas dodać nasze własne! Aby dodać menu wracamy do pliku main.js. Najpierw, dodajmy w drugiej linijce klasę Menu. Oprócz samego Menu przyda nam się możliwość otwierania linków w domyślnej przeglądarce. Do tego będziemy potrzebować klasy shell, która pozwala na zarządzanie plikami i linkami w ich domyślnych aplikacjach właśnie:

// Z modułu electron wyciągamy klasy app, BrowserWindow, Menu i shell
const { app, BrowserWindow, Menu, shell } = require('electron')

Teraz, po funkcji createWindow() zaczynając ok. 34. linijki możemy dodać szablon naszego menu:

// Szablon naszego menu
let template = [{
    label: 'Informacje',
    submenu: [{
        label: 'Otwórz Repozytorium',
        click: () => {
            shell.openExternal('https://github.com/mateuszmikos/electron-puerile-calculator');
        }
    }, {
        label: 'Przejdź na stronę kursu',
        click: () => {
            shell.openExternal('https://aimweb.pl/kurs-electron---pierwsza-aplikacja-okienkowa-z-html,-css-i-js');
        }
    }, {
        type: 'separator',
    }, {
        label: 'Przejdź na aimweb.pl',
        click: () => {
            shell.openExternal('https://aimweb.pl/');
        }
    }]
}]

Szablon jest dosyć prosty. Mamy tutaj tylko jeden element – Informacje – który posiada submenu z trzema etykietami: Otwórz Repozytorium, Przejdź na stronę kursu, Przejdź na aimweb.pl. Pomiędzy 2. i 3. etykietą dodałem separator. label: jest to wyświetlana treść etykiety, type: to typ elementu a click: to zdarzenie kliknięcia danego elementu. Jak widać w kodzie, do zdarzenia kliknięcia przypisana jest funkcja strzałkowa, która z pomocą shell i metody openExternal otwiera podany URL.

Co do kalkulatora to takie menu nam w zupełności wystarczy.

Ostatnim krokiem związanym z menu jest przypisanie stworzonego szablonu, gdy aplikacja będzie gotowa. Zmieńmy więc tę linijkę:

// Wykonaj funkcję createWindow, gdy inicjalizacja Electrona będzie zakończona
app.on('ready', createWindow)

Na taką:

// Wykonaj funkcję createWindow i przypisz menu, gdy inicjalizacja Electrona będzie zakończona
app.on('ready', () => {
    createWindow()
    const menu = Menu.buildFromTemplate(template)
    Menu.setApplicationMenu(menu)
})

I sprawdźmy rezultat (przy niektórych zmianach musimy zamknąć aplikację i uruchomić ją ponownie – npm start):

Kurs Electron – Menu

Wyłączmy jeszcze uruchamianie DevTools, komentując tę linijkę:

// Otwarcie narzędzi deweloperskich
// win.webContents.openDevTools()

Pakowanie Aplikacji – Electron Packager

Na koniec spakujmy nasz kalkulator i podarujmy go znajomym, na pewno nim pogardzą, ale będą się zastanawiać: “Jak on/a to zrobił/a?!”.

Jest wiele różnych sposobów na spakowanie naszej Electronowej aplikacji. Ja przeprowadzę Was przez jeden z wygodniejszych procesów – z wykorzystaniem Electron Packager’a.

Zanim w ogóle zainstalujemy Electron Packager musimy przygotować ikony dla naszej aplikacji – na Windows, Linux i macOS. Dla systemu Windows potrzebujemy ikony w formacie .ico, dla macOS w formacie .icns, a dla Linuxa w formacie .png. Na szczęście, wystarczy, że stworzymy ikonę o rozdzielczości 1024×1024 i zapiszemy ją w .png.:

Taka w zupełności wystarczy. Teraz wchodzimy na stronę, która pozwala na konwersję ikon online, np. CloudConvert. Wchodzimy tutaj (PNG to ICO), klikamy Select Files, wybieramy plik, klikamy Start Conversion, a gdy już się skonwertuje klikamy Download. Następnie wchodzimy tutaj (PNG to ICNS) i robimy to samo.

W naszym głównym folderze calculator tworzymy folder assets, w nim folder icons, a w icons tworzymy trzy foldery: win, mac i png. Robimy to, ponieważ właśnie taka powinna być struktura folderów. Dokładnym “układaniem” plików zajmiemy się w kolejnych częściach kursu. Do folderu win wrzucamy plik icon.ico, do folderu mac plik icon.icns i do folderu png plik icon.png.

Teraz zainstalujmy electron-packager za pomocą komendy: npm install electron-packager --save-dev.

Kurs Electron – Instalacja electron-packager

Wracamy znowu do package.json i dodajemy nazwę produktu np. zaraz pod "name":

"productName": "Puerile Calculator",

Natomiast do "scripts" dopiszmy skrypty package, żeby nie musieć wpisywać długich komend za każdym razem:

"scripts": {
    "start": "electron .",
    "package-mac": "electron-packager . --overwrite --platform=darwin --arch=x64 --icon=assets/icons/mac/icon.icns --prune=true --out=release-builds",
    "package-win": "electron-packager . calculator --overwrite --asar --platform=win32 --arch=ia32 --icon=assets/icons/win/icon.ico --prune=true --out=release-builds --version-string.ProductName=\"Puerile Calculator\"",
   "package-linux": "electron-packager . calculator --overwrite --asar --platform=linux --arch=x64 --icon=assets/icons/png/icon.png --prune=true --out=release-builds"
  },

Teraz wystarczy, że uruchomimy komendę npm run package-win następnie komendę: npm run package-mac i na końcu npm run package-linux i będziemy mieli gotowe aplikacje!

To tyle! Chyba udało nam się stworzyć jakiś zalążek aplikacji, która mogłaby być bardzo dobra, ale nikomu nie potrzebna! 😜

Cały kod źródłowy znajdziecie w repozytorium mateuszmikos/electron-puerile-calculator. Życzę owocnych prób napisania czegoś ciekawego i widzimy się w kolejnej części! Kolejne części kursu znajdziecie pod tagiem #kurs-electron, natomiast inne kursy w kategorii Kursy.

Zostaw komentarz i dołącz do dyskusji: