【山大会议】项目初始化

在本文中,我将描述为开发山大会议的客户端做的环境准备以及部分初始化工作。

前言

本项目是本人作为山东大学2019级项目实训的课题进行开发的。项目组成员共有四人:

  • 孤名为義:后端开发人员,负责基于SFU架构的多人视频会议后端的开发和维护
  • 东羚:后端开发人员,负责分布式系统网关及数据库的搭建维护、即时通讯功能后端部分的开发与维护
  • m0_54743939:负责加密算法的实现
  • 小栗帽今天吃什么:客户端开发人员

我(小栗帽今天吃什么)作为项目中的客户端开发人员,对于本项目将重点从我的工作部分出发进行描述。
我也将我的代码上传到了公开的 git 仓库中,可以自行选择进行访问:

项目定位

我们要开发的项目叫做山大会议,我们的目的是制作一款可供多人在线的视频会议软件。客户端全部由我进行开发,采用的技术栈为 electron.js + React.js ,使用 web 前端的写法打造一款以 WebRTC 技术为核心的桌面级多人视频会议软件。

技术选型

本人专精于 web 前端开发,更想将本次项目作为未来面试其他公司时的项目经历,因此选择采用 JavaScript 进行客户端的开发。但是 JavaScript 终究是要运行在浏览器环境下的,因此,为了使用 JavaScript 开发桌面级的应用程序,我选择将 JavaScript 与 electron.js 结合进行使用。

electron.js

electron 是由 GitHub 进行开发的一款使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。它内置了一个 Chromium 内核,允许脱离浏览器执行前端代码,同时又与 Node.js 结合,使得前端程序员可以通过书写 Node.js 代码调用强大的系统级 API ,实现传统前端无法实现的功能。

React.js

在项目立项时,我考虑到这将是一个极度复杂的项目,客户端内部必将划分为多个不同的模块。为了提高开发效率,我选择使用 React.js ,一款由 Facebook 开发的用于构建用户界面的 JavaScript 库来书写前端代码。

Webpack5

由于项目使用的底层依赖比较复杂,且该项目拥有高度的 webpack 自定义需求,因此我没有采用 React 提供的 create-react-app 脚手架,而是选择为项目手动添加 Webpack 并自行配置。

项目初始化

首先在工作目录下新建文件夹,命名为 sdu-meeting 。进入文件夹,打开终端,键入指令进行项目初始化:

yarn init -y

我们便在项目目录下得到了一个初始化的 package.json 文件。接下来我们需要为项目添加其他的底层依赖。

添加依赖

添加 webpack

yarn add -D webpack webpack-cli webpack-dev-server

Webpack 本身是能够打包 jsjson 文件的,但是为了我们的 Webpack 能够打包 csshtml 以及图片等其他资源文件,我们还需要为 webpack 安装一些插件:

yarn add -D css-loader file-loader resolve-url-loader style-loader url-loader clean-webpack-plugin html-webpack-plugin

由于传统的 css 具有一些缺陷,为了提高我们的开发效率,我决定使用 sass/scss 进行样式的书写,因此我们还需要引入额外的插件:

yarn add -D sass sass-loader

添加 React.js

yarn add react react-dom

然而,我们书写的 JSX 语法是不能直接被 Webpack 识别的,我们需要使用 babel 将其转译成 Webpack 能够读懂的 JS 语法,因此我们还需要安装这些依赖:

yarn add -D @babel/core @babel/plugin-proposal-class-properties @babel/preset-env @babel/preset-react babel-loader

添加 electron.js

yarn add -D electron

我们再添加一些项目中会使用到的依赖:

yarn add -D electron-packager
yarn add electron-store

编写基本文件

编写前端静态文件

首先,我们需要编写前端的静态文件,它将是整个前端项目的基础模板。我们首先在项目根目录下新建一个名为 public 的文件夹,在内部我们定义一个 index.html ,它将成为前端的模板页面。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>山大会议</title>
</head>

<body>
    <div id="root"></div>
</body>

</html>

我们后续将会通过 React 将需要的页面渲染至这个 HTML 文件的 #root 节点中。

electron.js 主进程

在根目录下,我们创建一个名为 main.js 的文件,它将是 electron 的入口文件。同时,我们将 package.json 中的 main 字段也改为 main.js

// package.json
{
    
    
  "name": "sdu-meeting",
  "version": "0.0.1",
  "main": "main.js",
  "author": "德布罗煜",
  "license": "MIT",
  "private": true,
  // , ...
}
// main.js Electron的主进程文件
const {
    
     app, BrowserWindow, Tray, Menu, screen, nativeImage, globalShortcut, safeStorage } = require('electron')
const path = require('path')
const url = require('url')
const Store = require('electron-store')
const store = new Store()

let loginWindow, mainWindow
let tray
let screenWidth, screenHeight
const ipc = require('electron').ipcMain
const DIRNAME = process.env.NODE_ENV === 'development' ? path.join(__dirname, 'public') : __dirname

function createLoginWindow() {
    
    
    loginWindow = new BrowserWindow({
    
    
        width: parseInt(screenWidth * 0.35),
        height: parseInt(screenHeight * 0.45),
        frame: false,
        transparent: true,
        show: false,
        // alwaysOnTop: true,
        resizable: false,
        fullscreenable: false,
        webPreferences: {
    
    
            nodeIntegration: true,
            contextIsolation: false,
        }
    })

    const contextMenu = Menu.buildFromTemplate([
        // {
    
    
        //     label: 'Login',
        //     click: () => {
    
    
        //         loginWindow.setSize(1000, 1000)
        //     }
        // },
        {
    
    
            label: '打开主面板',
            click: () => {
    
    
                loginWindow.show()
                // loginWindow.setSkipTaskbar(false)
                // loginWindow.restore()
            }
        },
        {
    
    
            label: '退出',
            click: () => {
    
    
                app.quit()
            },
            icon: nativeImage.createFromPath(path.join(DIRNAME, 'electronAssets/img/trayIcon/quit.png')).resize({
    
    
                width: 16,
                height: 16,
                quality: 'best'
            })
        }
    ])
    // loginWindow.webContents.openDevTools()

    tray = new Tray(path.join(DIRNAME, 'electronAssets/favicon.ico'))

    if (process.env.NODE_ENV === 'development') {
    
    
        loginWindow.loadURL('http://localhost:3000/login')
        // loginWindow.webContents.openDevTools()
    } else {
    
    
        loginWindow.loadURL(url.format({
    
    
            pathname: path.join(DIRNAME, 'login/index.html'),
            protocol: 'file:',
            slashes: true
        }))
    }

    tray.setToolTip(`假装这是一个QQ\n(¯﹃¯)`)
    tray.setContextMenu(contextMenu)
    tray.on('click', () => {
    
    
        if (loginWindow !== null) {
    
    
            loginWindow.show()
        } else {
    
    
            mainWindow.restore()
        }
    })

    ipc.on('login', (event, userId) => {
    
    
        createMainWindow()
        loginWindow.close();
    })

    ipc.on('safePsw', (event, shouldSaveStatus, userPsw) => {
    
    
        switch (shouldSaveStatus) {
    
    
            case 1: // 保存新密码
                store.set('userSafePsw', safeStorage.encryptString(userPsw))
                break;
            case -1: // 不保存密码
                store.clear('userSafePsw')
                break
            default: // 不需要变动密码
                break
        }
    })

    loginWindow.on('closed', () => {
    
    
        loginWindow = null
    })

    loginWindow.on('ready-to-show', () => {
    
    
        let hasUserPsw = false, userPsw
        if (store.get('userSafePsw')) {
    
    
            userPsw = safeStorage.decryptString(Buffer.from(store.get('userSafePsw').data))
            // console.log(userPsw);
            hasUserPsw = true
        }
        loginWindow.show()
        loginWindow.webContents.send('userSafePsw', hasUserPsw, userPsw)
    })
}

function createMainWindow() {
    
    
    const windowSize = store.get('mainWindowSize')

    mainWindow = new BrowserWindow({
    
    
        width: windowSize ? windowSize[0] : parseInt(screenWidth * 0.6),
        height: windowSize ? windowSize[1] : parseInt(screenHeight * 0.8),
        minWidth: 200,
        frame: false,
        transparent: true,
        show: false,
        fullscreenable: false,
        // alwaysOnTop: true,
        webPreferences: {
    
    
            nodeIntegration: true,
            contextIsolation: false,
            preload: path.join(DIRNAME, 'preload/mainWindowPreload.js')
        }
    })

    if (process.env.NODE_ENV === 'development') {
    
    
        mainWindow.loadURL('http://localhost:3000/main')
        mainWindow.webContents.openDevTools()
    } else {
    
    
        mainWindow.loadURL(url.format({
    
    
            pathname: path.join(DIRNAME, 'main/index.html'),
            protocol: 'file:',
            slashes: true
        }))
    }

    const contextMenu = Menu.buildFromTemplate([
        {
    
    
            label: '最小化',
            click: () => {
    
    
                if (loginWindow !== null) {
    
    
                    loginWindow.hide()
                    // loginWindow.setSkipTaskbar(true)
                    // loginWindow.minimize()
                } else {
    
    
                    mainWindow.minimize()
                }
            }
        },
        {
    
    
            type: 'separator'
        },
        {
    
    
            label: '退出',
            click: () => {
    
    
                app.quit()
            },
            icon: nativeImage.createFromPath(path.join(DIRNAME, 'electronAssets/img/trayIcon/quit.png')).resize({
    
    
                width: 16,
                height: 16,
                quality: 'best'
            })
        }
    ])

    tray.setContextMenu(contextMenu)

    let isMaximized = store.get('isMaximized')

    mainWindow.on('resize', () => {
    
    
        const isMax = mainWindow.isMaximized()
        if (isMaximized !== isMax) {
    
    
            store.set('isMaximized', isMax)
            mainWindow.webContents.send('exchangeMax')
            isMaximized = isMax
        } else if (!isMax) {
    
    
            store.set('mainWindowSize', mainWindow.getSize())
        }
    })

    mainWindow.on('closed', () => {
    
    
        mainWindow = null
    })

    mainWindow.on('ready-to-show', () => {
    
    
        if (isMaximized) {
    
    
            mainWindow.maximize()
            mainWindow.webContents.send('exchangeMax')
        }
        mainWindow.show()
        // winPushStream()
    })

    ipc.on('maximize', () => {
    
    
        if (mainWindow.isMaximized()) {
    
    
            mainWindow.unmaximize()
        } else {
    
    
            mainWindow.maximize()
        }
    })
}

app.on('ready', () => {
    
    
    screenWidth = screen.getPrimaryDisplay().workAreaSize.width;
    screenHeight = screen.getPrimaryDisplay().workAreaSize.height

    if (process.env.NODE_ENV !== 'development')
        globalShortcut.register("CommandOrControl+Shift+I", () => {
    
    
            // console.log("你想打开开发者工具?");
        })

    createLoginWindow()
    ipc.on('quit', () => {
    
    
        if (process.platform !== 'darwin') {
    
    
            app.quit()
        } else {
    
    
            loginWindow = null
            mainWindow = null
        }
    })
    ipc.on('minimize', () => {
    
    
        if (loginWindow !== null) {
    
    
            loginWindow.hide()
            // loginWindow.setSkipTaskbar(true)
            // loginWindow.minimize()
        } else {
    
    
            mainWindow.minimize()
        }
    })
})

app.on('window-all-closed', () => {
    
    
    // Mac平台下,关闭应用窗口后,应用会默认进入后台,需要用户手动终止程序
    if (process.platform !== 'darwin') {
    
    
        app.quit()
    }
})

app.on('activate', () => {
    
    
    if (loginWindow === null) {
    
    
        createLoginWindow()
    }
})

app.on('will-quit', () => {
    
    
    if (process.env.NODE_ENV !== 'development')
        globalShortcut.unregisterAll()
})

React 入口文件及登录页

接下来,我们先来完成 React 入口页面。我们在项目根目录下新建 src 文件夹,并在该文件夹下新建另外三个文件夹:

  • Views:我做的是一个 React 多页应用,这个文件夹作为不同页面的入口文件的容器。
  • Components:这里存放的是通过 React 制作的各种组件。
  • Utils:这里存放项目所需要的所有工具文件。

现在我们向 Views 文件夹中写入我们的登录页:

// index.jsx
import React from "react";
import ReactDOM from 'react-dom';
import App from "./App";

ReactDOM.render(<App />, document.getElementById('root'))
// App.jsx
import Icon, {
    
     LockOutlined, UserOutlined } from "@ant-design/icons";
import React from "react";
import RippleButton from "Components/RippleButton/RippleButton";
import './App.scss'
import {
    
     Victor } from './Victor'
import {
    
     Checkbox, Input } from "antd";
import $fetch from "Utils/Fetch/fetch";

export default class App extends React.Component {
    
    
    constructor(props) {
    
    
        super(props)
        this.state = {
    
    
            showRegister: false,
            rotating: false,
            rememberPassword: localStorage.getItem('rememberPassword') === 'true',
            autoLogin: localStorage.getItem('autoLogin') === 'true',
            userId: localStorage.getItem('userId'),
            userPassword: ''
        }
    }

    mainBodyRef = React.createRef()

    componentDidMount() {
    
    
        let victor = new Victor("header", "canvas");
        let theme = ["#18bbff", "#00486b"]
        victor(theme).set()
    }

    electron = window.require('electron')

    render() {
    
    
        this.electron.ipcRenderer.on('userSafePsw', (event, hasUserPsw, userPsw) => {
    
    
            if (hasUserPsw) {
    
    
                this.setState({
    
    
                    userPassword: userPsw
                })
            }
        })
        return (
            <>
                <div id="dragBar" />
                <div id="mainBody" ref={
    
    this.mainBodyRef}>
                    <div id="header">
                        <div id="titleBar"><LogoIcon style={
    
    {
    
     fontSize: '1.5rem' }} /><span style={
    
    {
    
     fontFamily: 'Microsoft Yahei' }}>山大会议</span>
                            <button className="titleBtn" id="shutdown" title="退出" onClick={
    
    () => {
    
     this.electron.ipcRenderer.send('quit') }}><ShutdownIcon /></button>
                            <button className="titleBtn" id="minimize" title="最小化" onClick={
    
    () => {
    
     this.electron.ipcRenderer.send('minimize') }}><MinimizeIcon /></button>
                            <button className="titleBtn" id="switch" title={
    
    this.state.showRegister ? "返回登录" : "注册账号"} onClick={
    
    () => {
    
     this.rotateTable() }}><RegisterIcon /></button>
                        </div>
                        <div id="canvas"></div>
                    </div>
                    <div className="main">
                        <div className="form" id="loginForm" style={
    
    {
    
     display: this.state.showRegister ? 'none' : 'block' }}>
                            <div>
                                <Input
                                    placeholder="请输入用户名或邮箱"
                                    spellCheck={
    
    false}
                                    prefix={
    
    <UserOutlined />}
                                    size={
    
    'large'}
                                    style={
    
    {
    
     width: '65%' }}
                                    value={
    
    this.state.userId}
                                    onChange={
    
    (event) => {
    
    
                                        this.setState({
    
    
                                            userId: event.target.value
                                        })
                                    }}
                                />
                            </div>
                            <div>
                                <Input.Password
                                    placeholder="请输入密码"
                                    spellCheck={
    
    false}
                                    prefix={
    
    <LockOutlined />}
                                    size={
    
    'large'}
                                    style={
    
    {
    
     width: '65%' }}
                                    value={
    
    this.state.userPassword}
                                    onChange={
    
    (event) => {
    
    
                                        this.setState({
    
    
                                            userPassword: event.target.value
                                        })
                                    }}
                                />
                            </div>
                            <div style={
    
    {
    
     marginBlock: '-0.5rem' }}>
                                <Checkbox
                                    style={
    
    {
    
     fontSize: '0.75rem' }}
                                    checked={
    
    this.state.rememberPassword}
                                    onChange={
    
    (e) => {
    
    
                                        this.setState({
    
    
                                            rememberPassword: e.target.checked
                                        })
                                    }}
                                >
                                    记住密码
                                </Checkbox>
                                <Checkbox
                                    style={
    
    {
    
     fontSize: '0.75rem' }}
                                    checked={
    
    this.state.autoLogin}
                                    onChange={
    
    (e) => {
    
    
                                        this.setState({
    
    
                                            autoLogin: e.target.checked
                                        })
                                    }}
                                >
                                    自动登录
                                </Checkbox>
                            </div>
                            <div>
                                <RippleButton className="submit" onClick={
    
    () => {
    
     this.login() }}>登 录</RippleButton>
                            </div>
                        </div>
                        <div className="form" id="registerForm" style={
    
    {
    
     display: this.state.showRegister ? 'block' : 'none' }}>
                            <div>

                            </div>
                            <div>

                            </div>
                            <div>
                                <RippleButton className="submit">注 册</RippleButton>
                            </div>
                        </div>
                    </div>
                </div>
            </>
        )
    }

    rotateTable() {
    
    
        if (!this.state.rotating) {
    
    
            this.setState({
    
    
                rotating: true
            }, () => {
    
    
                this.mainBodyRef.current.style.animationName = 'rotateOut'
                setTimeout(() => {
    
    
                    this.mainBodyRef.current.style.animationName = 'rotateIn'
                    this.setState({
    
    
                        showRegister: !this.state.showRegister,
                    }, () => {
    
    
                        setTimeout(() => {
    
    
                            this.setState({
    
    
                                rotating: false
                            })
                        }, 250)
                    })
                }, 250)
            })
        }
    }

    login() {
    
    
        // $fetch.post('http://localhost:8080/login', {
    
    
        //     username: this.state.userId,
        //     password: this.state.userPassword
        // }).then((res) => {
    
    
        //     console.log(res);
        // })

        new Promise((resolve, reject) => {
    
    
            resolve()
        }).then(() => {
    
    
            localStorage.setItem('rememberPassword', this.state.rememberPassword)
            localStorage.setItem('autoLogin', this.state.autoLogin)
            if (this.state.rememberPassword) {
    
    
                if (localStorage.getItem('userId') === this.state.userId) {
    
    
                    this.electron.ipcRenderer.send('safePsw', 0);
                } else {
    
    
                    this.electron.ipcRenderer.send('safePsw', 1, this.state.userPassword);
                }
            } else {
    
    
                this.electron.ipcRenderer.send('safePsw', -1);
            }
            localStorage.setItem('userId', this.state.userId)
        }).then(() => {
    
    
            this.electron.ipcRenderer.send('login', this.state.userId)
        })
    }
}

const LogoIcon = props => <Icon component={
    
    () => (
    <svg viewBox="0 0 1024 1024" width="1em" height="1em">
        <path d="M704.034133 261.905067l253.354667 253.354666a110.8992 110.8992 0 0 1 0 156.842667l-83.421867 83.421867a8.533333 8.533333 0 0 1-12.066133 0l-162.696533-162.7136-170.8544 170.871466-163.242667-163.089066a8.533333 8.533333 0 0 1-0.989867-10.888534l0.989867-1.194666 164.7616-164.5056 162.0992-162.0992a8.533333 8.533333 0 0 1 12.066133 0z m-384.068266 0a8.533333 8.533333 0 0 1 12.066133 0l161.8432 161.8432-331.776 331.776a8.533333 8.533333 0 0 1-10.888533 0.9728l-1.194667-0.9728-83.4048-83.421867a110.8992 110.8992 0 0 1 0-156.842667z" fill="currentColor" />
    </svg>
)} {
    
    ...props} />

const ShutdownIcon = props => <Icon component={
    
    () => (
    <svg viewBox="0 0 1024 1024" width="1em" height="1em">
        <path d="M109.9 935.8c-19.5-19.5-19.5-51.2 0-70.7l759.3-759.3c19.5-19.5 51.2-19.5 70.7 0s19.5 51.2 0 70.7L180.6 935.8c-19.6 19.6-51.2 19.6-70.7 0z" fill="currentColor" />
        <path d="M869.1 935.8L109.9 176.5c-19.5-19.5-19.5-51.2 0-70.7s51.2-19.5 70.7 0l759.3 759.3c19.5 19.5 19.5 51.2 0 70.7-19.6 19.6-51.2 19.6-70.8 0z" fill="currentColor" />
    </svg>
)} {
    
    ...props} />

const MinimizeIcon = props => <Icon component={
    
    () => (
    <svg viewBox="0 0 1024 1024" width="1em" height="1em">
        <path d="M923 571H130.7c-27.6 0-50-22.4-50-50s22.4-50 50-50H923c27.6 0 50 22.4 50 50s-22.4 50-50 50z" fill="currentColor" />
    </svg>
)} {
    
    ...props} />

const RegisterIcon = props => <Icon component={
    
    () => (
    <svg viewBox="0 0 1024 1024" width="1em" height="1em">
        <path d="M789.779 984.843c-31.732 0-61.762-6.041-90.096-18.134-28.334-12.088-53.08-28.709-74.234-49.865-21.151-21.156-37.772-45.897-49.864-74.229-12.087-28.334-18.134-58.362-18.134-90.1 0-31.732 6.047-61.762 18.134-90.095 12.091-28.333 28.714-52.893 49.864-73.664 21.154-20.78 45.899-37.214 74.234-49.301 28.333-12.091 58.363-18.136 90.096-18.136 31.734 0 61.765 6.044 90.098 18.136 28.334 12.087 52.888 28.521 73.665 49.301 20.779 20.774 37.21 45.334 49.3 73.664 12.088 28.333 18.137 58.362 18.137 90.095 0 31.737-6.049 61.766-18.137 90.1s-28.521 53.073-49.3 74.229c-20.78 21.157-45.332 37.778-73.665 49.865C851.545 978.802 821.514 984.843 789.779 984.843L789.779 984.843zM904.244 715.118l-83.865 0 0-78.197c0-10.581-3.395-19.645-10.198-27.203-6.801-7.554-15.489-11.332-26.069-11.332-10.575 0-18.887 3.778-24.929 11.332-6.043 7.559-9.068 16.622-9.068 27.203l0 78.197-73.665 0c-10.575 0-19.641 3.773-27.197 11.334-7.556 7.553-11.333 16.623-11.333 27.197 0 10.575 3.777 18.512 11.333 23.803 7.557 5.288 16.622 7.928 27.197 7.928l73.665 0 0 80.466c0 10.581 3.025 19.645 9.068 27.2 6.042 7.559 14.354 11.332 24.929 11.332 10.58 0 19.267-3.774 26.069-11.332 6.802-7.556 10.198-16.619 10.198-27.2L820.379 785.38l83.865 0 0 2.27c10.579 0 19.644-3.021 27.198-9.063 7.56-6.048 11.333-14.36 11.333-24.936 0-10.574-3.774-19.645-11.333-27.197C923.889 718.892 914.823 715.118 904.244 715.118L904.244 715.118zM624.321 432.927c-3.023 12.086-6.049 23.042-9.07 32.865-3.021 8.311-6.801 16.811-11.332 25.498-4.534 8.688-9.442 15.301-14.731 19.833-6.801 5.289-11.522 10.957-14.167 17-2.645 6.042-4.915 12.271-6.802 18.697-1.887 6.423-3.966 13.03-6.229 19.832-2.271 6.802-6.049 13.602-11.332 20.399-17.383 22.664-30.416 44.578-39.104 65.732-8.688 21.155-14.731 41.554-18.133 61.201-3.398 19.643-4.345 39.102-2.836 58.362 1.513 19.269 4.533 37.969 9.069 56.1 3.022 13.597 7.555 27.764 13.597 42.499 6.048 14.73 14.925 29.844 26.632 45.333 11.711 15.488 26.634 30.595 44.77 45.332 18.13 14.731 40.794 28.896 67.998 42.499-18.135 3.774-39.291 7.177-63.467 10.199-20.402 2.271-45.521 4.343-75.362 6.229-29.845 1.896-64.036 2.836-102.566 2.836-19.646 0-42.499-0.753-68.564-2.265-26.068-1.512-52.885-3.401-80.465-5.666-27.574-2.269-54.779-4.913-81.599-7.935-26.816-3.022-51.376-6.232-73.665-9.634-22.286-3.397-41.178-6.989-56.666-10.767-15.484-3.776-25.119-7.177-28.898-10.197-6.8-6.049-12.085-22.86-15.864-50.436-3.779-27.57-2.645-63.652 3.398-108.229 3.778-24.933 13.789-44.009 30.032-57.235 16.245-13.22 35.321-23.605 57.235-31.165 21.907-7.555 44.764-14.544 68.563-20.968 23.799-6.419 44.768-15.295 62.902-26.627 14.355-9.067 25.311-17.57 32.864-25.501 7.553-7.935 12.843-16.055 15.864-24.368 3.022-8.306 4.534-17 4.534-26.064 0-9.065-0.377-18.888-1.133-29.469-1.514-15.865-6.984-28.333-16.432-37.396-9.445-9.069-19.456-18.136-30.032-27.199-6.049-4.537-11.333-11.331-15.87-20.401-4.533-9.063-8.307-17.753-11.333-26.063-3.774-9.824-6.797-20.779-9.065-32.865-5.29-1.511-10.198-3.778-14.73-6.801-3.779-3.021-7.936-7.559-12.469-13.603-4.532-6.042-8.688-15.107-12.468-27.199-3.779-11.333-5.101-21.908-3.966-31.729 1.132-9.824 3.214-18.134 6.236-24.936 3.022-8.311 7.177-15.49 12.463-21.531 0-25.688 1.516-51.376 4.539-77.069 3.022-21.907 7.741-45.333 14.167-70.26 6.419-24.936 16.811-47.225 31.166-66.864 13.597-18.892 28.144-34.382 43.628-46.466 15.489-12.091 31.546-21.532 48.166-28.333 16.621-6.802 33.244-11.521 49.867-14.167C380.28 1.323 396.149 0 411.26 0c19.643 0 38.529 2.27 56.664 6.802 18.133 4.531 34.943 10.575 50.433 18.134 15.492 7.553 29.279 16.054 41.365 25.499 12.091 9.441 21.913 19.074 29.468 28.896 17.376 21.909 30.031 46.09 37.969 72.53 7.93 26.446 13.783 51.376 17.563 74.799 3.779 27.199 5.291 54.396 4.534 81.602 4.533 3.774 8.312 8.306 11.332 13.596 3.02 4.532 5.29 10.581 6.801 18.134 1.513 7.553 1.513 17 0 28.333-1.512 14.355-4.534 25.688-9.063 34-4.538 8.313-9.445 14.73-14.736 19.269C637.541 426.878 631.117 430.657 624.321 432.927L624.321 432.927z" fill="currentColor" />
    </svg>
)} {
    
    ...props} />
// App.scss
@import '~antd/dist/antd.css';

* {
    
    
    margin: 0;
    padding: 0;
}

body {
    
    
    width: 100vw;
    height: 100vh;
    overflow: hidden;
    background-color: rgba($color: #000000, $alpha: 0);
}

#mainBody {
    
    
    position: absolute;
    left: 0.1rem;
    top: 0.1rem;
    width: calc(100vw - 0.2rem);
    height: calc(100vh - 0.2rem);
    overflow: hidden;
    box-shadow: 0 0 0.1rem rgba(0, 0, 0, 0.8);
    animation-duration: 250ms;
    animation-fill-mode: forwards;
    animation-timing-function: linear;
    animation-direction: normal;
    animation-play-state: running;
}

#header {
    
    
    width: 100%;
    height: 40%;
}

#titleBar {
    
    
    position: absolute;
    width: 100%;
    height: 2rem;
    color: white;
    font-size: 1.3rem;
    line-height: 2rem;
    user-select: none;
}

#dragBar {
    
    
    -webkit-app-region: drag;
    position: absolute;
    left: 0.1rem;
    top: 0.1rem;
    width: 100%;
    height: calc(40% - 0.1rem);
    display: inline-block;
}

#canvas {
    
    
    width: 100%;
    height: 100%;
}

.titleBtn {
    
    
    border: none;
    outline: none;
    color: white;
    background: none;
    float: right;
    width: 2rem;
    height: 2rem;
    -webkit-app-region: no-drag;
    transition: 250ms;
}

#shutdown:hover {
    
    
    background-color: red;
}

#switch:hover,
#minimize:hover {
    
    
    background-color: rgba(255, 255, 255, 0.3);
}

@keyframes rotateIn {
    
    
    from {
    
    
        transform: perspective(1000px) rotateY(-90deg) scale(0.65);
    }

    to {
    
    
        transform: perspective(1000px) rotateY(0deg) scale(1);
    }
}

@keyframes rotateOut {
    
    
    from {
    
    
        transform: perspective(1000px) rotateY(0deg) scale(1);
    }

    to {
    
    
        transform: perspective(1000px) rotateY(90deg) scale(0.65);
    }
}

.main {
    
    
    width: 100%;
    height: 60%;
    text-align: center;
}

.main .form {
    
    
    position: absolute;
    width: 100%;
    height: 60%;
    background-color: white;
}

.main .form {
    
    
    width: 100%;
    height: 100%;

    div {
    
    
        margin-block: 1rem;
    }
}

.submit {
    
    
    position: relative;
    font-size: 1.25rem;
    padding: 0.75rem;
    width: 50%;
    background-color: #18bbff;
    color: white;
    border: none;
    border-radius: 0.25rem;
    outline: none;
    cursor: pointer;
    box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.3);
    transition: 500ms;
    overflow: hidden;
    -webkit-tap-highlight-color: transparent;
    z-index: 1;
    user-select: none;
}

.submit:hover {
    
    
    background-color: #28ccff;
}
// Victor.js
let CAV = {
    
     FRONT: 0, BACK: 1, DOUBLE: 2, SVGNS: "http://www.w3.org/2000/svg" }; CAV.Array = typeof Float32Array === "function" ? Float32Array : Array; CAV.Utils = {
    
     isNumber: function (a) {
    
     return !isNaN(parseFloat(a)) && isFinite(a) } };
(function () {
    
    
    for (var a = 0, b = ["ms", "moz", "webkit", "o"], c = 0; c < b.length && !window.requestAnimationFrame; ++c)window.requestAnimationFrame = window[b[c] + "RequestAnimationFrame"], window.cancelAnimationFrame = window[b[c] + "CancelAnimationFrame"] || window[b[c] + "CancelRequestAnimationFrame"]; if (!window.requestAnimationFrame) window.requestAnimationFrame = function (b) {
    
     var c = (new Date).getTime(), f = Math.max(0, 16 - (c - a)), g = window.setTimeout(function () {
    
     b(c + f) }, f); a = c + f; return g }; if (!window.cancelAnimationFrame) window.cancelAnimationFrame =
        function (a) {
    
     clearTimeout(a) }
})(); Math.PIM2 = Math.PI * 2; Math.PID2 = Math.PI / 2; Math.randomInRange = function (a, b) {
    
     return a + (b - a) * Math.random() }; Math.clamp = function (a, b, c) {
    
     a = Math.max(a, b); return a = Math.min(a, c) };
CAV.Vector3 = {
    
    
    create: function (a, b, c) {
    
     var d = new CAV.Array(3); this.set(d, a, b, c); return d }, clone: function (a) {
    
     var b = this.create(); this.copy(b, a); return b }, set: function (a, b, c, d) {
    
     a[0] = b || 0; a[1] = c || 0; a[2] = d || 0; return this }, setX: function (a, b) {
    
     a[0] = b || 0; return this }, setY: function (a, b) {
    
     a[1] = b || 0; return this }, setZ: function (a, b) {
    
     a[2] = b || 0; return this }, copy: function (a, b) {
    
     a[0] = b[0]; a[1] = b[1]; a[2] = b[2]; return this }, add: function (a, b) {
    
     a[0] += b[0]; a[1] += b[1]; a[2] += b[2]; return this }, addVectors: function (a, b, c) {
    
    
        a[0] = b[0] +
            c[0]; a[1] = b[1] + c[1]; a[2] = b[2] + c[2]; return this
    }, addScalar: function (a, b) {
    
     a[0] += b; a[1] += b; a[2] += b; return this }, subtract: function (a, b) {
    
     a[0] -= b[0]; a[1] -= b[1]; a[2] -= b[2]; return this }, subtractVectors: function (a, b, c) {
    
     a[0] = b[0] - c[0]; a[1] = b[1] - c[1]; a[2] = b[2] - c[2]; return this }, subtractScalar: function (a, b) {
    
     a[0] -= b; a[1] -= b; a[2] -= b; return this }, multiply: function (a, b) {
    
     a[0] *= b[0]; a[1] *= b[1]; a[2] *= b[2]; return this }, multiplyVectors: function (a, b, c) {
    
     a[0] = b[0] * c[0]; a[1] = b[1] * c[1]; a[2] = b[2] * c[2]; return this }, multiplyScalar: function (a,
        b) {
    
     a[0] *= b; a[1] *= b; a[2] *= b; return this }, divide: function (a, b) {
    
     a[0] /= b[0]; a[1] /= b[1]; a[2] /= b[2]; return this }, divideVectors: function (a, b, c) {
    
     a[0] = b[0] / c[0]; a[1] = b[1] / c[1]; a[2] = b[2] / c[2]; return this }, divideScalar: function (a, b) {
    
     b !== 0 ? (a[0] /= b, a[1] /= b, a[2] /= b) : (a[0] = 0, a[1] = 0, a[2] = 0); return this }, cross: function (a, b) {
    
     var c = a[0], d = a[1], e = a[2]; a[0] = d * b[2] - e * b[1]; a[1] = e * b[0] - c * b[2]; a[2] = c * b[1] - d * b[0]; return this }, crossVectors: function (a, b, c) {
    
    
            a[0] = b[1] * c[2] - b[2] * c[1]; a[1] = b[2] * c[0] - b[0] * c[2]; a[2] = b[0] * c[1] - b[1] *
                c[0]; return this
        }, min: function (a, b) {
    
     a[0] < b && (a[0] = b); a[1] < b && (a[1] = b); a[2] < b && (a[2] = b); return this }, max: function (a, b) {
    
     a[0] > b && (a[0] = b); a[1] > b && (a[1] = b); a[2] > b && (a[2] = b); return this }, clamp: function (a, b, c) {
    
     this.min(a, b); this.max(a, c); return this }, limit: function (a, b, c) {
    
     var d = this.length(a); b !== null && d < b ? this.setLength(a, b) : c !== null && d > c && this.setLength(a, c); return this }, dot: function (a, b) {
    
     return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] }, normalise: function (a) {
    
     return this.divideScalar(a, this.length(a)) }, negate: function (a) {
    
    
            return this.multiplyScalar(a,
                -1)
        }, distanceSquared: function (a, b) {
    
     var c = a[0] - b[0], d = a[1] - b[1], e = a[2] - b[2]; return c * c + d * d + e * e }, distance: function (a, b) {
    
     return Math.sqrt(this.distanceSquared(a, b)) }, lengthSquared: function (a) {
    
     return a[0] * a[0] + a[1] * a[1] + a[2] * a[2] }, length: function (a) {
    
     return Math.sqrt(this.lengthSquared(a)) }, setLength: function (a, b) {
    
     var c = this.length(a); c !== 0 && b !== c && this.multiplyScalar(a, b / c); return this }
};
CAV.Vector4 = {
    
    
    create: function (a, b, c) {
    
     var d = new CAV.Array(4); this.set(d, a, b, c); return d }, set: function (a, b, c, d, e) {
    
     a[0] = b || 0; a[1] = c || 0; a[2] = d || 0; a[3] = e || 0; return this }, setX: function (a, b) {
    
     a[0] = b || 0; return this }, setY: function (a, b) {
    
     a[1] = b || 0; return this }, setZ: function (a, b) {
    
     a[2] = b || 0; return this }, setW: function (a, b) {
    
     a[3] = b || 0; return this }, add: function (a, b) {
    
     a[0] += b[0]; a[1] += b[1]; a[2] += b[2]; a[3] += b[3]; return this }, multiplyVectors: function (a, b, c) {
    
     a[0] = b[0] * c[0]; a[1] = b[1] * c[1]; a[2] = b[2] * c[2]; a[3] = b[3] * c[3]; return this },
    multiplyScalar: function (a, b) {
    
     a[0] *= b; a[1] *= b; a[2] *= b; a[3] *= b; return this }, min: function (a, b) {
    
     a[0] < b && (a[0] = b); a[1] < b && (a[1] = b); a[2] < b && (a[2] = b); a[3] < b && (a[3] = b); return this }, max: function (a, b) {
    
     a[0] > b && (a[0] = b); a[1] > b && (a[1] = b); a[2] > b && (a[2] = b); a[3] > b && (a[3] = b); return this }, clamp: function (a, b, c) {
    
     this.min(a, b); this.max(a, c); return this }
}; CAV.Color = function (a, b) {
    
     this.rgba = CAV.Vector4.create(); this.hex = a || "#000000"; this.opacity = CAV.Utils.isNumber(b) ? b : 1; this.set(this.hex, this.opacity) };
CAV.Color.prototype = {
    
    
    set: function (a, b) {
    
     var a = a.replace("#", ""), c = a.length / 3; this.rgba[0] = parseInt(a.substring(c * 0, c * 1), 16) / 255; this.rgba[1] = parseInt(a.substring(c * 1, c * 2), 16) / 255; this.rgba[2] = parseInt(a.substring(c * 2, c * 3), 16) / 255; this.rgba[3] = CAV.Utils.isNumber(b) ? b : this.rgba[3]; return this }, hexify: function (a) {
    
     a = Math.ceil(a * 255).toString(16); a.length === 1 && (a = "0" + a); return a }, format: function () {
    
    
        var a = this.hexify(this.rgba[0]), b = this.hexify(this.rgba[1]), c = this.hexify(this.rgba[2]); return this.hex = "#" +
            a + b + c
    }
}; CAV.Object = function () {
    
     this.position = CAV.Vector3.create() }; CAV.Object.prototype = {
    
     setPosition: function (a, b, c) {
    
     CAV.Vector3.set(this.position, a, b, c); return this } }; CAV.Light = function (a, b) {
    
     CAV.Object.call(this); this.ambient = new CAV.Color(a || "#FFFFFF"); this.diffuse = new CAV.Color(b || "#FFFFFF"); this.ray = CAV.Vector3.create() }; CAV.Light.prototype = Object.create(CAV.Object.prototype); CAV.Vertex = function (a, b, c) {
    
     this.position = CAV.Vector3.create(a, b, c) };
CAV.Vertex.prototype = {
    
     setPosition: function (a, b, c) {
    
     CAV.Vector3.set(this.position, a, b, c); return this } };
CAV.Triangle = function (a, b, c) {
    
    
    this.a = a || new CAV.Vertex; this.b = b || new CAV.Vertex; this.c = c || new CAV.Vertex; this.vertices = [this.a, this.b, this.c]; this.u = CAV.Vector3.create(); this.v = CAV.Vector3.create(); this.centroid = CAV.Vector3.create(); this.normal = CAV.Vector3.create(); this.color = new CAV.Color; this.polygon = document.createElementNS(CAV.SVGNS, "polygon"); this.polygon.setAttributeNS(null, "stroke-linejoin", "round"); this.polygon.setAttributeNS(null, "stroke-miterlimit", "1"); this.polygon.setAttributeNS(null, "stroke-width",
        "1"); this.computeCentroid(); this.computeNormal()
};
CAV.Triangle.prototype = {
    
    
    computeCentroid: function () {
    
     this.centroid[0] = this.a.position[0] + this.b.position[0] + this.c.position[0]; this.centroid[1] = this.a.position[1] + this.b.position[1] + this.c.position[1]; this.centroid[2] = this.a.position[2] + this.b.position[2] + this.c.position[2]; CAV.Vector3.divideScalar(this.centroid, 3); return this }, computeNormal: function () {
    
    
        CAV.Vector3.subtractVectors(this.u, this.b.position, this.a.position); CAV.Vector3.subtractVectors(this.v, this.c.position, this.a.position); CAV.Vector3.crossVectors(this.normal,
            this.u, this.v); CAV.Vector3.normalise(this.normal); return this
    }
}; CAV.Geometry = function () {
    
     this.vertices = []; this.triangles = []; this.dirty = false }; CAV.Geometry.prototype = {
    
     update: function () {
    
     if (this.dirty) {
    
     var a, b; for (a = this.triangles.length - 1; a >= 0; a--)b = this.triangles[a], b.computeCentroid(), b.computeNormal(); this.dirty = false } return this } };
CAV.Plane = function (a, b, c, d) {
    
    
    let t0, t1
    CAV.Geometry.call(this); this.width = a || 100; this.height = b || 100; this.segments = c || 4; this.slices = d || 4; this.segmentWidth = this.width / this.segments; this.sliceHeight = this.height / this.slices; var e, f, g, c = []; e = this.width * -0.5; f = this.height * 0.5; for (a = 0; a <= this.segments; a++) {
    
     c.push([]); for (b = 0; b <= this.slices; b++)d = new CAV.Vertex(e + a * this.segmentWidth, f - b * this.sliceHeight), c[a].push(d), this.vertices.push(d) } for (a = 0; a < this.segments; a++)for (b = 0; b < this.slices; b++)d = c[a + 0][b + 0], e = c[a + 0][b +
        1], f = c[a + 1][b + 0], g = c[a + 1][b + 1], t0 = new CAV.Triangle(d, e, f), t1 = new CAV.Triangle(f, e, g), this.triangles.push(t0, t1)
}; CAV.Plane.prototype = Object.create(CAV.Geometry.prototype); CAV.Material = function (a, b) {
    
     this.ambient = new CAV.Color(a || "#444444"); this.diffuse = new CAV.Color(b || "#FFFFFF"); this.slave = new CAV.Color }; CAV.Mesh = function (a, b) {
    
     CAV.Object.call(this); this.geometry = a || new CAV.Geometry; this.material = b || new CAV.Material; this.side = CAV.FRONT; this.visible = true }; CAV.Mesh.prototype = Object.create(CAV.Object.prototype);
CAV.Mesh.prototype.update = function (a, b) {
    
    
    var c, d, e, f, g; this.geometry.update(); if (b) for (c = this.geometry.triangles.length - 1; c >= 0; c--) {
    
    
        d = this.geometry.triangles[c]; CAV.Vector4.set(d.color.rgba); for (e = a.length - 1; e >= 0; e--)f = a[e], CAV.Vector3.subtractVectors(f.ray, f.position, d.centroid), CAV.Vector3.normalise(f.ray), g = CAV.Vector3.dot(d.normal, f.ray), this.side === CAV.FRONT ? g = Math.max(g, 0) : this.side === CAV.BACK ? g = Math.abs(Math.min(g, 0)) : this.side === CAV.DOUBLE && (g = Math.max(Math.abs(g), 0)), CAV.Vector4.multiplyVectors(this.material.slave.rgba,
            this.material.ambient.rgba, f.ambient.rgba), CAV.Vector4.add(d.color.rgba, this.material.slave.rgba), CAV.Vector4.multiplyVectors(this.material.slave.rgba, this.material.diffuse.rgba, f.diffuse.rgba), CAV.Vector4.multiplyScalar(this.material.slave.rgba, g), CAV.Vector4.add(d.color.rgba, this.material.slave.rgba); CAV.Vector4.clamp(d.color.rgba, 0, 1)
    } return this
}; CAV.Scene = function () {
    
     this.meshes = []; this.lights = [] };
CAV.Scene.prototype = {
    
     add: function (a) {
    
     a instanceof CAV.Mesh && !~this.meshes.indexOf(a) ? this.meshes.push(a) : a instanceof CAV.Light && !~this.lights.indexOf(a) && this.lights.push(a); return this }, remove: function (a) {
    
     a instanceof CAV.Mesh && ~this.meshes.indexOf(a) ? this.meshes.splice(this.meshes.indexOf(a), 1) : a instanceof CAV.Light && ~this.lights.indexOf(a) && this.lights.splice(this.lights.indexOf(a), 1); return this } }; CAV.Renderer = function () {
    
     this.halfHeight = this.halfWidth = this.height = this.width = 0 };
CAV.Renderer.prototype = {
    
     setSize: function (a, b) {
    
     if (!(this.width === a && this.height === b)) return this.width = a, this.height = b, this.halfWidth = this.width * 0.5, this.halfHeight = this.height * 0.5, this }, clear: function () {
    
     return this }, render: function () {
    
     return this } }; CAV.CanvasRenderer = function () {
    
     CAV.Renderer.call(this); this.element = document.createElement("canvas"); this.element.style.display = "block"; this.context = this.element.getContext("2d"); this.setSize(this.element.width, this.element.height) };
CAV.CanvasRenderer.prototype = Object.create(CAV.Renderer.prototype); CAV.CanvasRenderer.prototype.setSize = function (a, b) {
    
     CAV.Renderer.prototype.setSize.call(this, a, b); this.element.width = a; this.element.height = b; this.context.setTransform(1, 0, 0, -1, this.halfWidth, this.halfHeight); return this }; CAV.CanvasRenderer.prototype.clear = function () {
    
     CAV.Renderer.prototype.clear.call(this); this.context.clearRect(-this.halfWidth, -this.halfHeight, this.width, this.height); return this };
CAV.CanvasRenderer.prototype.render = function (a) {
    
    
    CAV.Renderer.prototype.render.call(this, a); var b, c, d, e, f; this.clear(); this.context.lineJoin = "round"; this.context.lineWidth = 1; for (b = a.meshes.length - 1; b >= 0; b--)if (c = a.meshes[b], c.visible) {
    
    
        c.update(a.lights, true); for (d = c.geometry.triangles.length - 1; d >= 0; d--)e = c.geometry.triangles[d], f = e.color.format(), this.context.beginPath(), this.context.moveTo(e.a.position[0], e.a.position[1]), this.context.lineTo(e.b.position[0], e.b.position[1]), this.context.lineTo(e.c.position[0],
            e.c.position[1]), this.context.closePath(), this.context.strokeStyle = f, this.context.fillStyle = f, this.context.stroke(), this.context.fill()
    } return this
};

export function Victor(container, anitOut) {
    
    
    let J, l

    if (!!document.createElement("canvas").getContext) {
    
    
        var t = {
    
    
            width: 1.5,
            height: 1.5,
            depth: 10,
            segments: 12,
            slices: 6,
            xRange: 0.8,
            yRange: 0.1,
            zRange: 1,
            ambient: "#525252",
            diffuse: "#FFFFFF",
            speed: 0.0002
        };
        var G = {
    
    
            count: 2,
            xyScalar: 1,
            zOffset: 100,
            ambient: "#002c4a",
            diffuse: "#005584",
            speed: 0.001,
            gravity: 1200,
            dampening: 0.95,
            minLimit: 10,
            maxLimit: null,
            minDistance: 20,
            maxDistance: 400,
            autopilot: false,
            draw: false,
            bounds: CAV.Vector3.create(),
            step: CAV.Vector3.create(Math.randomInRange(0.2, 1), Math.randomInRange(0.2, 1), Math.randomInRange(0.2, 1))
        };
        var m = "canvas";
        var E = "svg";
        var x = {
    
    
            renderer: m
        };
        var i, n = Date.now();
        var L = CAV.Vector3.create();
        var k = CAV.Vector3.create();
        var z = document.getElementById(container || "container");
        var w = document.getElementById(anitOut || "anitOut");
        var D, I, h, q, y;
        var g;
        var r;

        function C() {
    
    
            F();
            p();
            s();
            B();
            v();
            K(z.offsetWidth, z.offsetHeight);
            o()
        }

        function F() {
    
    
            g = new CAV.CanvasRenderer();
            H(x.renderer)
        }

        function H(N) {
    
    
            if (D) {
    
    
                w.removeChild(D.element)
            }
            switch (N) {
    
    
                case m:
                    D = g;
                    break
            }
            D.setSize(z.offsetWidth, z.offsetHeight);
            w.appendChild(D.element)
        }

        function p() {
    
    
            I = new CAV.Scene()
        }

        function s() {
    
    
            I.remove(h);
            D.clear();
            q = new CAV.Plane(t.width * D.width, t.height * D.height, t.segments, t.slices);
            y = new CAV.Material(t.ambient, t.diffuse);
            h = new CAV.Mesh(q, y);
            I.add(h);
            var N, O;
            for (N = q.vertices.length - 1; N >= 0; N--) {
    
    
                O = q.vertices[N];
                O.anchor = CAV.Vector3.clone(O.position);
                O.step = CAV.Vector3.create(Math.randomInRange(0.2, 1), Math.randomInRange(0.2, 1), Math.randomInRange(0.2, 1));
                O.time = Math.randomInRange(0, Math.PIM2)
            }
        }

        function B() {
    
    
            var O, N;
            for (O = I.lights.length - 1; O >= 0; O--) {
    
    
                N = I.lights[O];
                I.remove(N)
            }
            D.clear();
            for (O = 0; O < G.count; O++) {
    
    
                N = new CAV.Light(G.ambient, G.diffuse);
                N.ambientHex = N.ambient.format();
                N.diffuseHex = N.diffuse.format();
                I.add(N);
                N.mass = Math.randomInRange(0.5, 1);
                N.velocity = CAV.Vector3.create();
                N.acceleration = CAV.Vector3.create();
                N.force = CAV.Vector3.create()
            }
        }

        function K(O, N) {
    
    
            D.setSize(O, N);
            CAV.Vector3.set(L, D.halfWidth, D.halfHeight);
            s()
        }

        function o() {
    
    
            i = Date.now() - n;
            u();
            M();
            requestAnimationFrame(o)
        }

        function u() {
    
    
            var Q, P, O, R, T, V, U, S = t.depth / 2;
            CAV.Vector3.copy(G.bounds, L);
            CAV.Vector3.multiplyScalar(G.bounds, G.xyScalar);
            CAV.Vector3.setZ(k, G.zOffset);
            for (R = I.lights.length - 1; R >= 0; R--) {
    
    
                T = I.lights[R];
                CAV.Vector3.setZ(T.position, G.zOffset);
                var N = Math.clamp(CAV.Vector3.distanceSquared(T.position, k), G.minDistance, G.maxDistance);
                var W = G.gravity * T.mass / N;
                CAV.Vector3.subtractVectors(T.force, k, T.position);
                CAV.Vector3.normalise(T.force);
                CAV.Vector3.multiplyScalar(T.force, W);
                CAV.Vector3.set(T.acceleration);
                CAV.Vector3.add(T.acceleration, T.force);
                CAV.Vector3.add(T.velocity, T.acceleration);
                CAV.Vector3.multiplyScalar(T.velocity, G.dampening);
                CAV.Vector3.limit(T.velocity, G.minLimit, G.maxLimit);
                CAV.Vector3.add(T.position, T.velocity)
            }
            for (V = q.vertices.length - 1; V >= 0; V--) {
    
    
                U = q.vertices[V];
                Q = Math.sin(U.time + U.step[0] * i * t.speed);
                P = Math.cos(U.time + U.step[1] * i * t.speed);
                O = Math.sin(U.time + U.step[2] * i * t.speed);
                CAV.Vector3.set(U.position, t.xRange * q.segmentWidth * Q, t.yRange * q.sliceHeight * P, t.zRange * S * O - S);
                CAV.Vector3.add(U.position, U.anchor)
            }
            q.dirty = true
        }

        function M() {
    
    
            D.render(I)
        }

        J = (O) => {
    
    
            var Q, N, S = O;
            var P = function (T) {
    
    
                for (Q = 0, l = I.lights.length; Q < l; Q++) {
    
    
                    N = I.lights[Q];
                    N.ambient.set(T);
                    N.ambientHex = N.ambient.format()
                }
            };
            var R = function (T) {
    
    
                for (Q = 0, l = I.lights.length; Q < l; Q++) {
    
    
                    N = I.lights[Q];
                    N.diffuse.set(T);
                    N.diffuseHex = N.diffuse.format()
                }
            };
            return {
    
    
                set: function () {
    
    
                    P(S[0]);
                    R(S[1])
                }
            }
        }

        function v() {
    
    
            window.addEventListener("resize", j)
        }

        function A(N) {
    
    
            CAV.Vector3.set(k, N.x, D.height - N.y);
            CAV.Vector3.subtract(k, L)
        }

        function j(N) {
    
    
            K(z.offsetWidth, z.offsetHeight);
            M()
        }
        C();
    }
    return J;
}

Webpack 配置

接下来我们来配置一下 Webpack 。我们先在项目根目录下创建一个文件,命名为 webpack.config.js
在它的内部我们写入如下代码:

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const {
    
     CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
    
    
    devServer: {
    
    
        static: path.join(__dirname, 'public'),
        host: '127.0.0.1',
        port: 3000,
    },
    resolve: {
    
    
        extensions: ['.js', '.jsx', '.json'],
        alias: {
    
    
            'Components': path.join(__dirname, 'src/Components'),
            'Views': path.join(__dirname, 'src/Views'),
            'Utils': path.join(__dirname, 'src/Utils')
        }
    },
    entry: {
    
    
        login: "./src/Views/Login/index.jsx",
    },
    output: {
    
    
        path: path.resolve(__dirname, "./build"),
        filename: "[name]/index.[chunkhash:8].js",
    },
    module: {
    
    
        rules: [
            {
    
    
                test: /\.(sa|sc|c)ss$/,
                use: [
                    'style-loader',
                    'css-loader',
                    'resolve-url-loader',
                    {
    
    
                        loader: 'sass-loader',
                        options: {
    
    
                            sourceMap: true
                        }
                    }
                ],
            },
            {
    
    
                test: /\.jsx?$/,
                exclude: /(node_modules|bower_components)/,
                use: {
    
    
                    loader: 'babel-loader',
                    options: {
    
    
                        presets: ['@babel/preset-react', '@babel/preset-env'],
                        plugins: ['@babel/plugin-proposal-class-properties']
                    }
                }
            },
            {
    
    
                test: /\.(png|jpg|gif)$/,
                use: [
                    {
    
    
                        loader: 'url-loader',
                        options: {
    
    
                            limit: 1024 //对图片的大小做限制,1kb
                        }
                    }
                ]
            }
        ],
    },
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
    
    
            filename: "login/index.html",
            chunks: ["login"],
            template: './public/index.html',

        }),
    ],
};

package.json 中,我们将调试用的脚本写好:

"scripts": {
    
    
    "build": "webpack --mode production",
    "start": "webpack serve --mode development --env development",
    "elect": "set NODE_ENV=development&&electron .",
}

致此,我们已完成项目的基本搭建。

猜你喜欢

转载自blog.csdn.net/qq_53126706/article/details/125067369