JavaScriptowa fizyka kwantowa

 czyli Twoja pierwsza, użyteczna apka w Electronie

👨‍💻1️⃣

  • Krystian Kościelniak
  • Front-End Developer @ Brainhub
  • Zdalnie @ Kraków
  • Zespół @ BH Bielsko-Biała

👨‍💻2️⃣

  • Łukasz Pluszczewski
  • Full-stack developer @ Brainhub
  • Zdalnie @ Kraków

📅

  • Electron Framework?
  • Co stworzymy?
  • Jakich narzędzi użyjemy?
  • Jak uruchomić projekt?
  • Show Time!

  • 4 zadania
  • GUI
  • Git-branche exercise-* zawierają rozwiązania zadań

⚛︎

  • Otwartoźródłowy, multiplatformowy framework dla aplikacji desktopowych
  • Aplikacje są pisane w JavaScript
  • Bazuje na projekcie Chromium i Node.js
  • Obsługuje natywne funkcjonalności systemów operacyjnych
  • Używany m.in. przez Atom, VS Code i Notion

🏗

  • Funkcjonalna aplikacja w Electronie
  • Wyświetlająca dane pogodowe
  • Korzystająca z natywnych funkcjonalności systemu operacyjnego

🛠

  • Maszyna wirtualna z istniejącym środowiskiem
  • Git-branche z poszczególnymi zadaniami
  • https://wttr.in/ proxy API

🚀

  1. Uruchomienie maszyny wirtualnej
  2. osboxes/osboxes.org
  3. Uruchomienie terminala
  4. cd electron-workshop
  5. git fetch origin
  6. npm install
  7. npm start

🚀

1️⃣ 

  • Pobranie danych z Wttr.in
  • Wyświetlenie ich w oknie mainWindow

💡

  • renderer.js

API

https://pogodynka.ml/Bielsko-biała

// OR

https://wttr.pluszczewski.pl/Bielsko-biała

const getWeatherData = (city = 'Bielsko-biała') => {
  return fetch(`https://pogodynka.ml/${encodeURI(city)}`)
    .then(response => response.json());
};

getWeatherData()
  .then(weatherData => updateCurrentWeather(weatherData.now));

2️⃣

  • Zapisanie danych do storage
  • Wyświetlenie zapisanych danych gdy brakuje dostępu do sieci

💡

  • electron-json-storage
  • renderer.js
  • Chrome DevTools

const storage = require('electron-json-storage'); // 👈

const getWeatherData = (city = 'Bielsko-biała') => {
  return fetch(`https://pogodynka.ml/${encodeURI(city)}`)
    .then(response => response.json())
    .then(weatherData => {
      storage.set('weatherData', weatherData); // 👈
      return weatherData;
    })
    .catch(() => { // 👈
      return new Promise((resolve, reject) => 
        storage.get('weatherData', (error, data) => { 
          error ? reject(error) : resolve(data);
        }));
    });
};

3️⃣

  • Zapisywanie danych do pliku JSON
  • Skrót klawiaturowy

💡

  • Szyna IPC

  • dialog
  • Menu
  • fs

Main

  • proces główny
  • pojedynczy
  • kontroluje cykl życia aplikacji
  • zarządza natywnymi komponentami
  • zawiera pełne Node API
  • proces przeglądarki

Renderer

  • proces renderowania
  • może być ich wiele
  • zarządza treścią pojedynczego oknem aplikacji
  • w osobnych procesach
  • zawiera część Node API 
  • proces zakładki

Komunikacja

  • Oparta o zdarzenia
  • Wykorzystuje szynę IPC (Inter-Process Communication) do komunikacji pomiędzy procesami Main i Renderer
  • Komunikacja pomiędzy oknami odbywa się za pośrednictwem procesu Main

Komunikacja

ipcMain

ipcRenderer

// emisja zdarzeń

window.webContents.send(
    'event-name', 
    dataToSend
);

// obsługa zdarzeń

const { ipcMain } = 
    require('electron');

ipcMain.on(
    'another-event', 
    (event, data) => {
        console.log(data);
    }
);
// emisja zdarzeń 

const { ipcRenderer } = 
    require('electron');

ipcRenderer.send(
    'another-event', 
    data
);

// obsługa zdarzeń

ipcRenderer.on(
    'event-name', 
    (event, data) => {
        console.log(data);
    }
);    

// renderer.js

const { ipcRenderer } = require('electron'); // 👈

const saveToJson = async () => { // 👈
  try {
    const data = await getWeatherData();
    updateCurrentWeather(data);
    ipcRenderer.send('save-to-json', data);
  } catch (error) { /* [...] */ }
};

getWeatherData()
  .then((weatherData) => {
    // [...]
    document.getElementById('saveToJsonBtn')
      .addEventListener('click', saveToJson); // 👈
  });
// main.js

const { ipcMain, dialog } = require('electron'); // 👈
const fs = require('fs');

ipcMain.on('save-to-json', (event, data) => { // 👈
  dialog.showSaveDialog(mainWindow, {
    title: 'Save to JSON',
  }, (filename) => {
    if (filename) {
      const serializedData = JSON.stringify(data, null, 2);
      fs.writeFile(filename, serializedData, (error) => { // 👈
        if (error) {
          console.error(error);
        }
      });
    }
  });
});
// main.js

const { Menu } = require('electron');

const menuTemplate = [{ // 👈
  label: app.getName(),
  submenu: [
    {
      label: 'Save to JSON',
      click: () => {
        window.webContents.send('save-to-json-shortcut'); // 👈
      },
      accelerator: 'CmdOrCtrl+S',
    },
    // [...]
  ],
}];

const menu = Menu.buildFromTemplate(menuTemplate); // 👈
Menu.setApplicationMenu(menu);
// main.js

app.on('ready', () => {
  const window = createWindow();
  createMenu(window); // 👈
});
// renderer.js

getWeatherData()
  .then((weatherData) => {
    // [...]
    ipcRenderer.on('save-to-json-shortcut', saveToJson); // 👈
  });

4️⃣

  • Ikona Tray'a
  • Menu kontekstowe
  • Powiadomienie po kliknięciu Show weather

💡

  • getWeatherData()
  • Tray
  • Menu
  • electron-main-notification

// main.js

let tray;

const createTray = (window) => {
  const trayMenuTemplate = [{ // 👈
      label: 'Show application',
      click: () => { window.show(); },
    },
    { label: 'Show weather', click: showNotification }, // 👈
    { role: 'separator' },
    { label: 'Quit', click: () => app.exit(0) },
  ];

  tray = new Tray('icons/cloud.png'); // 👈
  const contextMenu = Menu.buildFromTemplate(trayMenuTemplate); // 👈
  tray.setContextMenu(contextMenu); // 👈
};
// main.js

app.on('ready', () => {
  const window = createWindow();
  createMenu(window);
  createTray(window); // 👈
});
// main.js

const notify = require('electron-main-notification');
const getWeatherData = /* require it */

const showNotification = async () => {
  const weatherData = await getWeatherData();

  notify('Weather', {
    body: `
      Temperature: ${weatherData.now.temperatureLow}, 
      Wind direction: ${weatherData.now.windDirectionIcon},
      Wind speed: ${weatherData.now.windLow} km/h`,
  });
};

*️⃣

  • Dynamiczna ikona Tray'a
  • Globalny skrót klawiaturowy

💡

  • Tray.setImage()
    
  • globalShortcut

Podsumowując

  • Napisaliśmy pierwszą aplikacje w Electron
  • Wykorzystaliśmy 3 natywne API 
    • Stworzyliśmy okno aplikacji z menu
    • Dodaliśmy ikonę zasobnika
    • wykorzystaliśmy zapisywanie danych na dysku
    • Użyliśmy systemowych powiadomień
  • I zrobiliśmy to w niecałe dwie godziny

Dzięki!

  • @kkoscielniak
  • @Lukasz-pluszczewski