Jak jsem vytvořil svou první aplikaci Electron &You Can Too Pt.5 – Leštění, stavba a doprava

Chtít vytvořit elektronovou aplikaci je jedna věc. Doprava je jiná. Dnes dokončíme naši aplikaci a odešleme! Začneme vylepšením uživatelského rozhraní. Použijeme styled-components vytvořit komponenty pro vlastní zaškrtávací políčka a seznamy. Poté implementujeme jednoduché horní menu. Poté budeme používat electron-packager a nastavit skripty npm, abychom mohli vytvářet sestavení naší nové elektronové aplikace pro všechny hlavní platformy. Díky tomu bude naše aplikace připravena k vydání. Začínáme!

Jak jsem vytvořil svou první aplikaci Electron &You Can Too, část 1.

Jak jsem vytvořil svou první aplikaci Electron &You Can Too část 2.

Jak jsem vytvořil svou první aplikaci Electron &You Can Too část 3.

Jak jsem vytvořil svou první aplikaci Electron &You Can Too část 4.

Stejně jako v předchozích dílech, dovolte mi začít rychlým ukázkou aktuální struktury složek tohoto projektu. Urychlí a usnadní nám to práci a přesun, přes projekt. Kdykoli nebudete vědět kudy kam, můžete se podívat sem. Zde je tedy aktualizovaná verze struktury souborů. A díky tomu nyní můžeme pokračovat v práci na naší elektronové aplikaci.

grease-the-groove-app
├── builds/
├── dist/
├── node_modules/
├── src/
│   └── app/
│       └── components/
│           └── Timer.jsx
│       └── App.jsx
│   └── assets/
│       └── definite.mp3
│       └── grease-the-groove-icon.icns
│       └── grease-the-groove-icon.ico
│       └── grease-the-groove-icon.png
│       └── grease-the-groove-icon.svg
│   └── index.js
├── .babelrc
├── .editorconfig
├── main.js
├── package.json
├── README.md
├── webpack.build.config.js
├── webpack.dev.config.js
└── yarn.lock

Vytvoření vlastních zaškrtávacích políček

Jako první věc začněme něčím, co je jednodušší a jednodušší. Pokud si pamatujete, jedna z funkcí naší elektronové aplikace ukazuje uživateli, kolik sad musí během dne udělat. Tuto funkci jsme implementovali ve třetí části pomocí jednoduchého checkbox s span jako štítek oba zabalené uvnitř skutečného label . Naše řešení funguje dobře. No, na checkbox je těžké něco podělat a label . Jediný problém je, že nativní zaškrtávací políčka vypadají docela špatně.

Vše začíná React

Dobrou zprávou je, že to můžeme opravit jen s trochou CSS. Použijme tedy styled-components a vytvořte novou komponentu React pro vlastní zaškrtávací políčko. Nejprve budeme muset změnit strukturu našeho kódu. V tuto chvíli checkbox prvek je zabalen uvnitř label , spolu s span zalamování textu. Pokud chceme, aby naše vlastní zaškrtávací políčko fungovalo pouze s CSS, budeme muset změnit pořadí těchto prvků.

Nejprve nahradíme label , nyní obálka, s span a umístěte checkbox a label uvnitř toho. Nezapomeňte zadat label hned za checkbox . Jinak CSS a naše vlastní zaškrtávací políčko nebudou fungovat. Dále můžeme pracovat na vizuální stránce. K tomu použijeme styled-components . To také znamená, že budeme muset importovat tuto knihovnu, stejně jako React v horní části souboru s naším vlastním zaškrtávacím políčkem.

Celá komponenta React pro náš vlastní checkbox se bude skládat ze čtyř částí:CheckboxWrapper (span prvek), HTML input (checkbox ) a CheckboxLabel (label živel). Kromě toho bude tato komponenta přijímat dva parametry:id a label . Použijeme id vygenerovat jedinečnou hodnotu pro htmlFor atribut pro label stejně jako pro id a name atributy pro checkbox . Obsah prochází přes label bude vykreslen uvnitř label jako text.

…A pokračuje trochou CSS

Způsob, jakým bude naše vlastní zaškrtávací políčko fungovat, je velmi jednoduchý. Nejprve skryjeme původní HTML checkbox živel. Poté použijeme CSS ::before a ::after pseudo-prvky k vytvoření našeho vlastního zaškrtávacího políčka. ::before bude pro checkbox a ::after pro zaškrtnutí. Nakonec budeme „sledovat“ :checked a :not(:checked) „stavy“ skutečného HTML checkbox pro přepínání mezi různými styly CSS pro ::before a ::after .

Jednoduše řečeno, když není zaškrtávací políčko zaškrtnuté, zobrazí se šedé pole (přes ::before pseudoprvek). Když je zaškrtnuto, změníme barvu ohraničení (přes ::before pseudoprvek) a zobrazit značku zaškrtnutí (přes ::after pseudoprvek). Konečný kód bude vypadat takto.

// Checkbox component

// Import React library
import React from 'react'

// Import styled-components
import styled from 'styled-components'

const CheckBoxWrapper = styled.span`
  & [type=checkbox]:not(:checked) + label::after,
  & [type=checkbox]:checked + label::after,
  & [type=checkbox]:not(:checked) + label::before,
  & [type=checkbox]:checked + label::before {
    position: absolute;
    transition: all .2s;
  }

  & [type=checkbox]:not(:checked) + label::before,
  & [type=checkbox]:checked + label::before {
    content: '';
    top: 0;
    left: 0;
    width: 18px;
    height: 18px;
    background: #fff;
    border: 1px solid #ccc;
    border-radius: 4px;
  }

  & [type=checkbox]:not(:checked) + label::after,
  & [type=checkbox]:checked + label::after {
    top: 4px;
    left: 3px;
    content: '\u2714';
    font-family: Arial, sans-serif;
    font-size: 18px;
    line-height: 0.8;
    color: #ff8b09;
  }

  & > [type=checkbox]:not(:checked) + label::after {
    opacity: 0;
    transform: scale(0);
  }

  & > [type=checkbox]:checked + label::after {
    opacity: 1;
    transform: scale(1.15);
  }

  & > [type=checkbox]:checked + label::before,
  & > [type=checkbox] + label:hover::before {
    border: 1px solid #ff8b09;
  }
`

const CheckboxLabel = styled.label`
  position: relative;
  padding-left: 1.95em;
  cursor: pointer;
`

const Checkbox = ({id, label}) => {
  return(
    <CheckBoxWrapper>
      <input id={id} name={id} type="checkbox" hidden />

      <CheckboxLabel htmlFor={id} id={id} name={id} type="checkbox">{label}</CheckboxLabel>
    </CheckBoxWrapper>
  )
}

export default Checkbox

Nyní můžeme tento kód vložit do nového souboru s názvem Checkbox.jsx a vložte tento soubor do src\app\components\ . Poté jej do něj můžeme importovat do hlavního souboru naší elektronové aplikace, App.js uvnitř src\app\ . Poté můžeme nahradit kód HTML checkbox s touto komponentou. Ještě jedna věc, nezapomeňte předat nějaká data pro id a label argumenty.

// App.jsx
// Import React library
import React from 'react'

// Import checkbox
import Checkbox from './components/Checkbox'

// Import timer
import Timer from './components/Timer'

// Create the main component for our electron app
class App extends React.Component {

  // ... previous code

  // Create a method for generating list of items, one for one set we want to do
  // each item will contain checkbox and label
  generateSetsList() {
    // Prepare empty array for list items
    let setsItems = []

    // Generate number of list items based on 'numOfSets'
    for(let i = 0; i<this.state.numOfSets; i++) {
      setsItems.push(<li key={i}>
        {/* */}
        {/* NEW CHECKBOX COMPONENT GOES HERE: */}
        {/* */}
        <Checkbox
          id={`set${i}`}
          label={`Set number ${i+1}`}
        />
      </li>)
    }

    // Return the array with list items
    return setsItems
  }

  // ... the rest of the code
}

Leštění seznamu

Tento bude velmi rychlý. Odebereme výchozí odrážky a padding a přidejte nějaké margin na vrchol. Potom také použijeme nějaký margin mezi list items . Poté vyexportujeme náš nový List komponenta jako výchozí. Nakonec importujeme seznam do App.jsx soubor, stejně jako jsme to udělali s Checkbox komponent. Vytváříme List komponentu jako čistou sadu stylů pomocí styled-components . Takže nepotřebujeme ani nemusíme importovat React .

// List component - List.jsx
// Import only styled-components
import styled from 'styled-components'

const List = styled.ul`
  padding: 0;
  margin: 18px 0 0;
  list-style-type: none;

  li + li {
    margin-top: 12px;
  }
`

export default List
// App.jsx
// Import React library
import React from 'react'

// Import checkbox
import Checkbox from './components/Checkbox'

// Import lists
import List from './components/List'

// Import timer
import Timer from './components/Timer'

// Create the main component for our electron app
class App extends React.Component {

  // ... previous code

  // Create the main render method
  render() {
    return (
      <div>

        {/* ... previous code */}

        {/* Create list of sets to do */}
        {/* */}
        {/* NEW LIST COMPONENT GOES HERE: */}
        {/* */}
        <List>
          {this.generateSetsList()}
        </List>
      </div>
    )
  }
}

// Export the main component
export default App

Přidání jednoduché nabídky aplikace

Pravděpodobně jste si toho všimli. Když spustíme vývojovou verzi naší elektronové aplikace s npm run dev , v horní části okna je nativní nabídka. Když však vytvoříme produkční verzi naší elektronové aplikace, toto menu již není přítomno. To není takový problém, pokud nemáme nějaké užitečné možnosti pro uživatele, které by mohly být uvnitř nabídky. Můžeme například přidat možnost znovu načíst aplikaci, změnit přiblížení, navštívit dokumentaci nebo web věnovaný aplikaci a tak dále.

Implementujme tedy jednoduché menu jako jednu z posledních věcí, které v tomto tutoriálu uděláme. Pokud chceme vytvořit toto menu, musíme udělat několik kroků. Protože již máme Menu modul importován, nemusíme jej znovu importovat. Použili jsme to pro implementaci ikony na hlavním panelu. Místo toho můžeme tento krok přeskočit a přejít ke kroku číslo dvě. Tento druhý krok je o vytvoření šablony pro menu. Tato šablona bude array objektů. Každý objekt je určen pro jednu hlavní skupinu položek v nabídce.

Například dev verze naší elektronové aplikace má v nabídce následující hlavní skupiny:„Soubor“, „Upravit“, „Zobrazit“, „Okno“ a „Nápověda“. Každý z těchto objektů (skupin nabídek) obsahuje label nebo role klíč a konkrétní hodnotu tohoto klíče. V případě label , hodnota je text, který se zobrazí. Dále je zde druhý klíč, submenu . Toto obsahuje array objektu, jeden objekt pro jednu položku v rozevíracím seznamu. A uvnitř tohoto objektu je opět label nebo role klíč (role pro něco nativního pro elektrony) a konkrétní hodnota tohoto klíče.

Pokud je to něco přirozeného pro elektron, role klíč a hodnota je vše, co potřebujeme. Jinak použijeme klíč label s nějakým textem, který se zobrazí jako hodnota, a něčím jiným. Můžeme například přidat metodu pro click událost. Možná to nyní nedává příliš smysl, ale zlepší se to, až uvidíte kód. Nazvěme tuto proměnnou menuTemplate . Třetím krokem je použití Menu modul, který jsme importovali, a jednu z jeho metod, konkrétně buildFromTemplate . Předáme proměnnou se šablonou naší nabídky jako argument a vše uložíme do jiné proměnné, menu .

Čtvrtým krokem je použití Menu modul znovu a nyní s setApplicationMenu metoda předávání proměnné, kterou jsme vytvořili v předchozím, třetím, kroku. Nyní, když spustíme naši elektronovou aplikaci, měli bychom vidět naši novou nabídku na místě, ve vývojové i produkční verzi (sestavení). Ještě jedna věc. Kód pro menu vložíme do main.js přímo v kořenovém adresáři a uvnitř createWindow funkce. Pojďme se podívat na kód.

// main.js
'use strict'

// Require electron
const electron = require('electron')

// Module to control application life.
const app = electron.app

// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow

const path = require('path')
const url = require('url')

// Module to check for platform
const platform = require('os').platform()

// Modules to create app tray icon and context menu
const Menu = electron.Menu
const Tray = electron.Tray

// Create variables for icons to prevent disappearing icon when the JavaScript object is garbage collected.
let trayIcon = null
let appIcon = null

// Determine appropriate icon for platform
if (platform == 'darwin') {
  trayIcon = path.join(__dirname, 'src', 'assets/grease-the-groove-icon.png')
} else if (platform == 'win32') {
  trayIcon = path.join(__dirname, 'src', 'assets/grease-the-groove-icon.ico')
}

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow

// Keep a reference for dev mode
let dev = false

if (process.defaultApp || /[\\/]electron-prebuilt[\\/]/.test(process.execPath) || /[\\/]electron[\\/]/.test(process.execPath)) {
  dev = true
}

// Temporary fix broken high-dpi scale factor on Windows (125% scaling)
// info: https://github.com/electron/electron/issues/9691
if (process.platform === 'win32') {
  app.commandLine.appendSwitch('high-dpi-support', 'true')
  app.commandLine.appendSwitch('force-device-scale-factor', '1')
}

function createWindow() {
  // Create the browser window.
  // with specific icon and don't show it until it is ready (show: false)
  mainWindow = new BrowserWindow({
    icon: trayIcon,
    height: 667,
    show: false,
    title: 'Grease the Groove',
    width: 375
  })

  // Create tray icon
  appIcon = new Tray(trayIcon)

  // Create RightClick context menu for tray icon
  // with two items - 'Restore app' and 'Quit app'
  const contextMenu = Menu.buildFromTemplate([
    {
      label: 'Restore app',
      click: () => {
        mainWindow.show()
      }
    },
    {
      label: 'Quit app',
      click: () => {
        mainWindow.close()
      }
    }
  ])

  // Set title for tray icon
  appIcon.setTitle('Grease the Groove')

  // Set toot tip for tray icon
  appIcon.setToolTip('Grease the Groove')

  // Create RightClick context menu
  appIcon.setContextMenu(contextMenu)

  // Restore (open) the app after clicking on tray icon
  // if window is already open, minimize it to system tray
  appIcon.on('click', () => {
    mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show()
  })


  //
  // TEMPLATE FOR APP MENU BEGINNING
  //
  const menuTemplate = [
    {
      label: 'Edit',
      submenu: [
        {role: 'undo'}, // Native electron features
        {role: 'redo'}, // Native electron features
        {role: 'cut'}, // Native electron features
        {role: 'copy'}, // Native electron features
        {role: 'paste'}, // Native electron features
        {role: 'delete'} // Native electron features
      ]
    },
    {
      label: 'View',
      submenu: [
        {role: 'reload'}, // Native electron features
        {role: 'forcereload'}, // Native electron features
        {role: 'resetzoom'}, // Native electron features
        {role: 'zoomin'}, // Native electron features
        {role: 'zoomout'} // Native electron features
      ]
    },
    {
      role: 'window',
      submenu: [
        {role: 'minimize'}, // Native electron features
        {role: 'close'} // Native electron features
      ]
    },
    {
      role: 'help',
      submenu: [
        {
          label: 'Documentation',
          click: () => {require('electron').shell.openExternal('https://url.com/documentation')} // Opens a URL in a new window
        },
        {
          label: 'FAQ',
          click: () => {require('electron').shell.openExternal('https://url.com/faq')} // Opens a URL in a new window
        },
        {
          label: 'Issues',
          click: () => {require('electron').shell.openExternal('https://url.com/issues')} // Opens a URL in a new window
        }
      ]
    }
  ]

  // Build app menu from menuTemplate
  const menu = Menu.buildFromTemplate(menuTemplate)

  // Set menu to menuTemplate - "activate" the menu
  Menu.setApplicationMenu(menu)

  //
  // TEMPLATE FOR APP MENU END
  //


  // and load the index.html of the app.
  let indexPath

  // Setup for Webpack
  if (dev && process.argv.indexOf('--noDevServer') === -1) {
    indexPath = url.format({
      protocol: 'http:',
      host: 'localhost:8080',
      pathname: 'index.html',
      slashes: true
    })
  } else {
    indexPath = url.format({
      protocol: 'file:',
      pathname: path.join(__dirname, 'dist', 'index.html'),
      slashes: true
    })
  }

  mainWindow.loadURL(indexPath)

  // Don't show until we are ready and loaded
  mainWindow.once('ready-to-show', () => {
    mainWindow.show()

    // Open the DevTools automatically if developing
    if (dev) {
      mainWindow.webContents.openDevTools()
    }
  })

  // Emitted when the window is closed.
  mainWindow.on('closed', function() {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null
  })

  // Minimize window to system tray
  mainWindow.on('minimize',function(event){
      event.preventDefault()
      mainWindow.hide()
  })
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (mainWindow === null) {
    createWindow()
  }
})

Přidání skriptů sestavení

Teď poslední věc. Všichni uživatelé by měli mít možnost používat naši elektronovou aplikaci bez ohledu na to, jaký operační systém používají. Přidejme tedy skripty sestavení pro všechny hlavní platformy, Linux, OSX (také Mac App Store nebo mas) a Windows. Za tímto účelem přidáme jeden skript pro každou platformu do package.json . Poté také přidáme jeden další skript, který vytvoří naši elektronovou aplikaci pro všechny platformy najednou.

Použijeme electron-packager vytvořit sestavení pro každou platformu pomocí --platform vlajka se specifickou ikonou přes --icon příznak do konkrétního adresáře přes --out . A také použijeme --overwrite vlajka. Tento příznak vynutí electron-packager vždy přepsat všechny existující sestavení. Jedna věc o ikonách. Abychom se ujistili, že všechny platformy mají funkční ikonu, budeme potřebovat tři formáty:png pro ikonu v doku incs pro OS X a ico pro Windows.

Naštěstí nemusíme specifikovat formát ikony pro každé sestavení. Jediné, co musíme udělat, je zadat název obrázku ikony a jeho umístění. electron-packager udělá zbytek práce za nás a pro každou stavbu použije správnou ikonu. Pojďme se podívat na konečnou verzi package.json .

// package.json
{
  "name": "grease-the-groove-app",
  "version": "0.0.1",
  "description": "Electron app to help you practice Grease the Groove method to achieve your goals and get stronger 💪!",
  "license": "MIT",
  "private": false,
  "repository": {
    "type": "git",
    "url": "https://url.git"
  },
  "homepage": "https://url#readme",
  "bugs": {
    "url": "https://url/issues"
  },
  "author": {
    "name": "Your name",
    "email": "[email protected]",
    "url": "https://url.com/"
  },
  "engines": {
    "node": ">=9.0.0",
    "npm": ">=5.0.0",
    "yarn": ">=1.0.0"
  },
  "main": "main.js",
  "scripts": {
    "build": "webpack --config webpack.build.config.js",
    "dev": "webpack-dev-server --hot --host 0.0.0.0 --config=./webpack.dev.config.js",
    "package:all": "npm run build && electron-packager ./ --out=./builds --overwrite --platform=all --icon=src/assets/grease-the-groove-icon",
    "package:linux": "npm run build && electron-packager ./ --out=./builds --overwrite --platform=linux --icon=src/assets/grease-the-groove-icon",
    "package:macappstore": "npm run build && electron-packager ./ --out=./builds --overwrite --platform=mas --icon=src/assets/grease-the-groove-icon",
    "package:osx": "npm run build && electron-packager ./ --out=./builds --overwrite --platform=darwin --icon=src/assets/grease-the-groove-icon",
    "package:win": "npm run build && electron-packager ./ --out=./builds --overwrite --platform=win32 --icon=src/assets/grease-the-groove-icon",
    "prod": "npm run build && electron --noDevServer ."
  },
  "dependencies": {
    "electron": "^1.7.11",
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "styled-components": "^3.1.6"
  },
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-preset-env": "^1.6.1",
    "babel-preset-react": "^6.24.1",
    "babili-webpack-plugin": "^0.1.2",
    "electron-packager": "^10.1.2",
    "extract-text-webpack-plugin": "^3.0.2",
    "file-loader": "^1.1.6",
    "html-webpack-plugin": "^2.30.1",
    "webpack": "^3.10.0",
    "webpack-dev-server": "^2.11.1"
  }
}

Závěrečné úvahy o budování elektronové aplikace

To je ono! Právě jste dokončili pátý a poslední díl této mini série a vytvořili naši první elektronovou aplikaci. Gratulujeme! Dnes jste udělali spoustu práce, stejně jako v předchozích dílech. Díky vaší snaze a trpělivosti vaše první elektronová aplikace nejen dobře funguje, ale také vypadá, nebo řekněme slušně. A co víc, měli jste spoustu příležitostí procvičit si nebo se dozvědět o React a stylových knihovnách komponent a elektronovém frameworku. Nejlepší na tom je, že máte něco, na co můžete být hrdí, svou první elektronovou aplikaci!

To je také jeden z důvodů, proč věřím, že učení praxí je prostě nejlepší. Neexistuje žádný jiný způsob, který vám pomůže se něco naučit tak rychle a mít na konci něco hmatatelného, ​​něco, co můžete ukázat. Díky tomu, bez ohledu na to, jak náročný proces učení může být, stále existuje ten dobrý pocit, když můžete vidět některé výsledky své práce, jako je například elektronová aplikace, na které jsme pracovali prostřednictvím této mini série.

Tato mini série vám ukázala, jak vytvořit malou a jednoduchou elektronovou aplikaci. Takže moje poslední otázka zní. co tě čeká dál? Doufám, že to bude pouze první aplikace, kterou jste vytvořili, že vezmete jeden ze svých nápadů a proměníte je ve skutečnou věc, skutečnou aplikaci. Pamatujte, že učení nestačí a znalosti, které se nepoužívají, jsou k ničemu. Vezměte tedy to, co jste se naučili v této mini sérii, a začněte nový projekt. Sestavte si skvělou elektronovou aplikaci!

Jedna poznámka na závěr. Psal jsem tuto mini sérii, když jsem pracoval na skutečné verzi elektronové aplikace s názvem Grease the Groove nebo GtG. Najdete ho na GitHubu a npm.