#27 做点 GUI 吧!

今天就来做些 GUI 吧!

用 HTML + CSS 先把结构弄出来

app.html

<html>
    <head>
        <meta charset="UTF-8" />
        <link href="./style.css" rel="stylesheet" />
        <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'" />
        <title>Click! Serve!</title>
    </head>
    <body>
        <div id="container">
            <div id="controls">
                <div id="launch-area">
                    <button>启动</button>
                </div>
                <div id="config-area">
                    <label>资料夹</label>
                    <div>
                        <input type="text" id="folder" value="" />
                        <button id="folder-select">选择</button>
                    </div>
                    <label>Port</label>
                    <div>
                        <input type="number" id="port" value="" />
                    </div>
                    <label>Log</label>
                    <div>
                        <div class="toggle">
                            <input id="log" type="checkbox" /><br />
                            <b class="b switch"></b>
                            <b class="b track"></b>
                        </div>
                    </div>
                    <label>Log File</label>
                    <div>
                        <input type="text" id="logfile" value="" />
                        <button id="logfile-select">选择</button>
                    </div>
                </div>
            </div>
            <div id="logs"></div>
        </div>
        <script src="./app.js"></script>
    </body>
</html>

style.css

:root {
    /* 使用 Nord Theme 的配色,没有为什麽,只是我喜欢而已 */
    --nord0: #2e3440;
    --nord1: #3b4252;
    --nord2: #434c5e;
    --nord3: #4c566a;
    --nord4: #d8dee9;
    --nord5: #e5e9f0;
    --nord6: #eceff4;
    --nord7: #8fbcbb;
    --nord8: #88c0d0;
    --nord9: #81a1c1;
    --nord10: #5e81ac;
    --nord11: #bf616a;
    --nord12: #d08770;
    --nord13: #ebcb8b;
    --nord14: #a3be8c;
    --nord15: #b48ead;
}

/* body 预设会有个 padding,先把它弄掉 */
html,
body {
    margin: 0;
    padding: 0;
    overflow: hidden;
}

body {
    background: var(--nord6);
    color: var(--nord0);
}

#container {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}

/* 上面的那个区块 */
#controls {
    width: 100%;
    height: 320px;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    border-bottom: var(--nord4) solid 1px;
}

#launch-area {
    width: 320px;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
}

#launch-area > button {
    width: 160px;
    height: 160px;
    border-radius: 50%;
    background: var(--nord8);
    color: var(--nord1);
    font-size: 2.2rem;
    border: none;
    cursor: pointer;
    transition: all 0.3s;
}

#launch-area > button:hover {
    width: 180px;
    height: 180px;
    border-radius: 50%;
    background: var(--nord9);
    color: var(--nord6);
}

#config-area {
    flex: 1;
    height: 100%;
    display: grid;
    align-content: space-evenly;
    justify-items: center;
    grid-template-columns: 100px 1fr;
    grid-gap: 10px;
    font-size: 1.4rem;
}

#config-area > label {
    text-align: right;
    display: inline-block;
    width: 100%;
}

#config-area > div {
    width: calc(100% - 10px);
    padding: 0 10px 0 0;
    display: flex;
    align-items: center;
}

#config-area input,
#config-area button {
    font-size: 1.4rem;
    background: var(--nord5);
    border: none;
    border-radius: 6px;
    padding: 4px 8px;
    transition: all 0.3s;
}

#config-area input:hover,
#config-area input:focus,
#config-area button:hover,
#config-area button:focus {
    outline: none;
    background: var(--nord4);
}

#folder,
#logfile {
    flex: 1;
}

#config-area #folder-select,
#config-area #logfile-select {
    height: 100%;
    width: 100px;
    padding: 0;
    margin: 0 8px;
    cursor: pointer;
}

#port {
    width: 120px;
}

/* 下面的那个区块 */
#logs {
    width: 100%;
    flex: 1;
    overflow: auto;
    background: var(--nord5);
}

/* .toggle 就是那个 iOS 风格按钮 */
.toggle {
    position: absolute;
    width: 45px;
    height: 30px;
    border-radius: 75px;
    background-color: var(--nord5);
    overflow: hidden;
    box-shadow: inset 0 0 1.5px 0.75px var(--nord4);
    margin: 8px 0;
}

.toggle > input[type="checkbox"] {
    position: absolute;
    display: block;
    cursor: pointer;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    z-index: 6;
}

.toggle > input[type="checkbox"] ~ .switch {
    position: absolute;
    left: 1.5px;
    top: 1.5px;
    bottom: 1.5px;
    right: 16.5px;
    background-color: var(--nord6);
    border-radius: 27px;
    z-index: 1;
    transition: 0.25s cubic-bezier(0.785, 0.135, 0.15, 0.86);
    transition-property: left, right;
    transition-delay: 0s, 0.03s;
    box-shadow: 0 0.75px 1.5px var(--nord4);
}

.toggle > input[type="checkbox"] ~ .track {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    transition: 0.25s cubic-bezier(0.785, 0.135, 0.15, 0.86);
    box-shadow: inset 0 0 0 1.5px var(--nord4);
    border-radius: 30px;
}

.toggle > input[type="checkbox"]:checked ~ .track {
    box-shadow: inset 0 0 0 15px var(--nord8);
}

.toggle > input[type="checkbox"]:checked ~ .switch {
    right: 1.5px;
    left: 16.5px;
    transition: 0.25s cubic-bezier(0.785, 0.135, 0.15, 0.86);
    transition-property: left, right;
    transition-delay: 0.03s, 0s;
}

大概就是这样,做点样式,然後用 transition 做切换或滑过的动画。

原生档案位置选取

Electron 可以让我们用原生档案视窗选取档案或资料夹。

main.js

// 把前面引用加上 ipc 及 dialog
const { app, BrowserWindow, dialog, ipcMain: ipc } = require("electron");

// agent.js 会送来请求,我们开原生的 Dialog 再把选取的结果传回去
ipc.on("select-folder", async (evt) => {
    const path = await dialog.showOpenDialog({
        properties: ["openDirectory"],
        title: "请选择资料夹位置",
        defaultPath: process.cwd(),
    });
    evt.sender.send("selected-folder", path);
});

ipc.on("select-logfile", async (evt) => {
    const path = await dialog.showOpenDialog({
        properties: ["openFile"],
        title: "请选择 Log 档位置",
        defaultPath: process.cwd() + "/log.txt",
    });
    evt.sender.send("selected-logfile", path);
});

agent.js

const { ipcRenderer: ipc } = require("electron");

// 收到 main.js 回传的位置,套用到 input 上
ipc.on("selected-folder", (evt, path) => {
    if (path.canceled) return;
    document.querySelector("#folder").value = path.filePaths[0];
});
ipc.on("selected-logfile", (evt, path) => {
    if (path.canceled) return;
    document.querySelector("#logfile").value = path.filePaths[0];
});

// 当 DOM 载入完成後填上初始值
document.addEventListener("DOMContentLoaded", () => {
    document.querySelector("#folder").value = process.cwd() + "/www";
    document.querySelector("#port").value = "80";
    document.querySelector("#log").checked = false;
    document.querySelector("#logfile").value = process.cwd() + "/log.txt";

    registerListener();
});

function registerListener() {
    // 选择资料夹,用 ipc 与 main.js 沟通
    document.querySelector("#folder-select").addEventListener("click", () => {
        ipc.send("select-folder");
    });
    document.querySelector("#logfile-select").addEventListener("click", () => {
        ipc.send("select-logfile");
    });
}

每日铁人赛热门 Top 10 (1010)

以 10/10 20:00 ~ 10/11 20:00 文章观看数增加值排名

  1. +116 [职场]拒绝做白工!让自己的努力效益最大化
    • 作者: 宝宝出头天
    • 系列:全端工程师生存笔记
  2. +112 [DAY-27] 适合你的 才是真正的好职涯
    • 作者: flipsyde
    • 系列:带脑去上班 & No Rules Rules
  3. +110 Day 26: 人工智慧在音乐领域的应用 (AI作曲 - 生成对抗网路 Gan (干) )
    • 作者: fd2
    • 系列:人工智慧在音乐领域的应用
  4. +109 【Day25-评估】连韩组长也混淆的混淆矩阵?——学会正确解读模型价值的常用指标:Recall, Precision, Specificity, F1-Score
    • 作者: owo
    • 系列:资料三十-那些最基本的资料处理与分析技能
  5. +105 D26 - 「来互相伤害啊!」:站在巨人的肩膀上
    • 作者: 鳕鱼
    • 系列:你渴望连结吗?将 Web 与硬体连上线吧!
  6. +103 Proxmox VE 启用客体机复写及搭配迁移功能使用
    • 作者: Jason Cheng (节省哥)
    • 系列:突破困境:企业开源虚拟化管理平台
  7. +98 LeetCode 双刀流:62. Unique Paths
    • 作者: WeiYuan
    • 系列:LeetCode 双刀流:Python x JavaScript
  8. +91 [Python 爬虫这样学,一定是大拇指拉!] DAY26 - 实战演练:多执行绪 - 抓取多个个股收盘价
    • 作者: GreedIsGood
    • 系列:Python 爬虫这样学,一定是大拇指拉!
  9. +89 Day 26 批次网路影片下载工具 - youtube-dl-server
    • 作者: smlpoints
    • 系列:以 Docker 为始的多种开源服务初探
  10. +89 [Day26] Vue3 E2E Testing: Cypress 实战之 Todo MVC (中)
    • 作者: Mia Yang
    • 系列:前端工程师在工作中的各种实战技能 (Vue 3)

<<:  【程序】交朋友 转生成恶役菜鸟工程师避免 Bad End 的 30 件事 - 28

>>:  连续 30 天 玩玩看 ProtoPie - Day 26

Day 27 PostgreSQL 慢查询提速 50+ 倍?

Odoo的整体运作速度算是很快, 但遇到单资料表破千万笔资料时, 仍然有不断转圈圈的时候, 那该怎麽...

[Day2] 引擎简介 - RPG Maker

市面上可以制作游戏的引擎满多的 例Unity, Unreal, RPG Maker,... 等等 这...

day24 stateflow和shareflow是如何取代livedata的,聊聊use case吧!!

记得我们说的特性吧,stateflow会在旧值和新值相同的情况下不做更新,但有时我们需要在每次ret...

# TypeScript 能手养成之旅 Day 15 介面(Interface)

前言 上一篇结束後已经将型别部分,大致上了解差不多了,铁人文章也来到一半了。今天开始下半部的铁人,会...

Day15 - 请蛇上台

class Snake { constructor() { // 蛇头位子 this.head = ...