Work without a web server
This commit is contained in:
36
playwright-electron.config.ts
Normal file
36
playwright-electron.config.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
timeout: 120_000, // override the default 30s timeout
|
||||
testDir: './e2e/playwright',
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Do not retry */
|
||||
retries: process.env.CI ? 0 : 0,
|
||||
/* Different amount of parallelism on CI and local. */
|
||||
workers: process.env.CI ? 1 : 4,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: [
|
||||
[process.env.CI ? 'dot' : 'list'],
|
||||
['json', { outputFile: './test-results/report.json' }],
|
||||
['html'],
|
||||
],
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'retain-on-failure',
|
||||
actionTimeout: 15000,
|
||||
screenshot: 'only-on-failure',
|
||||
},
|
||||
})
|
||||
@ -1,6 +1,7 @@
|
||||
import { App } from './App'
|
||||
import {
|
||||
createBrowserRouter,
|
||||
createHashRouter,
|
||||
Outlet,
|
||||
redirect,
|
||||
RouterProvider,
|
||||
@ -42,7 +43,9 @@ import { coreDump } from 'lang/wasm'
|
||||
import { useMemo } from 'react'
|
||||
import { AppStateProvider } from 'AppState'
|
||||
|
||||
const router = createBrowserRouter([
|
||||
const createRouter = isDesktop() ? createHashRouter : createBrowserRouter
|
||||
|
||||
const router = createRouter([
|
||||
{
|
||||
loader: settingsLoader,
|
||||
id: PATHS.INDEX,
|
||||
|
||||
@ -27,6 +27,8 @@ const fromServer: FromServer | Error = FromServer.create()
|
||||
const initialise = async (wasmUrl: string) => {
|
||||
const input = await fetch(wasmUrl)
|
||||
const buffer = await input.arrayBuffer()
|
||||
const td = new TextDecoder()
|
||||
const text = td.decode(buffer)
|
||||
return init(buffer)
|
||||
}
|
||||
|
||||
|
||||
@ -87,7 +87,16 @@ export type { KclValue } from '../wasm-lib/kcl/bindings/KclValue'
|
||||
export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
|
||||
|
||||
export const wasmUrl = () => {
|
||||
const fullUrl = './wasm_lib_bg.wasm'
|
||||
// For when we're in electron (file based) or web server (network based)
|
||||
// For some reason relative paths don't work as expected. Otherwise we would
|
||||
// just do /wasm_lib_bg.wasm. In particular, the issue arises when the path
|
||||
// is used from within worker.ts.
|
||||
const fullUrl = document.location.protocol.includes('http')
|
||||
? document.location.origin + '/wasm_lib_bg.wasm'
|
||||
: document.location.protocol +
|
||||
document.location.pathname.split('/').slice(0, -1).join('/') +
|
||||
'/wasm_lib_bg.wasm'
|
||||
|
||||
console.log(`Full URL for WASM: ${fullUrl}`)
|
||||
|
||||
return fullUrl
|
||||
|
||||
18
src/main.ts
18
src/main.ts
@ -154,21 +154,3 @@ ipcMain.handle('find_machine_api', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
app.whenReady().then(() => {
|
||||
protocol.handle('file', (request) => {
|
||||
const filePath = request.url.slice('file://'.length)
|
||||
const maybeAbsolutePath = path.join(__dirname, filePath)
|
||||
const bypassCustomProtocolHandlers = true
|
||||
if (fss.existsSync(maybeAbsolutePath)) {
|
||||
console.log(
|
||||
`Intercepted local-asbolute path ${filePath}, rebuilt it as ${maybeAbsolutePath}`
|
||||
)
|
||||
return net.fetch(url.pathToFileURL(maybeAbsolutePath).toString(), {
|
||||
bypassCustomProtocolHandlers,
|
||||
})
|
||||
}
|
||||
console.log(`Default fetch to ${filePath}`)
|
||||
return net.fetch(request.url, { bypassCustomProtocolHandlers })
|
||||
})
|
||||
})
|
||||
|
||||
@ -1 +1,89 @@
|
||||
import 'lib/electron'
|
||||
import { ipcRenderer, contextBridge } from 'electron'
|
||||
import path from 'path'
|
||||
import fs from 'node:fs/promises'
|
||||
import packageJson from '../../package.json'
|
||||
import { components } from 'lib/machine-api'
|
||||
import { MachinesListing } from 'lib/machineManager'
|
||||
import kittycad from '@kittycad/lib/require'
|
||||
|
||||
const open = (args: any) => ipcRenderer.invoke('dialog.showOpenDialog', args)
|
||||
const save = (args: any) => ipcRenderer.invoke('dialog.showSaveDialog', args)
|
||||
const openExternal = (url: any) => ipcRenderer.invoke('shell.openExternal', url)
|
||||
const showInFolder = (path: string) =>
|
||||
ipcRenderer.invoke('shell.showItemInFolder', path)
|
||||
const login = (host: string): Promise<string> =>
|
||||
ipcRenderer.invoke('login', host)
|
||||
|
||||
const readFile = (path: string) => fs.readFile(path, 'utf-8')
|
||||
const rename = (prev: string, next: string) => fs.rename(prev, next)
|
||||
const writeFile = (path: string, data: string | Uint8Array) =>
|
||||
fs.writeFile(path, data, 'utf-8')
|
||||
const readdir = (path: string) => fs.readdir(path, 'utf-8')
|
||||
const stat = (path: string) =>
|
||||
fs.stat(path).catch((e) => Promise.reject(e.code))
|
||||
// Electron has behavior where it doesn't clone the prototype chain over.
|
||||
// So we need to call stat.isDirectory on this side.
|
||||
const statIsDirectory = (path: string) =>
|
||||
stat(path).then((res) => res.isDirectory())
|
||||
const getPath = async (name: string) => ipcRenderer.invoke('app.getPath', name)
|
||||
|
||||
const exposeProcessEnv = (varName: string) => {
|
||||
return {
|
||||
[varName](value?: string) {
|
||||
if (value !== undefined) {
|
||||
process.env[varName] = value
|
||||
} else {
|
||||
return process.env[varName]
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// We could probably do this from the renderer side, but I fear CORS will
|
||||
// bite our butts.
|
||||
const listMachines = async (): Promise<MachinesListing> => {
|
||||
const machineApi = await ipcRenderer.invoke('find_machine_api')
|
||||
if (!machineApi) return {}
|
||||
|
||||
return fetch(`http://${machineApi}/machines`).then((resp) => resp.json())
|
||||
}
|
||||
|
||||
const getMachineApiIp = async (): Promise<String | null> =>
|
||||
ipcRenderer.invoke('find_machine_api')
|
||||
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
login,
|
||||
// Passing fs directly is not recommended since it gives a lot of power
|
||||
// to the browser side / potential malicious code. We restrict what is
|
||||
// exported.
|
||||
readFile,
|
||||
writeFile,
|
||||
readdir,
|
||||
rename,
|
||||
rm: fs.rm,
|
||||
path,
|
||||
stat,
|
||||
statIsDirectory,
|
||||
mkdir: fs.mkdir,
|
||||
// opens a dialog
|
||||
open,
|
||||
save,
|
||||
// opens the URL
|
||||
openExternal,
|
||||
showInFolder,
|
||||
getPath,
|
||||
packageJson,
|
||||
arch: process.arch,
|
||||
platform: process.platform,
|
||||
version: process.version,
|
||||
process: {
|
||||
// Setter/getter has to be created because
|
||||
// these are read-only over the boundary.
|
||||
env: Object.assign({}, exposeProcessEnv('BASE_URL')),
|
||||
},
|
||||
kittycad: {
|
||||
users: kittycad.users,
|
||||
},
|
||||
listMachines,
|
||||
getMachineApiIp,
|
||||
})
|
||||
|
||||
@ -151,7 +151,7 @@ function OnboardingIntroductionInner() {
|
||||
<div className="max-w-3xl p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
|
||||
<h1 className="flex flex-wrap items-center gap-4 text-3xl font-bold">
|
||||
<img
|
||||
src={`/zma-logomark${getLogoTheme()}.svg`}
|
||||
src={`./zma-logomark${getLogoTheme()}.svg`}
|
||||
alt={APP_NAME}
|
||||
className="h-20 max-w-full"
|
||||
/>
|
||||
|
||||
@ -39,7 +39,7 @@ const SignIn = () => {
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<div>
|
||||
<img
|
||||
src={`/zma-logomark${getLogoTheme()}.svg`}
|
||||
src={`./zma-logomark${getLogoTheme()}.svg`}
|
||||
alt="Zoo Modeling App"
|
||||
className="w-48 inline-block"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user