new client electron
This commit is contained in:
parent
3698e6e3c0
commit
4e6e616015
26 changed files with 12537 additions and 0 deletions
27
client-electron/electron/electron-env.d.ts
vendored
Normal file
27
client-electron/electron/electron-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/// <reference types="vite-plugin-electron/electron-env" />
|
||||
|
||||
declare namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
/**
|
||||
* The built directory structure
|
||||
*
|
||||
* ```tree
|
||||
* ├─┬─┬ dist
|
||||
* │ │ └── index.html
|
||||
* │ │
|
||||
* │ ├─┬ dist-electron
|
||||
* │ │ ├── main.js
|
||||
* │ │ └── preload.js
|
||||
* │
|
||||
* ```
|
||||
*/
|
||||
DIST: string
|
||||
/** /dist/ or /public/ */
|
||||
VITE_PUBLIC: string
|
||||
}
|
||||
}
|
||||
|
||||
// Used in Renderer process, expose in `preload.ts`
|
||||
interface Window {
|
||||
ipcRenderer: import('electron').IpcRenderer
|
||||
}
|
||||
60
client-electron/electron/main.ts
Normal file
60
client-electron/electron/main.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import { app, BrowserWindow } from 'electron'
|
||||
import path from 'node:path'
|
||||
|
||||
// The built directory structure
|
||||
//
|
||||
// ├─┬─┬ dist
|
||||
// │ │ └── index.html
|
||||
// │ │
|
||||
// │ ├─┬ dist-electron
|
||||
// │ │ ├── main.js
|
||||
// │ │ └── preload.js
|
||||
// │
|
||||
process.env.DIST = path.join(__dirname, '../dist')
|
||||
process.env.VITE_PUBLIC = app.isPackaged ? process.env.DIST : path.join(process.env.DIST, '../public')
|
||||
|
||||
|
||||
let win: BrowserWindow | null
|
||||
// 🚧 Use ['ENV_NAME'] avoid vite:define plugin - Vite@2.x
|
||||
const VITE_DEV_SERVER_URL = process.env['VITE_DEV_SERVER_URL']
|
||||
|
||||
function createWindow() {
|
||||
win = new BrowserWindow({
|
||||
icon: path.join(process.env.VITE_PUBLIC, 'electron-vite.svg'),
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
},
|
||||
})
|
||||
|
||||
// Test active push message to Renderer-process.
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
win?.webContents.send('main-process-message', (new Date).toLocaleString())
|
||||
})
|
||||
|
||||
if (VITE_DEV_SERVER_URL) {
|
||||
win.loadURL(VITE_DEV_SERVER_URL)
|
||||
} else {
|
||||
// win.loadFile('dist/index.html')
|
||||
win.loadFile(path.join(process.env.DIST, 'index.html'))
|
||||
}
|
||||
}
|
||||
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
// for applications and their menu bar to stay active until the user quits
|
||||
// explicitly with Cmd + Q.
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
win = null
|
||||
}
|
||||
})
|
||||
|
||||
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 (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
|
||||
app.whenReady().then(createWindow)
|
||||
121
client-electron/electron/preload.ts
Normal file
121
client-electron/electron/preload.ts
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { contextBridge, ipcRenderer } from 'electron'
|
||||
|
||||
// --------- Expose some API to the Renderer process ---------
|
||||
contextBridge.exposeInMainWorld('ipcRenderer', withPrototype(ipcRenderer))
|
||||
|
||||
// `exposeInMainWorld` can't detect attributes and methods of `prototype`, manually patching it.
|
||||
function withPrototype(obj: Record<string, any>) {
|
||||
const protos = Object.getPrototypeOf(obj)
|
||||
|
||||
for (const [key, value] of Object.entries(protos)) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) continue
|
||||
|
||||
if (typeof value === 'function') {
|
||||
// Some native APIs, like `NodeJS.EventEmitter['on']`, don't work in the Renderer process. Wrapping them into a function.
|
||||
obj[key] = function (...args: any) {
|
||||
return value.call(obj, ...args)
|
||||
}
|
||||
} else {
|
||||
obj[key] = value
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
// --------- Preload scripts loading ---------
|
||||
function domReady(
|
||||
condition: DocumentReadyState[] = ['complete', 'interactive']
|
||||
) {
|
||||
return new Promise(resolve => {
|
||||
if (condition.includes(document.readyState)) {
|
||||
resolve(true)
|
||||
} else {
|
||||
document.addEventListener('readystatechange', () => {
|
||||
if (condition.includes(document.readyState)) {
|
||||
resolve(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const safeDOM = {
|
||||
append(parent: HTMLElement, child: HTMLElement) {
|
||||
if (!Array.from(parent.children).find(e => e === child)) {
|
||||
parent.appendChild(child)
|
||||
}
|
||||
},
|
||||
remove(parent: HTMLElement, child: HTMLElement) {
|
||||
if (Array.from(parent.children).find(e => e === child)) {
|
||||
parent.removeChild(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* https://tobiasahlin.com/spinkit
|
||||
* https://connoratherton.com/loaders
|
||||
* https://projects.lukehaas.me/css-loaders
|
||||
* https://matejkustec.github.io/SpinThatShit
|
||||
*/
|
||||
function useLoading() {
|
||||
const className = `loaders-css__square-spin`
|
||||
const styleContent = `
|
||||
@keyframes square-spin {
|
||||
25% { transform: perspective(100px) rotateX(180deg) rotateY(0); }
|
||||
50% { transform: perspective(100px) rotateX(180deg) rotateY(180deg); }
|
||||
75% { transform: perspective(100px) rotateX(0) rotateY(180deg); }
|
||||
100% { transform: perspective(100px) rotateX(0) rotateY(0); }
|
||||
}
|
||||
.${className} > div {
|
||||
animation-fill-mode: both;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background: #fff;
|
||||
animation: square-spin 3s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite;
|
||||
}
|
||||
.app-loading-wrap {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #282c34;
|
||||
z-index: 9;
|
||||
}
|
||||
`
|
||||
const oStyle = document.createElement('style')
|
||||
const oDiv = document.createElement('div')
|
||||
|
||||
oStyle.id = 'app-loading-style'
|
||||
oStyle.innerHTML = styleContent
|
||||
oDiv.className = 'app-loading-wrap'
|
||||
oDiv.innerHTML = `<div class="${className}"><div></div></div>`
|
||||
|
||||
return {
|
||||
appendLoading() {
|
||||
safeDOM.append(document.head, oStyle)
|
||||
safeDOM.append(document.body, oDiv)
|
||||
},
|
||||
removeLoading() {
|
||||
safeDOM.remove(document.head, oStyle)
|
||||
safeDOM.remove(document.body, oDiv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const { appendLoading, removeLoading } = useLoading()
|
||||
domReady().then(appendLoading)
|
||||
|
||||
window.onmessage = ev => {
|
||||
ev.data.payload === 'removeLoading' && removeLoading()
|
||||
}
|
||||
|
||||
setTimeout(removeLoading, 4999)
|
||||
Loading…
Add table
Add a link
Reference in a new issue