Как создать GUI для исполняемого exe файла CLI версии?
Имеется пакет gifski, у него есть CLI версия для windows, необходимо сделать простой GUI чтобы не вводить команды в терминал.
Как с помощью того же NodeJS можно собрать приложение на Electron, которое бы обращалось к данному файлу и выполняло команды?
Как правильно сформулировать свой вопрос для поиска в google? Не имею соображений к чему подступиться чтобы вопрос задать
Ответы (1 шт):
Исходники демонстрационного приложения https://github.com/norbornen/electron-terminal-command-wrapper
Создать electron-приложение, которое запускает консольную утилиту, довольно просто!
- Берёшь любой скелет electron-приложения или делаешь его сам:
package.json
{
"name": "electron-terminal-command-wrapper",
"version": "1.0.0",
"main": "src/main/index.js",
"license": "MIT",
"keywords": [
"electron",
"command line",
"wrapper"
],
"scripts": {
"rebuild": "electron-rebuild",
"start": "electron . --enable-logging",
"dev": "NODE_ENV=development npm run start",
"build": "echo 'dummy script'",
"electron-build": "yarn build && electron-builder",
"build:app": "yarn electron-build --dir",
"build:all": "yarn electron-build build -mwl",
"build:mac": "yarn electron-build build --mac",
"build:linux": "yarn electron-build build --linux",
"build:windows": "yarn electron-build build --windows --x64"
},
"dependencies": {
"electron-is-dev": "^1.2.0",
"execa": "^5.0.0"
},
"devDependencies": {
"@types/electron": "^1.6.10",
"@types/electron-is-dev": "^1.1.1",
"@types/node": "^14.14.19",
"electron": "^11.1.1",
"electron-builder": "^22.9.1",
"electron-rebuild": "^2.3.4",
"eslint": "^7.17.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.22.1"
},
"build": {
"appId": "org.dev.electron-terminal-command-wrapper",
"productName": "etcmdwrapper",
"artifactName": "${productName}.${ext}",
"directories": {
"output": "release"
},
"files": [
"src/**/*"
],
"win": {
"target": [
"portable"
]
},
"linux": {
"target": [
"deb",
"AppImage"
],
"category": "Development"
}
}
}
- Пишешь
main-часть, пуcкач приложения:
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
const isDev = require('electron-is-dev');
/** @type {BrowserWindow} */
let win;
function createMainWindow() {
win = new BrowserWindow({
width: 600,
height: 600,
webPreferences: {
nodeIntegration: true,
}
});
const filepath = path.resolve(__dirname, '../render/index.html');
win.loadURL(`file://${filepath}`);
if (isDev) {
win.webContents.openDevTools();
}
win.once('closed', () => {
win = null;
});
}
app.on('ready', () => {
createMainWindow();
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
- Пишешь
render-компоненту, которая поipcумеет общаться сmain-ом:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Terminal Command Run Electron Wrapper</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="text/javascript">
const { ipcRenderer } = require('electron');
const CHANNEL_NAME = 'channelCmd';
ipcRenderer.on(CHANNEL_NAME, (event, data) => {
const el = document.getElementById('console');
el.innerText = data;
});
function runIt(ev) {
ev.preventDefault();
const formEl = document.getElementById('exampleForm');
const formData = new FormData(formEl);
const command = (formData.get('command_command') || '').trim();
const options = (formData.get('command_options') || '').trim();
const argument = (formData.get('command_argument') || '').trim();
if (/\S/.test(command)) {
const data = { command };
if (/\S/.test(options)) {
data.options = options;
}
if (/\S/.test(argument)) {
data.argument = argument;
}
ipcRenderer.send(CHANNEL_NAME, data);
}
}
</script>
</head>
<body style="background: white">
<h2>Terminal Command Run Electron Wrapper</h2>
<div>
<form id="exampleForm" onsubmit="return false">
<div style="margin: 16px 0;"><input type="text" name="command_command" placeholder="command" style="padding: 5px;"></div>
<div style="margin: 16px 0;"><input type="text" name="command_options" placeholder="command options" style="padding: 5px;"></div>
<div style="margin: 16px 0;"><input type="text" name="command_argument" placeholder="command arguments" style="padding: 5px;"></div>
<div style="margin: 16px 0;">
<button onclick="runIt(event)">Run command</button>
<button type="reset">Reset</button>
</div>
</form>
</div>
<hr>
<pre id="console" style="padding: 10px; background-color: #eeeeee;"></pre>
</body>
</html>
- Теперь - самая главная часть, запуск команды.
Так как я не знаю ничего про gifski, я сделал пускач любой терминальной команды (для linux/macos):
const execa = require('execa');
...
app.on('ready', () => {
createMainWindow();
initIpc();
});
...
function initIpc() {
ipcMain.on('channelCmd', async (event, args) => {
const { command, options, argument } = typeof args === 'string' ? JSON.parse(args) : args;
try {
const { stdout, stderr } = await execa(command, [options, argument].filter((x) => typeof x === 'string' && /\S/.test(x)));
console.log(stdout);
console.log(stderr);
event.reply('channelCmd', stdout || stderr);
} catch (err) {
console.log(err);
event.reply('channelCmd', err.stack || err.message);
}
});
}
Можно было пойти через child_process.spawn или child_process.exec, но я взял пакет execa всё того же Sindre Sorhus (автора gifski (но не алгоритма конвертации)), чтобы упростить себе жизнь и не писать простыни обработчиков.
Тебе вообще нужно будет просто узнать как туда поставить gifski и подсунуть options и arguments для запуска.
