อิเลคตรอน: แอพเดสก์ท็อปข้ามแพลตฟอร์มที่ทำได้ง่าย

เผยแพร่แล้ว: 2022-03-11

เมื่อต้นปีนี้ Github ได้เปิดตัว Atom-Shell ซึ่งเป็นแกนหลักของโปรแกรมแก้ไขโอเพนซอร์สที่มีชื่อเสียงอย่าง Atom และเปลี่ยนชื่อเป็น Electron สำหรับโอกาสพิเศษ

อิเลคตรอน ซึ่งแตกต่างจากคู่แข่งรายอื่นในหมวดหมู่ของแอปพลิเคชันเดสก์ท็อปที่ใช้ Node.js นำความบิดเบี้ยวมาสู่ตลาดที่มีความมั่นคงอยู่แล้ว โดยผสมผสานพลังของ Node.js (io.js จนถึงรีลีสล่าสุด) เข้ากับ Chromium Engine เพื่อนำมาซึ่ง เราเป็นจาวาสคริปต์ฝั่งเซิร์ฟเวอร์และฝั่งไคลเอ็นต์ที่ดีที่สุด

ลองนึกภาพโลกที่เราสามารถสร้างแอปพลิเคชันเดสก์ท็อปข้ามแพลตฟอร์มที่มีประสิทธิภาพ ขับเคลื่อนด้วยข้อมูล ซึ่งขับเคลื่อนโดยไม่เพียงแต่พื้นที่เก็บข้อมูลของโมดูล NPM ที่เติบโตขึ้นเรื่อยๆ แต่ยังรวมถึงรีจิสทรี Bower ทั้งหมดเพื่อตอบสนองความต้องการฝั่งไคลเอ็นต์ทั้งหมดของเรา

ป้อนอิเล็กตรอน

การสร้างแอปเดสก์ท็อปข้ามแพลตฟอร์มด้วยอิเล็กตรอน

การสร้างแอปเดสก์ท็อปข้ามแพลตฟอร์มด้วยอิเล็กตรอน
ทวีต

ในบทช่วยสอนนี้ เราจะสร้างแอปพลิเคชันพวงกุญแจรหัสผ่านอย่างง่ายโดยใช้ Electron, Angular.js และ Loki.js ซึ่งเป็นฐานข้อมูลขนาดเล็กในหน่วยความจำพร้อมไวยากรณ์ที่คุ้นเคยสำหรับนักพัฒนา MongoDB

ซอร์สโค้ดแบบเต็มสำหรับแอปพลิเคชันนี้มีให้ที่นี่

บทช่วยสอนนี้ถือว่า:

  • เครื่องอ่านมี Node.js และ Bower ติดตั้งอยู่ในเครื่อง
  • พวกเขาคุ้นเคยกับ Node.js, Angular.js และไวยากรณ์การสืบค้นที่เหมือน MongoDB

รับสินค้า

อย่างแรกเลย เราจะต้องรับไบนารีของอิเล็กตรอนเพื่อทดสอบแอปของเราในเครื่อง เราสามารถติดตั้งได้ทั่วโลกและใช้เป็น CLI หรือติดตั้งในเครื่องในเส้นทางของแอปพลิเคชันของเรา ฉันแนะนำให้ติดตั้งทั่วโลก เพื่อที่เราจะได้ไม่ต้องทำซ้ำซ้ำแล้วซ้ำเล่าสำหรับทุกแอปที่เราพัฒนา

เราจะเรียนรู้วิธีบรรจุแอปพลิเคชันเพื่อแจกจ่ายโดยใช้อึกในภายหลัง กระบวนการนี้เกี่ยวข้องกับการคัดลอกไบนารีของอิเล็กตรอน ดังนั้นจึงไม่สมเหตุสมผลเลยที่จะติดตั้งด้วยตนเองในเส้นทางของแอปพลิเคชันของเรา

ในการติดตั้ง Electron CLI เราสามารถพิมพ์คำสั่งต่อไปนี้ในเทอร์มินัลของเรา:

 $ npm install -g electron-prebuilt

ในการทดสอบการติดตั้ง ให้พิมพ์ electron -h และควรแสดงเวอร์ชันของ Electron CLI

ในขณะที่เขียนบทความนี้ เวอร์ชันของอิเล็กตรอนคือ 0.31.2

ตั้งโครงการ

สมมติว่าโครงสร้างโฟลเดอร์พื้นฐานต่อไปนี้:

 my-app |- cache/ |- dist/ |- src/ |-- app.js | gulpfile.js

… โดยที่: - แคช/ จะถูกใช้เพื่อดาวน์โหลดไบนารีอิเล็กตรอนเมื่อสร้างแอป - dist/ จะมีไฟล์การแจกจ่ายที่สร้างขึ้น - src/ จะมีซอร์สโค้ดของเรา - src/app.js จะเป็นจุดเริ่มต้นของแอปพลิเคชันของเรา

ต่อไป เราจะไปที่โฟลเดอร์ src/ ในเทอร์มินัลของเรา และสร้างไฟล์ package.json และ bower.json สำหรับแอปของเรา:

 $ npm init $ bower init

เราจะติดตั้งแพ็คเกจที่จำเป็นในภายหลังในบทช่วยสอนนี้

ทำความเข้าใจกระบวนการอิเล็กตรอน

อิเล็กตรอนแยกความแตกต่างระหว่างกระบวนการสองประเภท:

  • กระบวนการหลัก : จุดเริ่มต้นของแอปพลิเคชันของเรา ไฟล์ที่จะดำเนินการทุกครั้งที่เราเรียกใช้แอป โดยทั่วไป ไฟล์นี้จะประกาศหน้าต่างต่างๆ ของแอป และสามารถเลือกใช้เพื่อกำหนดตัวฟังเหตุการณ์ทั่วโลกโดยใช้โมดูล IPC ของ Electron
  • กระบวนการ Renderer : ตัวควบคุมสำหรับหน้าต่างที่กำหนดในแอปพลิเคชันของเรา แต่ละหน้าต่างสร้างกระบวนการ Renderer ของตัวเอง

เพื่อความชัดเจนของโค้ด ควรใช้ไฟล์แยกกันสำหรับกระบวนการ Renderer แต่ละขั้นตอน ในการกำหนดกระบวนการหลักสำหรับแอปของเรา เราจะเปิด src/app.js และรวมโมดูล app เพื่อเริ่มแอป และโมดูล browser-window เพื่อสร้างหน้าต่างต่างๆ ของแอปของเรา (ทั้งส่วนของแกนอิเล็กตรอน) เช่นนี้:

 var app = require('app'), BrowserWindow = require('browser-window');

เมื่อแอปเริ่มทำงานจริง แอปจะเริ่มต้นเหตุการณ์ ready งาน ซึ่งเราสามารถผูกไว้ได้ ณ จุดนี้ เราสามารถยกตัวอย่างหน้าต่างหลักของแอพของเรา:

 var mainWindow = null; app.on('ready', function() { mainWindow = new BrowserWindow({ width: 1024, height: 768 }); mainWindow.loadUrl('file://' + __dirname + '/windows/main/main.html'); mainWindow.openDevTools(); });

ประเด็นสำคัญ:

  • เราสร้างหน้าต่างใหม่โดยการสร้างอินสแตนซ์ใหม่ของวัตถุ BrowserWindow
  • ใช้วัตถุเป็นอาร์กิวเมนต์เดียว ทำให้เราสามารถกำหนดการตั้งค่าต่างๆ ได้ ซึ่งได้แก่ ความกว้าง และ ความสูง เริ่มต้นของหน้าต่าง
  • อินสแตนซ์ของหน้าต่างมี loadUrl() ซึ่งช่วยให้เราโหลดเนื้อหาของไฟล์ HTML จริงในหน้าต่างปัจจุบันได้ ไฟล์ HTML สามารถเป็นได้ทั้งแบบ โลคัล หรือ แบบรีโมต
  • อินสแตนซ์หน้าต่างมี openDevTools() ที่ไม่บังคับ ซึ่งช่วยให้เราเปิดอินสแตนซ์ของเครื่องมือ Chrome Dev ในหน้าต่างปัจจุบันเพื่อจุดประสงค์ในการดีบัก

ต่อไป เราควรจัดระเบียบโค้ดของเราเล็กน้อย ฉันแนะนำให้สร้างโฟลเดอร์ windows/ ในโฟลเดอร์ src/ ของเรา และตำแหน่งที่เราสามารถสร้างโฟลเดอร์ย่อยสำหรับแต่ละหน้าต่างได้ดังนี้:

 my-app |- src/ |-- windows/ |--- main/ |---- main.controller.js |---- main.html |---- main.view.js

… โดยที่ main.controller.js จะมีตรรกะ "ฝั่งเซิร์ฟเวอร์" ของแอปพลิเคชันของเรา และ main.view.js จะมีตรรกะ "ฝั่งไคลเอ็นต์" ของแอปพลิเคชันของเรา

ไฟล์ main.html เป็นเพียงหน้าเว็บ HTML5 ดังนั้นเราจึงสามารถเริ่มต้นได้ดังนี้:

 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Password Keychain</title> </head> <body> <h1>Password Keychain</h1> </body> </html>

ณ จุดนี้ แอปของเราควรจะพร้อมทำงาน เพื่อทดสอบ เราสามารถพิมพ์สิ่งต่อไปนี้ในเทอร์มินัลของเราที่รูทของโฟลเดอร์ src :

 $ electron .

เราสามารถทำให้กระบวนการนี้เป็นไปโดยอัตโนมัติโดยกำหนดสคริปต์ start ของไฟล์ package.son

การสร้างแอปเดสก์ท็อปพวงกุญแจรหัสผ่าน

ในการสร้างแอปพลิเคชันพวงกุญแจรหัสผ่าน เราต้องการ: - วิธีเพิ่ม สร้าง และบันทึกรหัสผ่าน - วิธีที่สะดวกในการคัดลอกและลบรหัสผ่าน

การสร้างและบันทึกรหัสผ่าน

แบบฟอร์มง่าย ๆ ก็เพียงพอที่จะใส่รหัสผ่านใหม่ เพื่อประโยชน์ในการสาธิตการสื่อสารระหว่างหน้าต่างหลายบานในอิเล็กตรอน ให้เริ่มต้นด้วยการเพิ่มหน้าต่างที่สองในแอปพลิเคชันของเรา ซึ่งจะแสดงแบบฟอร์ม "แทรก" เนื่องจากเราจะเปิดและปิดหน้าต่างนี้หลายครั้ง เราควรสรุปตรรกะในเมธอด เพื่อให้เราสามารถเรียกมันได้เมื่อจำเป็น:

 function createInsertWindow() { insertWindow = new BrowserWindow({ width: 640, height: 480, show: false }); insertWindow.loadUrl('file://' + __dirname + '/windows/insert/insert.html'); insertWindow.on('closed',function() { insertWindow = null; }); }

ประเด็นสำคัญ:

  • เราจะต้องตั้งค่าคุณสมบัติการ แสดง เป็นเท็จ ในอ็อบเจ็กต์ตัวเลือกของตัวสร้างหน้าต่างเบราว์เซอร์ เพื่อป้องกันไม่ให้หน้าต่างเปิดโดยค่าเริ่มต้นเมื่อแอปพลิเคชันเริ่มทำงาน
  • เราจะต้องทำลายอินสแตนซ์ของ BrowserWindow ทุกครั้งที่หน้าต่างเริ่มทำงานกับเหตุการณ์ที่ ปิด

การเปิดและปิดหน้าต่าง “แทรก”

แนวคิดคือการสามารถเรียกใช้หน้าต่าง "แทรก" เมื่อผู้ใช้คลิกปุ่มในหน้าต่าง "หลัก" ในการดำเนินการนี้ เราจะต้องส่งข้อความจากหน้าต่างหลักไปยังกระบวนการหลักเพื่อสั่งให้เปิดหน้าต่างแทรก เราสามารถทำได้โดยใช้โมดูล IPC ของอิเล็กตรอน โมดูล IPC มีสองรูปแบบ:

  • หนึ่งสำหรับกระบวนการหลัก อนุญาตให้แอปสมัครรับข้อความที่ส่งจาก windows
  • หนึ่งสำหรับกระบวนการ Renderer ทำให้แอปสามารถส่งข้อความไปยังกระบวนการหลักได้

แม้ว่าช่องทางการสื่อสารของอิเล็กตรอนส่วนใหญ่จะเป็นแบบทิศทางเดียว แต่ก็สามารถเข้าถึงโมดูล IPC ของกระบวนการหลักในกระบวนการเรนเดอร์ได้โดยใช้โมดูลระยะไกล นอกจากนี้ กระบวนการหลักสามารถส่งข้อความกลับไปยังกระบวนการ Renderer ซึ่งเป็นที่มาของเหตุการณ์โดยใช้เมธอด Event.sender.send()

ในการใช้โมดูล IPC เราแค่ต้องการเช่นเดียวกับโมดูล NPM อื่นๆ ในสคริปต์กระบวนการหลักของเรา:

 var ipc = require('ipc');

… แล้วผูกกับเหตุการณ์ด้วย on() วิธีการ:

 ipc.on('toggle-insert-view', function() { if(!insertWindow) { createInsertWindow(); } return (!insertWindow.isClosed() && insertWindow.isVisible()) ? insertWindow.hide() : insertWindow.show(); });

ประเด็นสำคัญ:

  • เราสามารถตั้งชื่อเหตุการณ์ได้ตามที่เราต้องการ ตัวอย่างเป็นเพียงการกำหนดโดยพลการ
  • อย่าลืมตรวจสอบว่ามีการสร้างอินสแตนซ์ของ BrowserWindow ไว้แล้วหรือไม่ หากไม่ได้สร้างอินสแตนซ์นั้น
  • อินสแตนซ์ BrowserWindow มีวิธีการที่เป็นประโยชน์บางประการ:
    • isClosed() คืนค่าบูลีน ไม่ว่าหน้าต่างจะอยู่ในสถานะ closed หรือไม่
    • isVisible() : คืนค่าบูลีน ไม่ว่าหน้าต่างจะมองเห็นได้ในขณะนี้หรือไม่
    • show() / hide() : วิธีอำนวยความสะดวกในการแสดงและซ่อนหน้าต่าง

ตอนนี้ เราต้องเริ่มการทำงานของเหตุการณ์นั้นจากกระบวนการเรนเดอร์ เราจะสร้างไฟล์สคริปต์ใหม่ชื่อ main.view.js และเพิ่มลงในหน้า HTML ของเราเหมือนกับที่เราทำกับสคริปต์ทั่วไป:

 <script src="./main.view.js"></script>

การโหลดไฟล์สคริปต์ผ่านแท็ก script HTML จะโหลดไฟล์นี้ในบริบทฝั่งไคลเอ็นต์ ซึ่งหมายความว่า ตัวอย่างเช่น ตัวแปรส่วนกลางพร้อมใช้งานผ่าน window.<varname> ในการโหลดสคริปต์ในบริบทฝั่งเซิร์ฟเวอร์ เราสามารถใช้เมธอด require() โดยตรงในหน้า HTML ของเรา: require('./main.controller.js'); .

แม้ว่าสคริปต์จะโหลดในบริบท ฝั่งไคลเอ็นต์ แต่เรายังคงสามารถเข้าถึงโมดูล IPC สำหรับกระบวนการ Renderer ได้ในลักษณะเดียวกับที่เราสามารถทำได้สำหรับกระบวนการหลัก จากนั้นจึงส่งกิจกรรมของเราดังนี้:

 var ipc = require('ipc'); angular .module('Utils', []) .directive('toggleInsertView', function() { return function(scope, el) { el.bind('click', function(e) { e.preventDefault(); ipc.send('toggle-insert-view'); }); }; });

นอกจากนี้ยังมีเมธอด sendSync() ในกรณีที่เราจำเป็นต้องส่งกิจกรรมของเราพร้อมกัน

ตอนนี้ สิ่งที่เราต้องทำเพื่อเปิดหน้าต่าง "แทรก" คือการสร้างปุ่ม HTML ที่มีคำสั่ง Angular ที่ตรงกัน:

 <div ng-controller="MainCtrl as vm"> <button toggle-insert-view class="mdl-button"> <i class="material-icons">add</i> </button> </div>

และเพิ่มคำสั่งนั้นเป็นการพึ่งพาตัวควบคุม Angular ของหน้าต่างหลัก:

 angular .module('MainWindow', ['Utils']) .controller('MainCtrl', function() { var vm = this; }); 

กำลังสร้างรหัสผ่าน

เพื่อให้ง่ายขึ้น เราสามารถใช้โมดูล NPM uuid เพื่อสร้าง ID เฉพาะที่จะทำหน้าที่เป็นรหัสผ่านสำหรับวัตถุประสงค์ของบทช่วยสอนนี้ เราสามารถติดตั้งได้เหมือนกับโมดูล NPM อื่นๆ โดยกำหนดให้อยู่ในสคริปต์ 'Utils' ของเรา จากนั้นจึงสร้างโรงงานอย่างง่ายที่จะส่งคืน ID เฉพาะ:

 var uuid = require('uuid'); angular .module('Utils', []) ... .factory('Generator', function() { return { create: function() { return uuid.v4(); } }; })

ตอนนี้ สิ่งที่เราต้องทำคือสร้างปุ่มในมุมมองการแทรก และแนบคำสั่งที่จะฟังการคลิกเหตุการณ์บนปุ่มและเรียกเมธอด create()

 <!-- in insert.html --> <button generate-password class="mdl-button">generate</button>
 // in Utils.js angular .module('Utils', []) ... .directive('generatePassword', ['Generator', function(Generator) { return function(scope, el) { el.bind('click', function(e) { e.preventDefault(); if(!scope.vm.formData) scope.vm.formData = {}; scope.vm.formData.password = Generator.create(); scope.$apply(); }); }; }])

การบันทึกรหัสผ่าน

ณ จุดนี้ เราต้องการเก็บรหัสผ่านของเรา โครงสร้างข้อมูลสำหรับการป้อนรหัสผ่านของเราค่อนข้างง่าย:

 { "id": String "description": String, "username": String, "password": String }

สิ่งที่เราต้องมีก็คือฐานข้อมูลในหน่วยความจำบางประเภทที่สามารถเลือกที่จะซิงค์กับไฟล์เพื่อสำรองข้อมูลได้ เพื่อจุดประสงค์นี้ Loki.js ดูเหมือนจะเป็นตัวเลือกในอุดมคติ มันทำในสิ่งที่เราต้องการสำหรับจุดประสงค์ของแอปพลิเคชั่นนี้อย่างแท้จริง และนำเสนอคุณสมบัติ มุมมองไดนามิก ที่ด้านบน ทำให้เราสามารถทำสิ่งต่าง ๆ ที่คล้ายกับโมดูลการรวมของ MongoDB

มุมมองแบบไดนามิกไม่มีฟังก์ชันทั้งหมดที่โมดูลการรวมของ MongodDB มี โปรดดูเอกสารประกอบสำหรับข้อมูลเพิ่มเติม

เริ่มต้นด้วยการสร้างแบบฟอร์ม HTML อย่างง่าย:

 <div class="insert" ng-controller="InsertCtrl as vm"> <form name="insertForm" no-validate> <fieldset ng-disabled="!vm.loaded"> <div class="mdl-textfield"> <input class="mdl-textfield__input" type="text" ng-model="vm.formData.description" required /> <label class="mdl-textfield__label" for="description">Description...</label> </div> <div class="mdl-textfield"> <input class="mdl-textfield__input" type="text" ng-model="vm.formData.username" /> <label class="mdl-textfield__label" for="username">Username...</label> </div> <div class="mdl-textfield"> <input class="mdl-textfield__input" type="password" ng-model="vm.formData.password" required /> <label class="mdl-textfield__label" for="password">Password...</label> </div> <div class=""> <button generate-password class="mdl-button">generate</button> <button toggle-insert-view class="mdl-button">cancel</button> <button save-password class="mdl-button" ng-disabled="insertForm.$invalid">save</button> </div> </fieldset> </form> </div>

และตอนนี้ มาเพิ่มตรรกะของ JavaScript เพื่อจัดการกับการโพสต์และการบันทึกเนื้อหาของแบบฟอร์ม:

 var loki = require('lokijs'), path = require('path'); angular .module('Utils', []) ... .service('Storage', ['$q', function($q) { this.db = new loki(path.resolve(__dirname, '../..', 'app.db')); this.collection = null; this.loaded = false; this.init = function() { var d = $q.defer(); this.reload() .then(function() { this.collection = this.db.getCollection('keychain'); d.resolve(this); }.bind(this)) .catch(function(e) { // create collection this.db.addCollection('keychain'); // save and create file this.db.saveDatabase(); this.collection = this.db.getCollection('keychain'); d.resolve(this); }.bind(this)); return d.promise; }; this.addDoc = function(data) { var d = $q.defer(); if(this.isLoaded() && this.getCollection()) { this.getCollection().insert(data); this.db.saveDatabase(); d.resolve(this.getCollection()); } else { d.reject(new Error('DB NOT READY')); } return d.promise; }; }) .directive('savePassword', ['Storage', function(Storage) { return function(scope, el) { el.bind('click', function(e) { e.preventDefault(); if(scope.vm.formData) { Storage .addDoc(scope.vm.formData) .then(function() { // reset form & close insert window scope.vm.formData = {}; ipc.send('toggle-insert-view'); }); } }); }; }])

ประเด็นสำคัญ:

  • ก่อนอื่นเราต้องเริ่มต้นฐานข้อมูล กระบวนการนี้เกี่ยวข้องกับการสร้างอินสแตนซ์ใหม่ของ Loki Object โดยให้พาธไปยังไฟล์ฐานข้อมูลเป็นอาร์กิวเมนต์ ค้นหาว่ามีไฟล์สำรองนั้นอยู่หรือไม่ สร้างหากจำเป็น (รวมถึงคอลเล็กชัน 'Keychain') แล้วโหลดเนื้อหาของ ไฟล์นี้ในหน่วยความจำ
  • เราสามารถดึงข้อมูลคอลเลกชันเฉพาะในฐานข้อมูลด้วย getCollection()
  • วัตถุคอลเลกชันแสดงวิธีการต่างๆ รวมถึงวิธีการ insert() ซึ่งช่วยให้เราสามารถเพิ่มเอกสารใหม่ลงในคอลเลกชันได้
  • เพื่อคงเนื้อหาฐานข้อมูลไว้ในไฟล์ ออบเจ็กต์ Loki จะแสดง saveDatabase()
  • เราจะต้องรีเซ็ตข้อมูลของแบบฟอร์มและส่งเหตุการณ์ IPC เพื่อบอกให้กระบวนการหลักปิดหน้าต่างเมื่อบันทึกเอกสารแล้ว

ตอนนี้เรามีแบบฟอร์มง่ายๆ ที่ช่วยให้เราสร้างและบันทึกรหัสผ่านใหม่ได้ กลับไปที่มุมมองหลักเพื่อแสดงรายการเหล่านี้

แสดงรายการรหัสผ่าน

บางสิ่งต้องเกิดขึ้นที่นี่:

  • เราจำเป็นต้องได้รับเอกสารทั้งหมดในคอลเลกชันของเรา
  • เราจำเป็นต้องแจ้งมุมมองหลักทุกครั้งที่มีการบันทึกรหัสผ่านใหม่ เพื่อให้สามารถรีเฟรชมุมมองได้

เราสามารถเรียกรายการเอกสารโดยเรียก getCollection() บนอ็อบเจกต์ Loki เมธอดนี้ส่งคืนอ็อบเจ็กต์ที่มีคุณสมบัติที่เรียกว่า data ซึ่งเป็นอาร์เรย์ของเอกสารทั้งหมดในคอลเล็กชันนั้น:

 this.getCollection = function() { this.collection = this.db.getCollection('keychain'); return this.collection; }; this.getDocs = function() { return (this.getCollection()) ? this.getCollection().data : null; };

จากนั้นเราสามารถเรียก getDocs() ในตัวควบคุมเชิงมุมและดึงรหัสผ่านทั้งหมดที่จัดเก็บไว้ในฐานข้อมูล หลังจากที่เราเริ่มต้นใช้งาน:

 angular .module('MainView', ['Utils']) .controller('MainCtrl', ['Storage', function(Storage) { var vm = this; vm.keychain = null; Storage .init() .then(function(db) { vm.keychain = db.getDocs(); }); });

เทมเพลต Angular เล็กน้อย และเรามีรายการรหัสผ่าน:

 <tr ng-repeat="item in vm.keychain track by $index" class="item--{{$index}}"> <td class="mdl-data-table__cell--non-numeric">{{item.description}}</td> <td>{{item.username || 'n/a'}}</td> <td> <span ng-repeat="n in [1,2,3,4,5,6]">&bull;</span> </td> <td> <a href="#" copy-password="{{$index}}">copy</a> <a href="#" remove-password="{{item}}">remove</a> </td> </tr> 

คุณลักษณะที่เพิ่มเข้ามาที่ดีคือการรีเฟรชรายการรหัสผ่านหลังจากใส่รหัสผ่านใหม่ สำหรับสิ่งนี้ เราสามารถใช้โมดูล IPC ของอิเล็กตรอนได้ ดังที่ได้กล่าวไว้ก่อนหน้านี้ โมดูล IPC ของกระบวนการหลักสามารถเรียกในกระบวนการเรนเดอร์เพื่อเปลี่ยนให้เป็นกระบวนการฟัง โดยใช้โมดูลระยะไกล นี่คือตัวอย่างวิธีใช้งานใน main.view.js :

 var remote = require('remote'), remoteIpc = remote.require('ipc'); angular .module('MainView', ['Utils']) .controller('MainCtrl', ['Storage', function(Storage) { var vm = this; vm.keychain = null; Storage .init() .then(function(db) { vm.keychain = db.getDocs(); remoteIpc.on('update-main-view', function() { Storage .reload() .then(function() { vm.keychain = db.getDocs(); }); }); }); }]);

ประเด็นสำคัญ:

  • เราจะต้องใช้โมดูลระยะไกลโดยใช้วิธี require() ของตัวเองเพื่อกำหนดให้ใช้โมดูล IPC ระยะไกลจากกระบวนการหลัก
  • จากนั้นเราสามารถตั้งค่า Renderer Process ของเราให้เป็นผู้ฟังเหตุการณ์ผ่านเมธอด on() และเชื่อมโยงฟังก์ชันการเรียกกลับเข้ากับเหตุการณ์เหล่านี้

มุมมองแทรกจะรับผิดชอบในการดำเนินการกิจกรรมนี้ทุกครั้งที่มีการบันทึกเอกสารใหม่:

 Storage .addDoc(scope.vm.formData) .then(function() { // refresh list in main view ipc.send('update-main-view'); // reset form & close insert window scope.vm.formData = {}; ipc.send('toggle-insert-view'); });

กำลังคัดลอกรหัสผ่าน

โดยปกติแล้ว ไม่ควรแสดงรหัสผ่านเป็นข้อความธรรมดา แต่เราจะซ่อนและให้ปุ่มอำนวยความสะดวกที่อนุญาตให้ผู้ใช้คัดลอกรหัสผ่านโดยตรงสำหรับรายการเฉพาะ

ที่นี่อีกครั้ง Electron เข้ามาช่วยเหลือเราโดยมอบโมดูลคลิปบอร์ดที่มีวิธีการง่ายๆ ในการคัดลอกและวาง ไม่เพียงแต่เนื้อหาที่เป็นข้อความเท่านั้น แต่ยังรวมถึงรูปภาพและโค้ด HTML ด้วย:

 var clipboard = require('clipboard'); angular .module('Utils', []) ... .directive('copyPassword', [function() { return function(scope, el, attrs) { el.bind('click', function(e) { e.preventDefault(); var text = (scope.vm.keychain[attrs.copyPassword]) ? scope.vm.keychain[attrs.copyPassword].password : ''; // atom's clipboard module clipboard.clear(); clipboard.writeText(text); }); }; }]);

เนื่องจากรหัสผ่านที่สร้างขึ้นจะเป็นสตริงธรรมดา เราจึงสามารถใช้ writeText() เพื่อคัดลอกรหัสผ่านไปยังคลิปบอร์ดของระบบ จากนั้น เราสามารถอัปเดต HTML มุมมองหลักของเรา และเพิ่มปุ่มคัดลอกด้วยคำสั่ง copy-password ระบุดัชนีของอาร์เรย์ของรหัสผ่าน:

 <a href="#" copy-password="{{$index}}">copy</a>

การลบรหัสผ่าน

ผู้ใช้ปลายทางของเราอาจต้องการสามารถลบรหัสผ่านได้ ในกรณีที่รหัสผ่านล้าสมัย ในการทำเช่นนี้ สิ่งที่เราต้องทำคือเรียกเมธอด remove() บนคอลเลกชั่นพวงกุญแจ เราจำเป็นต้องจัดเตรียมเอกสารทั้งหมดให้กับเมธอด 'remove()' เช่น:

 this.removeDoc = function(doc) { return function() { var d = $q.defer(); if(this.isLoaded() && this.getCollection()) { // remove the doc from the collection & persist changes this.getCollection().remove(doc); this.db.saveDatabase(); // inform the insert view that the db content has changed ipc.send('reload-insert-view'); d.resolve(true); } else { d.reject(new Error('DB NOT READY')); } return d.promise; }.bind(this); };

เอกสารประกอบของ Loki.js ระบุว่าเราสามารถลบเอกสารด้วยรหัสของมันได้ แต่ดูเหมือนว่าจะไม่ทำงานตามที่คาดไว้

การสร้างเมนูเดสก์ท็อป

อิเลคตรอนผสานรวมกับสภาพแวดล้อมเดสก์ท็อป OS ของเราอย่างราบรื่นเพื่อมอบรูปลักษณ์และสัมผัสประสบการณ์ผู้ใช้ "ดั้งเดิม" ให้กับแอปของเรา ดังนั้น Electron จึงมาพร้อมกับโมดูล Menu ซึ่งใช้สำหรับสร้างโครงสร้างเมนูเดสก์ท็อปที่ซับซ้อนสำหรับแอปของเราโดยเฉพาะ

โมดูลเมนูเป็นหัวข้อที่กว้างใหญ่และเกือบจะสมควรได้รับบทช่วยสอนของตัวเอง ฉันขอแนะนำอย่างยิ่งให้คุณอ่านบทแนะนำการบูรณาการสภาพแวดล้อมเดสก์ท็อปของอิเล็กตรอนเพื่อค้นหาคุณสมบัติทั้งหมดของโมดูลนี้

สำหรับขอบเขตของบทช่วยสอนปัจจุบันนี้ เราจะดูวิธีสร้างเมนูแบบกำหนดเอง เพิ่มคำสั่งที่กำหนดเองลงไป และใช้คำสั่ง quit มาตรฐาน

การสร้างและกำหนดเมนูแบบกำหนดเองให้กับแอพของเรา

โดยทั่วไป ตรรกะ JavaScript สำหรับเมนูอิเล็กตรอนจะอยู่ในไฟล์สคริปต์หลักของแอปของเรา ซึ่งกำหนดกระบวนการหลักของเรา อย่างไรก็ตาม เราสามารถแยกเป็นไฟล์แยก และเข้าถึงโมดูลเมนูผ่านโมดูลระยะไกลได้:

 var remote = require('remote'), Menu = remote.require('menu');

ในการกำหนดเมนูอย่างง่าย เราจะต้องใช้ buildFromTemplate() :

 var appMenu = Menu.buildFromTemplate([ { label: 'Electron', submenu: [{ label: 'Credits', click: function() { alert('Built with Electron & Loki.js.'); } }] } ]);

รายการแรกในอาร์เรย์จะใช้เป็นรายการเมนู "เริ่มต้น" เสมอ

ค่าของคุณสมบัติ label ไม่สำคัญมากนักสำหรับรายการเมนูเริ่มต้น ในโหมด dev จะแสดง Electron เสมอ เราจะมาดูวิธีการกำหนดชื่อที่กำหนดเองให้กับรายการเมนูเริ่มต้นในขั้นตอนการสร้าง

สุดท้าย เราต้องกำหนดเมนูแบบกำหนดเองนี้เป็นเมนูเริ่มต้นสำหรับแอปของเราด้วย setApplicationMenu() :

 Menu.setApplicationMenu(appMenu);

แป้นพิมพ์ลัดการทำแผนที่

อิเลคตรอนจัดเตรียม "ตัวเร่งความเร็ว" ซึ่งเป็นชุดของสตริงที่กำหนดไว้ล่วงหน้าซึ่งจับคู่กับชุดแป้นพิมพ์จริง เช่น Command+A หรือ Ctrl+Shift+Z

ตัวเร่ง Command ไม่ทำงานบน Windows หรือ Linux สำหรับแอปพลิเคชันพวงกุญแจรหัสผ่าน เราควรเพิ่มรายการเมนู File โดยเสนอคำสั่งสองคำสั่ง:

  • สร้างรหัสผ่าน : เปิดมุมมองแทรกด้วย Cmd (หรือ Ctrl) + N
  • ออก : ออกจากแอปด้วย Cmd (หรือ Ctrl) + Q
 ... { label: 'File', submenu: [ { label: 'Create Password', accelerator: 'CmdOrCtrl+N', click: function() { ipc.send('toggle-insert-view'); } }, { type: 'separator' // to create a visual separator }, { label: 'Quit', accelerator: 'CmdOrCtrl+Q', selector: 'terminate:' // OS X only!!! } ] } ...

ประเด็นสำคัญ:

  • เราสามารถเพิ่มตัวคั่นด้วยภาพโดยการเพิ่มรายการในอาร์เรย์ด้วยคุณสมบัติ type ที่ตั้งค่าเป็น separator
  • ตัวเร่ง CmdOrCtrl เข้ากันได้กับทั้งแป้นพิมพ์ Mac และ PC
  • คุณสมบัติ selector ใช้ได้กับ OSX เท่านั้น!

ออกแบบแอพของเรา

คุณอาจสังเกตเห็นตัวอย่างโค้ดต่างๆ ที่อ้างอิงถึงชื่อคลาสที่ขึ้นต้นด้วย mdl- สำหรับจุดประสงค์ของบทช่วยสอนนี้ ฉันเลือกใช้เฟรมเวิร์ก Material Design Lite UI แต่คุณสามารถใช้เฟรมเวิร์ก UI ใดก็ได้ตามต้องการ

อะไรก็ตามที่เราสามารถทำได้ด้วย HTML5 สามารถทำได้ในอิเล็กตรอน เพียงจำไว้ว่าขนาดไบนารีของแอปที่เพิ่มขึ้น และปัญหาด้านประสิทธิภาพที่อาจเกิดขึ้นหากคุณใช้ไลบรารีของบุคคลที่สามมากเกินไป

แอพบรรจุภัณฑ์อิเล็กตรอนเพื่อการจัดจำหน่าย

คุณสร้างแอป Electron ขึ้นมา มันดูดีมาก คุณเขียนการทดสอบ e2e ด้วย Selenium และ WebDriver และคุณพร้อมที่จะเผยแพร่ไปทั่วโลก!

แต่คุณยังต้องการปรับแต่งให้เหมาะกับแต่ละบุคคล ตั้งชื่อที่กำหนดเองให้กับชื่ออื่นที่ไม่ใช่ "อิเลคตรอน" ที่เป็นค่าเริ่มต้น และอาจให้ไอคอนแอปพลิเคชันที่กำหนดเองสำหรับทั้งแพลตฟอร์ม Mac และ PC

สร้างด้วยอึก

ทุกวันนี้ มีปลั๊กอิน Gulp สำหรับทุกสิ่งที่เราคิดออก ทั้งหมดที่ฉันต้องทำคือพิมพ์ gulp electron ใน Google และแน่นอนว่ามีปลั๊กอิน gulp-electron!

ปลั๊กอินนี้ค่อนข้างใช้งานง่าย ตราบใดที่ยังคงรักษาโครงสร้างโฟลเดอร์ที่มีรายละเอียดไว้ตอนต้นของบทช่วยสอนนี้ ถ้าไม่อย่างนั้น คุณอาจต้องย้ายของไปรอบๆ สักหน่อย

ปลั๊กอินนี้สามารถติดตั้งได้เหมือนกับปลั๊กอิน Gulp อื่นๆ:

 $ npm install gulp-electron --save-dev

จากนั้นเราสามารถกำหนดงานอึกของเราได้ดังนี้:

 var gulp = require('gulp'), electron = require('gulp-electron'), info = require('./src/package.json'); gulp.task('electron', function() { gulp.src("") .pipe(electron({ src: './src', packageJson: info, release: './dist', cache: './cache', version: 'v0.31.2', packaging: true, platforms: ['win32-ia32', 'darwin-x64'], platformResources: { darwin: { CFBundleDisplayName: info.name, CFBundleIdentifier: info.bundle, CFBundleName: info.name, CFBundleVersion: info.version }, win: { "version-string": info.version, "file-version": info.version, "product-version": info.version } } })) .pipe(gulp.dest("")); });

ประเด็นสำคัญ:

  • โฟลเดอร์ src/ ต้องไม่เหมือนกับโฟลเดอร์ที่มี Gulpfile.js หรือโฟลเดอร์เดียวกับโฟลเดอร์การแจกจ่าย
  • เราสามารถกำหนดแพลตฟอร์มที่เราต้องการส่งออกผ่านอาร์เรย์ของ platforms
  • เราควรกำหนดโฟลเดอร์ cache ที่จะดาวน์โหลดไบนารีของอิเล็กตรอนเพื่อให้สามารถรวมเข้ากับแอปของเราได้
  • ต้องส่งเนื้อหาของไฟล์ package.json ของแอปไปยังงานอึกผ่านคุณสมบัติ packageJson
  • มีคุณสมบัติ packaging ที่เป็นตัวเลือก ทำให้เราสามารถสร้างไฟล์ zip ของแอพที่สร้างขึ้นได้
  • สำหรับแต่ละแพลตฟอร์มมีชุด "ทรัพยากรแพลตฟอร์ม" ที่แตกต่างกันซึ่งสามารถกำหนดได้

การเพิ่มไอคอนแอพ

หนึ่งในคุณสมบัติ platformResources คือคุณสมบัติ icon ซึ่งช่วยให้เราสามารถกำหนดไอคอนที่กำหนดเองสำหรับแอปของเรา:

 "icon": "keychain.ico"

OS X ต้องใช้ไอคอนที่มีนามสกุลไฟล์ . .icns มีเครื่องมือออนไลน์มากมายที่ช่วยให้เราสามารถแปลงไฟล์ . .png เป็น .ico และ . .icns ได้ฟรี

บทสรุป

ในบทความนี้ เราได้เพียงแต่ขีดข่วนพื้นผิวของสิ่งที่อิเล็กตรอนสามารถทำได้จริง ลองนึกถึงแอปดีๆ อย่าง Atom หรือ Slack ว่าเป็นแรงบันดาลใจที่คุณสามารถใช้เครื่องมือนี้ได้

ฉันหวังว่าคุณจะพบว่าบทช่วยสอนนี้มีประโยชน์ โปรดแสดงความคิดเห็นและแบ่งปันประสบการณ์ของคุณกับ Electron!