Compare commits

...

9 Commits

Author SHA1 Message Date
c825eac27e Cut release v0.11.0 (#826)
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2023-10-17 18:15:46 -04:00
82e8a491c4 honour sketch mode after execute (#885)
* enable stay in sketch mode after execute

* clean up
2023-10-17 17:03:02 -04:00
93e806fc99 don't query program memory for add sketch line (#895)
* don't query program memory for add sketch line

* add issue TODO

* fmt

* fix test tsc
2023-10-18 07:30:03 +11:00
f1a14f1e3d Guard all the vaules in the Metrics for undefined. (#891)
We've seen a few cases where the WebRTC metrics report contains
undefined values when the stream hasn't started yet. JavaScript, when we
send to the backend, drops `undefined` members of the object, for
example:

```
> JSON.stringify({rtc_frames_dropped: undefined})
< '{}'
```

This will fail the backend validation logic and cause an error to get
emitted to the front-end reporting the missing key. I don't think this
does anything to the session, but it's an error we can avoid by guarding
more of the statistics with a || 0.

Some of the values had this already, this just adds a few more.

Signed-off-by: Paul R. Tagliamonte <paul@kittycad.io>
2023-10-17 16:55:45 +00:00
57c01ec3a2 Use platform-agnostic file separators (#890)
* Use platform-agnostic path separators

* Fix file settings by fixing absolute file path

* Fix missing home link in AppHeader

* Found so many more instances of raw "/" characters

* Tiny Settings style fix

* Clean up onboarding behavior for XState and multi-file
2023-10-17 12:31:14 -04:00
ce951d7c12 Add https://tauri.localhost for Windows (#886)
* Add https://tauri.localhost for Windows
Fixes #878

* Comments and info logs
2023-10-17 09:37:16 -04:00
0aa2a6cee7 more clean up (#889) 2023-10-17 09:08:17 +00:00
ba8f5d9785 unused vars (#887) 2023-10-17 05:34:35 +00:00
50a133b2fa add wasm error banner (#882)
add wasm error banner
2023-10-17 01:07:48 +00:00
27 changed files with 362 additions and 154 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "untitled-app", "name": "untitled-app",
"version": "0.10.0", "version": "0.11.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.9.0", "@codemirror/autocomplete": "^6.9.0",

View File

@ -8,7 +8,7 @@
}, },
"package": { "package": {
"productName": "kittycad-modeling", "productName": "kittycad-modeling",
"version": "0.10.0" "version": "0.11.0"
}, },
"tauri": { "tauri": {
"allowlist": { "allowlist": {

View File

@ -31,6 +31,7 @@ import {
} from './lib/tauriFS' } from './lib/tauriFS'
import { metadata, type Metadata } from 'tauri-plugin-fs-extra-api' import { metadata, type Metadata } from 'tauri-plugin-fs-extra-api'
import DownloadAppBanner from './components/DownloadAppBanner' import DownloadAppBanner from './components/DownloadAppBanner'
import { WasmErrBanner } from './components/WasmErrBanner'
import { GlobalStateProvider } from './components/GlobalStateProvider' import { GlobalStateProvider } from './components/GlobalStateProvider'
import { import {
SETTINGS_PERSIST_KEY, SETTINGS_PERSIST_KEY,
@ -43,6 +44,7 @@ import * as Sentry from '@sentry/react'
import ModelingMachineProvider from 'components/ModelingMachineProvider' import ModelingMachineProvider from 'components/ModelingMachineProvider'
import { KclContextProvider } from 'lang/KclSinglton' import { KclContextProvider } from 'lang/KclSinglton'
import FileMachineProvider from 'components/FileMachineProvider' import FileMachineProvider from 'components/FileMachineProvider'
import { sep } from '@tauri-apps/api/path'
if (VITE_KC_SENTRY_DSN && !TEST) { if (VITE_KC_SENTRY_DSN && !TEST) {
Sentry.init({ Sentry.init({
@ -144,12 +146,13 @@ const router = createBrowserRouter(
path: paths.FILE + '/:id', path: paths.FILE + '/:id',
element: ( element: (
<Auth> <Auth>
<Outlet />
<FileMachineProvider> <FileMachineProvider>
<KclContextProvider> <KclContextProvider>
<ModelingMachineProvider> <ModelingMachineProvider>
<Outlet />
<App /> <App />
</ModelingMachineProvider> </ModelingMachineProvider>
<WasmErrBanner />
</KclContextProvider> </KclContextProvider>
</FileMachineProvider> </FileMachineProvider>
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />} {!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
@ -185,23 +188,23 @@ const router = createBrowserRouter(
if (params.id && params.id !== BROWSER_FILE_NAME) { if (params.id && params.id !== BROWSER_FILE_NAME) {
const decodedId = decodeURIComponent(params.id) const decodedId = decodeURIComponent(params.id)
const projectAndFile = decodedId.replace(defaultDir + '/', '') const projectAndFile = decodedId.replace(defaultDir + sep, '')
const firstSlashIndex = projectAndFile.indexOf('/') const firstSlashIndex = projectAndFile.indexOf(sep)
const projectName = projectAndFile.slice(0, firstSlashIndex) const projectName = projectAndFile.slice(0, firstSlashIndex)
const projectPath = defaultDir + '/' + projectName const projectPath = defaultDir + sep + projectName
const currentFileName = projectAndFile.slice(firstSlashIndex + 1) const currentFileName = projectAndFile.slice(firstSlashIndex + 1)
if (firstSlashIndex === -1 || !currentFileName) if (firstSlashIndex === -1 || !currentFileName)
return redirect( return redirect(
`${paths.FILE}/${encodeURIComponent( `${paths.FILE}/${encodeURIComponent(
`${params.id}/${PROJECT_ENTRYPOINT}` `${params.id}${sep}${PROJECT_ENTRYPOINT}`
)}` )}`
) )
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files // Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
const code = await readTextFile(decodedId) const code = await readTextFile(decodedId)
const entrypointMetadata = await metadata( const entrypointMetadata = await metadata(
projectPath + '/' + PROJECT_ENTRYPOINT projectPath + sep + PROJECT_ENTRYPOINT
) )
const children = await readDir(projectPath, { recursive: true }) const children = await readDir(projectPath, { recursive: true })
@ -268,9 +271,9 @@ const router = createBrowserRouter(
isProjectDirectory isProjectDirectory
) )
const projects = await Promise.all( const projects = await Promise.all(
projectsNoMeta.map(async (p) => ({ projectsNoMeta.map(async (p: FileEntry) => ({
entrypointMetadata: await metadata( entrypointMetadata: await metadata(
p.path + '/' + PROJECT_ENTRYPOINT p.path + sep + PROJECT_ENTRYPOINT
), ),
...p, ...p,
})) }))

View File

@ -32,13 +32,11 @@ export const AppHeader = ({
className className
} }
> >
{project && (
<ProjectSidebarMenu <ProjectSidebarMenu
renderAsLink={!enableMenu} renderAsLink={!enableMenu}
project={project.project} project={project?.project}
file={project.file} file={project?.file}
/> />
)}
{/* Toolbar if the context deems it */} {/* Toolbar if the context deems it */}
{showToolbar && ( {showToolbar && (
<div className="max-w-lg md:max-w-xl lg:max-w-2xl xl:max-w-4xl 2xl:max-w-5xl"> <div className="max-w-lg md:max-w-xl lg:max-w-2xl xl:max-w-4xl 2xl:max-w-5xl">
@ -47,7 +45,7 @@ export const AppHeader = ({
)} )}
{/* If there are children, show them, otherwise show User menu */} {/* If there are children, show them, otherwise show User menu */}
{children || ( {children || (
<div className="ml-auto flex items-center gap-1"> <div className="flex items-center gap-1 ml-auto">
<NetworkHealthIndicator /> <NetworkHealthIndicator />
<UserSidebarMenu user={user} /> <UserSidebarMenu user={user} />
</div> </div>

View File

@ -22,6 +22,7 @@ import {
} from '@tauri-apps/api/fs' } from '@tauri-apps/api/fs'
import { FILE_EXT, readProject } from 'lib/tauriFS' import { FILE_EXT, readProject } from 'lib/tauriFS'
import { isTauri } from 'lib/isTauri' import { isTauri } from 'lib/isTauri'
import { sep } from '@tauri-apps/api/path'
type MachineContext<T extends AnyStateMachine> = { type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T> state: StateFrom<T>
@ -56,7 +57,7 @@ export const FileMachineProvider = ({
setCommandBarOpen(false) setCommandBarOpen(false)
navigate( navigate(
`${paths.FILE}/${encodeURIComponent( `${paths.FILE}/${encodeURIComponent(
context.selectedDirectory + '/' + event.data.name context.selectedDirectory + sep + event.data.name
)}` )}`
) )
} }
@ -82,11 +83,11 @@ export const FileMachineProvider = ({
let name = event.data.name.trim() || DEFAULT_FILE_NAME let name = event.data.name.trim() || DEFAULT_FILE_NAME
if (event.data.makeDir) { if (event.data.makeDir) {
await createDir(context.selectedDirectory.path + '/' + name) await createDir(context.selectedDirectory.path + sep + name)
} else { } else {
await writeFile( await writeFile(
context.selectedDirectory.path + context.selectedDirectory.path +
'/' + sep +
name + name +
(name.endsWith(FILE_EXT) ? '' : FILE_EXT), (name.endsWith(FILE_EXT) ? '' : FILE_EXT),
'' ''
@ -103,9 +104,9 @@ export const FileMachineProvider = ({
let name = newName ? newName : DEFAULT_FILE_NAME let name = newName ? newName : DEFAULT_FILE_NAME
await renameFile( await renameFile(
context.selectedDirectory.path + '/' + oldName, context.selectedDirectory.path + sep + oldName,
context.selectedDirectory.path + context.selectedDirectory.path +
'/' + sep +
name + name +
(name.endsWith(FILE_EXT) || isDir ? '' : FILE_EXT) (name.endsWith(FILE_EXT) || isDir ? '' : FILE_EXT)
) )

View File

@ -24,9 +24,7 @@ import {
StateFrom, StateFrom,
} from 'xstate' } from 'xstate'
import { useCommandsContext } from 'hooks/useCommandsContext' import { useCommandsContext } from 'hooks/useCommandsContext'
import { invoke } from '@tauri-apps/api'
import { isTauri } from 'lib/isTauri' import { isTauri } from 'lib/isTauri'
import { VITE_KC_API_BASE_URL } from 'env'
type MachineContext<T extends AnyStateMachine> = { type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T> state: StateFrom<T>

View File

@ -172,27 +172,38 @@ export const ModelingMachineProvider = ({
} }
} }
), ),
'AST add line segment': ( 'AST add line segment': async (
{ sketchPathToNode, sketchEnginePathId }, { sketchPathToNode, sketchEnginePathId },
{ data: { coords, segmentId } } { data: { coords, segmentId } }
) => { ) => {
if (!sketchPathToNode) return if (!sketchPathToNode) return
const lastCoord = coords[coords.length - 1] const lastCoord = coords[coords.length - 1]
const { node: varDec } = getNodeFromPath<VariableDeclarator>( const pathInfo = await engineCommandManager.sendSceneCommand({
kclManager.ast, type: 'modeling_cmd_req',
sketchPathToNode, cmd_id: uuidv4(),
'VariableDeclarator' cmd: {
type: 'path_get_info',
path_id: sketchEnginePathId,
},
})
const firstSegment = pathInfo?.data?.data?.segments.find(
(seg: any) => seg.command === 'line_to'
) )
const variableName = varDec.id.name const firstSegCoords = await engineCommandManager.sendSceneCommand({
const sketchGroup = kclManager.programMemory.root[variableName] type: 'modeling_cmd_req',
if (!sketchGroup || sketchGroup.type !== 'SketchGroup') return cmd_id: uuidv4(),
const initialCoords = sketchGroup.value[0].from cmd: {
type: 'curve_get_control_points',
curve_id: firstSegment.command_id,
},
})
const startPathCoord = firstSegCoords?.data?.data?.control_points[0]
const isClose = compareVec2Epsilon(initialCoords, [ const isClose = compareVec2Epsilon(
lastCoord.x, [startPathCoord.x, startPathCoord.y],
lastCoord.y, [lastCoord.x, lastCoord.y]
]) )
let _modifiedAst: Program let _modifiedAst: Program
if (!isClose) { if (!isClose) {
@ -200,6 +211,7 @@ export const ModelingMachineProvider = ({
node: kclManager.ast, node: kclManager.ast,
programMemory: kclManager.programMemory, programMemory: kclManager.programMemory,
to: [lastCoord.x, lastCoord.y], to: [lastCoord.x, lastCoord.y],
from: [coords[0].x, coords[0].y],
fnName: 'line', fnName: 'line',
pathToNode: sketchPathToNode, pathToNode: sketchPathToNode,
}) })
@ -413,6 +425,12 @@ export const ModelingMachineProvider = ({
}) })
}, [modelingSend, modelingState.nextEvents]) }, [modelingSend, modelingState.nextEvents])
useEffect(() => {
kclManager.registerExecuteCallback(() => {
modelingSend({ type: 'Re-execute' })
})
}, [modelingSend])
// useStateMachineCommands({ // useStateMachineCommands({
// state: settingsState, // state: settingsState,
// send: settingsSend, // send: settingsSend,

View File

@ -7,6 +7,7 @@ import { Link } from 'react-router-dom'
import { ExportButton } from './ExportButton' import { ExportButton } from './ExportButton'
import { Fragment } from 'react' import { Fragment } from 'react'
import { FileTree } from './FileTree' import { FileTree } from './FileTree'
import { sep } from '@tauri-apps/api/path'
const ProjectSidebarMenu = ({ const ProjectSidebarMenu = ({
project, project,
@ -26,10 +27,10 @@ const ProjectSidebarMenu = ({
<img <img
src="/kitt-8bit-winking.svg" src="/kitt-8bit-winking.svg"
alt="KittyCAD App" alt="KittyCAD App"
className="h-9 w-auto" className="w-auto h-9"
/> />
<span <span
className="text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap hidden lg:block" className="hidden text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block"
data-testid="project-sidebar-link-name" data-testid="project-sidebar-link-name"
> >
{project?.name ? project.name : 'KittyCAD Modeling App'} {project?.name ? project.name : 'KittyCAD Modeling App'}
@ -44,16 +45,16 @@ const ProjectSidebarMenu = ({
<img <img
src="/kitt-8bit-winking.svg" src="/kitt-8bit-winking.svg"
alt="KittyCAD App" alt="KittyCAD App"
className="h-full w-auto" className="w-auto h-full"
/> />
<div className="flex flex-col items-start py-0.5"> <div className="flex flex-col items-start py-0.5">
<span className="text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap hidden lg:block"> <span className="hidden text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block">
{isTauri() && file?.name {isTauri() && file?.name
? file.name.slice(file.name.lastIndexOf('/') + 1) ? file.name.slice(file.name.lastIndexOf(sep) + 1)
: 'KittyCAD Modeling App'} : 'KittyCAD Modeling App'}
</span> </span>
{isTauri() && project?.name && ( {isTauri() && project?.name && (
<span className="text-xs text-chalkboard-70 dark:text-chalkboard-40 whitespace-nowrap hidden lg:block"> <span className="hidden text-xs text-chalkboard-70 dark:text-chalkboard-40 whitespace-nowrap lg:block">
{project.name} {project.name}
</span> </span>
)} )}
@ -68,7 +69,7 @@ const ProjectSidebarMenu = ({
leaveTo="opacity-0" leaveTo="opacity-0"
as={Fragment} as={Fragment}
> >
<Popover.Overlay className="fixed z-20 inset-0 bg-chalkboard-110/50" /> <Popover.Overlay className="fixed inset-0 z-20 bg-chalkboard-110/50" />
</Transition> </Transition>
<Transition <Transition
@ -81,7 +82,7 @@ const ProjectSidebarMenu = ({
as={Fragment} as={Fragment}
> >
<Popover.Panel <Popover.Panel
className="fixed inset-0 right-auto z-30 w-64 h-screen max-h-screen grid grid-cols-1 bg-chalkboard-10 dark:bg-chalkboard-100 border border-energy-100 dark:border-energy-100/50 shadow-md rounded-r-lg" className="fixed inset-0 right-auto z-30 grid w-64 h-screen max-h-screen grid-cols-1 border rounded-r-lg shadow-md bg-chalkboard-10 dark:bg-chalkboard-100 border-energy-100 dark:border-energy-100/50"
style={{ gridTemplateRows: 'auto 1fr auto' }} style={{ gridTemplateRows: 'auto 1fr auto' }}
> >
{({ close }) => ( {({ close }) => (
@ -90,7 +91,7 @@ const ProjectSidebarMenu = ({
<img <img
src="/kitt-8bit-winking.svg" src="/kitt-8bit-winking.svg"
alt="KittyCAD App" alt="KittyCAD App"
className="h-9 w-auto" className="w-auto h-9"
/> />
<div> <div>
@ -102,7 +103,7 @@ const ProjectSidebarMenu = ({
</p> </p>
{project?.entrypointMetadata && ( {project?.entrypointMetadata && (
<p <p
className="m-0 text-chalkboard-100 dark:text-energy-40 text-xs" className="m-0 text-xs text-chalkboard-100 dark:text-energy-40"
data-testid="createdAt" data-testid="createdAt"
> >
Created{' '} Created{' '}
@ -120,7 +121,7 @@ const ProjectSidebarMenu = ({
) : ( ) : (
<div className="flex-1 overflow-hidden" /> <div className="flex-1 overflow-hidden" />
)} )}
<div className="p-4 flex flex-col gap-2 bg-energy-10/25 dark:bg-energy-110"> <div className="flex flex-col gap-2 p-4 bg-energy-10/25 dark:bg-energy-110">
<ExportButton <ExportButton
className={{ className={{
button: button:

View File

@ -20,14 +20,13 @@ import kclLanguage from 'editor/lsp/language'
import { isTauri } from 'lib/isTauri' import { isTauri } from 'lib/isTauri'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import { writeTextFile } from '@tauri-apps/api/fs' import { writeTextFile } from '@tauri-apps/api/fs'
import { PROJECT_ENTRYPOINT } from 'lib/tauriFS'
import { toast } from 'react-hot-toast' import { toast } from 'react-hot-toast'
import { import {
EditorView, EditorView,
addLineHighlight, addLineHighlight,
lineHighlightField, lineHighlightField,
} from 'editor/highlightextension' } from 'editor/highlightextension'
import { isOverlap, roundOff } from 'lib/utils' import { roundOff } from 'lib/utils'
import { kclErrToDiagnostic } from 'lang/errors' import { kclErrToDiagnostic } from 'lang/errors'
import { CSSRuleObject } from 'tailwindcss/types/config' import { CSSRuleObject } from 'tailwindcss/types/config'
import { useModelingContext } from 'hooks/useModelingContext' import { useModelingContext } from 'hooks/useModelingContext'
@ -112,7 +111,7 @@ export const TextEditor = ({
}, [lspClient, isLSPServerReady]) }, [lspClient, isLSPServerReady])
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => { // const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
const onChange = (newCode: string, viewUpdate: ViewUpdate) => { const onChange = (newCode: string) => {
kclManager.setCodeAndExecute(newCode) kclManager.setCodeAndExecute(newCode)
if (isTauri() && pathParams.id) { if (isTauri() && pathParams.id) {
// Save the file to disk // Save the file to disk

View File

@ -0,0 +1,63 @@
import { Dialog } from '@headlessui/react'
import { useState } from 'react'
import { ActionButton } from './ActionButton'
import { faX } from '@fortawesome/free-solid-svg-icons'
import { useKclContext } from 'lang/KclSinglton'
export function WasmErrBanner() {
const [isBannerDismissed, setBannerDismissed] = useState(false)
const { wasmInitFailed } = useKclContext()
if (!wasmInitFailed) return null
return (
<Dialog
className="fixed inset-0 top-auto z-50 bg-warn-20 text-warn-80 px-8 py-4"
open={!isBannerDismissed}
onClose={() => ({})}
>
<Dialog.Panel className="max-w-3xl mx-auto">
<div className="flex gap-2 justify-between items-start">
<h2 className="text-xl font-bold mb-4">
Problem with our WASM blob :(
</h2>
<ActionButton
Element="button"
onClick={() => setBannerDismissed(true)}
icon={{
icon: faX,
bgClassName:
'bg-warn-70 hover:bg-warn-80 dark:bg-warn-70 dark:hover:bg-warn-80',
iconClassName:
'text-warn-10 group-hover:text-warn-10 dark:text-warn-10 dark:group-hover:text-warn-10',
}}
className="!p-0 !bg-transparent !border-transparent"
/>
</div>
<p>
<a
href="https://webassembly.org/"
rel="noopener noreferrer"
target="_blank"
className="text-warn-80 dark:text-warn-80 dark:hover:text-warn-70 underline"
>
WASM or web assembly
</a>{' '}
is core part of how our app works. It might because you OS is not
up-to-date. If you're able to update your OS to a later version, try
that. If not create an issue on{' '}
<a
href="https://github.com/KittyCAD/modeling-app"
rel="noopener noreferrer"
target="_blank"
className="text-warn-80 dark:text-warn-80 dark:hover:text-warn-70 underline"
>
our Github
</a>
.
</p>
</Dialog.Panel>
</Dialog>
)
}

View File

@ -7,6 +7,6 @@ export function useAbsoluteFilePath() {
return ( return (
paths.FILE + paths.FILE +
'/' + '/' +
encodeURIComponent(routeData?.project?.path || BROWSER_FILE_NAME) encodeURIComponent(routeData?.file?.path || BROWSER_FILE_NAME)
) )
} }

View File

@ -40,6 +40,7 @@ class KclManager {
private _logs: string[] = [] private _logs: string[] = []
private _kclErrors: KCLError[] = [] private _kclErrors: KCLError[] = []
private _isExecuting = false private _isExecuting = false
private _wasmInitFailed = true
engineCommandManager: EngineCommandManager engineCommandManager: EngineCommandManager
private _defferer = deferExecution((code: string) => { private _defferer = deferExecution((code: string) => {
@ -47,12 +48,14 @@ class KclManager {
this.executeAst(ast) this.executeAst(ast)
}, 600) }, 600)
private _isExecutingCallback: (a: boolean) => void = () => {} private _isExecutingCallback: (arg: boolean) => void = () => {}
private _codeCallBack: (arg: string) => void = () => {} private _codeCallBack: (arg: string) => void = () => {}
private _astCallBack: (arg: Program) => void = () => {} private _astCallBack: (arg: Program) => void = () => {}
private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {} private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {}
private _logsCallBack: (arg: string[]) => void = () => {} private _logsCallBack: (arg: string[]) => void = () => {}
private _kclErrorsCallBack: (arg: KCLError[]) => void = () => {} private _kclErrorsCallBack: (arg: KCLError[]) => void = () => {}
private _wasmInitFailedCallback: (arg: boolean) => void = () => {}
private _executeCallback: () => void = () => {}
get ast() { get ast() {
return this._ast return this._ast
@ -106,6 +109,14 @@ class KclManager {
this._isExecutingCallback(isExecuting) this._isExecutingCallback(isExecuting)
} }
get wasmInitFailed() {
return this._wasmInitFailed
}
set wasmInitFailed(wasmInitFailed) {
this._wasmInitFailed = wasmInitFailed
this._wasmInitFailedCallback(wasmInitFailed)
}
constructor(engineCommandManager: EngineCommandManager) { constructor(engineCommandManager: EngineCommandManager) {
this.engineCommandManager = engineCommandManager this.engineCommandManager = engineCommandManager
const storedCode = localStorage.getItem(PERSIST_CODE_TOKEN) const storedCode = localStorage.getItem(PERSIST_CODE_TOKEN)
@ -131,6 +142,7 @@ class KclManager {
setLogs, setLogs,
setKclErrors, setKclErrors,
setIsExecuting, setIsExecuting,
setWasmInitFailed,
}: { }: {
setCode: (arg: string) => void setCode: (arg: string) => void
setProgramMemory: (arg: ProgramMemory) => void setProgramMemory: (arg: ProgramMemory) => void
@ -138,6 +150,7 @@ class KclManager {
setLogs: (arg: string[]) => void setLogs: (arg: string[]) => void
setKclErrors: (arg: KCLError[]) => void setKclErrors: (arg: KCLError[]) => void
setIsExecuting: (arg: boolean) => void setIsExecuting: (arg: boolean) => void
setWasmInitFailed: (arg: boolean) => void
}) { }) {
this._codeCallBack = setCode this._codeCallBack = setCode
this._programMemoryCallBack = setProgramMemory this._programMemoryCallBack = setProgramMemory
@ -145,11 +158,26 @@ class KclManager {
this._logsCallBack = setLogs this._logsCallBack = setLogs
this._kclErrorsCallBack = setKclErrors this._kclErrorsCallBack = setKclErrors
this._isExecutingCallback = setIsExecuting this._isExecutingCallback = setIsExecuting
this._wasmInitFailedCallback = setWasmInitFailed
}
registerExecuteCallback(callback: () => void) {
this._executeCallback = callback
}
async ensureWasmInit() {
try {
await initPromise
if (this.wasmInitFailed) {
this.wasmInitFailed = false
}
} catch (e) {
this.wasmInitFailed = true
}
} }
async executeAst(ast: Program = this._ast, updateCode = false) { async executeAst(ast: Program = this._ast, updateCode = false) {
await this.ensureWasmInit()
this.isExecuting = true this.isExecuting = true
await initPromise
const { logs, errors, programMemory } = await executeAst({ const { logs, errors, programMemory } = await executeAst({
ast, ast,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
@ -164,9 +192,10 @@ class KclManager {
this._code = recast(ast) this._code = recast(ast)
this._codeCallBack(this._code) this._codeCallBack(this._code)
} }
this._executeCallback()
} }
async executeAstMock(ast: Program = this._ast, updateCode = false) { async executeAstMock(ast: Program = this._ast, updateCode = false) {
await initPromise await this.ensureWasmInit()
const newCode = recast(ast) const newCode = recast(ast)
const newAst = parse(newCode) const newAst = parse(newCode)
await this?.engineCommandManager?.waitForReady await this?.engineCommandManager?.waitForReady
@ -186,7 +215,7 @@ class KclManager {
this._programMemory = programMemory this._programMemory = programMemory
} }
async executeCode(code?: string) { async executeCode(code?: string) {
await initPromise await this.ensureWasmInit()
await this?.engineCommandManager?.waitForReady await this?.engineCommandManager?.waitForReady
if (!this?.engineCommandManager?.planesInitialized()) return if (!this?.engineCommandManager?.planesInitialized()) return
const result = await executeCode({ const result = await executeCode({
@ -306,6 +335,7 @@ const KclContext = createContext({
isExecuting: kclManager.isExecuting, isExecuting: kclManager.isExecuting,
errors: kclManager.kclErrors, errors: kclManager.kclErrors,
logs: kclManager.logs, logs: kclManager.logs,
wasmInitFailed: kclManager.wasmInitFailed,
}) })
export function useKclContext() { export function useKclContext() {
@ -326,6 +356,7 @@ export function KclContextProvider({
const [isExecuting, setIsExecuting] = useState(false) const [isExecuting, setIsExecuting] = useState(false)
const [errors, setErrors] = useState<KCLError[]>([]) const [errors, setErrors] = useState<KCLError[]>([])
const [logs, setLogs] = useState<string[]>([]) const [logs, setLogs] = useState<string[]>([])
const [wasmInitFailed, setWasmInitFailed] = useState(false)
useEffect(() => { useEffect(() => {
kclManager.registerCallBacks({ kclManager.registerCallBacks({
@ -335,6 +366,7 @@ export function KclContextProvider({
setLogs, setLogs,
setKclErrors: setErrors, setKclErrors: setErrors,
setIsExecuting, setIsExecuting,
setWasmInitFailed,
}) })
}, []) }, [])
return ( return (
@ -346,6 +378,7 @@ export function KclContextProvider({
isExecuting, isExecuting,
errors, errors,
logs, logs,
wasmInitFailed,
}} }}
> >
{children} {children}

View File

@ -450,18 +450,18 @@ export class EngineConnection {
videoTrackStats.forEach((videoTrackReport) => { videoTrackStats.forEach((videoTrackReport) => {
if (videoTrackReport.type === 'inbound-rtp') { if (videoTrackReport.type === 'inbound-rtp') {
client_metrics.rtc_frames_decoded = client_metrics.rtc_frames_decoded =
videoTrackReport.framesDecoded videoTrackReport.framesDecoded || 0
client_metrics.rtc_frames_dropped = client_metrics.rtc_frames_dropped =
videoTrackReport.framesDropped videoTrackReport.framesDropped || 0
client_metrics.rtc_frames_received = client_metrics.rtc_frames_received =
videoTrackReport.framesReceived videoTrackReport.framesReceived || 0
client_metrics.rtc_frames_per_second = client_metrics.rtc_frames_per_second =
videoTrackReport.framesPerSecond || 0 videoTrackReport.framesPerSecond || 0
client_metrics.rtc_freeze_count = client_metrics.rtc_freeze_count =
videoTrackReport.freezeCount || 0 videoTrackReport.freezeCount || 0
client_metrics.rtc_jitter_sec = videoTrackReport.jitter client_metrics.rtc_jitter_sec = videoTrackReport.jitter || 0.0
client_metrics.rtc_keyframes_decoded = client_metrics.rtc_keyframes_decoded =
videoTrackReport.keyFramesDecoded videoTrackReport.keyFramesDecoded || 0
client_metrics.rtc_total_freezes_duration_sec = client_metrics.rtc_total_freezes_duration_sec =
videoTrackReport.totalFreezesDuration || 0 videoTrackReport.totalFreezesDuration || 0
} else if (videoTrackReport.type === 'transport') { } else if (videoTrackReport.type === 'transport') {

View File

@ -138,6 +138,7 @@ show(mySketch001)`
node: ast, node: ast,
programMemory, programMemory,
to: [2, 3], to: [2, 3],
from: [0, 0],
fnName: 'lineTo', fnName: 'lineTo',
pathToNode: [ pathToNode: [
['body', ''], ['body', ''],

View File

@ -193,9 +193,6 @@ export const line: SketchLineHelper = {
pathToNode, pathToNode,
'VariableDeclarator' 'VariableDeclarator'
) )
const variableName = varDec.id.name
const sketch = previousProgramMemory?.root?.[variableName]
if (sketch.type !== 'SketchGroup') throw new Error('not a SketchGroup')
const newXVal = createLiteral(roundOff(to[0] - from[0], 2)) const newXVal = createLiteral(roundOff(to[0] - from[0], 2))
const newYVal = createLiteral(roundOff(to[1] - from[1], 2)) const newYVal = createLiteral(roundOff(to[1] - from[1], 2))
@ -969,7 +966,8 @@ export function addNewSketchLn({
to, to,
fnName, fnName,
pathToNode, pathToNode,
}: Omit<CreateLineFnCallArgs, 'from'>): { from,
}: CreateLineFnCallArgs): {
modifiedAst: Program modifiedAst: Program
pathToNode: PathToNode pathToNode: PathToNode
} { } {
@ -984,12 +982,6 @@ export function addNewSketchLn({
const { node: pipeExp, shallowPath: pipePath } = getNodeFromPath< const { node: pipeExp, shallowPath: pipePath } = getNodeFromPath<
PipeExpression | CallExpression PipeExpression | CallExpression
>(node, pathToNode, 'PipeExpression') >(node, pathToNode, 'PipeExpression')
const variableName = varDec.id.name
const sketch = previousProgramMemory?.root?.[variableName]
if (sketch.type !== 'SketchGroup') throw new Error('not a SketchGroup')
const last = sketch.value[sketch.value.length - 1] || sketch.start
const from = last.to
return add({ return add({
node, node,
previousProgramMemory, previousProgramMemory,

View File

@ -24,6 +24,7 @@ export interface PathReturn {
export interface ModifyAstBase { export interface ModifyAstBase {
node: Program node: Program
// TODO #896: Remove ProgramMemory from this interface
previousProgramMemory: ProgramMemory previousProgramMemory: ProgramMemory
pathToNode: PathToNode pathToNode: PathToNode
} }

View File

@ -66,13 +66,16 @@ const initialise = async () => {
typeof window === 'undefined' typeof window === 'undefined'
? 'http://127.0.0.1:3000' ? 'http://127.0.0.1:3000'
: window.location.origin.includes('tauri://localhost') : window.location.origin.includes('tauri://localhost')
? 'tauri://localhost' ? 'tauri://localhost' // custom protocol for macOS
: window.location.origin.includes('tauri.localhost')
? 'https://tauri.localhost' // fallback for Windows
: window.location.origin.includes('localhost') : window.location.origin.includes('localhost')
? 'http://localhost:3000' ? 'http://localhost:3000'
: window.location.origin && window.location.origin !== 'null' : window.location.origin && window.location.origin !== 'null'
? window.location.origin ? window.location.origin
: 'http://localhost:3000' : 'http://localhost:3000'
const fullUrl = baseUrl + '/wasm_lib_bg.wasm' const fullUrl = baseUrl + '/wasm_lib_bg.wasm'
console.log(`Full URL for WASM: ${fullUrl}`)
const input = await fetch(fullUrl) const input = await fetch(fullUrl)
const buffer = await input.arrayBuffer() const buffer = await input.arrayBuffer()
return init(buffer) return init(buffer)

View File

@ -5,10 +5,11 @@ import {
readDir, readDir,
writeTextFile, writeTextFile,
} from '@tauri-apps/api/fs' } from '@tauri-apps/api/fs'
import { documentDir, homeDir } from '@tauri-apps/api/path' import { documentDir, homeDir, sep } from '@tauri-apps/api/path'
import { isTauri } from './isTauri' import { isTauri } from './isTauri'
import { ProjectWithEntryPointMetadata } from '../Router' import { ProjectWithEntryPointMetadata } from '../Router'
import { metadata } from 'tauri-plugin-fs-extra-api' import { metadata } from 'tauri-plugin-fs-extra-api'
import { bracket } from './exampleKcl'
const PROJECT_FOLDER = 'kittycad-modeling-projects' const PROJECT_FOLDER = 'kittycad-modeling-projects'
export const FILE_EXT = '.kcl' export const FILE_EXT = '.kcl'
@ -70,7 +71,7 @@ export async function getProjectsInDir(projectDir: string) {
const projectsWithMetadata = await Promise.all( const projectsWithMetadata = await Promise.all(
readProjects.map(async (p) => ({ readProjects.map(async (p) => ({
entrypointMetadata: await metadata(p.path + '/' + PROJECT_ENTRYPOINT), entrypointMetadata: await metadata(p.path + sep + PROJECT_ENTRYPOINT),
...p, ...p,
})) }))
) )
@ -224,7 +225,7 @@ export async function createNewProject(
}) })
} }
await writeTextFile(path + '/' + PROJECT_ENTRYPOINT, '').catch((err) => { await writeTextFile(path + sep + PROJECT_ENTRYPOINT, bracket).catch((err) => {
console.error('Error creating new file:', err) console.error('Error creating new file:', err)
throw err throw err
}) })
@ -232,13 +233,13 @@ export async function createNewProject(
const m = await metadata(path) const m = await metadata(path)
return { return {
name: path.slice(path.lastIndexOf('/') + 1), name: path.slice(path.lastIndexOf(sep) + 1),
path: path, path: path,
entrypointMetadata: m, entrypointMetadata: m,
children: [ children: [
{ {
name: PROJECT_ENTRYPOINT, name: PROJECT_ENTRYPOINT,
path: path + '/' + PROJECT_ENTRYPOINT, path: path + sep + PROJECT_ENTRYPOINT,
children: [], children: [],
}, },
], ],

File diff suppressed because one or more lines are too long

View File

@ -52,19 +52,20 @@
"Update code selection cursors": "Complete line" | "Deselect all" | "Deselect axis" | "Deselect edge" | "Deselect face" | "Deselect point" | "Deselect segment" | "Select edge" | "Select face" | "Select point" | "Select segment"; "Update code selection cursors": "Complete line" | "Deselect all" | "Deselect axis" | "Deselect edge" | "Deselect face" | "Deselect point" | "Deselect segment" | "Select edge" | "Select face" | "Select point" | "Select segment";
"create path": "Select default plane"; "create path": "Select default plane";
"default_camera_disable_sketch_mode": "Cancel"; "default_camera_disable_sketch_mode": "Cancel";
"edit mode enter": "Enter sketch"; "edit mode enter": "Enter sketch" | "Re-execute";
"edit_mode_exit": "Cancel"; "edit_mode_exit": "Cancel";
"equip select": "CancelSketch" | "Constrain equal length" | "Constrain horizontally align" | "Constrain parallel" | "Constrain remove constraints" | "Constrain vertically align" | "Deselect point" | "Deselect segment" | "Enter sketch" | "Make segment horizontal" | "Make segment vertical" | "Select default plane" | "Select point" | "Select segment" | "Set selection" | "done.invoke.get-angle-info" | "done.invoke.get-horizontal-info" | "done.invoke.get-length-info" | "done.invoke.get-perpendicular-distance-info" | "done.invoke.get-vertical-info" | "error.platform.get-angle-info" | "error.platform.get-horizontal-info" | "error.platform.get-length-info" | "error.platform.get-perpendicular-distance-info" | "error.platform.get-vertical-info"; "equip select": "CancelSketch" | "Constrain equal length" | "Constrain horizontally align" | "Constrain parallel" | "Constrain remove constraints" | "Constrain vertically align" | "Deselect point" | "Deselect segment" | "Enter sketch" | "Make segment horizontal" | "Make segment vertical" | "Re-execute" | "Select default plane" | "Select point" | "Select segment" | "Set selection" | "done.invoke.get-angle-info" | "done.invoke.get-horizontal-info" | "done.invoke.get-length-info" | "done.invoke.get-perpendicular-distance-info" | "done.invoke.get-vertical-info" | "error.platform.get-angle-info" | "error.platform.get-horizontal-info" | "error.platform.get-length-info" | "error.platform.get-perpendicular-distance-info" | "error.platform.get-vertical-info";
"hide default planes": "Cancel" | "Select default plane" | "xstate.stop"; "hide default planes": "Cancel" | "Select default plane" | "xstate.stop";
"reset sketch metadata": "Cancel" | "Select default plane"; "reset sketch metadata": "Cancel" | "Select default plane";
"set default plane id": "Select default plane"; "set default plane id": "Select default plane";
"set sketch metadata": "Enter sketch"; "set sketch metadata": "Enter sketch";
"set sketchMetadata from pathToNode": "Re-execute";
"set tool": "Equip new tool"; "set tool": "Equip new tool";
"set tool line": "Equip tool"; "set tool line": "Equip tool";
"set tool move": "Equip move tool" | "Set selection"; "set tool move": "Equip move tool" | "Re-execute" | "Set selection";
"show default planes": "Enter sketch"; "show default planes": "Enter sketch";
"sketch exit execute": "Cancel" | "Complete line" | "xstate.stop"; "sketch exit execute": "Cancel" | "Complete line" | "xstate.stop";
"sketch mode enabled": "Enter sketch" | "Select default plane"; "sketch mode enabled": "Enter sketch" | "Re-execute" | "Select default plane";
"toast extrude failed": ""; "toast extrude failed": "";
}; };
eventsCausingDelays: { eventsCausingDelays: {

View File

@ -29,6 +29,7 @@ import useStateMachineCommands from '../hooks/useStateMachineCommands'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext' import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { useCommandsContext } from 'hooks/useCommandsContext' import { useCommandsContext } from 'hooks/useCommandsContext'
import { DEFAULT_PROJECT_NAME } from 'machines/settingsMachine' import { DEFAULT_PROJECT_NAME } from 'machines/settingsMachine'
import { sep } from '@tauri-apps/api/path'
// This route only opens in the Tauri desktop context for now, // This route only opens in the Tauri desktop context for now,
// as defined in Router.tsx, so we can use the Tauri APIs and types. // as defined in Router.tsx, so we can use the Tauri APIs and types.
@ -58,7 +59,7 @@ const Home = () => {
setCommandBarOpen(false) setCommandBarOpen(false)
navigate( navigate(
`${paths.FILE}/${encodeURIComponent( `${paths.FILE}/${encodeURIComponent(
context.defaultDirectory + '/' + event.data.name context.defaultDirectory + sep + event.data.name
)}` )}`
) )
} }
@ -91,7 +92,7 @@ const Home = () => {
name = interpolateProjectNameWithIndex(name, nextIndex) name = interpolateProjectNameWithIndex(name, nextIndex)
} }
await createNewProject(context.defaultDirectory + '/' + name) await createNewProject(context.defaultDirectory + sep + name)
if (shouldUpdateDefaultProjectName) { if (shouldUpdateDefaultProjectName) {
sendToSettings({ sendToSettings({
@ -114,8 +115,8 @@ const Home = () => {
} }
await renameFile( await renameFile(
context.defaultDirectory + '/' + oldName, context.defaultDirectory + sep + oldName,
context.defaultDirectory + '/' + name context.defaultDirectory + sep + name
) )
return `Successfully renamed "${oldName}" to "${name}"` return `Successfully renamed "${oldName}" to "${name}"`
}, },
@ -123,7 +124,7 @@ const Home = () => {
context: ContextFrom<typeof homeMachine>, context: ContextFrom<typeof homeMachine>,
event: EventFrom<typeof homeMachine, 'Delete project'> event: EventFrom<typeof homeMachine, 'Delete project'>
) => { ) => {
await removeDir(context.defaultDirectory + '/' + event.data.name, { await removeDir(context.defaultDirectory + sep + event.data.name, {
recursive: true, recursive: true,
}) })
return `Successfully deleted "${event.data.name}"` return `Successfully deleted "${event.data.name}"`
@ -172,9 +173,9 @@ const Home = () => {
} }
return ( return (
<div className="h-screen overflow-hidden relative flex flex-col"> <div className="relative flex flex-col h-screen overflow-hidden">
<AppHeader showToolbar={false} /> <AppHeader showToolbar={false} />
<div className="my-24 px-4 lg:px-0 overflow-y-auto max-w-5xl w-full mx-auto"> <div className="w-full max-w-5xl px-4 mx-auto my-24 overflow-y-auto lg:px-0">
<section className="flex justify-between"> <section className="flex justify-between">
<h1 className="text-3xl text-bold">Your Projects</h1> <h1 className="text-3xl text-bold">Your Projects</h1>
<div className="flex"> <div className="flex">
@ -235,7 +236,7 @@ const Home = () => {
) : ( ) : (
<> <>
{projects.length > 0 ? ( {projects.length > 0 ? (
<ul className="my-8 w-full grid grid-cols-4 gap-4"> <ul className="grid w-full grid-cols-4 gap-4 my-8">
{projects.sort(getSortFunction(sort)).map((project) => ( {projects.sort(getSortFunction(sort)).map((project) => (
<ProjectCard <ProjectCard
key={project.name} key={project.name}
@ -246,7 +247,7 @@ const Home = () => {
))} ))}
</ul> </ul>
) : ( ) : (
<p className="rounded my-8 border border-dashed border-chalkboard-30 dark:border-chalkboard-70 p-4"> <p className="p-4 my-8 border border-dashed rounded border-chalkboard-30 dark:border-chalkboard-70">
No Projects found, ready to make your first one? No Projects found, ready to make your first one?
</p> </p>
)} )}

View File

@ -24,8 +24,15 @@ export default function Export() {
Try opening the project menu and clicking "Export Model". Try opening the project menu and clicking "Export Model".
</p> </p>
<p className="my-4"> <p className="my-4">
KittyCAD Modeling App uses our open-source extension proposal for KittyCAD Modeling App uses{' '}
the GLTF file format.{' '} <a
href="https://kittycad.io/gltf-format-extension"
rel="noopener noreferrer"
target="_blank"
>
our open-source extension proposal
</a>{' '}
for the GLTF file format.{' '}
<a <a
href="https://kittycad.io/docs/api/convert-cad-file" href="https://kittycad.io/docs/api/convert-cad-file"
rel="noopener noreferrer" rel="noopener noreferrer"

View File

@ -4,13 +4,23 @@ import { useDismiss } from '.'
import { useEffect } from 'react' import { useEffect } from 'react'
import { bracket } from 'lib/exampleKcl' import { bracket } from 'lib/exampleKcl'
import { kclManager } from 'lang/KclSinglton' import { kclManager } from 'lang/KclSinglton'
import { useModelingContext } from 'hooks/useModelingContext'
export default function FutureWork() { export default function FutureWork() {
const { send } = useModelingContext()
const dismiss = useDismiss() const dismiss = useDismiss()
useEffect(() => { useEffect(() => {
if (kclManager.engineCommandManager.engineConnection?.isReady()) {
// If the engine is ready, promptly execute the loaded code
kclManager.setCodeAndExecute(bracket)
} else {
// Otherwise, just set the code and wait for the connection to complete
kclManager.setCode(bracket) kclManager.setCode(bracket)
}, [kclManager.setCode]) }
send({ type: 'Cancel' }) // in case the user hit 'Next' while still in sketch mode
}, [send])
return ( return (
<div className="fixed grid justify-center items-center inset-0 bg-chalkboard-100/50 z-50"> <div className="fixed grid justify-center items-center inset-0 bg-chalkboard-100/50 z-50">

View File

@ -10,6 +10,7 @@ import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { Themes, getSystemTheme } from 'lib/theme' import { Themes, getSystemTheme } from 'lib/theme'
import { bracket } from 'lib/exampleKcl' import { bracket } from 'lib/exampleKcl'
import { import {
PROJECT_ENTRYPOINT,
createNewProject, createNewProject,
getNextProjectIndex, getNextProjectIndex,
getProjectsInDir, getProjectsInDir,
@ -20,6 +21,7 @@ import { useNavigate } from 'react-router-dom'
import { paths } from 'Router' import { paths } from 'Router'
import { useEffect } from 'react' import { useEffect } from 'react'
import { kclManager } from 'lang/KclSinglton' import { kclManager } from 'lang/KclSinglton'
import { sep } from '@tauri-apps/api/path'
function OnboardingWithNewFile() { function OnboardingWithNewFile() {
const navigate = useNavigate() const navigate = useNavigate()
@ -41,12 +43,16 @@ function OnboardingWithNewFile() {
ONBOARDING_PROJECT_NAME, ONBOARDING_PROJECT_NAME,
nextIndex nextIndex
) )
const newFile = await createNewProject(defaultDirectory + '/' + name) const newFile = await createNewProject(defaultDirectory + sep + name)
navigate(`${paths.FILE}/${encodeURIComponent(newFile.path)}`) navigate(
`${paths.FILE}/${encodeURIComponent(
newFile.path + sep + PROJECT_ENTRYPOINT
)}${paths.ONBOARDING.INDEX}`
)
} }
return ( return (
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50"> <div className="fixed inset-0 z-50 grid place-content-center bg-chalkboard-110/50">
<div className="max-w-3xl bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded"> <div className="max-w-3xl p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
{!isTauri() ? ( {!isTauri() ? (
<> <>
<h1 className="text-2xl font-bold text-warn-80 dark:text-warn-10"> <h1 className="text-2xl font-bold text-warn-80 dark:text-warn-10">
@ -84,7 +90,7 @@ function OnboardingWithNewFile() {
</> </>
) : ( ) : (
<> <>
<h1 className="text-2xl font-bold flex gap-4 flex-wrap items-center"> <h1 className="flex flex-wrap items-center gap-4 text-2xl font-bold">
Would you like to create a new project? Would you like to create a new project?
</h1> </h1>
<section className="my-12"> <section className="my-12">
@ -110,7 +116,11 @@ function OnboardingWithNewFile() {
</ActionButton> </ActionButton>
<ActionButton <ActionButton
Element="button" Element="button"
onClick={createAndOpenNewProject} onClick={() => {
createAndOpenNewProject()
kclManager.setCode(bracket)
dismiss()
}}
icon={{ icon: faArrowRight }} icon={{ icon: faArrowRight }}
> >
Make a new project Make a new project
@ -138,21 +148,22 @@ export default function Introduction() {
: '' : ''
const dismiss = useDismiss() const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.CAMERA) const next = useNextClick(onboardingPaths.CAMERA)
const isStarterCode = kclManager.code === '' || kclManager.code === bracket
useEffect(() => { useEffect(() => {
if (kclManager.code === '') kclManager.setCode(bracket) if (kclManager.code === '') kclManager.setCode(bracket)
}, [kclManager.code, kclManager.setCode]) }, [])
return !(kclManager.code !== '' && kclManager.code !== bracket) ? ( return isStarterCode ? (
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50"> <div className="fixed inset-0 z-50 grid place-content-center bg-chalkboard-110/50">
<div className="max-w-3xl bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded"> <div className="max-w-3xl p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
<h1 className="text-2xl font-bold flex gap-4 flex-wrap items-center"> <h1 className="flex flex-wrap items-center gap-4 text-2xl font-bold">
<img <img
src={`/kcma-logomark${getLogoTheme()}.svg`} src={`/kcma-logomark${getLogoTheme()}.svg`}
alt="KittyCAD Modeling App" alt="KittyCAD Modeling App"
className="max-w-full h-20" className="h-20 max-w-full"
/> />
<span className="bg-energy-10 text-energy-80 px-3 py-1 rounded-full text-base"> <span className="px-3 py-1 text-base rounded-full bg-energy-10 text-energy-80">
Alpha Alpha
</span> </span>
</h1> </h1>

View File

@ -11,7 +11,13 @@ export default function Sketching() {
const next = useNextClick(onboardingPaths.FUTURE_WORK) const next = useNextClick(onboardingPaths.FUTURE_WORK)
useEffect(() => { useEffect(() => {
if (kclManager.engineCommandManager.engineConnection?.isReady()) {
// If the engine is ready, promptly execute the loaded code
kclManager.setCodeAndExecute('')
} else {
// Otherwise, just set the code and wait for the connection to complete
kclManager.setCode('') kclManager.setCode('')
}
}, []) }, [])
return ( return (

View File

@ -31,6 +31,7 @@ import {
interpolateProjectNameWithIndex, interpolateProjectNameWithIndex,
} from 'lib/tauriFS' } from 'lib/tauriFS'
import { ONBOARDING_PROJECT_NAME } from './Onboarding' import { ONBOARDING_PROJECT_NAME } from './Onboarding'
import { sep } from '@tauri-apps/api/path'
export const Settings = () => { export const Settings = () => {
const loaderData = const loaderData =
@ -95,7 +96,7 @@ export const Settings = () => {
ONBOARDING_PROJECT_NAME, ONBOARDING_PROJECT_NAME,
nextIndex nextIndex
) )
const newFile = await createNewProject(defaultDirectory + '/' + name) const newFile = await createNewProject(defaultDirectory + sep + name)
navigate(`${paths.FILE}/${encodeURIComponent(newFile.path)}`) navigate(`${paths.FILE}/${encodeURIComponent(newFile.path)}`)
} }
@ -116,7 +117,7 @@ export const Settings = () => {
Close Close
</ActionButton> </ActionButton>
</AppHeader> </AppHeader>
<div className="max-w-5xl mx-auto my-24"> <div className="max-w-5xl mx-5 lg:mx-auto my-24">
<h1 className="text-4xl font-bold">User Settings</h1> <h1 className="text-4xl font-bold">User Settings</h1>
<p className="max-w-2xl mt-6"> <p className="max-w-2xl mt-6">
Don't see the feature you want? Check to see if it's on{' '} Don't see the feature you want? Check to see if it's on{' '}

View File

@ -2,12 +2,10 @@ import { create } from 'zustand'
import { persist } from 'zustand/middleware' import { persist } from 'zustand/middleware'
import { addLineHighlight, EditorView } from './editor/highlightextension' import { addLineHighlight, EditorView } from './editor/highlightextension'
import { parse, Program, _executor, ProgramMemory } from './lang/wasm' import { parse, Program, _executor, ProgramMemory } from './lang/wasm'
import { Selection, Selections, SelectionRangeTypeMap } from 'lib/selections' import { Selection } from 'lib/selections'
import { enginelessExecutor } from './lib/testHelpers' import { enginelessExecutor } from './lib/testHelpers'
import { EditorSelection } from '@codemirror/state'
import { EngineCommandManager } from './lang/std/engineConnection' import { EngineCommandManager } from './lang/std/engineConnection'
import { KCLError } from './lang/errors' import { KCLError } from './lang/errors'
import { kclManager } from 'lang/KclSinglton'
import { DefaultPlanes } from './wasm-lib/kcl/bindings/DefaultPlanes' import { DefaultPlanes } from './wasm-lib/kcl/bindings/DefaultPlanes'
export type ToolTip = export type ToolTip =