P.S. Каждая часть — это часть, сама по себе смысла не имеет, чтобы обзавестись необходимым контекстом и не испытывать когнитивный диссонанс от отсутствия так необходимых блоков текста начните читать с 1 части
Setting — Electron компонент, элемент настройки приложения.
Структура папок.
index.js — Файл, в котором создается Electron компонент.
Cодержимое файла index.js.
index.html — HTML страница окна.
Cодержимое файла index.html.
creater-option.js — Создает option для всех select на странице.
Cодержимое файла creater-option.js.
style.css — Стили для страницы.
Cодержимое файла style.css.
Application
Использование в приложении: /app/Setting.
API
Интерфейс компонента Setting.
Test
Версия для тестирования: /app_test/Setting.
9 часть — Callback компонент
Setting — Electron компонент, элемент настройки приложения.
Структура папок.
context
¦
¦ index.js
¦
L---client // все что относится к клиенту
¦ creater-option.js
¦ index.html
¦ style.css
¦
L---fonts
font1.woff2
font2.woff2
font3.woff2
font4.woff2
font5.woff2
font6.woff2
font7.woff2
index.js — Файл, в котором создается Electron компонент.
Cодержимое файла index.js.
Код
const { BrowserWindow, ipcMain } = require('electron')
module.exports = class Notification {
constructor(parent) {
this.root = new BrowserWindow({
frame: false, // убираем рамку
transparent: true, // устанавливаем прозрачность
resizable: false, // запрещаем масштабирование
show: false, // запрещаем показывать окно после загрузки
height: 520,
width: 350,
center: true,
parent
})
// загружаем страницу
this.root.loadURL(`${__dirname}/client/index.html`)
// Обработчик сигнала SETTING_CLOSE (Скрывает окна)
ipcMain.on('SETTING_CLOSE', e => {
this.root.hide()
e.returnValue = 'ok'
})
}
onSave(cb) {
// Обработчик сигнала SETTING_SAVE сохраняет и скрывает окно
ipcMain.on('SETTING_SAVE', e => {
cb()
this.root.hide()
e.returnValue = 'ok'
})
}
ready() {
// Ожидаем пока окно полностью инициализируется
return new Promise(res => {
this.root.once('ready-to-show', res)
})
}
show() {
// Показываем окно
this.root.show()
}
hide() {
// Скрываем окно
this.root.hide()
}
get() {
// Запрашиваем настройки
return new Promise(resolve => {
this.root.webContents.send('SETTING_GET')
ipcMain.once('SETTING_DATA', (e, data) => {
resolve(JSON.parse(data))
})
})
}
}
index.html — HTML страница окна.
Cодержимое файла index.html.
Код
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<link rel='stylesheet' type='text/css' href='style.css'>
</head>
<body>
<div class='body'>
<div class='settings'>
<div class='wrapper'>
<div>Количество сессий (от)</div>
<select name='NumVpnSessions'>
<option value='-'>---</option>
</select>
</div>
<div class='wrapper'>
<div>Ping (до)</div>
<select name='Ping'>
<option value='-'>---</option>
</select>
</div>
<div class='wrapper'>
<div>Всего подключений (от)</div>
<select name='TotalUsers'>
<option value='-'>---</option>
</select>
</div>
<div class='wrapper'>
<div>Страна</div>
<select name='CountryLong'>
<option value='-'>---</option>
</select>
</div>
<div class='wrapper'>
<div>Время работы (от)</div>
<select name='Uptime'>
<option value='-'>---</option>
</select>
</div>
<div class='wrapper'>
<div>Скорость (от)</div>
<select name='Speed'>
<option value='-'>---</option>
</select>
</div>
<div class='wrapper'>
<div>Общий трафик (от)</div>
<select name='TotalTraffic'>
<option value='-'>---</option>
</select>
</div>
<div class='wrapper'>
<div>Качество соединения (от)</div>
<select name='Score'>
<option value='-'>---</option>
</select>
</div>
<div class='wrapper'>
<div>Расположен. увед.</div>
<select name='PositionNotify'>
<option value='BR'>Низ право</option>
<option value='TR'>Верх право</option>
<option value='BL'>Низ лево</option>
<option value='TL'>Верх лево</option>
</select>
</div>
<label for='auto-reconnect'>
<div class='wrapper'>
<div>Авто-переподключение к VPN</div>
<input type="checkbox" name='AutoReconnect' id='auto-reconnect' hidden>
<div class='new_check'></div>
</div>
</label>
<label for='auto-update'>
<div class='wrapper'>
<div>Авто-обновление серверов</div>
<input type="checkbox" name='AutoUpdate' id='auto-update' hidden>
<div class='new_check'></div>
</div>
</label>
<label for='permutation'>
<div class='wrapper'>
<div>Показывать по центру (при запуске)</div>
<input type="checkbox" name='Permutation' id='permutation' hidden>
<div class='new_check'></div>
</div>
</label>
<label for='start-hidden'>
<div class='wrapper'>
<div>Запускать свернутым</div>
<input type="checkbox" name='StartHidden' id='start-hidden' hidden>
<div class='new_check'></div>
</div>
</label>
</div>
<div class='btn-nav'>
<div class='btn save'>Сохранить</div>
<div class='btn close'>Закрыть</div>
</div>
</div>
<script>
const save = document.getElementsByClassName('save')[0]
, close = document.getElementsByClassName('close')[0]
, NumVpnSessions = document.getElementsByName('NumVpnSessions')[0]
, Ping = document.getElementsByName('Ping')[0]
, TotalUsers = document.getElementsByName('TotalUsers')[0]
, CountryLong = document.getElementsByName('CountryLong')[0]
, Uptime = document.getElementsByName('Uptime')[0]
, Speed = document.getElementsByName('Speed')[0]
, TotalTraffic = document.getElementsByName('TotalTraffic')[0]
, Score = document.getElementsByName('Score')[0]
, AutoReconnect = document.getElementsByName('AutoReconnect')[0]
, Permutation = document.getElementsByName('Permutation')[0]
, AutoUpdate = document.getElementsByName('AutoUpdate')[0]
, StartHidden = document.getElementsByName('StartHidden')[0]
, PositionNotify = document.getElementsByName('PositionNotify')[0]
, { ipcRenderer, shell } = require('electron')
// преобразуем настройки в строку JSON
const getSetting = () => JSON.stringify({
Ping: Ping.value || '-',
NumVpnSessions: NumVpnSessions.value || '-',
TotalUsers: TotalUsers.value || '-',
CountryLong: CountryLong.value || '-',
Uptime: Uptime.value || '-',
Speed: Speed.value || '-',
TotalTraffic: TotalTraffic.value || '-',
Score: Score.value || '-',
AutoReconnect: AutoReconnect.checked || false,
AutoUpdate: AutoUpdate.checked || false,
Permutation: Permutation.checked || false,
StartHidden: StartHidden.checked || false,
PositionNotify: PositionNotify.value || 'BR'
})
// сохранение настроек
save.addEventListener('click', () => {
// сохраняем в настройки в localStorage
localStorage.setItem('vpn_setting', getSetting())
// отсылам сигнал SETTING_SAVE в компонент
ipcRenderer.sendSync('SETTING_SAVE')
})
// закрытие окна
close.addEventListener('click', () => {
// отсылам сигнал SETTING_CLOSE в компонент
ipcRenderer.sendSync('SETTING_CLOSE')
})
</script>
<script src='creater-option.js'></script>
<script>
// устанавливаем всем полям значения из настроен
const vpn_setting = JSON.parse(localStorage.vpn_setting || "{}")
Ping.value = vpn_setting.Ping || '-'
NumVpnSessions.value = vpn_setting.NumVpnSessions || '-'
TotalUsers.value = vpn_setting.TotalUsers || '-'
CountryLong.value = vpn_setting.CountryLong || '-'
Uptime.value = vpn_setting.Uptime || '-'
Speed.value = vpn_setting.Speed || '-'
TotalTraffic.value = vpn_setting.TotalTraffic || '-'
Score.value = vpn_setting.Score || '-'
AutoReconnect.checked = vpn_setting.AutoReconnect || false
AutoUpdate.checked = vpn_setting.AutoUpdate || false
Permutation.checked = vpn_setting.Permutation || false
StartHidden.checked = vpn_setting.StartHidden || false
PositionNotify.value = vpn_setting.PositionNotify || 'BR'
// обработчик сигналов от компонента
ipcRenderer.on('SETTING_GET', (e, data) => {
// отсылам сигнал SETTING_DATA в компонент и с ним настройки
ipcRenderer.send('SETTING_DATA', getSetting())
})
</script>
</body>
</html>
creater-option.js — Создает option для всех select на странице.
Cодержимое файла creater-option.js.
Код
// Активных сессий
NumVpnSessions.innerHTML += Array(100).fill(1).map((e, i) => {
if (i < 10) {
return `<option value='${i+1}'>${i+1}</option>`
}
return `<option value='${parseInt(i*1.5)}'>${parseInt(i*1.5)}</option>`
}).join('')
// Пинг
Ping.innerHTML += Array(150).fill(1).map((e, i) => {
if (i < 10) {
return `<option value='${i+1}'>${i+1}ms</option>`
}
return `<option value='${parseInt(i*1.5)}'>${parseInt(i*1.5)}ms</option>`
}).join('')
// Общее количество сессий
TotalUsers.innerHTML += Array(150).fill(1).map((e, i) => `<option value='${parseInt((i+1)*(i+1))}'>${parseInt((i+1)*(i+1))}</option>`).join('')
// Страна
const Contrys = ['Japan', 'Korea Republic of', 'United States', 'Thailand', 'Germany',
'New Zealand', 'Argentina', 'Poland', 'United Kingdom', 'Hong Kong',
'Viet Nam', 'Russian Federation', 'France', 'China', 'Singapore',
'Trinidad and Tobago', 'Moldova Republic of', 'Canada', 'Romania',
'Algeria', 'Venezuela', 'Indonesia', 'Brazil', 'Mexico', 'Cyprus',
'Iceland', 'Bangladesh', 'Morocco', 'Iraq', 'Ukraine', 'Iran',
'Turkey', 'Angola', 'El Salvador', 'Chile', 'Egypt', 'Spain',
'Netherlands', 'Colombia'
]
CountryLong.innerHTML += Contrys.map(e => `<option value='${e}'>${e}</option>`).join('')
// Время работы
Uptime.innerHTML += Array(79).fill(1).map((e, i) => {
if (i < 15) {
return `<option value='${(i+1)*60*1000}'>${i+1} мин.</option>`
}
if (i < 18) {
return `<option value='${(i-13)*15*60*1000}'>${(i-13)*15} мин.</option>`
}
if (i < 40) {
return `<option value='${(i-16)*60*60*1000}'>${(i-16)} час.</option>`
}
if (i < 69) {
return `<option value='${(i-38)*24*60*60*1000}'>${(i-38)} ден.</option>`
}
if (i < 79) {
return `<option value='${(i-67)*30*24*60*60*1000}'>${(i-67)} мес.</option>`
}
}).join('')
// Скорость
Speed.innerHTML += Array(100).fill(1).map((e, i) => {
if (i < 5) {
return `<option value='${parseInt(1048576*((i+1)/100))}'>${(i+1)/100} Mbit</option>`
}
if (i < 9) {
return `<option value='${parseInt(1048576*((i+1)/10))}'>${(i+1)/10} Mbit</option>`
}
if (i < 19) {
return `<option value='${parseInt(1048576*(i-8))}'>${(i-8)} Mbit</option>`
}
return `<option value='${parseInt(1048576*((i-18)*i))}'>${(i-18)*i} Mbit</option>`
}).join('')
// Общий трафик
TotalTraffic.innerHTML += Array(150).fill(1).map((e, i) => {
if (i < 10) {
return `<option value='${parseInt(1048576*(i+1))}'>${i+1} Mbit</option>`
}
if (i < 20) {
return `<option value='${parseInt(1048576*((i+1)*10))}'>${(i+1)*10} Mbit</option>`
}
if (i < 50) {
return `<option value='${parseInt(1048576*((i+1)*100))}'>${(i+1)*100} Mbit</option>`
}
if (i < 60) {
return `<option value='${parseInt(1073741824*((i-45)*1))}'>${(i-45)*1} Gbit</option>`
}
if (i < 70) {
return `<option value='${parseInt(1073741824*((i-45)*10))}'>${(i-45)*10} Gbit</option>`
}
if (i < 150) {
return `<option value='${parseInt(1073741824*((i-45)*100))}'>${(i-45)*100} Gbit</option>`
}
}).join('')
// Качество соединения
Score.innerHTML += Array(100).fill(1).map((e, i) => `<option value='${(i+25)*(i+25)*(i+25)}'>${i+1}%</option>`).reverse().join('')
style.css — Стили для страницы.
Cодержимое файла style.css.
Код
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(fonts/font1.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(fonts/font2.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(fonts/font3.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(fonts/font4.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(fonts/font5.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(fonts/font6.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(fonts/font7.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
* {
font-family: 'Source Sans Pro', sans-serif;
padding: 0;
margin: 0;
overflow: hidden;
background: rgba(0, 0, 0, 0);
color: #eee;
}
.body {
font-size: 14px;
width: 345px;
border-radius: 10px;
background: rgba(36, 39, 39, 0.9);
padding: 20px 0px 20px 0px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
height: 480px;
}
.settings {
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: column;
}
.wrapper {
display: flex;
align-items: center;
justify-content: space-between;
width: 300px
}
select {
outline: none;
border: none;
background: rgba(51, 55, 55, 0.9);
border-radius: 6px;
padding: 4px 10px 4px 10px;
cursor: pointer;
}
select:hover {
background: rgba(53, 57, 57, 0.9);
}
option {
background: rgba(56, 60, 60, 0.9);
}
.btn-nav {
display: flex;
justify-content: space-around;
align-items: center;
margin-top: 30px;
}
.btn {
cursor: pointer;
margin-top: 10px;
padding: 4px 10px 4px 10px;
border-radius: 6px;
user-select: none;
margin: 0px 10px 0px 10px;
}
.btn:hover {
color: #ccc
}
.button {
margin-top: 10px;
}
.new_check {
cursor: pointer;
width: 20px;
height: 20px;
border-radius: 100%;
margin-right: 30px;
background: rgba(51, 55, 55, 0.9);
}
#auto-reconnect:checked + .new_check {
background: radial-gradient(rgba(244, 244, 244, 0.9) 30%, rgba(51, 55, 55, 0.9) 29%);
}
#auto-update:checked + .new_check {
background: radial-gradient(rgba(244, 244, 244, 0.9) 30%, rgba(51, 55, 55, 0.9) 29%);
}
#permutation:checked + .new_check {
background: radial-gradient(rgba(244, 244, 244, 0.9) 30%, rgba(51, 55, 55, 0.9) 29%);
}
#start-hidden:checked + .new_check {
background: radial-gradient(rgba(244, 244, 244, 0.9) 30%, rgba(51, 55, 55, 0.9) 29%);
}
Application
Использование в приложении: /app/Setting.
API
Интерфейс компонента Setting.
const { app } = require('electron')
, SETTING = require('./../../app/components/setting')
app.on('ready', async() => {
const Setting = new SETTING()
// Только после того как окно инициализируются программа продолжит исполнятся
await Setting.ready()
// Показывает окно
Setting.show()
// Обработчик сохранения
Setting.onSave(async () => {
// Запрашиваем настройки
const vpn_setting = await Setting.get()
console.log(vpn_setting)
})
})
Test
Версия для тестирования: /app_test/Setting.
9 часть — Callback компонент
Навигация
1 часть — Вводная
2 часть — Разработка
3 часть — OpenVPN компонент
4 часть — Configs компонент
5 часть — Vpn компонент
6 часть — Notify компонент
7 часть — Context компонент
8 часть — Setting компонент
9 часть — Callback компонент
10 часть — Объединение всех компонентов
11 часть — Сборка приложения под Windows
2 часть — Разработка
3 часть — OpenVPN компонент
4 часть — Configs компонент
5 часть — Vpn компонент
6 часть — Notify компонент
7 часть — Context компонент
8 часть — Setting компонент
9 часть — Callback компонент
10 часть — Объединение всех компонентов
11 часть — Сборка приложения под Windows
Sabubu
Вопрос немного не по теме, но стоит ли добавлять свои шрифты и утяжелять приложение? Стандартный шрифт не годится?
trickst_r
Само приложение будет весить около 50мб, так как нужно тащить Chromium, так что шрифт погоды не сделает)