From 1bae31444952af3464e1b06fedecc86eb0df1773 Mon Sep 17 00:00:00 2001 From: kerms Date: Fri, 22 Mar 2024 14:06:06 +0800 Subject: [PATCH] initial commit --- .eslintrc.cjs | 18 ++ .gitignore | 31 +++ .prettierrc.json | 8 + README.md | 1 + auto-imports.d.ts | 9 + components.d.ts | 20 ++ env.d.ts | 1 + index.html | 13 ++ package.json | 46 ++++ postcss.config.js | 6 + src/App.vue | 64 ++++++ src/api/apiWifi.ts | 55 +++++ src/api/index.ts | 4 + src/assets/icon/cross.svg | 1 + src/assets/icon/home.svg | 1 + src/assets/icon/test.svg | 3 + src/assets/icon/wifi-0.svg | 1 + src/assets/icon/wifi-1.svg | 1 + src/assets/icon/wifi-2.svg | 1 + src/assets/icon/wifi-3.svg | 1 + src/assets/tailwind.css | 4 + src/components/InlineSvg.vue | 22 ++ src/composables/broadcastChannelDef.ts | 43 ++++ src/composables/websocket/websocketService.ts | 116 ++++++++++ src/composables/websocket/websocketWrapper.ts | 210 ++++++++++++++++++ src/composables/websocket/ws.sharedworker.ts | 39 ++++ src/i18n.ts | 21 ++ src/locales/en.ts | 3 + src/locales/zh.ts | 17 ++ src/main.ts | 16 ++ src/router/index.ts | 29 +++ src/stores/counter.ts | 12 + src/stores/websocket.ts | 11 + src/views/About.vue | 12 + src/views/Gpio.vue | 13 ++ src/views/Home.vue | 23 ++ src/views/Wifi.vue | 158 +++++++++++++ src/views/navigation/NavBar.vue | 156 +++++++++++++ tailwind.config.js | 12 + tsconfig.app.json | 27 +++ tsconfig.json | 11 + tsconfig.node.json | 19 ++ vite.config.ts | 77 +++++++ 43 files changed, 1336 insertions(+) create mode 100644 .eslintrc.cjs create mode 100644 .gitignore create mode 100644 .prettierrc.json create mode 100644 README.md create mode 100644 auto-imports.d.ts create mode 100644 components.d.ts create mode 100644 env.d.ts create mode 100644 index.html create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 src/App.vue create mode 100644 src/api/apiWifi.ts create mode 100644 src/api/index.ts create mode 100644 src/assets/icon/cross.svg create mode 100644 src/assets/icon/home.svg create mode 100644 src/assets/icon/test.svg create mode 100644 src/assets/icon/wifi-0.svg create mode 100644 src/assets/icon/wifi-1.svg create mode 100644 src/assets/icon/wifi-2.svg create mode 100644 src/assets/icon/wifi-3.svg create mode 100644 src/assets/tailwind.css create mode 100644 src/components/InlineSvg.vue create mode 100644 src/composables/broadcastChannelDef.ts create mode 100644 src/composables/websocket/websocketService.ts create mode 100644 src/composables/websocket/websocketWrapper.ts create mode 100644 src/composables/websocket/ws.sharedworker.ts create mode 100644 src/i18n.ts create mode 100644 src/locales/en.ts create mode 100644 src/locales/zh.ts create mode 100644 src/main.ts create mode 100644 src/router/index.ts create mode 100644 src/stores/counter.ts create mode 100644 src/stores/websocket.ts create mode 100644 src/views/About.vue create mode 100644 src/views/Gpio.vue create mode 100644 src/views/Home.vue create mode 100644 src/views/Wifi.vue create mode 100644 src/views/navigation/NavBar.vue create mode 100644 tailwind.config.js create mode 100644 tsconfig.app.json create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..1ca5824 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,18 @@ +/* eslint-env node */ +require('@rushstack/eslint-patch/modern-module-resolution') + +module.exports = { + root: true, + 'extends': [ + 'plugin:vue/vue3-essential', + 'eslint:recommended', + '@vue/eslint-config-typescript', + '@vue/eslint-config-prettier/skip-formatting' + ], + parserOptions: { + ecmaVersion: 'latest' + }, + rules: { + 'vue/multi-word-component-names': "off" + }, +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a3842d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/ +!.vscode/extensions.json +.idea/ +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo +package-lock.json diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..66e2335 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "semi": false, + "tabWidth": 2, + "singleQuote": true, + "printWidth": 100, + "trailingComma": "none" +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..64ef6a2 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# 允斯调试器的内嵌网页版上位机 \ No newline at end of file diff --git a/auto-imports.d.ts b/auto-imports.d.ts new file mode 100644 index 0000000..1d89ee8 --- /dev/null +++ b/auto-imports.d.ts @@ -0,0 +1,9 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +export {} +declare global { + +} diff --git a/components.d.ts b/components.d.ts new file mode 100644 index 0000000..1a27d34 --- /dev/null +++ b/components.d.ts @@ -0,0 +1,20 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +export {} + +declare module 'vue' { + export interface GlobalComponents { + ElAutocomplete: typeof import('element-plus/es')['ElAutocomplete'] + ElButton: typeof import('element-plus/es')['ElButton'] + ElDrawer: typeof import('element-plus/es')['ElDrawer'] + ElForm: typeof import('element-plus/es')['ElForm'] + ElFormItem: typeof import('element-plus/es')['ElFormItem'] + ElInput: typeof import('element-plus/es')['ElInput'] + InlineSvg: typeof import('./src/components/InlineSvg.vue')['default'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + } +} diff --git a/env.d.ts b/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/index.html b/index.html new file mode 100644 index 0000000..a888544 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..47c463e --- /dev/null +++ b/package.json @@ -0,0 +1,46 @@ +{ + "name": "vue-project", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build --force", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", + "format": "prettier --write src/" + }, + "dependencies": { + "element-plus": "^2.6.1", + "mitt": "^3.0.1", + "pinia": "^2.1.7", + "vue": "^3.4.21", + "vue-i18n": "^9.10.2", + "vue-router": "^4.3.0" + }, + "devDependencies": { + "@rushstack/eslint-patch": "^1.3.3", + "@tsconfig/node20": "^20.1.2", + "@types/node": "^20.11.28", + "@vitejs/plugin-vue": "^5.0.4", + "@vue/eslint-config-prettier": "^8.0.0", + "@vue/eslint-config-typescript": "^12.0.0", + "@vue/tsconfig": "^0.5.1", + "autoprefixer": "^10.4.19", + "eslint": "^8.49.0", + "eslint-plugin-vue": "^9.17.0", + "lightningcss": "^1.24.1", + "npm-run-all2": "^6.1.2", + "postcss": "^8.4.38", + "prettier": "^3.0.3", + "tailwindcss": "^3.4.1", + "typescript": "~5.4.0", + "unplugin-auto-import": "^0.17.5", + "unplugin-vue-components": "^0.26.0", + "vite": "^5.1.6", + "vite-svg-loader": "^5.1.0", + "vue-tsc": "^2.0.6" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..1189d23 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/src/api/apiWifi.ts b/src/api/apiWifi.ts new file mode 100644 index 0000000..9ef8e61 --- /dev/null +++ b/src/api/apiWifi.ts @@ -0,0 +1,55 @@ +import {sendJsonMsg} from '@/composables/broadcastChannelDef' + + +import {type ApiJsonMsg} from '@/api' + +const WifiModuleID = 1; +enum WifiCmd { + UNKNOWN = 0, + WIFI_API_JSON_GET_AP_INFO, + WIFI_API_JSON_CONNECT, + WIFI_API_JSON_GET_SCAN, + WIFI_API_JSON_DISCONNECT, +} + +interface WifiMsgOut extends ApiJsonMsg { + ssid?: string; + password?: string; +} + +export function wifi_get_scan_list() { + const msg : WifiMsgOut = { + module: WifiModuleID, + cmd: WifiCmd.WIFI_API_JSON_GET_SCAN, + } + sendJsonMsg(msg); +} + +export function wifi_get_ap_info() { + const msg : WifiMsgOut = { + module: WifiModuleID, + cmd: WifiCmd.WIFI_API_JSON_GET_AP_INFO, + } + sendJsonMsg(msg); +} + +export function wifi_connect_to(ssid: string, password: string) { + const msg: WifiMsgOut = { + module: WifiModuleID, + cmd: WifiCmd.WIFI_API_JSON_CONNECT, + ssid: ssid, + password: password, + } + sendJsonMsg(msg); +} + +export interface WifiInfo { + rssi: number; + ssid: string; + mac: string; + wifiLogo?: string; +} + +export interface WifiList { + scan_list: Array; +} diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..582c7df --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,4 @@ +export interface ApiJsonMsg { + module: number; + cmd: number; +} \ No newline at end of file diff --git a/src/assets/icon/cross.svg b/src/assets/icon/cross.svg new file mode 100644 index 0000000..d7f7c13 --- /dev/null +++ b/src/assets/icon/cross.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icon/home.svg b/src/assets/icon/home.svg new file mode 100644 index 0000000..d16652e --- /dev/null +++ b/src/assets/icon/home.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icon/test.svg b/src/assets/icon/test.svg new file mode 100644 index 0000000..ac539a4 --- /dev/null +++ b/src/assets/icon/test.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/icon/wifi-0.svg b/src/assets/icon/wifi-0.svg new file mode 100644 index 0000000..6f127bc --- /dev/null +++ b/src/assets/icon/wifi-0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icon/wifi-1.svg b/src/assets/icon/wifi-1.svg new file mode 100644 index 0000000..a2a6597 --- /dev/null +++ b/src/assets/icon/wifi-1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icon/wifi-2.svg b/src/assets/icon/wifi-2.svg new file mode 100644 index 0000000..9c19b2e --- /dev/null +++ b/src/assets/icon/wifi-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icon/wifi-3.svg b/src/assets/icon/wifi-3.svg new file mode 100644 index 0000000..af2f167 --- /dev/null +++ b/src/assets/icon/wifi-3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/tailwind.css b/src/assets/tailwind.css new file mode 100644 index 0000000..adbf45b --- /dev/null +++ b/src/assets/tailwind.css @@ -0,0 +1,4 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; +@tailwind variants; \ No newline at end of file diff --git a/src/components/InlineSvg.vue b/src/components/InlineSvg.vue new file mode 100644 index 0000000..e95bfe5 --- /dev/null +++ b/src/components/InlineSvg.vue @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/src/composables/broadcastChannelDef.ts b/src/composables/broadcastChannelDef.ts new file mode 100644 index 0000000..2f90e6e --- /dev/null +++ b/src/composables/broadcastChannelDef.ts @@ -0,0 +1,43 @@ +import {type ApiJsonMsg} from "@/api" +import {getWebsocketService} from "@/composables/websocket/websocketService"; + +export const toServer = new BroadcastChannel("toServer"); +export const toClient = new BroadcastChannel("toClient"); +export const toWebsocketCtrl = new BroadcastChannel("toWebsocketCtrl"); +export const toClientCtrl = new BroadcastChannel("toClientCtrl"); + +export enum ControlMsgType { + WS_EVENT = "WS_EVENT", + WS_SET_HOST = "WS_SET_HOST", + WS_GET_STATE = "WS_GET_STATE", +} + +export enum ControlEvent { + DISCONNECTED = "DISCONNECTED", + LOADED = "LOADED", + CONNECTED = "CONNECTED", + CONNECTING = "CONNECTING", +} + +export interface ControlMsg { + type: ControlMsgType, + data: ControlEvent | string, +} + +export interface ServerMsg { + type: "json" | "binary" + data: object +} + +export function sendJsonMsg(apiJsonMsg: ApiJsonMsg) { + const msg: ServerMsg = { + type: "json", + data: apiJsonMsg, + }; + getWebsocketService().send(msg); + // toServer.postMessage(msg); +} + +export function sendBinMsg(msg: ApiJsonMsg) { + // toServer.postMessage(JSON.stringify(msg)); +} diff --git a/src/composables/websocket/websocketService.ts b/src/composables/websocket/websocketService.ts new file mode 100644 index 0000000..c81de5e --- /dev/null +++ b/src/composables/websocket/websocketService.ts @@ -0,0 +1,116 @@ +import MyWorker from '@/composables/websocket/ws.sharedworker?sharedworker&inline' +import {WebsocketWrapper} from "@/composables/websocket/websocketWrapper"; +import type {ControlMsg, ServerMsg} from "@/composables/broadcastChannelDef"; +import {ControlMsgType, toClient, toClientCtrl} from "@/composables/broadcastChannelDef"; + +const toServer = new BroadcastChannel("toServer"); + +export interface IWebsocketService { + init(host: string, + msgCallback: (msg: ServerMsg) => any, + ctrlCallback: (msg: ControlMsg) => any) + : void; + + deinit(): void; + + send(msg: ServerMsg): void; +} + +/** + * Websocket that run in a shared worker, shared across tabs + */ +class WebsocketShared implements IWebsocketService{ + private static instance: IWebsocketService; + + private worker: SharedWorker; + private msgCallback: (msg: ServerMsg) => any; + + private ctrlCallback: (msg: ControlMsg) => any; + + public static getInstance(): IWebsocketService { + if (!WebsocketShared.instance) { + WebsocketShared.instance = new WebsocketShared(); + } + return WebsocketShared.instance; + } + + private constructor() { + console.log("Shared Websocket init") + this.msgCallback = () => {} + this.ctrlCallback = () => {} + this.messageEventProxy = this.messageEventProxy.bind(this); + this.controlEventProxy = this.controlEventProxy.bind(this); + this.worker = new MyWorker(); + } + + init(host: string, msgCallback: (msg: ServerMsg) => any, ctrlCallback: (msg: ControlMsg) => any): void { + console.log("webworker init") + toClient.removeEventListener("message", this.messageEventProxy); + toClientCtrl.removeEventListener("message", this.controlEventProxy); + this.msgCallback = msgCallback; + this.ctrlCallback = ctrlCallback; + toClient.addEventListener("message", this.messageEventProxy); + toClientCtrl.addEventListener("message", this.controlEventProxy); + // this.worker.port.onmessage = this.controlEventProxy; + this.worker.port.postMessage({type: ControlMsgType.WS_SET_HOST, data: host} as ControlMsg) + } + + deinit(): void { + toClient.removeEventListener("message", this.messageEventProxy); + toClientCtrl.removeEventListener("message", this.controlEventProxy); + } + + + send(msg: ServerMsg): void { + console.log("Websocket Service send (not really)", msg) + toServer.postMessage(msg); + } + + messageEventProxy(ev: MessageEvent) { + this.msgCallback(ev.data); + } + + controlEventProxy(ev: MessageEvent) { + this.ctrlCallback(ev.data); + } +} + +class WebsocketClassic implements IWebsocketService{ + private static instance: IWebsocketService; + private socket: WebsocketWrapper; + private static count: number = 0; + + public static getInstance(): IWebsocketService { + if (!WebsocketClassic.instance) { + WebsocketClassic.instance = new WebsocketClassic(); + } + return WebsocketClassic.instance; + } + + private constructor() { + this.socket = new WebsocketWrapper(); + WebsocketClassic.count++; + } + + init(host: string, msgCallback: (ev: ServerMsg) => any, ctrlCallback: (msg: ControlMsg) => any): void { + console.log("Websocket Service INIT called", WebsocketClassic.count); + + this.socket.init(host, msgCallback, ctrlCallback); + } + + deinit(): void { + + } + + send(msg: ServerMsg): void { + this.socket.send(msg); + } +} + +export function getWebsocketService(): IWebsocketService { + if (typeof SharedWorker !== 'undefined') { + return WebsocketShared.getInstance(); + } else { + return WebsocketClassic.getInstance(); + } +} diff --git a/src/composables/websocket/websocketWrapper.ts b/src/composables/websocket/websocketWrapper.ts new file mode 100644 index 0000000..c7613b0 --- /dev/null +++ b/src/composables/websocket/websocketWrapper.ts @@ -0,0 +1,210 @@ +import type {ServerMsg, ControlMsg} from "@/composables/broadcastChannelDef"; +import {ControlEvent, ControlMsgType} from "@/composables/broadcastChannelDef"; + + +interface IWebsocket { + + close(): void; + + send(msg: ServerMsg): void; +} + +class WebsocketDummy implements IWebsocket { + close(): void { + + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + send(msg: ServerMsg) { + + } +} + +class OneTimeWebsocket implements IWebsocket { + private readonly heartBeatTimeout: number = 2; + private readonly host: string; + private readonly intervalId: number; + private readonly msgCallback: (ev: ServerMsg) => any; + private readonly ctrlCallback: (msg: ControlMsg) => any; + private readonly closeCallback: () => void; + private socket: WebSocket; + private heartBeatTimeCount: number; + private stoped: boolean; + + constructor(host: string, + msgCallback: (ev: ServerMsg) => any, + ctrlCallback: (msg: ControlMsg) => any, + closeCallback: () => void + ) { + this.host = host; + this.stoped = false; + this.msgCallback = msgCallback; + this.ctrlCallback = ctrlCallback; + this.closeCallback = closeCallback; + this.heartBeatTimeCount = 0; + this.intervalId = 0; + + this.socket = new WebSocket(`ws://${this.host}/ws`); + this.setSocketProp(); + + this.intervalId = setInterval(() => { + if (this.heartBeatTimeCount > this.heartBeatTimeout) { + /* did not receive packet "heartBeatTimeout" times, + * connection may be lost: close the socket */ + this.socket.close(); + console.log("interval: ", this.heartBeatTimeCount, "state: ", this.socket.readyState); + } + + this.heartBeatTimeCount++; + }, 2000); + } + + private setSocketProp() { + this.socket.binaryType = 'arraybuffer' + this.heartBeatTimeCount = 0; + + this.socket.onmessage = (ev) => { + this.heartBeatTimeCount = 0; + if (!ev.data) + return + + const msg: ServerMsg = { + data: {}, + type: "json", + } + this.msgCallback(msg); + if (typeof ev.data === "string") { + try { + msg.data = JSON.parse(ev.data); + this.msgCallback(msg); + } catch (e) { + return; + } + } else { + console.log(typeof ev.data); + } + } + + this.socket.onclose = () => { + console.log('WebSocket Disconnected'); + + clearInterval(this.intervalId); + this.socket.onclose = null + this.socket.onopen = null + this.socket.onerror = null + this.socket.onmessage = null; + + const msg: ControlMsg = { + type: ControlMsgType.WS_EVENT, + data: ControlEvent.DISCONNECTED, + } + this.ctrlCallback(msg); + this.closeCallback(); + }; + + this.socket.onerror = (error) => { + console.error('WebSocket Error', error); + this.socket.close(); + }; + + this.socket.onopen = ev => { + console.log('WebSocket Connected'); + if (this.stoped) { + this.close(); + return; + } + this.heartBeatTimeCount = 0; + const msg: ControlMsg = { + type: ControlMsgType.WS_EVENT, + data: ControlEvent.CONNECTED, + } + this.ctrlCallback(msg); + }; + + const msg: ControlMsg = { + type: ControlMsgType.WS_EVENT, + data: ControlEvent.CONNECTING, + } + this.ctrlCallback(msg); + } + + close() { + this.stoped = true; + this.socket.close(); + } + + send(msg: ServerMsg) { + if (this.socket.readyState !== WebSocket.OPEN) { + return; + } + + console.log('WebSocket proxies data ', msg); + if (msg.type === "binary") { + // this.socket.send(msg.data); + } else if (msg.type === "json") { + this.socket.send(JSON.stringify(msg.data)); + } + } +} + +export class WebsocketWrapper { + private socket: IWebsocket; + private msgCallback: (ev: ServerMsg) => any; + private ctrlCallback: (msg: ControlMsg) => any; + private timeoutId: number; + private host: string; + private stoped: boolean; + + constructor() { + this.socket = new WebsocketDummy(); + this.msgCallback = () => {}; + this.ctrlCallback = () => {}; + this.timeoutId = 0; + this.host = ""; + this.stoped = false; + + this.closeCallback = this.closeCallback.bind(this); + } + + init(host: string, + msgCallback: (ev: ServerMsg) => any, + ctrlCallback: (msg: ControlMsg) => any) + { + if (this.host === host) { + return; + } + this.newConnection(host, msgCallback, ctrlCallback); + } + + private newConnection(host: string, + msgCallback: (ev: ServerMsg) => any, + ctrlCallback: (msg: ControlMsg) => any) + { + this.socket.close(); + this.host = host; + this.msgCallback = msgCallback; + this.ctrlCallback = ctrlCallback; + this.socket = new OneTimeWebsocket(host, this.msgCallback, this.ctrlCallback, this.closeCallback); + + } + + private closeCallback() { + if (this.stoped) { + return; + } + this.timeoutId = setTimeout(() => + this.newConnection(this.host, this.msgCallback, this.ctrlCallback), + 2000); + } + + deinit() { + this.socket.close(); + this.stoped = true; + clearTimeout(this.timeoutId); + } + + send(msg: ServerMsg) { + console.log('WebSocket send: not ready', msg); + // this.socket.send(msg) + } +} diff --git a/src/composables/websocket/ws.sharedworker.ts b/src/composables/websocket/ws.sharedworker.ts new file mode 100644 index 0000000..c35d43f --- /dev/null +++ b/src/composables/websocket/ws.sharedworker.ts @@ -0,0 +1,39 @@ +declare const self: SharedWorkerGlobalScope; + +import {WebsocketWrapper} from "@/composables/websocket/websocketWrapper"; +import type {ControlMsg, ServerMsg} from "@/composables/broadcastChannelDef"; +import {ControlEvent, ControlMsgType, toClient, toClientCtrl, toServer} from "@/composables/broadcastChannelDef"; + +const websocket = new WebsocketWrapper(); +let host = ""; + +function ctrlBroadcast(msg: ControlMsg) { + toClientCtrl.postMessage(msg); +} + +function msgBroadcast(msg: ServerMsg) { + toClient.postMessage(msg); +} + +// self.onconnect +self.onconnect = function(event) { + const port = event.ports[0]; + port.onmessage = function (e: MessageEvent) { + console.log('Received message in SharedWorker:', e.data); + if (e.data.type === ControlMsgType.WS_SET_HOST) { + if (host === "" && e.data.data !== "") { + host = e.data.data; + websocket.init(host, msgBroadcast, ctrlBroadcast); + } + } + }; + const msg: ControlMsg = { + type: ControlMsgType.WS_EVENT, + data: ControlEvent.LOADED, + } + port.postMessage(msg); +}; + +toServer.onmessage = (ev: MessageEvent) => { + websocket.send(ev.data); +}; diff --git a/src/i18n.ts b/src/i18n.ts new file mode 100644 index 0000000..2fd3a6a --- /dev/null +++ b/src/i18n.ts @@ -0,0 +1,21 @@ +import { createI18n } from 'vue-i18n'; +import zh from '@/locales/zh' +import en from '@/locales/en' + +// const locale = localStorage.getItem('lang') || 'zh'; +const locale = 'zh'; + +console.log("langggggg:", locale); + +const i18n = createI18n({ + globalInjection: true, + legacy: false, + locale: locale, + fallbackLocale: 'zh', + messages: { + zh, + // en, + } +}); + +export default i18n; diff --git a/src/locales/en.ts b/src/locales/en.ts new file mode 100644 index 0000000..9715b27 --- /dev/null +++ b/src/locales/en.ts @@ -0,0 +1,3 @@ +export default { + disconnected: "disconnected" +}; \ No newline at end of file diff --git a/src/locales/zh.ts b/src/locales/zh.ts new file mode 100644 index 0000000..80defde --- /dev/null +++ b/src/locales/zh.ts @@ -0,0 +1,17 @@ +export default { + DISCONNECTED: "未连接", + CONNECTED: "已连接", + CONNECTING: "连接中", + + WS: { + DISCONNECTED: "未连接", + CONNECTED: "已连接", + CONNECTING: "连接中", + }, + + PAGE: { + HOME: "主页", + ABOUT: "关于", + FEEDBACK: "反馈", + }, +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..e91a666 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,16 @@ +import '@/assets/tailwind.css' + +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import i18n from '@/i18n'; + +import App from './App.vue' +import router from './router' + +const app = createApp(App) + +app.use(createPinia()) +app.use(router) +app.use(i18n); + +app.mount('#app') diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..f24dc5b --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,29 @@ +import { createRouter, createWebHistory } from 'vue-router' +import Home from '@/views/Home.vue' +import Wifi from '@/views/Wifi.vue' + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/', + name: 'home', + component: Home + }, { + path: '/home:ext(.*)', + component: Home, + }, { + path: '/wifi:ext(.*)', + component: Wifi, + }, { + path: '/about:ext(.*)', + name: 'about', + // route level code-splitting + // this generates a separate chunk (About.[hash].js) for this route + // which is lazy-loaded when the route is visited. + component: () => import('@/views/About.vue') + } + ] +}) + +export default router diff --git a/src/stores/counter.ts b/src/stores/counter.ts new file mode 100644 index 0000000..b6757ba --- /dev/null +++ b/src/stores/counter.ts @@ -0,0 +1,12 @@ +import { ref, computed } from 'vue' +import { defineStore } from 'pinia' + +export const useCounterStore = defineStore('counter', () => { + const count = ref(0) + const doubleCount = computed(() => count.value * 2) + function increment() { + count.value++ + } + + return { count, doubleCount, increment } +}) diff --git a/src/stores/websocket.ts b/src/stores/websocket.ts new file mode 100644 index 0000000..4a2f39f --- /dev/null +++ b/src/stores/websocket.ts @@ -0,0 +1,11 @@ +import {defineStore} from "pinia"; +import {ControlEvent} from "@/composables/broadcastChannelDef"; + +export const useWsStore = defineStore('websocket', { + state: () => { + return { + state: ControlEvent.DISCONNECTED as ControlEvent, + dummy_var: 1 as number, + }; + }, +}); diff --git a/src/views/About.vue b/src/views/About.vue new file mode 100644 index 0000000..7d99a3d --- /dev/null +++ b/src/views/About.vue @@ -0,0 +1,12 @@ + + + + diff --git a/src/views/Gpio.vue b/src/views/Gpio.vue new file mode 100644 index 0000000..95080ed --- /dev/null +++ b/src/views/Gpio.vue @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/src/views/Home.vue b/src/views/Home.vue new file mode 100644 index 0000000..50aff6c --- /dev/null +++ b/src/views/Home.vue @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/src/views/Wifi.vue b/src/views/Wifi.vue new file mode 100644 index 0000000..eda250f --- /dev/null +++ b/src/views/Wifi.vue @@ -0,0 +1,158 @@ + + + + + + diff --git a/src/views/navigation/NavBar.vue b/src/views/navigation/NavBar.vue new file mode 100644 index 0000000..05eb146 --- /dev/null +++ b/src/views/navigation/NavBar.vue @@ -0,0 +1,156 @@ + + + + + \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..a28ce71 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,12 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{vue,js,ts,jsx,tsx}", + ], + theme: { + extend: {}, + }, + plugins: [], +} + diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..86dc256 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,27 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": [ + "env.d.ts", + "src/**/*", + "src/**/*.vue", + "src/**/*.ts", + "src/**/*.d.ts", + "src/**/*.tsx", + "node_modules/@vue/runtime-core/dist/runtime-core.d.ts" + ], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + "lib": [ + "DOM", /* for document.* */ + "WebWorker", /* for *WorkerGlobalScope */ + "ESNext" + ] + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..66b5e57 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..f094063 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,19 @@ +{ + "extends": "@tsconfig/node20/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "nightwatch.conf.*", + "playwright.config.*" + ], + "compilerOptions": { + "composite": true, + "noEmit": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node"] + } +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..d59fc90 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,77 @@ +import { fileURLToPath, URL } from 'node:url' +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import svgLoader from "vite-svg-loader"; + + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + AutoImport({ + resolvers: [ElementPlusResolver()], + }), + Components({ + resolvers: [ElementPlusResolver()], + }), + svgLoader(), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + }, + build: { + outDir: '/tmp/zhuang/dap-web-dist/', + emptyOutDir: true, + cssMinify: 'lightningcss', + rollupOptions: { + output: { + assetFileNames: (assetInfo) => { + if (!assetInfo || !assetInfo.name) { + return 'default-filename.ext'; + } + const info = assetInfo.name.split("."); + let extType = info[info.length - 1]; + if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) { + extType = "img"; + } else if (/woff|woff2/.test(extType)) { + extType = "css"; + } else if (/css/.test(extType)) { + extType = "css"; + return "style.css" + } + // return `[name]-[hash][extname]`; + return `[name][extname]`; + }, + // chunkFileNames: "[name]-[hash].js", + chunkFileNames: "[name].js", + // entryFileNames: "[name]-[hash].js", + entryFileNames: (chunkInfo) => { + // console.log(chunkInfo) + return "script.js" + }, + + sourcemapFileNames: "map-[name].js", + // sanitizeFileName: "anit-[name].js", + // entryFileNames: (chunkInfo) => { + // console.log(chunkInfo) + // return `${chunkInfo.name}.js` + // }, + manualChunks(id) { + /* if (id.match('.*!/src/.*shared[a-zA-Z0-9-_]*[.](ts|js).*')) { + // Prevent bundling node_modules into common chunks + return 'bundle-shared'; + } + else */{ + // Prevent bundling node_modules into common chunks + return 'script' + } + }, + }, + } + }, +})