Woongjae Lee
NHN Dooray - Frontend Team
2woongjae@gmail.com
➜ mkdir electron-bookmark
➜ cd electron-bookmark
➜ npm init -y
➜ npm i electron -D
➜ code .
electron-bookmark
- src
- browser
- main.js
- BookmarkApp.js
- renderer
- index.js
- BoookmarkView.js
- static
- index.html
- icon.png
- icon@2x.png
const {BookmarkApp} = require('./BookmarkApp');
function main() {
new BookmarkApp();
}
main();
const {app} = require('electron');
class BookmarkApp {
constructor() {
app.on('ready', this._ready.bind(this));
}
_ready() {
console.log('_ready');
}
}
module.exports = {
BookmarkApp
};
const {app, Tray} = require('electron');
class BookmarkApp {
constructor() {
this._tray = null;
app.on('ready', this._ready.bind(this));
}
_ready() {
// 트레이 생성 및 메뉴 처리
this._tray = new Tray(path.join(__dirname, '../../static/icon.png'));
this._tray.setContextMenu(this._getTrayMenu());
// 트레이의 이벤트에 함수 바인딩
if (process.platform === 'darwin') {
this._tray.on('right-click', this._toggle.bind(this));
} else {
this._tray.on('click', this._toggle.bind(this));
}
}
_getTrayMenu() {
return Menu.buildFromTemplate([
{
label: 'Open',
click: () => {
}
},
{
label: 'Save',
submenu: [
{
label: 'Home',
click: () => {
}
},
{
label: 'Github',
click: () => {
}
}
]
},
{type: 'separator'},
{
label: 'Quit',
click: () => {
app.quit();
}
}
]);
}
_toggle() {
}
}
module.exports = {
BookmarkApp
};
const {app, Tray, BrowserWindow} = require('electron');
const url = require('url');
const path = require('path');
const HTML = url.format({
protocol: 'file',
pathname: path.join(__dirname, '../../static/index.html')
});
class BookmarkApp {
constructor() {
this._win = null;
this._tray = null;
app.on('ready', this._ready.bind(this));
}
_ready() {
// 트레이 생성 및 메뉴 처리
this._tray = new Tray(path.join(__dirname, '../../static/icon.png'));
this._tray.setContextMenu(this._getTrayMenu());
// 트레이의 이벤트에 함수 바인딩
if (process.platform === 'darwin') {
this._tray.on('right-click', this._toggle.bind(this));
} else {
this._tray.on('click', this._toggle.bind(this));
}
// 랜더러의 위치를 가져오기 위한 작업
const bounds = this._tray.getBounds();
// 랜더러 생성
this._win = new BrowserWindow({
width: 400,
height: 400,
x: Math.round(bounds.x - 200 + (bounds.width / 2)),
y: (process.platform === 'darwin') ? bounds.y + bounds.height + 10 : bounds.y - 400 - 10,
show: false,
resizable: false,
movable: false,
acceptFirstMouse: true,
frame: false
});
// 컨텐츠 로드
this._win.loadURL(HTML);
this._win.webContents.openDevTools();
// 랜더러의 이벤트에 함수 바인딩
this._win.once('ready-to-show', this._update.bind(this));
if (process.platform === 'darwin') {
this._win.on('blur', () => this._win.hide());
}
}
_getTrayMenu() {
return Menu.buildFromTemplate([
{
label: 'Open',
click: () => {
this._win.show();
}
},
{
label: 'Save',
submenu: [
{
label: 'Home',
click: () => {
}
},
{
label: 'Github',
click: () => {
}
}
]
},
{type: 'separator'},
{
label: 'Quit',
click: () => {
app.quit();
}
}
]);
}
_toggle() {
if (this._win.isVisible()) {
this._win.hide();
} else {
this._win.show();
}
}
_update() {
}
}
module.exports = {
BookmarkApp
};
➜ npm i photonkit -S
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" href="../node_modules/photonkit/dist/css/photon.css">
</head>
<body>
<div class="window">
<header class="toolbar toolbar-header">
<div class="toolbar-actions">
<h1 class="title">Mark's Bookmark</h1>
<div class="btn-group">
<button id="btn_home" class="btn btn-default active">
<span class="icon icon-home"></span>
</button>
<button id="btn_github" class="btn btn-default">
<span class="icon icon-github"></span>
</button>
</div>
</div>
</header>
<div class="window-content">
<ul class="list-group" id="data"></ul>
</div>
</div>
<script>
require('../src/renderer');
</script>
</body>
</html>
const {BookmarkView} = require('./BookmarkView');
function main() {
new BookmarkView();
}
document.addEventListener('DOMContentLoaded', main);
class BookmarkView {
constructor() {
this._btnHome = document.querySelector('#btn_home');
this._btnGithub = document.querySelector('#btn_github');
this._dataDom = document.querySelector('#data');
this._bindDomEvent();
this._bindIpcEvent();
}
_bindDomEvent() {
}
_bindIpcEvent() {
}
}
module.exports = {
BookmarkView
}
const {ipcRenderer, clipboard, shell} = require('electron');
class BookmarkView {
constructor() {
this._btnHome = document.querySelector('#btn_home');
this._btnGithub = document.querySelector('#btn_github');
this._dataDom = document.querySelector('#data');
this._bindDomEvent();
this._bindIpcEvent();
}
_bindDomEvent() {
this._btnHome.addEventListener('click', () => {
this._changeType('home');
});
this._btnGithub.addEventListener('click', () => {
this._changeType('github');
});
document.addEventListener('paste', () => {
this._paste();
});
}
_bindIpcEvent() {
ipcRenderer.on('data', (event, data) => {
console.log(data);
this._dataDom.innerHTML = this._getHtml(data);
this._setItemDom();
});
}
_changeType(type) {
// UI 변경
if (type === 'home') {
this._btnHome.classList.add('active');
this._btnGithub.classList.remove('active');
} else if (type === 'github') {
this._btnHome.classList.remove('active');
this._btnGithub.classList.add('active');
}
// Data 변경
ipcRenderer.send('type', type);
}
_paste() {
const text = clipboard.readText();
ipcRenderer.send('paste', text);
}
_getHtml(data) {
const html = data.map(item => {
return `
<li class="list-group-item">
<div class="media-body">
<strong><a href="#" class="clickLink">${item.url}</a></strong>
<p>
${item.title}
<span class="icon icon-trash pull-right"></span>
</p>
</div>
</li>
`;
});
return html.join('');
}
_setItemDom() {
const removeDoms = document.querySelectorAll('.icon-trash');
removeDoms.forEach((removeDom, index) => {
removeDom.addEventListener('click', () => {
ipcRenderer.send('remove', index);
});
});
const clickDoms = document.querySelectorAll('.clickLink');
clickDoms.forEach(clickDom => {
clickDom.addEventListener('click', e => {
shell.openExternal(e.target.innerHTML);
});
});
}
}
module.exports = {
BookmarkView
}
➜ npm i superagent get-title -S
const {app, BrowserWindow, ipcMain, dialog, Tray, Menu, clipboard} = require('electron');
const url = require('url');
const path = require('path');
const fs = require('fs');
const request = require('superagent');
const getTitle = require('get-title');
...
const DATA_PATH = path.join(app.getPath('userData'), 'data.json');
class BookmarkApp {
constructor() {
this._tray = null;
this._win = null;
this._type = 'home';
this._data = [];
app.on('ready', this._ready.bind(this));
}
...
_ready() {
// 데이터 로컬에서 가져오기
this._initData();
...
// ipc 이벤트에 함수 바인딩
ipcMain.on('type', this._ipcType.bind(this));
ipcMain.on('paste', this._ipcPaste.bind(this));
ipcMain.on('remove', this._ipcRemove.bind(this));
}
_initData() {
if (!fs.existsSync(DATA_PATH)) {
fs.writeFileSync(DATA_PATH, JSON.stringify([]));
}
const fileData = JSON.parse(fs.readFileSync(DATA_PATH).toString());
fileData.forEach(item => {
this._data.push(item);
});
}
_update() {
const updateData = this._data.filter(item => item.type === this._type);
if (this._win !== null) {
this._win.webContents.send('data', updateData);
}
}
_ipcType(event, type) {
this._type = type;
this._update();
}
_ipcPaste(event, saveUrl) {
this._saveUrl(this._type, saveUrl);
}
_ipcRemove(event, index) {
this._removeUrl(index);
}
_saveUrl(type, saveUrl) {
if (saveUrl.indexOf('http://') > -1 || saveUrl.indexOf('https://') > -1) {
request.get(saveUrl)
.end((err, response) => {
getTitle(response.res.text).then(title => {
this._data.push({type, url: saveUrl, title});
fs.writeFileSync(DATA_PATH, JSON.stringify(this._data));
if (this._type === type) {
this._update();
}
});
});
} else {
dialog.showErrorBox('경고', 'url 이 아닌듯 합니다.');
}
}
_removeUrl(index) {
const currentData = this._data.filter((item, i) => {
item.index = i;
return item.type === this._type;
});
let removeId = null;
currentData.forEach((item, i) => {
if (i === index) {
removeId = item.index;
}
});
this._data.splice(removeId, 1);
fs.writeFileSync(DATA_PATH, JSON.stringify(this._data));
this._update();
}
}
By Woongjae Lee
일렉트론으로 만드는 북마크 어플리케이션 수업