Work without a web server

This commit is contained in:
49lf
2024-08-14 11:15:08 -04:00
parent 1b01e10eed
commit 72d6234f30
8 changed files with 143 additions and 23 deletions

View 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',
},
})

View File

@ -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,

View File

@ -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)
}

View File

@ -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

View File

@ -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 })
})
})

View File

@ -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,
})

View File

@ -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"
/>

View File

@ -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"
/>