Electron Bookmark App by Mark

2woongjae@gmail.com

Project Setting

Terminal

➜ mkdir electron-bookmark

➜ cd electron-bookmark

➜ npm init -y

➜ npm i electron -D

➜ code .

Project Folder

electron-bookmark

    - src

        - browser

            - main.js

            - BookmarkApp.js

        - renderer

            - index.js

            - BoookmarkView.js

    - static

        - index.html

        - icon.png

        - icon@2x.png

static/icon.png    

static/icon@2x.png   

package.json

Tray, BrowserWindow

src/browser/main.js

const {BookmarkApp} = require('./BookmarkApp');

function main() {
    new BookmarkApp();
}

main();

src/browser/main.js

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

class BookmarkApp {
    constructor() {
        app.on('ready', this._ready.bind(this));
    }

    _ready() {
        console.log('_ready');
    }
}

module.exports = {
    BookmarkApp
};

src/browser/main.js

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
};

src/browser/main.js

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
};

View

Terminal

➜ npm i photonkit -S

Photonkit

static/index.html

<!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>

Renderer Process

src/renderer/index.js

const {BookmarkView} = require('./BookmarkView');

function main() {
    new BookmarkView();
}

document.addEventListener('DOMContentLoaded', main);

src/renderer/BookmarkView.js

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
}

src/renderer/BookmarkView.js

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
}

DATA & ipcMain

Terminal

➜ npm i superagent get-title -S

src/browser/BookmarkApp.js

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();
    }
}

    

Bookmark Application by Electron

By Woongjae Lee

Bookmark Application by Electron

일렉트론으로 만드는 북마크 어플리케이션 수업

  • 1,275