Electron with TypeScript 3rd

2woongjae@gmail.com

Plan

  • 3주차 작업 목록 (2017년 9월 15일)
    • 일렉트론에서 무작정 데이터 보내기
    • 메세지 제대로 작성하기
    • 랜더러에서 데이터 뿌리기
    • 서브 메뉴 열기
    • 로그인 실패 처리
    • Shift + Enter 키로 작성하기
    • 다른 유저들과 대화하기
    • 리팩토링 1

일렉트론에서 무작정 데이터 보내기

firebase write

const ref = database.ref();

ref.child('general').push().set({
    이메일: '메일 주소',
    닉네임: '누구',
    내용: '어쩌구',
    시간: '언제'   
});

파이어베이스 쓰기 방식

  • step1. database 의 reference 를 얻고, 쓰기할 위치로 이동한다.
    • database.ref().child('general')
  • step2. 그 위치에 오브젝트를 입력하기 위한 프로퍼티를 생성한다.
    • database.ref().child('general').push()
    • push() 의 결과로 그 프로퍼티가 리턴된다.
  • step3. 그 프로퍼티에 객체를 만들어서 붙인다.
    • push().set(오브젝트)

버튼 클릭 시 ipcRenderer 로 send 처리 - index.html

<section class="section" id="write-section">
    <div class="container">
        <div class="field">
            <div class="control">
                <textarea class="textarea" placeholder="Explain how we can help you"></textarea>
            </div>
        </div>
        <div class="field">
            <div class="control">
                <button class="button is-primary" id="btn-send-message">Send message</button>
            </div>
        </div>
    </div>
</section>

버튼 클릭 시 ipcRenderer 로 send 처리 - renderer

const btnSendMessage = document.querySelector('#btn-send-message') as HTMLButtonElement;

btnSendMessage.addEventListener('click', () => {
    console.log('#btn-send-message click');

    ipcRenderer.send('send-message');
});

버튼 클릭 시 ipcRenderer 로 send 처리 - browser

ipcMain.on('send-message', async event => {
    if (auth.currentUser) {
        const ref = database.ref();
        ref.child('general').push().set({
            email: '이메일 주소',
            name: '이름',
            message: '메세지',
            time: '시간'
        });
    }
});

버튼 클릭 시 ipcRenderer 로 send 처리

실제 데이터

랜더러에서 데이터 뿌리기

src/common/type.ts

export interface LoginObjectType {
    email: string;
    password: string;
}

export interface MessageObjectType {
    id: string;
    email: string;
    name: string;
    message: string;
    time: string;
}

to renderer : browser

ref.child('general').on('value', snapshot => {
    if (snapshot) {
        const data = 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);
    }
});

renderer

src/renderer/index.ts

ipcRenderer.on('general-message', (event, arg: MessageObjectType[]) => {
    console.log('receive : general-message');
    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;
});

로그인 직후

메세지 추가

메세지 제대로 작성하기

src/renderer/index.ts

btnSendMessage.addEventListener('click', () => {
    console.log('#btn-send-message click');

    const messageDom = document.querySelector('#message') as HTMLTextAreaElement;
    const message = messageDom.value;
        
    if (message === '') {
        return;
    }

    ipcRenderer.send('send-message', message);
});

src/browser/index.ts

ipcMain.on('send-message', async (event, message: string) => {
    if (auth.currentUser) {
        const ref = database.ref();
        ref.child('general').push().set({
            email: auth.currentUser.email,
            name: '2woongjae',
            message: message,
            time: new Date().toISOString()
        });
    }
});

메세지 작성

메세지 보낸 후, textarea 비우기

 
btnSendMessage.addEventListener('click', () => {
    console.log('#btn-send-message click');

    const messageDom = document.querySelector('#message') as HTMLTextAreaElement;
    const message = messageDom.value;
        
    if (message === '') {
        return;
    }

    ipcRenderer.send('send-message', message);
    messageDom.value = '';
});

서브 메뉴 열기

browser/index.ts

const win = new BrowserWindow({
    width: 500,
    minWidth: 500,
    maxWidth: 500,
    height: 700,
    minHeight: 700,
    maxHeight: 700,
    maximizable: false
});

static/style.css

#btn-toggle {
    display: none;
}

static/index.html

<section class="hero is-primary is-medium" id="nav-section">
    <div class="hero-head">
        <header class="nav">
            <div class="container">
                <div class="nav-left">
                    <a class="nav-item is-active">
                        Electron with TypeScript Hands-On Labs
                    </a>
                </div>
                <span class="nav-toggle" id="btn-toggle" data-target="nav-menu">
                    <span></span>
                    <span></span>
                    <span></span>
                </span>
                <div class="nav-right nav-menu" id="nav-menu">
                    <a class="nav-item is-active">
                        #general
                    </a>
                    <span class="nav-item" id="btn-logout">
                        <a class="button is-primary is-inverted">
                            <span class="icon">
                                <i class="fa fa-sign-out"></i>
                            </span>
                            <span>Logout</span>
                        </a>
                    </span>
                </div>
            </div>
        </header>
    </div>
</section>

renderer/index.ts

const btnToggle = document.querySelector('#btn-toggle') as HTMLSpanElement;
const navMenu = document.querySelector(`#nav-menu`) as HTMLDivElement;

btnToggle.addEventListener('click', () => {
    btnToggle.classList.toggle('is-active');
    navMenu.classList.toggle('is-active');
});

ipcRenderer.on('login-success', (event, arg) => {
    console.log('receive : login-success');

    loginSection.style.display = 'none';
    chatSection.style.display = 'block';
    writeSection.style.display = 'block';

    btnToggle.style.display = 'block';
});

ipcRenderer.on('logout-success', (event, arg) => {
    console.log('receive : logout-success');

    loginSection.style.display = 'block';
    chatSection.style.display = 'none';
    writeSection.style.display = 'none';

    btnToggle.style.display = 'none';
    btnToggle.classList.toggle('is-active');
    navMenu.classList.toggle('is-active');
});

로그인 전

로그인 후

로그인 실패 처리

로그인 실패 경우

  • 클라이언트
    • 이메일인지
      • 이메일의 최소 글자 수
      • 이메일의 규칙에 맞는지
    • 패스워드인지
      • 패스워드를 적었는지 안적었는지
  • 서버 : 다이얼로그 처리
    • 이메일이 맞는지
      • 이메일이 틀립니다.
    • 패스워드가 맞는지
      • 패스워드가 틀립니다.

remote 와 dialog 사용

import {ipcRenderer, remote} from 'electron';

const {dialog} = remote;

클라이언트 에러 처리

const btnLogin = document.querySelector('#btn-login') as HTMLButtonElement;
const btnLogout = document.querySelector('#btn-logout') as HTMLButtonElement;

btnLogin.addEventListener('click', () => {
    console.log('#btn-login click');

    if (input_email.value.length < 4 || !validateEmail(input_email.value)) {
        const win = remote.getCurrentWindow();
        dialog.showMessageBox(win, {
            message: 'Login Failed',
            detail: '메일 주소가 유효하지 않습니다.'
        }, () => {
            input_email.focus();
        });
        return;
    }

    if (input_password.value.length < 4) {
        const win = remote.getCurrentWindow();
        dialog.showMessageBox(win, {
            message: 'Login Failed',
            detail: '패스워드가 유효하지 않습니다.'
        }, () => {
            input_password.focus();
        });
        return;
    }

    const loginObj: LoginObjectType = {
        email: input_email.value,
        password: input_password.value
    };

    ipcRenderer.send('request-login', loginObj);
});

서버 에러 처리 - browser

try {
    user = await auth.signInWithEmailAndPassword(arg.email, arg.password);
} catch (error) {
    if (isFirebaseError(error)) {
        console.log(error);
        event.sender.send('login-error', error.code);
        return;
    } else {
        throw error;
    }
}

서버 에러 처리 - renderer

ipcRenderer.on('login-error', (event, code: string) => {
    console.log('receive : login-error');
    console.error(code);
    if (code === 'auth/user-not-found') {
        const win = remote.getCurrentWindow();
        dialog.showMessageBox(win, {
            message: 'Login Failed',
            detail: '등록되지 않은 이메일 주소입니다.'
        }, () => {
            input_email.focus();
        });
        return;
    } else if (code === 'auth/wrong-password') {
        const win = remote.getCurrentWindow();
        dialog.showMessageBox(win, {
            message: 'Login Failed',
            detail: '잘못된 비밀번호 입니다.'
        }, () => {
            input_password.focus();
        });
        return;
    }
});

Shift + Enter 키로 작성하기

renderer/index.ts

messageDom.addEventListener('keypress', e => {
    if (e.shiftKey && e.keyCode === 13) {
        e.preventDefault();

        const message = messageDom.value;
        if (message === '') {
            return;
        }

        ipcRenderer.send('send-message', message);
        messageDom.value = '';
    }
});

다른 유저들과 대화하기

리팩토링 1

Electron with TypeScript (3)

By Woongjae Lee

Electron with TypeScript (3)

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

  • 1,102