Electron with TypeScript 4th

2woongjae@gmail.com

Plan

  • 4주차 작업 목록 (2017년 9월 22일)
    • 리팩토링 2
    • 데이터베이스 null 처리
    • Tray 처리
    • Native Menu 처리
    • Enter 로 로그인
    • 새 메시지 도착 시 채팅 스크롤 내리기
    • 프로필 처리
    • 패키징
    • 디플로이는 ??

리팩토링 2

src/browser/SimpleChatApp.ts

// 1. ipcMain 이벤트에 바인딩 된 함수를 분리
ipcMain.on('request-login', this._ipcRequestLogin);
ipcMain.on('request-logout', this._ipcRequestLogout);
ipcMain.on('send-message', this._ipcSendMessage);

// 2. log 에 TAG 추가
console.log(TAG, '_btnLoginClicked');

// 3. 모든 함수에 타입 처리

src/browser/SimpleChatView.ts

// 1. Dom 이벤트 바인딩과 ipcRenderer 이벤트 바인딩 함수
this._bindDomEvent();
this._bindIpc();

// 2. Dom 이벤트에 바인딩 된 함수를 분리
this._btnLogin.addEventListener('click', this._btnLoginClicked);
this._btnLogout.addEventListener('click', this._btnLogoutClicked);
this._btnSendMessage.addEventListener('click', this._btnSendMessageClicked);
this._btnToggle.addEventListener('click', this._btnToggleClicked);
this._messageDom.addEventListener('keypress', this._messageDomKeypressed);

// 3. ipcRenderer 이벤트에 바인딩 된 함수를 분리
ipcRenderer.on('login-success', this._ipcLoginSuccess);
ipcRenderer.on('login-error', this._ipcLoginError);
ipcRenderer.on('logout-success', this._ipcLogoutSuccess);
ipcRenderer.on('general-message', this._ipcGeneralMessage);

// 4. log 에 TAG 추가
console.log(TAG, '_btnLoginClicked');

// 5. 모든 함수에 타입 처리

재현

ref.child('general').on('value', snapshot => {
    if (snapshot) {
        const data = null; // snapshot.val();
        const messageObjects: MessageObjectType[] = Object.keys(data).map(id => {
            const messageObject: MessageObjectType = {
                id,
                email: data[id].email,
                name: data[id].name,
                message: data[id].message,
                time: data[id].time
            };
            return messageObject;
        });
        event.sender.send('general-message', messageObjects);
    }
});

재현

수정

ref.child('general').on('value', snapshot => {
    if (snapshot) {
        const data = snapshot.val();
        const messageObjects: MessageObjectType[] = data ? Object.keys(data).map(id => {
            const messageObject: MessageObjectType = {
                id,
                email: data[id].email,
                name: data[id].name,
                message: data[id].message,
                time: data[id].time
            };
            return messageObject;
        }) : [];
        event.sender.send('general-message', messageObjects);
    }
});

Tray 처리

static/tray.png - 16px

static/tray@2x.png - 32px

src/browser/SimpleChatApp.ts

import {app, BrowserWindow, ipcMain, Tray} from 'electron';

const TRAY_ICON_PATH = path.join(__dirname, '../../static/tray.png');

private _ready = (): void => {
    console.log(TAG, '_ready');
    this._tray = new Tray(TRAY_ICON_PATH);
    ...
}
        

Tray Menu

import {app, BrowserWindow, ipcMain, Tray, Menu} from 'electron';

private _ready = (): void => {
    console.log(TAG, '_ready');
    this._tray = new Tray(TRAY_ICON_PATH);
    this._tray.setContextMenu(this._getTrayMenu());
}

private _getTrayMenu(): Electron.Menu {
    return Menu.buildFromTemplate([
        {
            label: 'menu',
            click: () => {
                console.log('menu');
            }
        }
    ]);
}

Tray Menu

BrowserWindow 설정

this._win = new BrowserWindow({
    width: 500,
    minWidth: 500,
    maxWidth: 500,
    height: 700,
    minHeight: 700,
    maxHeight: 700,
    maximizable: false,
    show: false
});
this._win.loadURL(HTML);
this._win.once('ready-to-show', () => {
    this._win.show();
});
this._win.on('close', (event) => {
    this._win.hide();
    event.preventDefault();
});

Tray Menu 설정

private _getTrayMenu(): Electron.Menu {
    return Menu.buildFromTemplate([
        {
            label: 'Open',
            click: () => {
                if (this._win) {
                    this._win.show();
                }
            }
        },
        {
            type: 'separator'
        },
        {
            label: 'Exit',
            click: () => {
                this._app.exit();
            }
        }
    ]);
}

Tray 버그 (윈도우)

this._app.on('quit', this._quit);

private _quit = () => {
    console.log(TAG, '_quit');
    if (this._tray) {
        this._tray.destory();
    }
}

Native Menu 처리

Native Menu ?

src/browser/SimpleChatApp.ts

private _ready = (): void => {
    ...
    Menu.setApplicationMenu(this._getApplicationMenu());
    ...
}

private _getApplicationMenu(): Electron.Menu {
    return Menu.buildFromTemplate([
        {
            label: 'SimpleChat',
            submenu: [
                {
                    label: 'Open',
                    click: () => {
                        this._win.show();
                    }
                },
                {
                    type: 'separator'
                },
                {
                    label: 'Quit',
                    click: () => {
                        this._app.exit();
                    }
                }
            ]
        }
    ]);
}

role

{
    label: 'Edit',
    submenu: [
        {role: 'undo'},
        {role: 'redo'},
        {type: 'separator'},
        {role: 'cut'},
        {role: 'copy'},
        {role: 'paste'},
        {role: 'pasteandmatchstyle'},
        {role: 'delete'},
        {role: 'selectall'}
    ]
}

role 추가

Enter 로 로그인

src/renderer/SimpleChatView.ts

this._input_email.addEventListener('keypress', this._inputEmailKeypressed);
this._input_password.addEventListener('keypress', this._inputPasswordKeypressed);

private _inputEmailKeypressed = (event): void => {
    console.log(TAG, '_inputEmailKeypressed');
    if (event.keyCode === 13) {
        this._input_password.focus();
    }
}
private _inputPasswordKeypressed = (event): void => {
    console.log(TAG, '_inputPasswordKeypressed');
    if (event.keyCode === 13) {
        this._requestLogin();
    }
}
private _requestLogin = (): void => {
    const win = remote.getCurrentWindow();
    if (this._input_email.value.length < 4 || !validateEmail(this._input_email.value)) {
        dialog.showMessageBox(win, {
            message: 'Login Failed',
            detail: '메일 주소가 유효하지 않습니다.'
        }, () => {
            this._input_email.focus();
        });
        return;
    }
    if (this._input_password.value.length < 4) {
        dialog.showMessageBox(win, {
            message: 'Login Failed',
            detail: '패스워드가 유효하지 않습니다.'
        }, () => {
            this._input_password.focus();
        });
        return;
    }
    const loginObj: LoginObjectType = {
        email: this._input_email.value,
        password: this._input_password.value
    };
    ipcRenderer.send('request-login', loginObj);
}

새 메시지 도착 시 채팅 스크롤 내리기

src/renderer/SimpleChatView.ts

private _ipcGeneralMessage = (event, arg: MessageObjectType[]): void => {
    console.log(TAG, '_ipcGeneralMessage');
    const messagesHTML = arg.map(messageObject => {
        return `
<div class="box">
    <article class="media">
        <div class="media-content">
            <div class="content">
                <p>
                    <strong>${messageObject.name}</strong> <small>${messageObject.email}</small> <small>${messageObject.time}</small>
                    <br>
                    ${messageObject.message}
                </p>
            </div>
        </div>
    </article>
</div>
        `;
    }).join('');
    const messageContainer = document.querySelector('#message-container') as HTMLDivElement;
    messageContainer.innerHTML = messagesHTML;
    document.getElementById('chat-section').scrollTop = messageContainer.scrollHeight;
}

프로필 처리

src/browser/SimpleChatApp.ts

ref.child(`profile`).on('value', snapshot => {
    if (snapshot) {
        const data = snapshot.val();
        if (data) {
            const isExist = Object.keys(data).filter(id => data[id].email === arg.email);
            if (isExist.length > 0) {
                this._name = data[isExist[0]].name;
            } else {
                const name = arg.email.split('@')[0];
                ref.child(`profile`).push().set({
                    email: arg.email,
                    name,
                    createAt: new Date().toISOString()
                });
                this._name = name;
            }
        } else {
            const name = arg.email.split('@')[0];
            ref.child(`profile`).push().set({
                email: arg.email,
                name,
                createAt: new Date().toISOString()
            });
            this._name = name;
        }
    }
});

src/browser/SimpleChatApp.ts

private _ipcSendMessage = async (event, arg: string): Promise<void> => {
    console.log(TAG, '_ipcSendMessage');
    if (this._auth.currentUser) {
        const ref = this._database.ref();
        ref.child('general').push().set({
            email: this._auth.currentUser.email,
            name: this._name,
            message: arg,
            time: new Date().toISOString()
        });
    }
}

패키징

디플로이는 ??

Electron with TypeScript (4)

By Woongjae Lee

Electron with TypeScript (4)

타입스크립트 한국 유저 그룹 일렉트론 워크샵 201709

  • 1,161