Merge branch 'perf/ui-blocked' into feature/ui

This commit is contained in:
geekgeekrun
2024-03-10 21:44:14 +08:00
25 changed files with 576 additions and 510 deletions

View File

@@ -1,103 +0,0 @@
import * as path from 'node:path'
import * as os from 'node:os'
import * as fs from 'node:fs'
import type { InstalledBrowser } from '@puppeteer/browsers'
import { is } from '@electron-toolkit/utils'
import electron from 'electron'
import { saveLastUsedAndAvailableBrowserInfo, BrowserInfo } from './history-utils'
export const EXPECT_CHROMIUM_BUILD_ID = '113.0.5672.63'
const cacheDir = path.join(
os.homedir(),
'.geekgeekrun',
'cache'
)
const getPuppeteerManagerModule = async () => {
let puppeteerManager
if (is.dev) {
puppeteerManager = await import('@puppeteer/browsers')
} else {
puppeteerManager = (
await import(
'file://' +
path.resolve(
electron.app.getAppPath(),
'..',
'external-node-runtime-dependencies/index.mjs'
)
)
).puppeteerManager
}
return puppeteerManager
}
export const getExpectCachedPuppeteerExecutable = async (): Promise<BrowserInfo> => {
const puppeteerManager = await getPuppeteerManagerModule()
const executablePath = puppeteerManager.computeExecutablePath({
browser: puppeteerManager.Browser.CHROME,
cacheDir,
buildId: EXPECT_CHROMIUM_BUILD_ID
})
const browser = puppeteerManager.Browser.CHROME[0].toUpperCase() + puppeteerManager.Browser.CHROME.slice(1) + ' ' + EXPECT_CHROMIUM_BUILD_ID
return {
executablePath,
browser
}
}
export const checkCachedPuppeteerExecutable = async () => {
try {
const executablePath = (await getExpectCachedPuppeteerExecutable()).executablePath
return fs.existsSync(executablePath)
} catch {
// should limit [ERR_MODULE_NOT_FOUND]
return false
}
}
const checkAndDownloadPuppeteerExecutable = async (
options: {
downloadProgressCallback?: (downloadedBytes: number, totalBytes: number) => void
confirmContinuePromise?: Promise<void>
} = {}
) => {
const puppeteerManager = await getPuppeteerManagerModule()
let installedBrowser: InstalledBrowser
if (!(await checkCachedPuppeteerExecutable())) {
try {
await options.confirmContinuePromise
} catch {
throw new Error('USER_CANCEL_DOWNLOAD_PUPPETEER')
}
// maybe the exist installation is broken.
await puppeteerManager.uninstall({
cacheDir,
buildId: EXPECT_CHROMIUM_BUILD_ID,
browser: puppeteerManager.Browser.CHROME
})
installedBrowser = await puppeteerManager.install({
browser: puppeteerManager.Browser.CHROME,
cacheDir,
buildId: EXPECT_CHROMIUM_BUILD_ID,
downloadProgressCallback: options.downloadProgressCallback
})
} else {
installedBrowser = (
await puppeteerManager.getInstalledBrowsers({
cacheDir
})
).find((it) => it.buildId === EXPECT_CHROMIUM_BUILD_ID)!
}
await saveLastUsedAndAvailableBrowserInfo({
executablePath: installedBrowser.executablePath,
browser: installedBrowser.browser[0].toUpperCase() + installedBrowser.browser.slice(1) + ' ' + EXPECT_CHROMIUM_BUILD_ID
})
return installedBrowser
}
export default checkAndDownloadPuppeteerExecutable

View File

@@ -1,66 +0,0 @@
import { is } from '@electron-toolkit/utils'
import electron from 'electron'
import * as os from 'node:os'
import * as fs from 'node:fs'
import path from 'node:path'
import { EXPECT_CHROMIUM_BUILD_ID } from './check-and-download-puppeteer-executable'
import {
getExecutableFileVersion
} from '@geekgeekrun/utils/windows-only/file.mjs'
import { BrowserInfo } from './history-utils'
export default async function findAndLocateExistedChromiumExecutable(): Promise<BrowserInfo> {
const exceptChromiumMainVersion = Number(EXPECT_CHROMIUM_BUILD_ID.split('.')[0])
// For windows, try to find Edge(chromium)
if (os.platform() === 'win32') {
// TODO: handle windows
const edgeExecutableLocation = path.join(
process.env['ProgramFiles(x86)']!,
'Microsoft/Edge/Application',
'msedge.exe'
)
if (
fs.existsSync(edgeExecutableLocation)
) {
try {
const version = await getExecutableFileVersion(edgeExecutableLocation)
const mainVersion = Number(version.split('.')[0])
if ( mainVersion >= exceptChromiumMainVersion) {
return {
executablePath: edgeExecutableLocation,
browser: `Edge ${version}`
}
}
} catch(err) {
console.log(err)
}
}
}
// For other, use findChrome
let findChrome: typeof import('find-chrome-bin').findChrome
if (is.dev) {
findChrome = (await import('find-chrome-bin')).findChrome
} else {
findChrome = (
await import(
'file://' +
path.resolve(
electron.app.getAppPath(),
'..',
'external-node-runtime-dependencies/index.mjs'
)
)
).findChromeBin.findChrome
}
const targetBrowser = await findChrome({
min: exceptChromiumMainVersion
})
if (!targetBrowser?.executablePath) {
throw new Error('NO_EXPECT_CHROMIUM_FOUND')
}
return {
executablePath: targetBrowser.executablePath,
browser: targetBrowser.browser
}
}

View File

@@ -1,53 +1,14 @@
import { app } from 'electron'
import checkAndDownloadPuppeteerExecutable, {
checkCachedPuppeteerExecutable,
getExpectCachedPuppeteerExecutable
} from './check-and-download-puppeteer-executable'
import { checkAndDownloadPuppeteerExecutable } from './utils/puppeteer-executable/index'
import * as fs from 'fs'
import { pipeWriteRegardlessError } from '../utils/pipe'
import {
removeLastUsedAndAvailableBrowserPath,
getLastUsedAndAvailableBrowser,
saveLastUsedAndAvailableBrowserInfo,
BrowserInfo
} from './history-utils'
import findAndLocateExistedChromiumExecutable from './check-and-locate-existed-chromium-executable'
import {
sleep
} from '@geekgeekrun/utils/sleep.mjs'
import { sleep } from '@geekgeekrun/utils/sleep.mjs'
export enum DOWNLOAD_ERROR_EXIT_CODE {
NO_ERROR = 0,
DOWNLOAD_ERROR = 1
}
export const getAnyAvailablePuppeteerExecutable = async (): Promise<BrowserInfo | null> => {
const lastUsedOne = await getLastUsedAndAvailableBrowser()
if (lastUsedOne) {
return lastUsedOne
}
// find existed browser - the one maybe actively installed by user or ship with os like Edge on windows
try {
const existedOne = (await findAndLocateExistedChromiumExecutable())
await saveLastUsedAndAvailableBrowserInfo(existedOne)
// save its path
return existedOne
} catch {
console.log('no existed browser path found')
}
// find existed browser - the fallback one
if (await checkCachedPuppeteerExecutable()) {
const cachedOne = await getExpectCachedPuppeteerExecutable()
await saveLastUsedAndAvailableBrowserInfo(cachedOne)
return cachedOne
}
// if no one available, then return null and remove last used browser
await removeLastUsedAndAvailableBrowserPath()
return null
}
export const checkAndDownloadDependenciesForInit = async () => {
process.on('disconnect', () => app.exit())
app.dock?.hide()

View File

@@ -4,8 +4,8 @@ import * as fs from 'fs'
import * as fsPromise from 'fs/promises'
export interface BrowserInfo {
browser: string;
executablePath: string;
browser: string
executablePath: string
}
const runtimeFolderPath = path.join(os.homedir(), '.geekgeekrun')
@@ -27,7 +27,7 @@ export const getLastUsedAndAvailableBrowser = async (): Promise<BrowserInfo | nu
}
try {
const fileContent = (await fsPromise.readFile(lastUsedBrowserRecordFilePath)).toString()
const [path, browser] = fileContent.split('\n').map(it => it.trim())
const [path, browser] = fileContent.split('\n').map((it) => it.trim())
if (!path || !fs.existsSync(path)) {
await removeLastUsedAndAvailableBrowserPath()
return null
@@ -48,10 +48,8 @@ export const saveLastUsedAndAvailableBrowserInfo = async (browserInfo: BrowserIn
await fsPromise.mkdir(runtimeFolderPath)
}
await fsPromise.writeFile(
lastUsedBrowserRecordFilePath, [
browserInfo.executablePath,
browserInfo.browser
].join('\n')
lastUsedBrowserRecordFilePath,
[browserInfo.executablePath, browserInfo.browser].join('\n')
)
} catch {
console.warn('lastUsedBrowserRecordFile write error')

View File

@@ -0,0 +1,213 @@
import * as path from 'node:path'
import * as os from 'node:os'
import * as fs from 'node:fs'
import type { InstalledBrowser } from '@puppeteer/browsers'
import {
saveLastUsedAndAvailableBrowserInfo,
BrowserInfo,
getLastUsedAndAvailableBrowser,
removeLastUsedAndAvailableBrowserPath
} from '../browser-history'
import { getExecutableFileVersion } from '@geekgeekrun/utils/windows-only/file.mjs'
import CheckAndLocateExistedChromiumExecutableWorker from './worker/find-and-locate-existed-chromium-executable?nodeWorker&url'
import { type Worker } from 'worker_threads'
const getPuppeteerManagerModule = async () => {
const electron = await import('electron')
let puppeteerManager
if (process.env.NODE_ENV === 'development') {
puppeteerManager = await import('@puppeteer/browsers')
} else {
puppeteerManager = (
await import(
'file://' +
path.resolve(
electron.app.getAppPath(),
'..',
'external-node-runtime-dependencies/index.mjs'
)
)
).puppeteerManager
}
return puppeteerManager
}
const EXPECT_CHROMIUM_BUILD_ID = '113.0.5672.63'
const cacheDir = path.join(os.homedir(), '.geekgeekrun', 'cache')
const getExpectCachedPuppeteerExecutable = async (): Promise<BrowserInfo> => {
const puppeteerManager = await getPuppeteerManagerModule()
const executablePath = puppeteerManager.computeExecutablePath({
browser: puppeteerManager.Browser.CHROME,
cacheDir,
buildId: EXPECT_CHROMIUM_BUILD_ID
})
const browser =
puppeteerManager.Browser.CHROME[0].toUpperCase() +
puppeteerManager.Browser.CHROME.slice(1) +
' ' +
EXPECT_CHROMIUM_BUILD_ID
return {
executablePath,
browser
}
}
const checkCachedPuppeteerExecutable = async () => {
try {
const executablePath = (await getExpectCachedPuppeteerExecutable()).executablePath
return fs.existsSync(executablePath)
} catch {
// should limit [ERR_MODULE_NOT_FOUND]
return false
}
}
export const checkAndDownloadPuppeteerExecutable = async (
options: {
downloadProgressCallback?: (downloadedBytes: number, totalBytes: number) => void
confirmContinuePromise?: Promise<void>
} = {}
) => {
const puppeteerManager = await getPuppeteerManagerModule()
let installedBrowser: InstalledBrowser
if (!(await checkCachedPuppeteerExecutable())) {
try {
await options.confirmContinuePromise
} catch {
throw new Error('USER_CANCEL_DOWNLOAD_PUPPETEER')
}
// maybe the exist installation is broken.
await puppeteerManager.uninstall({
cacheDir,
buildId: EXPECT_CHROMIUM_BUILD_ID,
browser: puppeteerManager.Browser.CHROME
})
installedBrowser = await puppeteerManager.install({
browser: puppeteerManager.Browser.CHROME,
cacheDir,
buildId: EXPECT_CHROMIUM_BUILD_ID,
downloadProgressCallback: options.downloadProgressCallback
})
} else {
installedBrowser = (
await puppeteerManager.getInstalledBrowsers({
cacheDir
})
).find((it) => it.buildId === EXPECT_CHROMIUM_BUILD_ID)!
}
await saveLastUsedAndAvailableBrowserInfo({
executablePath: installedBrowser.executablePath,
browser:
installedBrowser.browser[0].toUpperCase() +
installedBrowser.browser.slice(1) +
' ' +
EXPECT_CHROMIUM_BUILD_ID
})
return installedBrowser
}
export const getAnyAvailablePuppeteerExecutable = async (): Promise<BrowserInfo | null> => {
const lastUsedOne = await getLastUsedAndAvailableBrowser()
if (lastUsedOne) {
return lastUsedOne
}
// find existed browser - the one maybe actively installed by user or ship with os like Edge on windows
try {
const existedOne = await findAndLocateUserInstalledChromiumExecutable()
await saveLastUsedAndAvailableBrowserInfo(existedOne)
// save its path
return existedOne
} catch (err) {
console.error(err)
console.log('no existed browser path found')
}
// find existed browser - the fallback one
if (await checkCachedPuppeteerExecutable()) {
const cachedOne = await getExpectCachedPuppeteerExecutable()
await saveLastUsedAndAvailableBrowserInfo(cachedOne)
return cachedOne
}
// if no one available, then return null and remove last used browser
await removeLastUsedAndAvailableBrowserPath()
return null
}
export async function findAndLocateUserInstalledChromiumExecutableSync(): Promise<BrowserInfo> {
const electron = await import('electron')
const exceptChromiumMainVersion = Number(EXPECT_CHROMIUM_BUILD_ID.split('.')[0])
// For windows, try to find Edge(chromium)
if (os.platform() === 'win32') {
// TODO: handle windows
const edgeExecutableLocation = path.join(
process.env['ProgramFiles(x86)']!,
'Microsoft/Edge/Application',
'msedge.exe'
)
if (fs.existsSync(edgeExecutableLocation)) {
try {
const version = await getExecutableFileVersion(edgeExecutableLocation)
const mainVersion = Number(version.split('.')[0])
if (mainVersion >= exceptChromiumMainVersion) {
return {
executablePath: edgeExecutableLocation,
browser: `Edge ${version}`
}
}
} catch (err) {
console.log(err)
}
}
}
// For other, use findChrome
let findChrome: typeof import('find-chrome-bin').findChrome
if (process.env.NODE_ENV === 'development') {
findChrome = (await import('find-chrome-bin')).findChrome
} else {
findChrome = (
await import(
'file://' +
path.resolve(
electron.app.getAppPath(),
'..',
'external-node-runtime-dependencies/index.mjs'
)
)
).findChromeBin.findChrome
}
const targetBrowser = await findChrome({
min: exceptChromiumMainVersion
})
if (!targetBrowser?.executablePath) {
throw new Error('NO_EXPECT_CHROMIUM_FOUND')
}
return {
executablePath: targetBrowser.executablePath,
browser: targetBrowser.browser
}
}
export async function findAndLocateUserInstalledChromiumExecutable(): Promise<BrowserInfo> {
return new Promise((resolve, reject) => {
const worker: Worker = new CheckAndLocateExistedChromiumExecutableWorker()
worker.once('message', (data) => {
if (data.type === 'RESULT') {
resolve(data.data)
}
})
worker.once('message', (data) => {
if (data.type === 'ERROR') {
reject(data.error)
}
})
})
}

View File

@@ -0,0 +1,18 @@
import { parentPort } from 'node:worker_threads'
import { findAndLocateUserInstalledChromiumExecutableSync } from '../index'
;(async () => {
try {
const result = await findAndLocateUserInstalledChromiumExecutableSync()
parentPort?.postMessage({
type: 'RESULT',
data: result
})
} catch (error) {
parentPort?.postMessage({
type: 'ERROR',
error
})
}
process.exit(0)
})()

View File

@@ -3,8 +3,8 @@ import { app } from 'electron'
import { SyncHook, AsyncSeriesHook } from 'tapable'
import { readConfigFile } from '@geekgeekrun/geek-auto-start-chat-with-boss/runtime-file-utils.mjs'
import * as fs from 'fs'
import { pipeWriteRegardlessError } from './utils/pipe'
import { getAnyAvailablePuppeteerExecutable } from './CHECK_AND_DOWNLOAD_DEPENDENCIES'
import { pipeWriteRegardlessError } from '../utils/pipe'
import { getAnyAvailablePuppeteerExecutable } from '../CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable'
const { groupRobotAccessToken: dingTalkAccessToken } = readConfigFile('dingtalk.json')

View File

@@ -3,11 +3,6 @@ import { main, loginEventBus } from '@geekgeekrun/launch-bosszhipin-login-page-w
import { pipeWriteRegardlessError } from './utils/pipe'
import fs from "node:fs";
export enum DOWNLOAD_ERROR_EXIT_CODE {
NO_ERROR = 0,
DOWNLOAD_ERROR = 1
}
export const launchBossZhipinLoginPageWithPreloadExtension = async () => {
process.on('disconnect', () => app.exit())
app.dock?.hide()

View File

@@ -1,4 +1,4 @@
import { runAutoChat } from './flow/GEEK_AUTO_START_CHAT_WITH_BOSS'
import { runAutoChat } from './flow/GEEK_AUTO_START_CHAT_WITH_BOSS/index'
import { openSettingWindow } from './flow/OPEN_SETTING_WINDOW'
import { checkAndDownloadDependenciesForInit } from './flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/index'
import { launchBossZhipinLoginPageWithPreloadExtension } from './flow/LAUNCH_BOSS_ZHIPIN_LOGIN_PAGE_WITH_PRELOAD_EXTENSION'

View File

@@ -1,11 +1,9 @@
import { BrowserWindow, ipcMain, shell } from 'electron'
import path from 'path'
import * as childProcess from 'node:child_process'
import { is } from '@electron-toolkit/utils'
import {
ensureConfigFileExist,
ensureStorageFileExist,
configFileNameList,
readConfigFile,
writeConfigFile,
@@ -15,10 +13,8 @@ import {
import { ChildProcess } from 'child_process'
import * as JSONStream from 'JSONStream'
import { checkCookieListFormat } from '../../common/utils/cookie'
import {
DOWNLOAD_ERROR_EXIT_CODE,
getAnyAvailablePuppeteerExecutable
} from '../flow/CHECK_AND_DOWNLOAD_DEPENDENCIES'
import { getAnyAvailablePuppeteerExecutable } from '../flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/utils/puppeteer-executable/index'
import { DOWNLOAD_ERROR_EXIT_CODE } from '../flow/CHECK_AND_DOWNLOAD_DEPENDENCIES/index'
import { sleep } from '@geekgeekrun/utils/sleep.mjs'
let mainWindow: BrowserWindow | null = null
@@ -31,8 +27,8 @@ export function createMainWindow(): void {
autoHideMenuBar: true,
...(process.platform === 'linux'
? {
/* icon */
}
/* icon */
}
: {}),
webPreferences: {
preload: path.join(__dirname, '../preload/index.js'),
@@ -40,7 +36,7 @@ export function createMainWindow(): void {
}
})
is.dev && mainWindow.webContents.openDevTools()
process.env.NODE_ENV === 'development' && mainWindow.webContents.openDevTools()
mainWindow.on('ready-to-show', () => {
mainWindow.show()
@@ -53,7 +49,7 @@ export function createMainWindow(): void {
// HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production.
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
if (process.env.NODE_ENV === 'development' && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'))
@@ -70,7 +66,7 @@ export function createMainWindow(): void {
return readConfigFile(fileName)
})
const result = {
config: {},
config: {}
}
configFileNameList.forEach((fileName, index) => {
@@ -106,17 +102,28 @@ export function createMainWindow(): void {
// const currentExecutablePath = app.getPath('exe')
// console.log(currentExecutablePath)
ipcMain.handle('prepare-run-geek-auto-start-chat-with-boss', async () => {
mainWindow?.webContents.send('locating-puppeteer-executable')
const puppeteerExecutable = await getAnyAvailablePuppeteerExecutable()
if (!puppeteerExecutable) {
return Promise.reject('NEED_TO_CHECK_RUNTIME_DEPENDENCIES')
}
mainWindow?.webContents.send('puppeteer-executable-is-located')
})
let subProcessOfPuppeteer: ChildProcess | null = null
ipcMain.handle('run-geek-auto-start-chat-with-boss', async () => {
if (subProcessOfPuppeteer) {
return
}
const puppeteerExecutable = await getAnyAvailablePuppeteerExecutable()
if (!puppeteerExecutable) {
return Promise.reject('NEED_TO_CHECK_RUNTIME_DEPENDENCIES')
}
const subProcessEnv = {
...process.env,
MAIN_BOSSGEEKGO_UI_RUN_MODE: 'geekAutoStartWithBoss',
PUPPETEER_EXECUTABLE_PATH: (await getAnyAvailablePuppeteerExecutable())!.executablePath
PUPPETEER_EXECUTABLE_PATH: puppeteerExecutable.executablePath
}
subProcessOfPuppeteer = childProcess.spawn(process.argv[0], process.argv.slice(1), {
env: subProcessEnv,
@@ -171,7 +178,7 @@ export function createMainWindow(): void {
}
const subProcessEnv = {
...process.env,
MAIN_BOSSGEEKGO_UI_RUN_MODE: 'checkAndDownloadDependenciesForInit',
MAIN_BOSSGEEKGO_UI_RUN_MODE: 'checkAndDownloadDependenciesForInit'
}
subProcessOfCheckAndDownloadDependencies = childProcess.spawn(
process.argv[0],
@@ -194,7 +201,7 @@ export function createMainWindow(): void {
}
case 'PUPPETEER_DOWNLOAD_ENCOUNTER_ERROR': {
console.error(data)
break;
break
}
default: {
return

View File

@@ -1,6 +1,8 @@
<template>
<el-config-provider :locale="zhCn">
<RouterView />
<Suspense>
<RouterView />
</Suspense>
</el-config-provider>
</template>

View File

@@ -1,39 +0,0 @@
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import DependenciesSetupProgressIndicatorDialog from './index.vue'
export const mountGlobalDialog = (o: { dependenciesStatus: Record<string, boolean>, processWaitee? }) => {
const containerElId = `elForDependenciesSetupProgressIndicatorDialog`
if (document.getElementById(containerElId)) {
return
}
let containerEl: null | HTMLElement = (() => {
const el = document.createElement('div')
el.id = containerElId
return el
})()
document.body.append(containerEl)
const dispose = () => {
app?.unmount()
containerEl?.remove()
app = null
containerEl = null
}
let app: null | ReturnType<typeof createApp> = createApp(DependenciesSetupProgressIndicatorDialog, {
modelValue: true,
onClosed() {
dispose()
},
dispose,
dependenciesStatus: o?.dependenciesStatus,
processWaitee: o?.processWaitee
}).use(ElementPlus)
app.mount(containerEl)
return {
dispose
}
}

View File

@@ -1,106 +1,116 @@
<template>
<div
class="container"
class="flying-company-logo-list-container"
:style="{
gridTemplateColumns: `repeat(${colCount}, 1fr)`,
gridTemplateRows: `repeat(${rowCount}, 1fr)`
}"
>
<img
v-for="n in rowCount * colCount"
:key="n"
class="dot"
:src="currentIndexToSrcMap[n - 1]"
@animationiteration="
(ev) => {
handleAnimationiteration(ev, n - 1)
}
"
/>
</div>
ref="imageElContainer"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { logoQueueInjectKey } from './types'
const logoQueue = ref<string[]>([])
import { onMounted, ref } from 'vue'
const rowCount = 4
const colCount = 6
const currentIndexToSrcMap: string[] = ref([])
const logoQueue: HTMLImageElement[] = []
const imageElContainer = ref<HTMLElement>()
onMounted(async () => {
const res = (
await Promise.all(
[...Object.values(import.meta.glob('./resources/*.png', { as: 'url' }))].map((it) => it())
)
).map((url) => {
const img = new Image()
img.src = url
img.onanimationiteration = () => {
const newImg = logoQueue.shift()!
newImg.classList.add('dot')
Object.assign(newImg.style, {
position: 'relative',
left: (Math.random() > 0.5 ? -1 : 1) * (100 * Math.random()) + 'px',
bottom: (Math.random() > 0.5 ? -1 : 1) * (100 * Math.random()) + 'px',
transform: `translateZ(${-5000}px)`
})
img.replaceWith(newImg)
img.classList.remove('dot')
Object.assign(img.style, {
position: '',
left: '',
bottom: '',
transform: ``
})
logoQueue.push(img as HTMLImageElement)
}
Promise.all(
[...Object.values(import.meta.glob('./resources/*.png', { as: 'url' }))].map((it) => it())
).then((res) => {
logoQueue.value = logoQueue.value.concat(res)
currentIndexToSrcMap.value = logoQueue.value.splice(0, rowCount * colCount)
return img
})
logoQueue.push(...res)
for (let i = 0; i < rowCount * colCount; i++) {
const img = logoQueue.shift()!
Object.assign(img.style, {
position: 'relative',
left: -40 * Math.random() + 'px',
bottom: -40 * Math.random() + 'px',
transform: `translateZ(${-5000}px)`
})
img.classList.add('dot')
imageElContainer.value?.append(img)
}
})
const handleAnimationiteration = (ev, indexInSrcMap) => {
logoQueue.value.push(currentIndexToSrcMap.value[indexInSrcMap])
currentIndexToSrcMap.value[indexInSrcMap] = logoQueue.value.shift()
}
</script>
<style lang="scss" scoped>
@keyframes fly-in {
0% {
transform: translateZ(-2500px);
opacity: 0;
}
25% {
opacity: 1;
}
75% {
opacity: 0.5;
}
100% {
transform: translateZ(0);
opacity: 0;
}
}
.container {
height: 600px;
perspective: 1200px;
.flying-company-logo-list-container {
perspective: 600px;
display: grid;
align-items: center;
justify-items: center;
justify-content: center;
gap: 30px;
}
</style>
.dot {
display: block;
--dot-run-duration: 1s;
animation: fly-in var(--dot-run-duration) linear infinite;
transform-origin: center;
mix-blend-mode: darken;
width: 200px;
<style lang="scss">
@keyframes fly-in {
0% {
opacity: 0;
}
70% {
opacity: 0.8;
}
100% {
transform: translateZ(0);
opacity: 0;
}
}
.dot:nth-child(2n) {
animation-delay: calc(-1 * var(--dot-run-duration));
}
.dot:nth-child(2n + 1) {
animation-delay: calc(-0.1 * var(--dot-run-duration));
}
.dot:nth-child(3n + 1) {
animation-delay: calc(-0.2 * var(--dot-run-duration));
}
.dot:nth-child(5n + 1) {
animation-delay: calc(-0.1 * var(--dot-run-duration));
}
.dot:nth-child(7n + 1) {
animation-delay: calc(-0.4 * var(--dot-run-duration));
}
.dot:nth-child(11n + 1) {
animation-delay: calc(-0.5 * var(--dot-run-duration));
}
.dot:nth-child(13n + 1) {
animation-delay: calc(0 * var(--dot-run-duration));
}
.dot:nth-child(17n + 1) {
animation-delay: calc(-0.1 * var(--dot-run-duration));
.flying-company-logo-list-container {
.dot {
display: block;
--dot-run-duration: 2.5s;
animation: fly-in var(--dot-run-duration) ease-in-out infinite;
transform-origin: center;
mix-blend-mode: darken;
width: 200px;
}
.dot:nth-child(2n) {
animation-delay: calc(-0.35 * var(--dot-run-duration));
}
.dot:nth-child(2n + 1) {
animation-delay: calc(-0.15 * var(--dot-run-duration));
}
.dot:nth-child(3n + 1) {
animation-delay: calc(-0.22 * var(--dot-run-duration));
}
.dot:nth-child(5n + 1) {
animation-delay: calc(-0.05 * var(--dot-run-duration));
}
.dot:nth-child(7n + 1) {
animation-delay: calc(-0.1 * var(--dot-run-duration));
}
}
</style>

View File

@@ -1,38 +0,0 @@
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import WaitForLogin from './index.vue'
export const mountGlobalDialog = (o: { processWaitee? }) => {
const containerElId = `elForWaitForLogin`
if (document.getElementById(containerElId)) {
return
}
let containerEl: null | HTMLElement = (() => {
const el = document.createElement('div')
el.id = containerElId
return el
})()
document.body.append(containerEl)
const dispose = () => {
app?.unmount()
containerEl?.remove()
app = null
containerEl = null
}
let app: null | ReturnType<typeof createApp> = createApp(WaitForLogin, {
modelValue: true,
onClosed() {
dispose()
},
dispose,
processWaitee: o?.processWaitee
}).use(ElementPlus)
app.mount(containerEl)
return {
dispose
}
}

View File

@@ -0,0 +1,41 @@
<template>
<div>
<div>愿你心想事成</div>
<RouterView
:dependencies-status="checkDependenciesResult"
:process-waitee="downloadProcessWaitee"
></RouterView>
</div>
</template>
<script lang="ts" setup>
import { useRouter } from 'vue-router'
import { onMounted, ref } from 'vue'
import { sleep } from '@geekgeekrun/utils/sleep.mjs'
const router = useRouter()
const checkDependenciesResult = ref({})
const downloadProcessWaitee = ref(null)
onMounted(async () => {
checkDependenciesResult.value = await electron.ipcRenderer.invoke('check-dependencies')
downloadProcessWaitee.value = Promise.withResolvers()
if (Object.values(checkDependenciesResult.value).includes(false)) {
router.replace('/downloadingDependencies')
} else {
downloadProcessWaitee.value!.resolve()
}
downloadProcessWaitee.value!.promise.then(async () => {
const isCookieFileValid = await electron.ipcRenderer.invoke('check-boss-zhipin-cookie-file')
if (!isCookieFileValid) {
router.replace('/cookieAssistant')
} else {
await sleep(1000)
router.replace('/configuration')
}
})
})
</script>

View File

@@ -1,20 +1,12 @@
<template>
<el-dialog
v-bind="$attrs"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
@open="handleDialogOpen"
>
<template v-if="!copiedDependenciesStatus.puppeteerExecutableAvailable">
<div mb14px>正在下载核心组件</div>
<el-progress
:percentage="browserDownloadPercentage"
:format="(n) => `${n.toFixed(1)}%`"
:stroke-width="10"
/>
</template>
</el-dialog>
<div v-if="!dependenciesStatus.puppeteerExecutableAvailable">
<div mb14px>正在下载核心组件</div>
<el-progress
:percentage="browserDownloadPercentage"
:format="(n) => `${n.toFixed(1)}%`"
:stroke-width="10"
/>
</div>
</template>
<script lang="ts" setup>
@@ -22,7 +14,6 @@ import { ref, onUnmounted, PropType } from 'vue'
import { ElMessageBox } from 'element-plus'
const props = defineProps({
dispose: Function,
dependenciesStatus: {
type: Object as PropType<Record<string, boolean>>,
default: () => ({})
@@ -30,15 +21,6 @@ const props = defineProps({
processWaitee: Object
})
// shallow copy
const copiedDependenciesStatus = {
...props.dependenciesStatus
}
const handleDialogOpen = () => {
browserDownloadPercentage.value = 0
}
const browserDownloadPercentage = ref(0)
const handleBrowserDownloadProgress = (ev, { downloadedBytes, totalBytes }) => {
browserDownloadPercentage.value = (downloadedBytes / totalBytes) * 100
@@ -63,11 +45,11 @@ const processDownloadBrowser = async () => {
const promiseList: Array<Promise<void>> = []
const processTasks = async () => {
if (!copiedDependenciesStatus.puppeteerExecutableAvailable) {
if (!props.dependenciesStatus.puppeteerExecutableAvailable) {
const p = processDownloadBrowser()
promiseList.push(p)
p.then(() => {
copiedDependenciesStatus.puppeteerExecutableAvailable = true
props.dependenciesStatus.puppeteerExecutableAvailable = true
})
}
@@ -77,7 +59,6 @@ const processTasks = async () => {
p.then(() => {
if (!promiseList.length) {
props.processWaitee?.resolve?.()
props.dispose?.()
}
})
await p
@@ -95,7 +76,6 @@ const processTasks = async () => {
.catch(() => {
// FIXME: should exit app here
promiseList.length = 0
props.dispose?.()
})
}
}

View File

@@ -28,9 +28,8 @@
<script setup lang="ts">
import { ref } from 'vue'
import { ElForm, ElMessage } from 'element-plus'
import router from '../../router/index'
import { mountGlobalDialog as mountDependenciesSetupProgressIndicatorDialog } from '@renderer/features/DependenciesSetupProgressIndicatorDialog/operations'
import { mountGlobalDialog as mountWaitForLoginDialog } from '@renderer/features/WaitForLoginDialog/operations'
import { useRouter } from 'vue-router'
const router = useRouter()
const formContent = ref({
dingtalkRobotAccessToken: '',
@@ -51,28 +50,7 @@ const handleSubmit = async () => {
await formRef.value!.validate()
await electron.ipcRenderer.invoke('save-config-file-from-ui', JSON.stringify(formContent.value))
try {
const res = await electron.ipcRenderer.invoke(
'run-geek-auto-start-chat-with-boss',
JSON.stringify(formContent.value)
)
if (res.type === 'GEEK_AUTO_START_CHAT_WITH_BOSS_STARTED') {
router.replace('/geekAutoStartChatWithBoss/runningStatus')
}
} catch (err) {
if (err instanceof Error && err.message.includes('NEED_TO_CHECK_RUNTIME_DEPENDENCIES')) {
ElMessage.error({
message: `核心组件损坏,正在尝试修复`
})
const checkDependenciesResult = await electron.ipcRenderer.invoke('check-dependencies')
if (Object.values(checkDependenciesResult).includes(false)) {
mountDependenciesSetupProgressIndicatorDialog(checkDependenciesResult)
// TODO: should continue interrupted task
}
}
console.error(err)
}
router.replace('/geekAutoStartChatWithBoss/prepareRun')
}
const handleSave = async () => {
await formRef.value!.validate()
@@ -89,7 +67,7 @@ const handleExpectCompaniesInputBlur = (event) => {
}
const handleClickLaunchLogin = () => {
mountWaitForLoginDialog()
router.replace('/cookieAssistant')
}
</script>

View File

@@ -2,9 +2,8 @@
<script lang="ts" setup>
import { onMounted, onUnmounted } from 'vue'
import { mountGlobalDialog as mountDependenciesSetupProgressIndicatorDialog } from '@renderer/features/DependenciesSetupProgressIndicatorDialog/operations'
import { mountGlobalDialog as mountWaitForLoginDialog } from '@renderer/features/WaitForLoginDialog/operations'
import { useRouter } from 'vue-router'
const router = useRouter()
const unmountedCbs: Array<InstanceType<typeof Function>> = []
onUnmounted(() => {
while (unmountedCbs.length) {
@@ -14,31 +13,27 @@ onUnmounted(() => {
} catch {}
}
})
const goToCheckBossZhipinCookieFile = () => router.replace('/cookieAssistant')
onMounted(() => {
electron.ipcRenderer.on('check-boss-zhipin-cookie-file', mountWaitForLoginDialog)
electron.ipcRenderer.on('check-boss-zhipin-cookie-file', goToCheckBossZhipinCookieFile)
})
onUnmounted(() => {
electron.ipcRenderer.removeListener('check-boss-zhipin-cookie-file', mountWaitForLoginDialog)
electron.ipcRenderer.removeListener(
'check-boss-zhipin-cookie-file',
goToCheckBossZhipinCookieFile
)
})
;(async () => {
const checkDependenciesResult = await electron.ipcRenderer.invoke('check-dependencies')
if (Object.values(checkDependenciesResult).includes(false)) {
const processWaitee = Promise.withResolvers()
mountDependenciesSetupProgressIndicatorDialog({
checkDependenciesResult, processWaitee
})
await processWaitee.promise
router.replace('/')
return
}
const isCookieFileValid = await electron.ipcRenderer.invoke('check-boss-zhipin-cookie-file')
if (!isCookieFileValid) {
const processWaitee = Promise.withResolvers()
mountWaitForLoginDialog({
processWaitee
})
await processWaitee.promise
router.replace('/cookieAssistant')
return
}
})()
</script>

View File

@@ -1,14 +1,6 @@
<template>
<el-dialog
v-bind="$attrs"
:close-on-click-modal="false"
:close-on-press-escape="!cookieInvalid"
title="Boss直聘 Cookie助手"
:width="720"
top="20px"
lock-scroll
:show-close="!cookieInvalid"
>
<div class="cookie-assistant-page">
<div ml1em mt1em mb1em >Cookie 助手</div>
<el-alert
v-if="cookieInvalid"
type="warning"
@@ -18,21 +10,17 @@
由于您是首次使用本程序或者您之前使用的Boss直聘账号登录状态失效因此您需要重新获取登录凭证
</el-alert>
<div ml1em mt1em line-height-normal>
如果您了解如何获取Cookie了解有效的Cookie格式可以直接在下方输入框中进行编辑<br />
手动编辑较为麻烦建议您打开已登录过Boss直聘的浏览器使用
<a
如果您了解如何获取Cookie了解有效的Cookie格式可以直接在下方输入框中进行编辑由于手动编辑较为麻烦建议您打开已登录过Boss直聘的浏览器使用<a
color-blue
decoration-none
href="javascript:void(0)"
@click.prevent="handleEditThisCookieExtensionStoreLinkClick"
>EditThisCookie 扩展程序</a
>
复制Cookie然后粘贴在下方输入框中<br />
格式为被序列化为JSON的数组不含两侧引号
>复制Cookie然后粘贴在下方输入框中文本格式为被序列化为JSON的数组不含两侧引号
</div>
<br />
<div ml1em line-height-normal>
如果您不了解Cookie相关概念或者不能访问Chrome扩展程序商店下载EditThisCookie来获取Cookie请按照以下步骤进行操作
如果您不了解Cookie相关概念或者期望操作简单一些请按照以下步骤进行操作
</div>
<ol lh-2em mt-0>
<li>
@@ -104,23 +92,20 @@
>
</el-form-item>
</el-form>
<template #footer>
<el-button v-if="!cookieInvalid" @click="dispose">关闭</el-button>
<el-button type="primary" @click="handleSubmit">保存Cookie</el-button>
</template>
</el-dialog>
<footer flex mt20px pb20px flex-justify-end>
<el-button v-if="!cookieInvalid" @click="handleCancel">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</footer>
</div>
</template>
<script lang="ts" setup>
import { ElForm, ElMessage } from 'element-plus'
import { ref, onUnmounted, onMounted } from 'vue'
import { checkCookieListFormat } from '../../../../common/utils/cookie'
import { useRouter } from 'vue-router';
const props = defineProps({
dispose: Function,
processWaitee: Object
})
const router = useRouter()
const cookieInvalid = ref(false)
enum LOGIN_COOKIE_WAITING_STATUS {
@@ -195,6 +180,9 @@ const handleEditThisCookieExtensionStoreLinkClick = () => {
)
}
const handleCancel = () => {
router.replace('/configuration')
}
const handleSubmit = async () => {
await formRef.value!.validate()
await electron.ipcRenderer.invoke('write-storage-file', {
@@ -202,8 +190,7 @@ const handleSubmit = async () => {
data: formContent.value.collectedCookies
})
ElMessage.success('Boss直聘 Cookie 保存成功')
props.processWaitee?.resolve?.()
props.dispose()
router.replace('/configuration')
}
const handleBossZhipinLoginPageClosed = () => {
@@ -235,6 +222,13 @@ onUnmounted(() => {
})
</script>
<style lang="scss" scoped>
.cookie-assistant-page {
max-width: 640px;
margin: 0 auto;
}
</style>
<style lang="scss">
.cookie-form.el-form {
.el-form-item__error--inline {

View File

@@ -0,0 +1,29 @@
<template>
<div class="geek-auto-start-chat-with-boss__prepare-run">
<div>{{ statusText }}</div>
</div>
</template>
<script lang="ts" setup>
import { hasOwn } from '@vue/shared'
import { PropType, computed } from 'vue'
const statusTextMap = {
_: '请稍后,正在进行一些处理',
'locating-puppeteer-executable': '正在寻找可用的浏览器'
} as const
const props = defineProps({
status: {
type: String as PropType<keyof typeof statusText>,
default: '_'
}
})
const statusText = computed(() => {
if (hasOwn(statusTextMap, props.status)) {
return statusTextMap[props.status]
} else {
return statusTextMap._
}
})
</script>

View File

@@ -1,5 +1,6 @@
<template>
<div class="geek-auto-start-chat-with-boss__running-status">
<FlyingCompanyLogoList class="flying-company-logo-list" />
<div class="tip">
<article>
<h1>👋 BOSS炸弹正在运行</h1>
@@ -9,14 +10,14 @@
</article>
<el-button :disabled="isStopping" @click="handleStopButtonClick">停止开聊</el-button>
</div>
<FlyingCompanyLogoList class="flying-company-logo-list" />
</div>
</template>
<script setup lang="ts">
import { ref, onUnmounted } from 'vue';
import { ref, onUnmounted, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import FlyingCompanyLogoList from '../../features/FlyingCompanyLogoList/index.vue'
import { ElMessage } from 'element-plus';
const { ipcRenderer } = electron
const router = useRouter()
@@ -40,16 +41,46 @@ onUnmounted(() => {
ipcRenderer.removeListener('geek-auto-start-chat-with-boss-stopped', handleStopped)
ipcRenderer.removeListener('geek-auto-start-chat-with-boss-stopping', handleStopping)
})
onMounted(async () => {
try {
await electron.ipcRenderer.invoke('run-geek-auto-start-chat-with-boss')
} catch (err) {
if (err instanceof Error && err.message.includes('NEED_TO_CHECK_RUNTIME_DEPENDENCIES')) {
ElMessage.error({
message: `核心组件损坏,正在尝试修复`
})
const checkDependenciesResult = await electron.ipcRenderer.invoke('check-dependencies')
if (Object.values(checkDependenciesResult).includes(false)) {
router.replace('/')
// TODO: should continue interrupted task
}
}
console.error(err)
}
})
</script>
<style scoped lang="scss">
.geek-auto-start-chat-with-boss__running-status {
max-width: 100%;
max-height: 100%;
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.tip {
margin: 0 auto;
margin-top: -15vh;
max-width: 640px;
}
.flying-company-logo-list {
position: absolute;
inset: 0;
z-index: -1;
opacity: 0.25;
}
}
</style>

View File

@@ -1 +1,37 @@
<template><RouterView /></template>
<template><RouterView :status="currentStatus" /></template>
<script lang="ts" setup>
import { ElMessage } from 'element-plus'
import { ref, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const currentStatus = ref('')
onMounted(() => {
const promise = electron.ipcRenderer.invoke('prepare-run-geek-auto-start-chat-with-boss')
const handleLocatingPuppeteerExecutable = () => {
currentStatus.value = 'locating-puppeteer-executable'
}
electron.ipcRenderer.once('locating-puppeteer-executable', handleLocatingPuppeteerExecutable)
onUnmounted(() => {
electron.ipcRenderer.removeListener(
'locating-puppeteer-executable',
handleLocatingPuppeteerExecutable
)
})
promise
.then(() => {
router.replace('/geekAutoStartChatWithBoss/runningStatus')
})
.catch(async (err) => {
if (err instanceof Error && err.message.includes('NEED_TO_CHECK_RUNTIME_DEPENDENCIES')) {
ElMessage.error({
message: `核心组件损坏,正在尝试修复`
})
router.replace('/')
}
console.error(err)
})
})
</script>

View File

@@ -1,12 +1,13 @@
import { defineComponent, h } from 'vue'
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
import BootstrapSplash from '@renderer/page/BootstrapSplash/index.vue'
const routes: Array<RouteRecordRaw> = [
{
path: '/',
component: defineComponent({ setup: () => h('div') }),
redirect: '/configuration',
children: []
path: '/cookieAssistant',
component: () => import('@renderer/page/CookieAssistant/index.vue'),
meta: {
title: 'Cookie 助手'
}
},
{
path: '/configuration',
@@ -26,6 +27,13 @@ const routes: Array<RouteRecordRaw> = [
path: '/geekAutoStartChatWithBoss',
component: () => import('@renderer/page/GeekAutoStartChatWithBoss/index.vue'),
children: [
{
path: 'prepareRun',
component: () => import('@renderer/page/GeekAutoStartChatWithBoss/PrepareRun.vue'),
meta: {
title: 'BOSS炸弹 正在预热'
}
},
{
path: 'runningStatus',
component: () => import('@renderer/page/GeekAutoStartChatWithBoss/RunningStatus.vue'),
@@ -34,7 +42,23 @@ const routes: Array<RouteRecordRaw> = [
}
}
]
}
},
{
path: '/',
component: BootstrapSplash,
meta: {
title: '薪想事成'
},
children: [
{
path: '/downloadingDependencies',
component: () => import('@renderer/page/BootstrapSplash/page/DownloadingDependencies.vue'),
meta: {
title: '正在下载浏览器'
},
}
]
},
]
const router = createRouter({