first commit

This commit is contained in:
Xen 2020-05-04 22:00:55 +08:00
commit 1b5bb54dce
24 changed files with 6794 additions and 0 deletions

17
.eslintrc Normal file
View File

@ -0,0 +1,17 @@
{
"env": {
"browser": true,
"commonjs": true,
"es6": true
},
"extends": "eslint:recommended",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
}
}

89
.gitignore vendored Normal file
View File

@ -0,0 +1,89 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
.DS_Store
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# Webpack
.webpack/
# Electron-Forge
out/

22
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,22 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Debug Main Process",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
},
"args": [
"."
],
"outputCapture": "std"
}
]
}

23
LICENSE Normal file
View File

@ -0,0 +1,23 @@
MIT License
Copyright (c) 2019 - present Xen Kuo
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

80
README.md Normal file
View File

@ -0,0 +1,80 @@
# comNG -- 串口助手
`comNG` 是一款具有现代化 UI 设计并且功能强大的串口助手软件。
## 介绍
comNG 区别于其他串口助手的地方在于其强大的 “现场数据分析“ 能力。简单来说就是 comNG 提供的多种功能以帮助用户更方便的分析打印输出文本。这些功能包括:
- 内建的 comNGLang 高亮语法
- 内建的手动文本高亮功能(类似于 notepad++的 Style Token
- 搜索文本高亮 (类似于 vscode 的搜索文本高亮)
- 选择文本高亮 (类似于 vscode 的选择文本高亮)
- 基于文本内容的中断功能,全新的功能
- 日志文档的签名(时间和姓名)
- 跨平台Windows Mac OS 以及 Ubuntu 等 Linux 系统
另外还包含一些串口助手通用的功能:
- Modem 信号指示和控制
- 自定义波特率
- 十六进制接收
- 接收时间戳
- 发送文本
- 流控
- 文件保存和打开,支持拖动
当然还有一些不支持的功能,比如:
- 十六进制发送
- 文件发送
- 抓取至文件
## 用户界面
![image](/image/preview.jpg)
## 使用方法
下载对应系统的安装文件,安装,然后就应该可以正常使用了。对于 Linux 系统,可能对串口设备文件执行以下命令:
`sudo chmod 666 /dev/ttyS1`
记得把 `ttyS1` 替换为你的串口设备文件。
## 开发
### 克隆代码
```
git clone git@gitee.com:xenkuo/comNG.git
```
### 安装依赖文件
```
cd comNG
yarn
```
Windows 下安装 node 和 electron 比较麻烦,建议使用以下 `.npmrc` 文件配置:
```
registry=https://registry.npm.taobao.org
electron_mirror=https://cdn.npm.taobao.org/dist/electron/
electron_custom_dir=7.1.11
```
Windows 下安装 native 编译工具更麻烦,建议多试试,因为我现在在其他 Windows 上也安装不成功了。。。
### 编译
```
code .
yarn start
yarn make
```
## Licence
comNG is [MIT](https://opensource.org/licenses/MIT) licensed and all it's dependencies are MIT licensed.

BIN
image/advance.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

BIN
image/bar-color-1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
image/bar-color-2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
image/comNGLang.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
image/dmg-background.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
image/highlighter.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
image/logo.icns Executable file

Binary file not shown.

BIN
image/logo.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
image/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
image/preview.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

102
package.json Normal file
View File

@ -0,0 +1,102 @@
{
"name": "comNG",
"productName": "comNG",
"version": "1.0.4",
"description": "My Electron application description",
"repository": "https://github.com/xenkuo/comNG",
"main": "src/main.js",
"scripts": {
"start": "electron-forge start",
"rebuild": "electron-rebuild -f -w serialport",
"package": "electron-forge package",
"make": "electron-forge make",
"publish": "electron-forge publish",
"lint": "echo \"No linting configured\""
},
"keywords": [],
"author": {
"name": "Xen",
"email": "xenkuo@gmail.com"
},
"license": "MIT",
"config": {
"forge": {
"packagerConfig": {
"asar": true,
"ignore": [
"node_modules/monaco-editor/(dev|esm|min-maps|monaco.d.ts)",
"node_modules/materialize-css/(extras|js|sass)",
"node_modules/material-design-icons/(?!iconfont)"
],
"icon": "image/logo"
},
"makers": [
{
"name": "@electron-forge/maker-squirrel",
"config": {
"authors": "Xen",
"setupIcon": "image/logo.ico"
}
},
{
"name": "@electron-forge/maker-dmg",
"config": {
"icon": "./image/logo.icns",
"background": "./image/dmg-background.png",
"format": "ULFO"
}
},
{
"name": "@electron-forge/maker-deb",
"config": {
"options": {
"maintainer": "Xen",
"icon": "./image/logo.png",
"mimeType": [
"cnl",
"text"
],
"section": "utils",
"categories": [
"Utilify"
],
"homepage": "https://xenkuo.github.io",
"description": "comNG is a serialport tool from next generation!"
}
}
}
],
"publishers": [
{
"name": "@electron-forge/publisher-github",
"config": {
"repository": {
"owner": "xenkuo",
"name": "comNG"
},
"draft": true
}
}
]
}
},
"dependencies": {
"electron-localshortcut": "^3.2.1",
"electron-squirrel-startup": "^1.0.0",
"electron-store": "^5.1.0",
"material-design-icons": "^3.0.1",
"materialize-css": "^1.0.0-rc.2",
"monaco-editor": "^0.20.0",
"serialport": "^8.0.7"
},
"devDependencies": {
"@electron-forge/cli": "6.0.0-beta.47",
"@electron-forge/maker-deb": "6.0.0-beta.47",
"@electron-forge/maker-dmg": "^6.0.0-beta.49",
"@electron-forge/maker-squirrel": "6.0.0-beta.47",
"@electron-forge/publisher-github": "^6.0.0-beta.50",
"electron": "7.1.11",
"electron-rebuild": "^1.10.0",
"eslint": "^6.8.0"
}
}

548
src/editor.js Normal file
View File

@ -0,0 +1,548 @@
/* eslint-disable no-unused-vars */
/* eslint-disable no-undef */
const fs = require("fs");
const path = require("path");
const amdLoader = require("../node_modules/monaco-editor/min/vs/loader.js");
const { dialog } = require("electron").remote;
const amdRequire = amdLoader.require;
const hexmodeUnitWidth = 8;
const decoMod = 7;
const decoTable = [
{ style: "hl-red", color: "#ff8a80" },
{ style: "hl-orange", color: "#ffd180" },
{ style: "hl-yellow", color: "#ffff8d" },
{ style: "hl-green", color: "#b9f6ca" },
{ style: "hl-blue", color: "8dd8ff" },
{ style: "hl-indigo", color: "8c9eff" },
{ style: "hl-purple", color: "ea80fc" },
];
var editor;
var hexmodeIndex = 0;
var hexmodeUnitIndex = 0;
var breakpointHit = false;
var breakpointAfterLines = 0;
var breakpointBuff = [];
var half_line = false;
var decoIndex = 0;
function uriFromPath(_path) {
var pathName = path.resolve(_path).replace(/\\/g, "/");
if (pathName.length > 0 && pathName.charAt(0) !== "/") {
pathName = "/" + pathName;
}
return encodeURI("file://" + pathName);
}
function decoGet() {
return decoTable[decoIndex++ % decoMod];
}
function decoApply(m, text) {
let matches = m.findMatches(
text,
false,
false,
true,
"`~!@#$%^&*()-=+[{]}\\|;:'\",.<>/?",
false
);
let decoration = decoGet();
console.log(decoration.style, decoration.color);
for (let i of matches) {
let range = i.range;
m.deltaDecorations(
[],
[
{
range: range,
options: {
className: decoration.style,
overviewRuler: {
color: decoration.color,
position: 4, // position right
},
},
},
]
);
}
}
function decoRemove(m, text) {
let matches = m.findMatches(
text,
false,
false,
true,
"`~!@#$%^&*()-=+[{]}\\|;:'\",.<>/?",
false
);
for (let match of matches) {
let decos = m.getDecorationsInRange(match.range);
// super word remove decoration will cause sub word decoration to 1
for (let deco of decos) {
m.deltaDecorations([deco.id], []);
}
}
}
function highlightToggle() {
console.log("highligh toggle");
let decoExpectedLength = 1;
let m = editor.getModel();
let range = editor.getSelection();
let text = m.getValueInRange(range);
if (text === "") {
let word = m.getWordAtPosition(editor.getPosition());
text = word.word;
range.startColumn = word.startColumn;
range.endColumn = word.endColumn;
decoExpectedLength = 2;
}
if (text === "") return;
let decos = m.getDecorationsInRange(range);
if (decos.length === decoExpectedLength) {
decoApply(m, text);
} else {
decoRemove(m, text);
}
return null;
}
function openFile() {
dialog
.showOpenDialog({
properties: ["openFile"],
filters: [{ name: "comNG log", extensions: ["cnl", "txt"] }],
})
.then((result) => {
if (result.canceled === false) {
const file = result.filePaths[0];
const text = fs.readFileSync(file).toString();
editor.getModel().setValue(text);
}
});
}
function saveToFile() {
let fileName = new Date(+new Date() + 8 * 3600 * 1000);
fileName = "Log-" + fileName.toISOString();
fileName = fileName.replace(/[.|:]/g, "-");
if (config.advance.sign.switch === true && config.advance.sign.name !== "")
fileName += "-" + config.advance.sign.name;
dialog
.showSaveDialog({
properties: ["createDirectory"],
defaultPath: fileName,
filters: [{ name: "comNG Log", extensions: ["cnl"] }],
})
.then((result) => {
if (result.canceled === false) {
const file = result.filePath;
const text = editor.getModel().getValue();
fs.writeFileSync(file, text);
}
});
}
function getTimestamp() {
const t = new Date();
return (
t.toLocaleTimeString().split(" ")[0] +
":" +
t.getMilliseconds().toString().padStart(3, 0) +
" "
);
}
function editorAppend(text) {
const lineCount = editor.getModel().getLineCount();
const lastLineLength = editor.getModel().getLineMaxColumn(lineCount);
const range = new monaco.Range(
lineCount,
lastLineLength,
lineCount,
lastLineLength
);
editor.getModel().applyEdits([
{
forceMoveMarkers: true,
range: range,
text: text.toString(),
},
]);
editor.revealLine(editor.getModel().getLineCount());
}
function buff2hex(buff) {
return Array.prototype.map
.call(new Uint8Array(buff), (x) => ("00" + x.toString(16)).slice(-2))
.join("");
}
function showHex(buff) {
let hexBuff = buff2hex(buff);
while (hexBuff.length !== 0) {
let len = hexmodeUnitWidth - hexmodeIndex;
if (hexBuff.length < len) len = hexBuff.length;
let line = hexBuff.slice(0, len);
hexmodeIndex = (hexmodeIndex + len) % hexmodeUnitWidth;
if (hexmodeIndex === 0) {
hexmodeUnitIndex++;
if (hexmodeUnitIndex === 4) {
hexmodeUnitIndex = 0;
line += "\n";
} else {
line += " ";
}
}
editorAppend(line);
hexBuff = hexBuff.slice(len, hexBuff.length);
}
}
function breakpointProcess(line) {
if (breakpointHit === false) {
let bpLine = line;
if (breakpointBuff.length !== 0) {
bpLine = Buffer.concat(
[breakpointBuff, line],
line.length + breakpointBuff.length
);
breakpointBuff = [];
}
if (bpLine.includes(config.advance.breakpoint.onText) === true) {
breakpointHit = true;
breakpointAfterLines = 0;
}
} else {
breakpointAfterLines++;
if (breakpointAfterLines >= config.advance.breakpoint.afterLines) {
breakpointHit = false;
breakpointAfterLines = 0;
return true;
}
}
return false;
}
function processSerialData(buff) {
if (config.general.hexmode === true) {
showHex(buff);
} else {
let index = -1;
// console.log(buff)
// console.log(buff.toString())
while ((index = buff.indexOf("\n")) !== -1) {
let line = buff.slice(0, index + 1);
// console.log(line)
if (half_line === true) {
editorAppend(line);
half_line = false;
} else {
let timestamp = "";
if (config.general.timestamp === true) timestamp = getTimestamp();
editorAppend(timestamp + line);
}
buff = buff.slice(index + 1, buff.length);
// console.log(buff)
if (config.advance.breakpoint.switch === true) {
if (breakpointProcess(line) === true) {
buff = [];
serialClose();
}
}
}
if (buff.length !== 0) {
if (half_line === false) {
let timestamp = "";
if (config.general.timestamp === true) timestamp = getTimestamp();
editorAppend(timestamp + buff);
half_line = true;
} else {
editorAppend(buff);
}
}
if (config.advance.breakpoint.switch === true) {
breakpointBuff = buff;
}
}
}
amdRequire.config({
// eslint-disable-next-line no-undef
baseUrl: uriFromPath(
path.join(__dirname, "../node_modules/monaco-editor/min")
),
});
// workaround monaco-css not understanding the environment
self.module = undefined;
amdRequire(["vs/editor/editor.main"], function () {
monaco.languages.register({
id: "comNGLang",
});
monaco.languages.setMonarchTokensProvider("comNGLang", {
defaultToken: "",
tokenizer: {
root: [
[/^\[?[f|F][a|A][t|T][a|A][l|L]\]?\s.*/, "fatal"],
[/\s+\[?[f|F][a|A][t|T][a|A][l|L]\]?\s+/, "fatal"],
[/^\[?F\]?\s.*/, "fatal"],
[/\s+\[?F\]?\s+/, "fatal"],
[/^\[?[e|E][r|R][r|R][o|O][r|R]\]?\s.*/, "error"],
[/\s+\[?[e|E][r|R][r|R][o|O][r|R]\]?\s+/, "error"],
[/^\[?E\]?\s.*/, "error"],
[/\s+\[?E\]?\s+/, "error"],
[/^\[?[w|W][a|A][r|R][n|N]\]?\s.*/, "warn"],
[/\s+\[?[w|W][a|A][r|R][n|N]\]?\s+/, "warn"],
[/^\[?W\]?\s.*/, "warn"],
[/\s+\[?W\]?\s+/, "warn"],
[/^\[?[i|I][n|N][f|F][o|O]\]?\s.*/, "info"],
[/\s+\[?[i|I][n|N][f|F][o|O]\]?\s+/, "info"],
[/^\[?I\]?\s.*/, "info"],
[/\s+\[?I\]?\s+/, "info"],
[/^\[?[t|T][r|R][a|A][c|C][e|E]\]?\s.*/, "trace"],
[/\s+\[?[t|T][r|R][a|A][c|C][e|E]\]?\s+/, "trace"],
[/^\[?T\]?\s.*/, "trace"],
[/\s+\[?T\]?\s+/, "trace"],
[/^\[?[d|D][e|E][b|B][u|U][g|G]\]?\s.*/, "debug"],
[/\s+\[?[d|D][e|E][b|B][u|U][g|G]\]?\s+/, "debug"],
[/^\[?D\]?\s.*/, "debug"],
[/\s+\[?D\]?\s+/, "debug"],
[/\[\d;\d{2}m/, "useless"],
[/\[\dm/, "useless"],
[/[{}()[\]]/, "bracket"],
[/^\d{1,2}:\d{2}:\d{2}:\d{1,3}/, "timestamp"],
[/\d{1,4}(-|\/|\.|:)\d{1,2}\1\d{1,4}/, "time"],
[
/(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)(-|\/|\.|:)(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\2(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\2(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)/,
"ip",
],
[
/[0-9a-fA-F]{2}(-|\/|\.|:)[0-9a-fA-F]{2}\1[0-9a-fA-F]{2}\1[0-9a-fA-F]{2}\1[0-9a-fA-F]{2}\1[0-9a-fA-F]{2}/,
"mac",
],
[/\d*\.\d+([eE][-+]?\d+)?/, "number"],
[/0[xX][0-9a-fA-F]+/, "number"],
[/[0-9a-fA-F]{4,}/, "number"],
[/\d+/, "number"],
],
},
});
// Define a new theme that contains only rules that match this language
monaco.editor.defineTheme("comNGTheme", {
base: "vs",
inherit: false,
colors: {
"editor.background": "#fafafa",
"scrollbarSlider.background": "#fafafa",
},
rules: [
{ token: "number", foreground: "2e7d32" },
{ token: "bracket", foreground: "ff9800" },
{ token: "timestamp", foreground: "009688" },
{ token: "time", foreground: "2196f3" },
{ token: "ip", foreground: "03a9f4" },
{ token: "mac", foreground: "00bcd4" },
{ token: "fatal", foreground: "e91e63" },
{ token: "error", foreground: "f44336" },
{ token: "warn", foreground: "ff9800" },
{ token: "info", foreground: "9e9e9e" },
{ token: "trace", foreground: "9e9d24" },
{ token: "debug", foreground: "2e7d32" },
{ token: "useless", foreground: "cecece" },
],
});
editor = monaco.editor.create(document.getElementById("editor-area"), {
theme: "comNGTheme",
language: "comNGLang",
automaticLayout: true,
readOnly: true,
folding: false,
fontFamily: config.general.fontFamily,
fontSize: config.general.fontSize,
overviewRulerBorder: false,
scrollBeyondLastLine: false,
smoothScrolling: true,
mouseWheelZoom: true, // combined with Ctrl
wordWrap: "on",
wordWrapBreakAfterCharacters: "",
wordWrapBreakBeforeCharacters: "",
lineNumbersMinChars: 3,
minimap: {
enabled: false,
},
scrollbar: {
vertical: "auto",
useShadows: false,
verticalScrollbarSize: 10,
},
});
let editorConfig = {
brackets: [
["{", "}"],
["[", "]"],
["(", ")"],
['"', '"'],
["'", "'"],
],
};
monaco.languages.setLanguageConfiguration("comNGLang", editorConfig);
editor.addAction({
id: "highlight-toggle",
label: "Highlight Toggle",
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_E],
precondition: null,
keybindingContext: null,
contextMenuGroupId: "9_cutcopypaste",
contextMenuOrder: 1.5,
run: highlightToggle,
});
// editor.addAction({
// id: "open-file",
// label: "Open File...",
// keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_O],
// precondition: null,
// keybindingContext: null,
// contextMenuGroupId: "9_cutcopypaste",
// contextMenuOrder: 1.5,
// run: dummy,
// });
// editor.addAction({
// id: "save-to-file",
// label: "Save To File...",
// keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S],
// precondition: null,
// keybindingContext: null,
// contextMenuGroupId: "9_cutcopypaste",
// contextMenuOrder: 1.5,
// run: dummy,
// });
editor.addCommand(monaco.KeyMod.CtrlCmd + monaco.KeyCode.KEY_W, () => {
// Do nothing but prevent default action: close window
});
editor.addCommand(monaco.KeyMod.CtrlCmd + monaco.KeyCode.KEY_X, () => {
// Do nothing but prevent default action: close window
});
});
document.getElementById("clear-btn").onclick = () => {
let value = "";
if (config.advance.sign.switch === true) {
value = "Captured at " + new Date().toLocaleString() + " with comNG";
if (config.advance.sign.name !== "")
value += " by " + config.advance.sign.name + ".";
value += "\n";
}
hexmodeIndex = 0;
editor.getModel().setValue(value);
};
document.getElementById("editor-font-family").onblur = (e) => {
let font = e.target.value.trim();
if (font === "")
font =
"Consolas, 'SF Mono', Menlo, 'Lucida Console', 'Courier New', monospace";
editor.updateOptions({ fontFamily: font });
configUpdate("general.fontFamily", font);
};
document.getElementById("editor-font-size").onblur = (e) => {
let size = e.target.value.trim();
if (size === "") size = 12;
editor.updateOptions({ fontSize: size });
configUpdate("general.fontSize", size);
};
document.getElementById("breakpoint-switch").onclick = (e) => {
if (e.target.checked === true) {
if (config.advance.breakpoint.onText.length === 0) {
toast("Error: Breakpoint on-text cant be empty");
e.target.checked = false;
return;
}
}
configUpdate("advance.breakpoint.switch", e.target.checked);
breakpointHit = false;
breakpointAfterLines = 0;
};
document.getElementById("breakpoint-on-text").onblur = (e) => {
configUpdate("advance.breakpoint.onText", e.target.value);
};
document.getElementById("breakpoint-after-lines").onblur = (e) => {
let lines = parseInt(e.target.value);
if (isNaN(lines) === true) lines = 5;
configUpdate("advance.breakpoint.afterLines", lines);
};
document.getElementById("editor-area").ondragover = () => {
return false;
};
document.getElementById("editor-area").ondragleave = () => {
return false;
};
document.getElementById("editor-area").ondragend = () => {
return false;
};
document.getElementById("editor-area").ondrop = (e) => {
console.log("ondrop");
e.preventDefault();
let f = e.dataTransfer.files[0];
f.text().then((text) => {
editor.getModel().setValue(text);
});
return false;
};

297
src/index.css Executable file
View File

@ -0,0 +1,297 @@
html {
height: 100%;
font-size: 12px;
}
body {
border: 1px solid #26a69a;
height: 100%;
background-color: #fafafa;
}
:root {
--menu-height: 200px;
--bar-height: 53px;
--trans-btn-height: 20px;
--modem-btn-height: 10px;
--bar-color-head: #fafafa;
--bar-color-middle: #fafafa;
--bar-color-tail: #fafafa;
}
::-webkit-scrollbar {
display: none;
}
nav {
-webkit-app-region: drag;
background-color: #fafafa;
height: 24px;
line-height: 24px;
}
blockquote {
margin-top: 14px;
margin-bottom: 6px;
font-size: 12px;
font-weight: bold;
border-left: 4px solid #26a69a;
}
.nav-wrapper {
height: 24px;
line-height: 24px;
}
.head-li {
width: 24px;
height: 24px;
line-height: 24px;
}
.tiny-btn {
-webkit-app-region: no-drag;
margin: 5px !important;
width: 12px;
height: 12px;
}
.no-head {
margin-top: 12px;
padding: 0px !important;
}
.select-wrapper input.select-dropdown {
padding-left: 14px;
padding-right: 20px;
box-sizing: border-box;
text-overflow: ellipsis;
border-radius: 12px;
height: 24px;
font-size: 12px;
z-index: inherit;
}
.dropdown-content {
border-radius: 12px;
}
.dropdown-content li {
min-height: 24px;
}
.dropdown-content li > a,
.dropdown-content li > span {
font-size: 12px;
line-height: 24px;
padding-top: 4px;
padding-bottom: 4px;
}
.tabs {
height: 26px;
}
.tabs .tab {
height: 26px;
}
.tabs .tab a {
color: #26a69a;
height: 26px;
line-height: 26px;
font-size: 12px;
font-weight: bold;
/* border-radius: 6px 6px 6px 6px; */
}
.tabs .indicator {
background-color: #fafafa;
}
.tabs .tab a:hover,
.tabs .tab a.active,
.tabs .tab a:focus.active {
color: #26a69a;
background-color: #fafafa;
}
input {
font-size: 12px !important;
}
.input-field {
margin-top: 0px;
margin-bottom: 5px;
}
.config-row {
margin-top: 0px;
margin-bottom: 0px;
}
.config-key {
font-weight: bold;
}
.config-item {
line-height: 42px;
margin-top: 0px;
}
.label-row {
margin-bottom: 0px;
padding-left: 10px;
}
.label-key {
font-weight: bold;
}
.toast {
background-color: #fafafa;
color: #26a69a;
margin-bottom: 254px;
margin-left: 150px;
max-width: 300px;
font-weight: bold;
text-align: center;
}
#logo {
zoom: 80%;
height: 20px !important;
line-height: 24px !important;
margin-left: 6px;
margin-top: 2px;
color: #26a69a;
}
#menu-area {
height: var(--menu-height);
width: calc(100%);
background-color: #fafafa;
}
#menu-tabs-row {
padding-left: 0px;
padding-right: 0px;
}
#menu-tabs {
background-color: #e0f2f1;
}
#bar-area {
width: calc(100% - 2px);
height: var(--bar-height);
position: fixed;
bottom: 1px;
margin-bottom: 0px;
line-height: 54px;
background: -webkit-linear-gradient(
60deg,
var(--bar-color-head),
var(--bar-color-middle),
var(--bar-color-tail)
);
}
#modem-signal-bar {
height: var(--modem-btn-height);
/* background-color: aqua; */
position: fixed;
bottom: var(--bar-height) - var(--modem-btn-height);
}
.modem-btn {
position: sticky;
bottom: var(--bar-height) - var(--modem-btn-height);
font-size: var(--modem-btn-height);
height: var(--modem-btn-height) !important;
line-height: var(--modem-btn-height) !important;
border-radius: 0px 0px var(--modem-btn-height) var(--modem-btn-height);
}
#trans-log-area {
font-size: 12px;
max-height: 136px;
overflow-y: scroll;
}
#trans-eof-row {
margin-top: 10px;
margin-left: 10px;
}
#trans-send-btn,
#trans-log-btn {
margin-top: 16px;
height: var(--trans-btn-height) !important;
line-height: var(--trans-btn-height) !important;
border-radius: calc(var(--trans-btn-height) / 2);
}
#color-panel {
height: 43px;
line-height: 43px;
}
#licence-row {
margin-top: 6px;
}
#licence-key {
height: 24px;
margin-bottom: 0px;
}
#refresh-btn {
transform: scale(0.6);
transform-origin: left;
line-height: 24px;
}
#clear-btn {
line-height: 24px;
}
#baud-input,
#path-input {
margin: 15px 0px;
}
.hl-red {
background-color: #ff8a80 !important;
border-radius: 4px;
}
.hl-orange {
background-color: #ffd180 !important;
border-radius: 4px;
}
.hl-yellow {
background-color: #ffff8d !important;
border-radius: 4px;
}
.hl-green {
background-color: #b9f6ca !important;
border-radius: 4px;
}
.hl-blue {
background-color: #80d8ff !important;
border-radius: 4px;
}
.hl-indigo {
background-color: #8c9eff !important;
border-radius: 4px;
}
.hl-purple {
background-color: #ea80fc !important;
border-radius: 4px;
}

459
src/index.html Executable file
View File

@ -0,0 +1,459 @@
<!DOCTYPE html>
<html>
<head>
<!--Import Google Icon Font-->
<link
rel="stylesheet"
href="../node_modules/material-design-icons/iconfont/material-icons.css"
/>
<!--Import materialize.css-->
<link
type="text/css"
rel="stylesheet"
href="../node_modules/materialize-css/dist/css/materialize.css"
media="screen,projection"
/>
<link rel="stylesheet" href="index.css" />
<meta charset="UTF-8" />
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' 'unsafe-inline'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; connect-src 'self' https://api.github.com/repos/xenkuo/comNG/releases/latest"
/>
</head>
<body>
<nav class="z-depth-0" id="nav-area">
<div class="nav-wrapper" id="header">
<i class="material-icons left" id="logo">vpn_key</i>
<ul class="right">
<li class="head-li">
<a id="min-btn" class="waves-effect btn-floating tiny-btn grey"></a>
</li>
<li class="head-li">
<a
id="max-btn"
class="waves-effect btn-floating tiny-btn white"
></a>
</li>
<li class="head-li">
<a
id="close-btn"
class="waves-effect btn-floating tiny-btn red"
></a>
</li>
</ul>
</div>
</nav>
<div id="editor-area"></div>
<div id="menu-area" hidden>
<div class="row">
<div class="col s12" id="menu-tabs-row">
<ul class="tabs" id="menu-tabs">
<li class="tab col s3">
<a class="active" href="#general-tab">general</a>
</li>
<li class="tab col s3"><a href="#transmit-tab">transmit</a></li>
<li class="tab col s3"><a href="#advance-tab">advance</a></li>
<li class="tab col s3"><a href="#about-tab">about</a></li>
</ul>
</div>
<div id="general-tab" class="col s12">
<div class="col s6">
<div class="row config-row">
<div class="col s5 config-key">
<p>Hex Mode</p>
</div>
<div class="col s7 config-item">
<div class="switch">
<label>
Off
<input type="checkbox" id="hexmode-switch" />
<span class="lever"></span>
On
</label>
</div>
</div>
</div>
<div class="row config-row">
<div class="col s5 config-key">
<p>Timestamp</p>
</div>
<div class="col s7 config-item">
<div class="switch">
<label>
Off
<input type="checkbox" id="timestamp-switch" />
<span class="lever"></span>
On
</label>
</div>
</div>
</div>
<div class="row config-row">
<div class="col s5 config-key">
<p>Modem Signal</p>
</div>
<div class="col s7 config-item">
<div class="switch">
<label>
Off
<input type="checkbox" id="modem-signal-switch" />
<span class="lever"></span>
On
</label>
</div>
</div>
</div>
<div class="row config-row">
<div class="col s7 config-key">
<p>Customized Baud Rate</p>
</div>
<div class="input-field col s3 config-item">
<input
id="customized"
value="9600"
type="text"
class="validate"
/>
</div>
</div>
</div>
<div class="col s2 no-head">
<div class="input-field col s12">
<select id="databits-select">
<option value="1" selected>8</option>
<option value="2">7</option>
<option value="3">6</option>
<option value="4">5</option>
</select>
</div>
<div class="input-field col s12">
<select id="parity-select">
<option value="1" selected>None</option>
<option value="2">Even</option>
<option value="3">Odd</option>
<option value="4">Mark</option>
<option value="5">Space</option>
</select>
</div>
<div class="input-field col s12">
<select id="stopbits-select">
<option value="1" selected>1</option>
<option value="2">2</option>
</select>
</div>
<div class="input-field col s12">
<select id="flowcontrol-select">
<option value="1" selected>None</option>
<option value="2">RTSCTS</option>
<option value="3">XON</option>
<option value="4">XOFF</option>
</select>
</div>
</div>
<div class="col s1 no-head"></div>
<div class="col s3 no-head">
<div class="input-field col s12 config-item">
<input
id="editor-font-family"
placeholder="Font Family"
type="text"
/>
</div>
<div class="input-field col s12 config-item">
<input
id="editor-font-size"
placeholder="Font Size"
type="text"
/>
</div>
</div>
</div>
<div id="transmit-tab" class="col s12">
<div class="col s7">
<div class="row config-row">
<div class="input-field col s8">
<input value="AT" id="trans-data" type="text" />
</div>
<div class="col s4">
<a class="btn-small waves-effect" id="trans-send-btn"
><i class="material-icons">send</i></a
>
</div>
</div>
<div class="row config-row">
<div class="col s1 config-key">
<p>EOF</p>
</div>
<div class="input-field col s4" id="trans-eof-row">
<select id="trans-eof-select">
<option value="1" selected>CRLF</option>
<option value="2">LF</option>
<option value="3">CR</option>
</select>
</div>
</div>
<div class="row config-row">
<div class="col s2 config-key">
<p>Repeat</p>
</div>
<div class="col s5 config-item">
<div class="switch">
<label>
Off
<input type="checkbox" id="trans-repeat-switch" />
<span class="lever"></span>
On
</label>
</div>
</div>
<div class="col s2 config-key">
<p>Interval</p>
</div>
<div class="input-field col s3 config-item">
<input
id="trans-repeat-interval"
placeholder="ms"
value="1000"
type="text"
/>
</div>
</div>
</div>
<div class="col s5">
<!-- <p id="trans-log-p">transmit log</p> -->
<div class="row" id="trans-log-row">
<div class="input-field col s9">
<textarea
id="trans-log-area"
class="materialize-textarea"
placeholder="History"
readonly
></textarea>
</div>
<div class="col s3 right-btn">
<a class="btn-small waves-effect red" id="trans-log-btn"
><i class="material-icons">clear</i></a
>
</div>
</div>
</div>
</div>
<div id="advance-tab" class="col s12">
<div class="col s4">
<blockquote>Sign</blockquote>
<div class="divider"></div>
<div class="row config-row">
<div class="col s12 config-item center">
<div class="switch">
<label>
Off
<input type="checkbox" id="sign-switch" />
<span class="lever"></span>
On
</label>
</div>
</div>
</div>
<div class="row config-row">
<div class="input-field col s12">
<input placeholder="Name" type="text" id="sign-name" />
</div>
</div>
</div>
<div class="col s4">
<blockquote>Breakpoint</blockquote>
<div class="divider"></div>
<div class="row config-row">
<div class="col s12 config-item center">
<div class="switch">
<label>
Off
<input type="checkbox" id="breakpoint-switch" />
<span class="lever"></span>
On
</label>
</div>
</div>
</div>
<div class="row config-row">
<div class="input-field col s12">
<input
value="Error"
id="breakpoint-on-text"
type="text"
maxlength="16"
/>
</div>
</div>
<div class="row config-row">
<div class="input-field col s12">
<input value="5" id="breakpoint-after-lines" type="text" />
</div>
</div>
</div>
<div class="col s4">
<blockquote>Bar Color</blockquote>
<div class="divider"></div>
<div class="center" id="color-panel">
<input value="#ffba3a" id="bar-color-head" type="color" />
<input value="#ff8a80" id="bar-color-middle" type="color" />
<input value="#80d8ff" id="bar-color-tail" type="color" />
</div>
</div>
</div>
<div id="about-tab" class="col s12">
<div class="row label-row">
<div class="col s3 label-key">
<p>Version</p>
</div>
<div class="col s3">
<p id="app-version"></p>
</div>
<div class="col s3 label-key">
<p>Licence</p>
</div>
<div class="col s3">
<p>MIT</p>
</div>
</div>
<div class="row label-row">
<div class="col s3 label-key">
<p>Issue</p>
</div>
<div class="col s9">
<p>
<a href="https://github.com/xenkuo/comNG/issues" id="issue"
>https://github.com/xenkuo/comNG/issues</a
>
</p>
</div>
</div>
<div class="row label-row">
<div class="col s3 label-key">
<p>Introduction</p>
</div>
<div class="col s9">
<p>
<a
href="https://xenkuo.github.io/2019-08-01-comNG"
id="introduction"
>comNG: a modern and powerful COM tool</a
>
</p>
</div>
</div>
<div class="row label-row">
<div class="col s3 label-key">
<p>Highlight Syntax</p>
</div>
<div class="col s9">
<p>
<a
href="https://xenkuo.github.io/2019-08-03-comNGLang"
id="comnglang"
>https://xenkuo.github.io/2019-08-03-comNGLang</a
>
</p>
</div>
</div>
</div>
</div>
</div>
<div class="row" id="bar-area">
<div class="col s12" id="modem-signal-bar" hidden>
<div class="col s1"></div>
<a class="btn-small modem-btn col s1 disabled" id="cts-btn">CTS</a>
<div class="col s1"></div>
<a class="btn-small modem-btn col s1 disabled" id="dsr-btn">DSR</a>
<div class="col s1"></div>
<a class="btn-small modem-btn col s1 disabled" id="dcd-btn">DCD</a>
<div class="col s1"></div>
<div class="col s1"></div>
<a
class="btn-small waves-effect z-depth-0 modem-btn col s1"
id="rts-btn"
>RTS</a
>
<div class="col s1"></div>
<a
class="btn-small waves-effect z-depth-0 modem-btn col s1"
id="dtr-btn"
>DTR</a
>
</div>
<div class="col s2">
<a id="menu-btn" class="waves-effect btn-floating btn-small"
><i class="material-icons">menu</i></a
>
</div>
<div class="col s10">
<div class="input-field col s3" id="baud-input">
<div class="container">
<select id="baud-select">
<option value="1">9600</option>
<option value="2">19200</option>
<option value="3" selected>115200</option>
<option value="4">921600</option>
</select>
</div>
</div>
<div class="input-field col s5" id="path-input">
<select id="path-select">
<option value="0">Select port ...</option>
</select>
</div>
<div class="col s1">
<a class="waves-effect btn-floating" id="refresh-btn"
><i class="material-icons">refresh</i></a
>
</div>
<div class="col s1">
<a class="waves-effect btn-floating btn-small red" id="clear-btn"
><i class="material-icons">delete</i></a
>
</div>
<div class="switch col s2">
<label>
<input type="checkbox" id="port-switch" />
<span class="lever"></span>
</label>
</div>
</div>
</div>
<!-- You can also require other files to run in this process -->
<script src="editor.js"></script>
<script src="serialport.js"></script>
<script src="index.js"></script>
</body>
</html>

536
src/index.js Executable file
View File

@ -0,0 +1,536 @@
/* eslint-disable no-unused-vars */
/* eslint-disable no-undef */
const { remote, shell, ipcRenderer, clipboard } = require("electron");
const Store = require("electron-store");
const appVersion = remote.app.getVersion();
const appUpdaterUrl =
"https://api.github.com/repos/xenkuo/comNG/releases/latest";
var M = require("materialize-css");
M.AutoInit();
var config;
var barHeight;
var menuHeight;
const store = new Store({
projectVersion: appVersion,
migrations: {
"1.0.3": (store) => {
store.delete("desert");
store.delete("about");
store.set("window.width", 600);
store.set("window.height", 640);
store.set("window.widthBefore", 600);
store.set("window.heightBefore", 640);
store.set("window.xBefore", 0);
store.set("window.yBefore", 0);
store.set("menu.hidden", true);
store.set("menu.tab", "general");
store.set("baudIndex", 2);
store.set("pathIndex", 0);
store.set("general.hexmode", false);
store.set("general.timestamp", false);
store.set("general.customized", 9600);
store.set("general.databitsIndex", 0);
store.set("general.parityIndex", 0);
store.set("general.stopbitsIndex", 0);
store.set("general.flowcontrolIndex", 0);
store.set("transmit.eof", "\r\n");
store.set("advance.sign.switch", false);
store.set("advance.sign.name", "");
store.set("advance.breakpoint.switch", false);
store.set("advance.breakpoint.onText", "Error");
store.set("advance.breakpoint.afterLines", 5);
store.set("advance.barColor.head", "#fafafa");
store.set("advance.barColor.middle", "#fafafa");
store.set("advance.barColor.tail", "#26a69a");
},
"1.0.4": (store) => {
store.set("general.modemSignal", false);
store.set(
"general.fontFamily",
"Consolas, 'SF Mono', Menlo, 'Lucida Console', 'Courier New', monospace"
);
store.set("general.fontSize", 12);
},
},
});
function configUpdate(key, value) {
let keyArray = key.split(".");
if (keyArray.length === 1) {
config[keyArray[0]] = value;
} else if (keyArray.length === 2) {
config[keyArray[0]][keyArray[1]] = value;
} else if (keyArray.length === 3) {
config[keyArray[0]][keyArray[1]][keyArray[2]] = value;
} else {
console.error("config key structure error");
}
store.set(key, value);
}
ipcRenderer.on("main-cmd", (event, arg) => {
console.log(arg);
switch (arg) {
case "Clear": {
let text = editor.getModel().getValue();
clipboard.writeText(text);
editor.getModel().setValue("");
break;
}
case "Switch":
document.getElementById("port-switch").click();
break;
case "Open":
openFile();
break;
case "Save":
saveToFile();
break;
default:
console.log("Unknown cmds");
break;
}
});
window.onload = () => {
config = store.store;
document.getElementById("menu-area").hidden = config.menu.hidden;
barHeight = getComputedStyle(document.documentElement)
.getPropertyValue("--bar-height")
.trim()
.split("px")[0];
barHeight = parseInt(barHeight);
menuHeight = getComputedStyle(document.documentElement)
.getPropertyValue("--menu-height")
.trim()
.split("px")[0];
menuHeight = parseInt(menuHeight);
let nav = document.getElementById("nav-area");
let bar = document.getElementById("bar-area");
let menu = document.getElementById("menu-area");
let editor = document.getElementById("editor-area");
editor.style.height =
window.innerHeight -
nav.offsetHeight -
bar.offsetHeight -
menu.offsetHeight +
"px";
M.Tabs.getInstance(document.getElementById("menu-tabs")).select(
config.menu.tab
);
let baudSelect = document.getElementById("baud-select");
baudSelect.options[0].text = config.general.customized;
baudSelect.selectedIndex = config.baudIndex;
M.FormSelect.init(baudSelect);
portUpdate();
document.getElementById("hexmode-switch").checked = config.general.hexmode;
document.getElementById("timestamp-switch").checked =
config.general.timestamp;
document.getElementById("modem-signal-switch").checked =
config.general.modemSignal;
if (config.general.modemSignal === true) {
document.getElementById("modem-signal-bar").hidden = false;
} else {
document.getElementById("modem-signal-bar").hidden = true;
}
document.getElementById("customized").value = config.general.customized;
let databits = document.getElementById("databits-select");
databits.selectedIndex = config.general.databitsIndex;
M.FormSelect.init(databits);
let parity = document.getElementById("parity-select");
parity.selectedIndex = config.general.parityIndex;
M.FormSelect.init(parity);
let stopbits = document.getElementById("stopbits-select");
stopbits.selectedIndex = config.general.stopbitsIndex;
M.FormSelect.init(stopbits);
let flowcontrol = document.getElementById("flowcontrol-select");
flowcontrol.selectedIndex = config.general.flowcontrolIndex;
M.FormSelect.init(flowcontrol);
document.getElementById("editor-font-family").value =
config.general.fontFamily;
document.getElementById("editor-font-size").value = config.general.fontSize;
document.getElementById("breakpoint-switch").checked =
config.advance.breakpoint.switch;
document.getElementById("breakpoint-on-text").value =
config.advance.breakpoint.onText;
document.getElementById("breakpoint-after-lines").value =
config.advance.breakpoint.afterLines;
document.getElementById("sign-switch").checked = config.advance.sign.switch;
document.getElementById("sign-name").value = config.advance.sign.name;
document.getElementById("bar-color-head").value =
config.advance.barColor.head;
document.getElementById("bar-color-middle").value =
config.advance.barColor.middle;
document.getElementById("bar-color-tail").value =
config.advance.barColor.tail;
document.documentElement.style.setProperty(
"--bar-color-head",
config.advance.barColor.head
);
document.documentElement.style.setProperty(
"--bar-color-middle",
config.advance.barColor.middle
);
document.documentElement.style.setProperty(
"--bar-color-tail",
config.advance.barColor.tail
);
document.getElementById("app-version").innerHTML = appVersion;
console.log("Current Version: ", appVersion);
fetch(appUpdaterUrl)
.then((data) => {
return data.json();
})
.then((res) => {
let latest = res.tag_name.split("v")[1];
if (latest !== appVersion) {
const dialogOpts = {
type: "info",
buttons: ["Download Now", "Later"],
message: "Version: " + latest + " released!",
detail: res.body,
};
dialog.showMessageBox(dialogOpts).then((returnValue) => {
if (returnValue.response === 0) shell.openExternal(res.html_url);
});
}
});
};
window.onresize = () => {
configUpdate("window.width", window.innerWidth);
configUpdate("window.height", window.innerHeight);
let nav = document.getElementById("nav-area");
let bar = document.getElementById("bar-area");
let menu = document.getElementById("menu-area");
let editor = document.getElementById("editor-area");
editor.style.height =
window.innerHeight -
nav.offsetHeight -
bar.offsetHeight -
menu.offsetHeight +
"px";
};
document.onkeydown = function (e) {
e = e || window.event;
switch (e.which || e.keyCode) {
case 13:
console.log("hello world 13");
if (document.activeElement.id === "trans-data") {
document.getElementById("trans-send-btn").click();
}
break;
default:
console.log("unknown event");
break;
}
};
// prevent text select for double click action
document.getElementById("nav-area").onmousedown = () => {
return false;
};
document.getElementById("nav-area").ondblclick = () => {
if (
window.innerWidth === screen.width ||
window.innerHeight === screen.height
) {
window.resizeTo(config.window.widthBefore, config.window.heightBefore);
window.moveTo(config.window.xBefore, config.window.yBefore);
} else {
configUpdate("window.widthBefore", window.innerWidth);
configUpdate("window.heightBefore", window.innerHeight);
configUpdate("window.xBefore", window.screenX);
configUpdate("window.yBefore", window.screenY);
window.resizeTo(screen.width, screen.height);
window.moveTo(0, 0);
}
};
document.getElementById("logo").onclick = () => {
shell.openExternal("https://github.com/xenkuo/comNG");
};
document.getElementById("min-btn").onclick = () => {
remote.getCurrentWindow().minimize();
};
document.getElementById("max-btn").onclick = () => {
configUpdate("window.widthBefore", window.innerWidth);
configUpdate("window.heightBefore", window.innerHeight);
configUpdate("window.xBefore", window.screenX);
configUpdate("window.yBefore", window.screenY);
window.resizeTo(screen.width, screen.height);
window.moveTo(0, 0);
};
document.getElementById("close-btn").onclick = () => {
window.close();
};
document.getElementById("menu-btn").onclick = () => {
let menu = document.getElementById("menu-area");
let editor = document.getElementById("editor-area");
if (menu.hidden === true) {
editor.style.height = editor.offsetHeight - menuHeight + "px";
menu.hidden = false;
configUpdate("menu.hidden", false);
} else {
editor.style.height = editor.offsetHeight + menuHeight + "px";
menu.hidden = true;
configUpdate("menu.hidden", true);
}
};
document.body.onclick = (e) => {
// don't process fake click
if (e.isTrusted === false) return;
// don't process when click on menu-btn
if (e.target.parentNode.id === "menu-btn") return;
// don't process when we are in Transmit tab
if (document.getElementById("menu-tabs").M_Tabs.index === 1) return;
let pos = e.clientY;
let range = document.body.offsetHeight;
if (pos > range - barHeight || pos < range - barHeight - menuHeight) {
let menu = document.getElementById("menu-area");
let editor = document.getElementById("editor-area");
if (menu.hidden === false) {
editor.style.height = editor.offsetHeight + menuHeight + "px";
menu.hidden = true;
configUpdate("menu.hidden", true);
}
}
};
document.getElementById("menu-tabs").onclick = () => {
let tabs = M.Tabs.getInstance(document.getElementById("menu-tabs"));
configUpdate("menu.tab", tabs.$content[0].id);
};
document.getElementById("hexmode-switch").onclick = (e) => {
configUpdate("general.hexmode", e.target.checked);
};
document.getElementById("timestamp-switch").onclick = (e) => {
configUpdate("general.timestamp", e.target.checked);
};
document.getElementById("modem-signal-switch").onclick = (e) => {
let state = e.target.checked;
configUpdate("general.modemSignal", state);
if (state === true) {
document.getElementById("modem-signal-bar").hidden = false;
} else {
document.getElementById("modem-signal-bar").hidden = true;
}
};
document.getElementById("customized").onblur = (e) => {
let customized = parseInt(e.target.value);
if (isNaN(customized) === true) customized = 9600;
configUpdate("general.customized", customized);
let baudSelect = document.getElementById("baud-select");
baudSelect.options[0].text = customized;
M.FormSelect.init(baudSelect);
};
document.getElementById("databits-select").onchange = (e) => {
configUpdate("general.databitsIndex", e.target.selectedIndex);
};
document.getElementById("parity-select").onchange = (e) => {
configUpdate("general.parityIndex", e.target.selectedIndex);
};
document.getElementById("stopbits-select").onchange = (e) => {
configUpdate("general.stopbitsIndex", e.target.selectedIndex);
};
document.getElementById("flowcontrol-select").onchange = (e) => {
configUpdate("general.flowcontrolIndex", e.target.selectedIndex);
};
document.getElementById("sign-switch").onclick = (e) => {
configUpdate("advance.sign.switch", e.target.checked);
};
document.getElementById("sign-name").onblur = (e) => {
configUpdate("advance.sign.name", e.target.value);
};
let transRepeatTimer = undefined;
document.getElementById("trans-eof-select").onchange = (e) => {
let index = e.target.selectedIndex;
let eof = "\r\n";
switch (index) {
case 1:
eof = "\r";
break;
case 2:
eof = "\n";
break;
default:
break;
}
configUpdate("transmit.eof", eof);
};
// document.getElementById('trans-clear-btn').onclick = () => {
// document.getElementById('trans-data').value = ''
// }
document.getElementById("trans-send-btn").onclick = () => {
const p = document.getElementById("trans-log-area");
let data = document.getElementById("trans-data").value;
// if (data.trim() === "") return;
data.trim();
data += config.transmit.eof;
if (serialWrite(data) === false) return;
p.value += "\n" + document.getElementById("trans-data").value;
M.updateTextFields(p);
M.textareaAutoResize(p);
p.scrollTop = p.scrollHeight;
if (document.getElementById("trans-repeat-switch").checked === true) {
if (transRepeatTimer !== undefined) clearInterval(transRepeatTimer);
let interval = document.getElementById("trans-repeat-interval").value;
interval = parseInt(interval);
if (isNaN(interval) === true) interval = 1000;
transRepeatTimer = setInterval(() => {
serialWrite(data);
}, interval);
}
};
document.getElementById("trans-repeat-switch").onchange = (e) => {
let checked = e.target.checked;
if (checked === false && transRepeatTimer !== undefined)
clearInterval(transRepeatTimer);
};
document.getElementById("trans-log-btn").onclick = () => {
let p = document.getElementById("trans-log-area");
p.value = "";
M.updateTextFields(p);
M.textareaAutoResize(p);
};
document.getElementById("bar-color-head").oninput = (e) => {
let color = e.target.value;
document.documentElement.style.setProperty("--bar-color-head", color);
configUpdate("advance.barColor.head", color);
};
document.getElementById("bar-color-middle").oninput = (e) => {
let color = e.target.value;
document.documentElement.style.setProperty("--bar-color-middle", color);
configUpdate("advance.barColor.middle", color);
};
document.getElementById("bar-color-tail").oninput = (e) => {
let color = e.target.value;
document.documentElement.style.setProperty("--bar-color-tail", color);
configUpdate("advance.barColor.tail", color);
};
document.getElementById("issue").onclick = (e) => {
console.log("licence click", e);
e.preventDefault();
shell.openExternal(e.target.href);
};
document.getElementById("introduction").onclick = (e) => {
console.log("licence click", e);
e.preventDefault();
shell.openExternal(e.target.href);
};
document.getElementById("comnglang").onclick = (e) => {
console.log("licence click", e);
e.preventDefault();
shell.openExternal(e.target.href);
};
document.getElementById("baud-select").onchange = (e) => {
configUpdate("baudIndex", e.target.selectedIndex);
};
// document.getElementById("path-input").addEventListener(
// "click",
// e => {
// console.log("path select add event");
// if (e.isTrusted === false) return;
// console.log("path x");
// e.stopPropagation();
// // portUpdate();
// setTimeout(() => {
// try {
// let evt = document.createEvent("Event");
// evt.initEvent("click", true, true);
// document.getElementById("path-select").dispatchEvent(evt);
// } catch (e) {
// console.error(e);
// }
// }, 5000);
// },
// true
// );
document.getElementById("path-select").onchange = (e) => {
configUpdate("pathIndex", e.target.selectedIndex);
};
document.getElementById("refresh-btn").onclick = portUpdate;

106
src/main.js Executable file
View File

@ -0,0 +1,106 @@
const { app, BrowserWindow } = require("electron");
const path = require("path");
const Shortcut = require("electron-localshortcut");
const Store = require("electron-store");
const store = new Store();
const widthDefault = 600;
const widthMin = 600;
const widthMax = 1024;
const heightDefault = 640;
const heightMin = 400;
const heightMax = 768;
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require("electron-squirrel-startup")) {
// eslint-disable-line global-require
app.quit();
}
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;
const createWindow = () => {
let width = store.get("window.width", widthDefault);
let height = store.get("window.height", heightDefault);
if (width > widthMax) width = widthMax;
if (height > heightMax) height = heightMax;
// Create the browser window.
mainWindow = new BrowserWindow({
width: width,
minWidth: widthMin,
height: height,
minHeight: heightMin,
alwaysOnTop: false,
frame: false,
icon: path.join(__dirname, "../image/logo.png"),
webPreferences: {
nodeIntegration: true,
preload: path.join(__dirname, "./preload.js"),
},
});
// and load the index.html of the app.
mainWindow.loadFile(path.join(__dirname, "index.html"));
// Open the DevTools.
// mainWindow.webContents.openDevTools()
// Emitted when the window is closed.
mainWindow.on("closed", () => {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null;
});
Shortcut.register(mainWindow, "CmdOrCtrl+X", () => {
console.log("You pressed cmd/ctrl x");
mainWindow.webContents.send("main-cmd", "Clear");
});
Shortcut.register(mainWindow, "CmdOrCtrl+D", () => {
console.log("You pressed cmd/ctrl d");
mainWindow.webContents.send("main-cmd", "Switch");
});
Shortcut.register(mainWindow, "CmdOrCtrl+O", () => {
console.log("You pressed cmd/ctrl o");
mainWindow.webContents.send("main-cmd", "Open");
});
Shortcut.register(mainWindow, "CmdOrCtrl+S", () => {
console.log("You pressed cmd/ctrl s");
mainWindow.webContents.send("main-cmd", "Save");
});
};
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on("ready", createWindow);
// Quit when all windows are closed.
app.on("window-all-closed", () => {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow();
}
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here.

8
src/preload.js Executable file
View File

@ -0,0 +1,8 @@
// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
// const M = require('materialize-css')
// window.addEventListener('DOMContentLoaded', () => {
// M.AutoInit()
// })

237
src/serialport.js Normal file
View File

@ -0,0 +1,237 @@
/* eslint-disable no-unused-vars */
/* eslint-disable no-undef */
const serial = require("serialport");
var port, modemSignalTimer;
var modemSignal = {
cts: false,
dsr: false,
dcd: false,
rts: true,
dtr: true,
};
function portUpdate() {
let pSelect = document.getElementById("path-select");
pSelect.options.length = 0;
serial
.list()
.then((ports) => {
ports.forEach((item, index) => {
console.log(item, index);
pSelect.options.add(new Option(item.path, index));
if (index === config.pathIndex) pSelect.selectedIndex = index;
});
M.FormSelect.init(pSelect);
})
.catch((e) => {
console.error(e);
});
}
function modemSignalTimerHandle() {
if (port === undefined || port.isOpen === false)
return clearInterval(modemSignalTimer);
port.get((e, signal) => {
if (e) return console.error(e);
if (signal.cts !== modemSignal.cts) {
modemSignal.cts = signal.cts;
if (signal.cts === false) {
document.getElementById("cts-btn").style.cssText =
"background-color: #dfdfdf !important";
} else {
document.getElementById("cts-btn").style.cssText =
"background-color: #26a69a !important";
}
}
if (signal.dsr !== modemSignal.dsr) {
modemSignal.dsr = signal.dsr;
if (signal.dsr === false) {
document.getElementById("dsr-btn").style.cssText =
"background-color: #dfdfdf !important";
} else {
document.getElementById("dsr-btn").style.cssText =
"background-color: #26a69a !important";
}
}
if (signal.dcd !== modemSignal.dcd) {
modemSignal.dcd = signal.dcd;
if (signal.dcd === false) {
document.getElementById("dcd-btn").style.cssText =
"background-color: #dfdfdf !important";
} else {
document.getElementById("dcd-btn").style.cssText =
"background-color: #26a69a !important";
}
}
});
}
function modemSignalReset() {
modemSignal.cts = false;
modemSignal.dsr = false;
modemSignal.dcd = false;
modemSignal.rts = true;
modemSignal.dtr = true;
document.getElementById("cts-btn").style.cssText =
"background-color: #dfdfdf !important";
document.getElementById("dsr-btn").style.cssText =
"background-color: #dfdfdf !important";
document.getElementById("dcd-btn").style.cssText =
"background-color: #dfdfdf !important";
document.getElementById("rts-btn").style.backgroundColor = "";
document.getElementById("dtr-btn").style.backgroundColor = "";
}
function serialGetOptions() {
let openOptions = {};
let baudRate = parseInt(
document.getElementById("baud-select").options[config.baudIndex].text
);
if (isNaN(baudRate) === true) baudRate = 115200;
openOptions.baudRate = baudRate;
let dataBits = parseInt(
document.getElementById("databits-select").options[
config.general.databitsIndex
].text
);
if (isNaN(dataBits) === true) dataBits = 8;
openOptions.dataBits = dataBits;
let parity = document
.getElementById("parity-select")
.options[config.general.parityIndex].text.toLowerCase();
openOptions.parity = parity;
let stopBits = parseInt(
document.getElementById("stopbits-select").options[
config.general.stopbitsIndex
].text
);
if (isNaN(stopBits) === true) stopBits = 1;
openOptions.stopBits = stopBits;
let flowcontrol = document
.getElementById("flowcontrol-select")
.options[config.general.flowcontrolIndex].text.toLowerCase();
openOptions[flowcontrol] = true;
openOptions.autoOpen = true;
return openOptions;
}
function toast(text) {
M.toast({ html: text, displayLength: 1000 });
// alert(text)
}
function serialClose() {
port === undefined ? null : port.close();
}
function serialWrite(data) {
if (port === undefined || port.isOpen === false) {
toast("Error: No port opened, cannot write");
if (transRepeatTimer !== undefined) clearInterval(transRepeatTimer);
return false;
}
port.write(data);
return true;
}
document.getElementById("port-switch").onclick = (e) => {
if (e.target.checked === true) {
let pathSelect = document.getElementById("path-select");
let portPath = pathSelect.options[pathSelect.selectedIndex].label;
port = new serial(portPath, serialGetOptions());
port.on("open", () => {
console.log("port open event");
if (modemSignalTimer !== undefined) clearInterval(modemSignalTimer);
if (config.general.modemSignal === true) {
modemSignalTimer = setInterval(modemSignalTimerHandle, 100);
}
port.set({rts: true, dtr: true}, (e) => {
if (e !== null) console.error(e);
});
});
port.on("error", (e) => {
toast(e.message);
document.getElementById("port-switch").checked = false;
if (transRepeatTimer !== undefined) clearInterval(transRepeatTimer);
if (modemSignalTimer !== undefined) clearInterval(modemSignalTimer);
});
port.on("close", (e) => {
console.log("port close event");
if (e !== null) console.error(e);
document.getElementById("port-switch").checked = false;
if (transRepeatTimer !== undefined) clearInterval(transRepeatTimer);
if (modemSignalTimer !== undefined) {
clearInterval(modemSignalTimer);
}
modemSignalReset();
});
port.on("drain", () => {
toast("Error: Write failed, please try again");
});
port.on("data", processSerialData);
} else {
if (port === undefined || port.isOpen === false) {
document.getElementById("port-switch").checked = false;
} else {
port.close();
}
}
};
document.getElementById("rts-btn").onclick = (e) => {
console.log("rts click");
if (port === undefined || port.isOpen === false) return;
if (modemSignal.rts === true) {
modemSignal.rts = false;
e.target.style.backgroundColor = "#dfdfdf";
port.set({ rts: false }, (e) => {
if (e !== null) console.error(e);
});
} else {
modemSignal.rts = true;
e.target.style.backgroundColor = "";
port.set({ rts: modemSignal.rts, dtr: modemSignal.dtr }, (e) => {
if (e !== null) console.error(e);
});
}
};
document.getElementById("dtr-btn").onclick = (e) => {
console.log("dtr click");
if (port === undefined || port.isOpen === false) return;
if (modemSignal.dtr === true) {
modemSignal.dtr = false;
e.target.style.backgroundColor = "#dfdfdf";
port.set({ dtr: false }, (e) => {
if (e !== null) console.error(e);
});
} else {
modemSignal.dtr = true;
e.target.style.backgroundColor = "";
port.set({ rts: modemSignal.rts, dtr: true }, (e) => {
if (e !== null) console.error(e);
});
}
};

4270
yarn.lock Normal file

File diff suppressed because it is too large Load Diff