TCP Socket Router 예제를 통한 Electron Application 작성방법에 대한 연재가 완료되었습니다.
Electron + Vue.js 애플리케이션 만들기
1. Electron 프로젝트 생성하기(Create Electron Application Project)
2. Electron IPC 통신(Electron Architecture, IPC Main/IPC Renderer)
3. TCP Router 기능 구현(Implements TCP Router Communication)
5. 환경설정 구현하기(Preferences Window)
6. 메뉴 사용하기(Application Menu & Context Menu)
7. 시스템 트레이 사용 및 창 최소화(System Tray & Minimize to Tray)
8. Bootstrap Vue와 Font Awesome 사용하기(Using Bootstrap Vue & Font Awesome)
9. Dark Layout과 Frameless Window(Dark Layout & Frameless Window)
10. 빌드 및 배포, 자동 업데이트(Build, Auto Updater)
이번 포스트에서는 Bootstrap을 설치 및 Awesome Font를 설치하여 화면 UI를 개선하는 방법을 설명합니다.
Boostrap Vue 설치 및 설정하기
Bootstrap Vue 설치
# With npm
npm install vue bootstrap-vue bootstrap
# With yarn
yarn add vue bootstrap-vue bootstrap
Bootstrap Vue 설정하기
/src/renderer/main.js 에 Bootstrap Vue 초기화 코드를 삽입합니다. 본 예시에서는 css 대신에 scss를 사용하기 위해 css 는 주석처리하였습니다.
// Bootstrap Vue
import { BootstrapVue, BootstrapVueIcons } from 'bootstrap-vue'
// import 'bootstrap/dist/css/bootstrap.css'
// import 'bootstrap-vue/dist/bootstrap-vue.css'
Vue.use(BootstrapVue)
Vue.use(BootstrapVueIcons)
import '@babel/polyfill'
/sec/renderer/App.vue 에 scss를 정의합니다.
<style lang="scss">
@import 'node_modules/bootstrap/scss/bootstrap';
@import 'node_modules/bootstrap-vue/src/index.scss';
</style>
.electron-vue/webpack.renderer.config.js 파일에 whiteListedModules에 bootstrap-vue를 추가합니다.
// let whiteListedModules = ['vue']
let whiteListedModules = ['vue', 'bootstrap-vue']
polyfill 설치 및 설정하기
오래된 브라우저의 호환성을 위하여 polyfill을 설치합니다.
npm install @babel/polyfill
/src/renderer/main.js에 polyfill 설정을 추가합니다.
import '@babel/polyfill'
화면 코드 수정하기
메인 UI 코드
bootstrap vue의 b-card를 사용하여 적절하게 화면을 구성합니다.
<template>
<b-card no-body class="h-100 main-wrapper" @click="clearActiveItem">
<b-card-body>
<div class="row h-100">
<div class="col-md-6">
<b-card no-body class="h-100">
<b-card-header>TCP Router Connections</b-card-header>
<b-card-body>
<div class="items overflow-y">
<div class="item" v-for="(item, key) in tcpRouterState"
:key="key"
:class="{'selected': activeItem[key]}"
@click.prevent.stop="setActiveItem(key)"
>
<div class="name">{{ item.listen }}, {{item.host}}:{{item.port}}</div>
<div class="value">Connections : {{ item.count }}</div>
<div class="value">Read : {{ item.bytes.clientReadBytes }} bytes</div>
<div class="value">Write : {{ item.bytes.clientWriteBytes }} bytes</div>
</div>
</div>
</b-card-body>
</b-card>
</div>
<div class="col-md-6" v-if="selectedItem && selectedState">
<b-card no-body class="h-100">
<b-card-header>Connection Details</b-card-header>
<b-card-body>
<div class="details overflow-y">
<div class="name">{{ selectedState.listen }}, {{selectedState.host}}:{{selectedState.port}}</div>
<div class="value">Connections : {{ selectedState.count }}</div>
<div class="value">Client Read : {{ selectedState.bytes.clientReadBytes }} bytes</div>
<div class="value">Client Write : {{ selectedState.bytes.clientWriteBytes }} bytes</div>
<div class="value">Server Read : {{ selectedState.bytes.serverReadBytes }} bytes</div>
<div class="value">Server Write : {{ selectedState.bytes.serverWriteBytes }} bytes</div>
<div class="value" v-for="(detail, index) in selectedItem" :key="index">
<div class="name">{{ detail.address }} : {{ detail.port }}</div>
<div class="value">Client Read : {{ detail.clientReadBytes }} bytes / Client Write : {{ detail.clientWriteBytes }} bytes</div>
<div class="value">Server Read : {{ detail.serverReadBytes }} bytes / Server Write : {{ detail.serverWriteBytes }} bytes</div>
</div>
</div>
</b-card-body>
</b-card>
</div>
<div class="col-md-6" v-show="!selectedItem">
<b-card no-body class="h-100">
<b-card-header>Event Log</b-card-header>
<b-card-body>
<div class="logs overflow-y" ref="logs">
<div class="log" v-for="(log, index) in logs" :key="index">
<span class="event-date">{{ log.date }}</span> : <span>{{ log.text }}</span>
</div>
</div>
</b-card-body>
</b-card>
</div>
</div>
</b-card-body>
<b-card-footer>
<div class="row">
<div class="col-md-6 text-left">
<button class="alt" @click="handleStartup"><b-icon icon="play" font-scale="1.5"></b-icon> Start</button>
<button @click="handleShutdown"><b-icon icon="stop" font-scale="1.5"></b-icon> Shutdown</button>
</div>
<div class="col-md-6 text-right">
<button class="alt" @click="handleClearLog"><b-icon icon="trash" font-scale="1.5"></b-icon> Clear Log</button>
<button @click="handlePreferences"><b-icon icon="gear" font-scale="1.5"></b-icon> Preferences</button>
</div>
</div>
</b-card-footer>
</b-card>
</template>
<script>
......(중간생략)......
</script>
......(이하생략)......
메인 화면 보기
환경설정 UI 코드
bootstrap vue의 b-card를 사용하여 적절하게 화면을 구성합니다.
<template>
<b-card no-body class="h-100 prefs-wrapper">
<b-tabs card>
<b-tab active>
<template v-slot:title>
<b-icon icon='play' font-scale='1.5'></b-icon> Common
</template>
<b-card-body>
<b-form-group label="Startup Options:">
<b-form-checkbox name="autostartup" id="autostartup" v-model="preferences.common.autostartup">Auto start program when operating system starts</b-form-checkbox>
<b-form-checkbox name="autoservice" id="autoservice" v-model="preferences.common.autoservice">Service auto start at program start</b-form-checkbox>
<b-form-checkbox name="minimizestart" id="minimizestart" v-model="preferences.common.minimizestart">Start with minimize at program start</b-form-checkbox>
</b-form-group>
<b-form-group label="Window minimize & close:">
<b-form-checkbox name="minimizeToTray" id="minimizeToTray" v-model="preferences.common.minimizeToTray">Hide in system tray when minimizing window</b-form-checkbox>
<b-form-checkbox name="closeToTray" id="closeToTray" v-model="preferences.common.closeToTray">Hide in system tray when closing window</b-form-checkbox>
</b-form-group>
</b-card-body>
</b-tab>
<b-tab title="TCP Router">
<template v-slot:title>
<b-icon icon='cloud' font-scale='1.5'></b-icon> TCP Router
</template>
<b-card-group>
<b-card no-body style="max-width: 250px;">
<b-card-text class="items overflow-y">
<div class="item" v-for="(item, index) in preferences.routers"
:key="index"
:class="{'selected': selectedIndex == index}"
@click.prevent.stop="setselectedIndex(index)"
>
<div class="name">{{ item.listen }}, {{item.host}}:{{item.port}}</div>
</div>
</b-card-text>
</b-card>
<b-card no-body>
<b-card-body>
<b-row class="my-1">
<b-col sm="3">Listen</b-col>
<b-col sm="9"><b-form-input type="number" name="listen" v-model="selectedItem.listen" /></b-col>
</b-row>
<b-row class="my-1">
<b-col sm="3">Host</b-col>
<b-col sm="9"><b-form-input type="text" name="host" v-model="selectedItem.host" /></b-col>
</b-row>
<b-row class="my-1">
<b-col sm="3">Port</b-col>
<b-col sm="9"><b-form-input type="number" name="port" v-model="selectedItem.port" /></b-col>
</b-row>
</b-card-body>
<b-card-footer class="text-center">
<b-row>
<b-col sm="5" class="text-left">
<button class="sml alt" @click="handleNew">New Router</button>
</b-col>
<b-col sm="7" class="text-right">
<button class="sml alt" @click="handleAdd" v-if="selectedIndex >= 0">Modify</button>
<button class="sml alt" @click="handleAdd" v-else>Add</button>
<button class="sml alt" @click="handleDelete" v-if="selectedIndex >= 0">Delete</button>
<button class="sml" @click="handleReset">Reset</button>
</b-col>
</b-row>
</b-card-footer>
</b-card>
</b-card-group>
</b-tab>
</b-tabs>
<b-card-footer class="text-right">
<button @click="handleSave"><b-icon icon="check-circle" font-scale="1.5"></b-icon> Save</button>
<button class="alt" @click="handleClose"><b-icon icon="x" font-scale="1.5"></b-icon> Close</button>
</b-card-footer>
</b-card>
</template>
<script>
......(중간생략)......
</script>
......(이하생략)......
메인 화면 보기
Font Awesome 설치하기
아래의 명령을 수행하여 Font Awesome 모듈을 설치합니다.
npm i --save @fortawesome/fontawesome-svg-core
npm i --save @fortawesome/free-regular-svg-icons
npm i --save @fortawesome/free-solid-svg-icons
npm i --save @fortawesome/free-brands-svg-icons
npm i --save @fortawesome/vue-fontawesome
/src/rederer/main.js 에 Font Awesome 초기화코드를 추가합니다.
import { library } from '@fortawesome/fontawesome-svg-core'
import { far } from '@fortawesome/free-regular-svg-icons'
import { fas } from '@fortawesome/free-solid-svg-icons'
import { fab } from '@fortawesome/free-brands-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
library.add(far)
library.add(fas)
library.add(fab)
Vue.component('font-awesome-icon', FontAwesomeIcon)
화면 코드 수정하기
메인 UI 코드메인 화면 보기
<template>
<b-card no-body class="h-100 main-wrapper" @click="clearActiveItem">
<b-card-body>
<div class="row h-100">
<div class="col-md-6">
<b-card no-body class="h-100">
<b-card-header>TCP Router Connections</b-card-header>
<b-card-body>
<div class="items overflow-y">
<div class="item" v-for="(item, key) in tcpRouterState"
:key="key"
:class="{'selected': activeItem[key]}"
@click.prevent.stop="setActiveItem(key)"
>
<div class="name"><font-awesome-icon :icon="['fas', 'bezier-curve']" /> {{ item.listen }}, <font-awesome-icon :icon="['fas', 'cloud']" /> {{item.host}}:{{item.port}}</div>
<div class="value"><font-awesome-icon :icon="['fas', 'link']" /> Connections : {{ item.count }}</div>
<div class="value"><font-awesome-icon :icon="['fas', 'download']" /> Read : {{ item.bytes.clientReadBytes }} bytes</div>
<div class="value"><font-awesome-icon :icon="['fas', 'upload']" /> Write : {{ item.bytes.clientWriteBytes }} bytes</div>
</div>
</div>
</b-card-body>
</b-card>
</div>
<div class="col-md-6" v-if="selectedItem && selectedState">
<b-card no-body class="h-100">
<b-card-header>Connection Details</b-card-header>
<b-card-body>
<div class="details overflow-y">
<div class="name"><font-awesome-icon :icon="['fas', 'bezier-curve']" /> {{ selectedState.listen }}, <font-awesome-icon :icon="['fas', 'cloud']" /> {{selectedState.host}}:{{selectedState.port}}</div>
<div class="value"><font-awesome-icon :icon="['fas', 'link']" /> Connections : {{ selectedState.count }}</div>
<div class="value"><font-awesome-icon :icon="['fas', 'download']" /> Client Read : {{ selectedState.bytes.clientReadBytes }} bytes</div>
<div class="value"><font-awesome-icon :icon="['fas', 'upload']" /> Client Write : {{ selectedState.bytes.clientWriteBytes }} bytes</div>
<div class="value"><font-awesome-icon :icon="['fas', 'cloud-download-alt']" /> Server Read : {{ selectedState.bytes.serverReadBytes }} bytes</div>
<div class="value"><font-awesome-icon :icon="['fas', 'cloud-upload-alt']" /> Server Write : {{ selectedState.bytes.serverWriteBytes }} bytes</div>
<div class="value" v-for="(detail, index) in selectedItem" :key="index">
<div class="name"><font-awesome-icon :icon="['fas', 'link']" /> {{ detail.address }} : {{ detail.port }}</div>
<div class="value"><font-awesome-icon :icon="['fas', 'download']" /> Client Read : {{ detail.clientReadBytes }} bytes / <font-awesome-icon :icon="['fas', 'upload']" /> Client Write : {{ detail.clientWriteBytes }} bytes</div>
<div class="value"><font-awesome-icon :icon="['fas', 'cloud-download-alt']" /> Server Read : {{ detail.serverReadBytes }} bytes / <font-awesome-icon :icon="['fas', 'cloud-upload-alt']" /> Server Write : {{ detail.serverWriteBytes }} bytes</div>
</div>
</div>
</b-card-body>
</b-card>
</div>
<div class="col-md-6" v-show="!selectedItem">
<b-card no-body class="h-100">
<b-card-header>Event Log</b-card-header>
<b-card-body>
<div class="logs overflow-y" ref="logs">
<div class="log" v-for="(log, index) in logs" :key="index">
<span class="event-date">{{ log.date }}</span> : <span>{{ log.text }}</span>
</div>
</div>
</b-card-body>
</b-card>
</div>
</div>
</b-card-body>
<b-card-footer>
<div class="row">
<div class="col-md-5 text-left">
<button class="alt" @click="handleStartup"><b-icon icon="play" font-scale="1.5"></b-icon> Start</button>
<button @click="handleShutdown"><b-icon icon="stop" font-scale="1.5"></b-icon> Shutdown</button>
</div>
<div class="col-md-7 text-right">
<button class="alt" @click="handleFontawesome"><font-awesome-icon :icon="['fab', 'font-awesome']" size="lg" /></button>
<button class="alt" @click="handleClearLog"><b-icon icon="trash" font-scale="1.5"></b-icon> Clear Log</button>
<button @click="handlePreferences"><b-icon icon="gear" font-scale="1.5"></b-icon> Preferences</button>
</div>
</div>
</b-card-footer>
</b-card>
</template>
<script>
......(중간생략)......
export default {
......(중간생략)......
}
</script>
......(이하생략)......
환경설정 UI 코드메인 화면 보기
참고 자료
- Getting Started
https://bootstrap-vue.js.org/docs/ - The Font Awesome Vue.js component
https://fontawesome.com/how-to-use/on-the-web/using-with/vuejs - FortAwesome/vue-fontawesome
https://github.com/FortAwesome/vue-fontawesome - How to use Font Awesome 5 on VueJS Project
https://medium.com/front-end-weekly/how-to-use-fon-awesome-5-on-vuejs-project-ff0f28310821 - The full guide to using Font Awesome icons in Vue.js apps
https://blog.logrocket.com/full-guide-to-using-font-awesome-icons-in-vue-js-apps-5574c74d9b2d/
소스코드
본 포스트 관련 소스코드는 여기에서 다운로드 가능합니다.
'Development > Node.js, Vue.js, Electron.js' 카테고리의 다른 글
[TCP Socket Router #10] Build, Distribute and Auto Updater (2) | 2020.03.05 |
---|---|
[TCP Socket Router #09] Dark Layout & Frameless Window (0) | 2020.03.04 |
[TCP Socket Router #07] System Tray & Minimize to Tray (0) | 2020.02.28 |
[TCP Socket Router #06] Application Menu & Context Menu (0) | 2020.02.27 |
[TCP Socket Router #05] Preferences Window (0) | 2020.02.27 |