Skip to content

Commit e59b621

Browse files
committed
feat(electron): read electron build
1 parent 410909d commit e59b621

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1964
-254
lines changed

package-lock.json

Lines changed: 1486 additions & 210 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/electron-app/.gitignore

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
.DS_Store
12+
dist
13+
dist-ssr
14+
dist-electron
15+
coverage
16+
*.local
17+
18+
/cypress/videos/
19+
/cypress/screenshots/
20+
21+
# Editor directories and files
22+
.vscode/*
23+
!.vscode/extensions.json
24+
.idea
25+
*.suo
26+
*.ntvs*
27+
*.njsproj
28+
*.sln
29+
*.sw?
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
3+
}
File renamed without changes.

packages/electron-app/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<link rel="icon" href="/favicon.ico">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>Vite App</title>
8+
</head>
9+
<body>
10+
<div id="app"></div>
11+
<script type="module" src="/src/main.ts"></script>
12+
</body>
13+
</html>

packages/electron-app/package.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "electron-app",
3+
"version": "0.0.0",
4+
"private": true,
5+
"main": "dist-electron/main.js",
6+
"scripts": {
7+
"dev": "vite",
8+
"build": "run-p type-check build-only",
9+
"preview": "vite preview",
10+
"build-only": "vite build",
11+
"type-check": "vue-tsc --noEmit"
12+
},
13+
"dependencies": {
14+
"vue": "^3.2.45"
15+
},
16+
"devDependencies": {
17+
"@types/node": "^18.11.12",
18+
"@vitejs/plugin-vue": "^4.0.0",
19+
"@vue/tsconfig": "^0.1.3",
20+
"electron": "^22.0.0",
21+
"npm-run-all": "^4.1.5",
22+
"typescript": "~4.7.4",
23+
"vite": "^4.0.0",
24+
"vite-plugin-electron": "^0.11.1",
25+
"vue-tsc": "^1.0.12"
26+
}
27+
}
4.19 KB
Binary file not shown.
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import path from 'path'
2+
import fs from 'fs'
3+
4+
import DirectoryEntry from '@core/entities/directory-entry'
5+
6+
import type Drive from '@core/gateways/drive/drive'
7+
8+
export default class FSDrive implements Drive {
9+
public config = {
10+
path: '',
11+
}
12+
13+
public resolve(args: string | string[], separator = path.sep) {
14+
args = Array.isArray(args) ? args : [args]
15+
16+
return args
17+
.map((a) => a.split(/\\|\//))
18+
.reduce((all, a) => all.concat(a), [])
19+
.filter((a) => a !== '')
20+
.join(separator)
21+
}
22+
23+
public async list(path: string): Promise<DirectoryEntry[]> {
24+
const systemPath = this.resolve([this.config.path, path])
25+
26+
const isValid = await fs.promises
27+
.stat(systemPath)
28+
.then((s) => s.isDirectory())
29+
.catch(() => false)
30+
31+
if (!isValid) return []
32+
33+
const entries = await fs.promises.readdir(systemPath, { withFileTypes: true })
34+
35+
return entries.map((e) => {
36+
const filename = this.resolve([path, e.name], '/')
37+
38+
if (e.isDirectory()) {
39+
return DirectoryEntry.directory(filename)
40+
}
41+
42+
return DirectoryEntry.file(filename)
43+
})
44+
}
45+
46+
public async get(entryPath: string) {
47+
const systemPath = this.resolve([this.config.path, entryPath])
48+
49+
const exists = await this.exists(entryPath)
50+
51+
if (!exists) return null
52+
53+
return await fs.promises
54+
.stat(systemPath)
55+
.then((e) =>
56+
e.isDirectory()
57+
? DirectoryEntry.directory(entryPath)
58+
: DirectoryEntry.file(entryPath)
59+
)
60+
.catch(() => null)
61+
}
62+
63+
public async exists(path: string) {
64+
const systemPath = this.resolve([this.config.path, path])
65+
66+
return fs.promises
67+
.stat(systemPath)
68+
.then(() => true)
69+
.catch(() => false)
70+
}
71+
72+
public async move(source: string, target: string) {
73+
const systemPath = {
74+
source: this.resolve([this.config.path, source]),
75+
target: this.resolve([this.config.path, target]),
76+
}
77+
78+
const targetExist = await this.exists(target)
79+
80+
if (targetExist) {
81+
throw new Error('Target filename already exists')
82+
}
83+
84+
await fs.promises.rename(systemPath.source, systemPath.target)
85+
}
86+
87+
public async mkdir(entryPath: string) {
88+
const systemPath = this.resolve([this.config.path, entryPath])
89+
90+
await fs.promises.mkdir(systemPath, { recursive: true })
91+
92+
return DirectoryEntry.directory(entryPath)
93+
}
94+
95+
public async write(entryPath: string, content: Uint8Array) {
96+
const systemPath = this.resolve([this.config.path, entryPath])
97+
98+
await fs.promises.mkdir(path.dirname(systemPath), { recursive: true })
99+
100+
await fs.promises.writeFile(systemPath, content)
101+
}
102+
103+
public async read(entryPath: string): Promise<Uint8Array | null> {
104+
const systemPath = this.resolve([this.config.path, entryPath])
105+
106+
const isFile = await fs.promises
107+
.stat(systemPath)
108+
.then((s) => s.isFile())
109+
.catch(() => false)
110+
111+
if (!isFile) return null
112+
113+
return await fs.promises.readFile(systemPath)
114+
}
115+
116+
public async delete(path: string) {
117+
const systemPath = this.resolve([this.config.path, path])
118+
119+
await fs.promises.rm(systemPath, { recursive: true })
120+
}
121+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { BrowserWindow, ipcMain, app, Menu, MenuItem } from 'electron'
2+
3+
import fs from 'fs'
4+
import path from 'path'
5+
import debounce from 'lodash/debounce'
6+
7+
import IndexSan from '@core/app'
8+
9+
interface Preference {
10+
name: string
11+
value: any
12+
}
13+
14+
import AppConfig from '@core/config/app'
15+
import NodeVMEvaluation from '@core/gateways/evaluation/implementations/node-vm-evaluation'
16+
17+
import WorkspaceRepository from './workspace-repository'
18+
19+
import Drive from './drive'
20+
21+
const config = new AppConfig({
22+
drives: { fs: new Drive() },
23+
repositories: { workspace: new WorkspaceRepository() },
24+
services: { evaluation: new NodeVMEvaluation() },
25+
})
26+
27+
const indexSan = new IndexSan(config)
28+
29+
if (process.env.VITE_DEV_SERVER_URL) {
30+
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
31+
}
32+
33+
app.whenReady().then(async () => {
34+
const preferences: Preference[] = await fs.promises
35+
.readFile(path.resolve(app.getPath('userData'), 'preferences.json'), { encoding: 'utf8' })
36+
.then((text) => JSON.parse(text))
37+
.catch(() => [])
38+
39+
const boundPreference = preferences.find((p) => p.name === 'window:bounds')
40+
41+
const bounds = boundPreference ? boundPreference.value : {}
42+
43+
const window = new BrowserWindow({
44+
...bounds,
45+
title: 'Index-san',
46+
autoHideMenuBar: true,
47+
webPreferences: {
48+
preload: path.join(__dirname, './preload.js'),
49+
contextIsolation: true,
50+
nodeIntegration: true,
51+
spellcheck: true,
52+
},
53+
icon: path.resolve(__dirname, '..', 'assets', 'logo.ico'),
54+
})
55+
56+
window.on(
57+
'resize',
58+
debounce(async () => {
59+
const bounds = window.getBounds()
60+
61+
await fs.promises
62+
.writeFile(
63+
path.resolve(app.getPath('userData'), 'preferences.json'),
64+
JSON.stringify([
65+
{
66+
name: 'window:bounds',
67+
value: bounds,
68+
},
69+
])
70+
)
71+
.catch(Boolean)
72+
}, 1000)
73+
)
74+
75+
ipcMain.handle('use-case', async (_, name, args) => {
76+
return indexSan.useCase(name, args)
77+
})
78+
79+
window.webContents.on('context-menu', (event, params) => {
80+
const menu = new Menu()
81+
82+
// Add each spelling suggestion
83+
for (const suggestion of params.dictionarySuggestions) {
84+
menu.append(
85+
new MenuItem({
86+
label: suggestion,
87+
click: () => window.webContents.replaceMisspelling(suggestion),
88+
})
89+
)
90+
}
91+
92+
// Allow users to add the misspelled word to the dictionary
93+
if (params.misspelledWord) {
94+
menu.append(
95+
new MenuItem({
96+
label: 'Add to dictionary',
97+
click: () =>
98+
window.webContents.session.addWordToSpellCheckerDictionary(
99+
params.misspelledWord
100+
),
101+
})
102+
)
103+
}
104+
105+
menu.popup()
106+
})
107+
108+
if (process.env.VITE_DEV_SERVER_URL) {
109+
window.loadURL(process.env.VITE_DEV_SERVER_URL)
110+
} else {
111+
window.loadFile(path.resolve(__dirname, '..', 'dist', 'index.html'))
112+
}
113+
})
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { contextBridge, ipcRenderer, shell } from 'electron'
2+
3+
async function useCase(name: string, args?: any) {
4+
return ipcRenderer.invoke('use-case', name, args)
5+
}
6+
7+
const electronConfig = {
8+
useCase,
9+
open: {
10+
url: shell.openExternal,
11+
},
12+
}
13+
14+
contextBridge.exposeInMainWorld('electronConfig', electronConfig)

0 commit comments

Comments
 (0)