Compare commits
9 Commits
franknoiro
...
v0.11.0
Author | SHA1 | Date | |
---|---|---|---|
c825eac27e | |||
82e8a491c4 | |||
93e806fc99 | |||
f1a14f1e3d | |||
57c01ec3a2 | |||
ce951d7c12 | |||
0aa2a6cee7 | |||
ba8f5d9785 | |||
50a133b2fa |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "untitled-app",
|
||||
"version": "0.10.0",
|
||||
"version": "0.11.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.9.0",
|
||||
|
@ -8,7 +8,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "kittycad-modeling",
|
||||
"version": "0.10.0"
|
||||
"version": "0.11.0"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
|
@ -31,6 +31,7 @@ import {
|
||||
} from './lib/tauriFS'
|
||||
import { metadata, type Metadata } from 'tauri-plugin-fs-extra-api'
|
||||
import DownloadAppBanner from './components/DownloadAppBanner'
|
||||
import { WasmErrBanner } from './components/WasmErrBanner'
|
||||
import { GlobalStateProvider } from './components/GlobalStateProvider'
|
||||
import {
|
||||
SETTINGS_PERSIST_KEY,
|
||||
@ -43,6 +44,7 @@ import * as Sentry from '@sentry/react'
|
||||
import ModelingMachineProvider from 'components/ModelingMachineProvider'
|
||||
import { KclContextProvider } from 'lang/KclSinglton'
|
||||
import FileMachineProvider from 'components/FileMachineProvider'
|
||||
import { sep } from '@tauri-apps/api/path'
|
||||
|
||||
if (VITE_KC_SENTRY_DSN && !TEST) {
|
||||
Sentry.init({
|
||||
@ -144,12 +146,13 @@ const router = createBrowserRouter(
|
||||
path: paths.FILE + '/:id',
|
||||
element: (
|
||||
<Auth>
|
||||
<Outlet />
|
||||
<FileMachineProvider>
|
||||
<KclContextProvider>
|
||||
<ModelingMachineProvider>
|
||||
<Outlet />
|
||||
<App />
|
||||
</ModelingMachineProvider>
|
||||
<WasmErrBanner />
|
||||
</KclContextProvider>
|
||||
</FileMachineProvider>
|
||||
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
|
||||
@ -185,23 +188,23 @@ const router = createBrowserRouter(
|
||||
|
||||
if (params.id && params.id !== BROWSER_FILE_NAME) {
|
||||
const decodedId = decodeURIComponent(params.id)
|
||||
const projectAndFile = decodedId.replace(defaultDir + '/', '')
|
||||
const firstSlashIndex = projectAndFile.indexOf('/')
|
||||
const projectAndFile = decodedId.replace(defaultDir + sep, '')
|
||||
const firstSlashIndex = projectAndFile.indexOf(sep)
|
||||
const projectName = projectAndFile.slice(0, firstSlashIndex)
|
||||
const projectPath = defaultDir + '/' + projectName
|
||||
const projectPath = defaultDir + sep + projectName
|
||||
const currentFileName = projectAndFile.slice(firstSlashIndex + 1)
|
||||
|
||||
if (firstSlashIndex === -1 || !currentFileName)
|
||||
return redirect(
|
||||
`${paths.FILE}/${encodeURIComponent(
|
||||
`${params.id}/${PROJECT_ENTRYPOINT}`
|
||||
`${params.id}${sep}${PROJECT_ENTRYPOINT}`
|
||||
)}`
|
||||
)
|
||||
|
||||
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
|
||||
const code = await readTextFile(decodedId)
|
||||
const entrypointMetadata = await metadata(
|
||||
projectPath + '/' + PROJECT_ENTRYPOINT
|
||||
projectPath + sep + PROJECT_ENTRYPOINT
|
||||
)
|
||||
const children = await readDir(projectPath, { recursive: true })
|
||||
|
||||
@ -268,9 +271,9 @@ const router = createBrowserRouter(
|
||||
isProjectDirectory
|
||||
)
|
||||
const projects = await Promise.all(
|
||||
projectsNoMeta.map(async (p) => ({
|
||||
projectsNoMeta.map(async (p: FileEntry) => ({
|
||||
entrypointMetadata: await metadata(
|
||||
p.path + '/' + PROJECT_ENTRYPOINT
|
||||
p.path + sep + PROJECT_ENTRYPOINT
|
||||
),
|
||||
...p,
|
||||
}))
|
||||
|
@ -32,13 +32,11 @@ export const AppHeader = ({
|
||||
className
|
||||
}
|
||||
>
|
||||
{project && (
|
||||
<ProjectSidebarMenu
|
||||
renderAsLink={!enableMenu}
|
||||
project={project.project}
|
||||
file={project.file}
|
||||
/>
|
||||
)}
|
||||
<ProjectSidebarMenu
|
||||
renderAsLink={!enableMenu}
|
||||
project={project?.project}
|
||||
file={project?.file}
|
||||
/>
|
||||
{/* Toolbar if the context deems it */}
|
||||
{showToolbar && (
|
||||
<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 */}
|
||||
{children || (
|
||||
<div className="ml-auto flex items-center gap-1">
|
||||
<div className="flex items-center gap-1 ml-auto">
|
||||
<NetworkHealthIndicator />
|
||||
<UserSidebarMenu user={user} />
|
||||
</div>
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
} from '@tauri-apps/api/fs'
|
||||
import { FILE_EXT, readProject } from 'lib/tauriFS'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { sep } from '@tauri-apps/api/path'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -56,7 +57,7 @@ export const FileMachineProvider = ({
|
||||
setCommandBarOpen(false)
|
||||
navigate(
|
||||
`${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
|
||||
|
||||
if (event.data.makeDir) {
|
||||
await createDir(context.selectedDirectory.path + '/' + name)
|
||||
await createDir(context.selectedDirectory.path + sep + name)
|
||||
} else {
|
||||
await writeFile(
|
||||
context.selectedDirectory.path +
|
||||
'/' +
|
||||
sep +
|
||||
name +
|
||||
(name.endsWith(FILE_EXT) ? '' : FILE_EXT),
|
||||
''
|
||||
@ -103,9 +104,9 @@ export const FileMachineProvider = ({
|
||||
let name = newName ? newName : DEFAULT_FILE_NAME
|
||||
|
||||
await renameFile(
|
||||
context.selectedDirectory.path + '/' + oldName,
|
||||
context.selectedDirectory.path + sep + oldName,
|
||||
context.selectedDirectory.path +
|
||||
'/' +
|
||||
sep +
|
||||
name +
|
||||
(name.endsWith(FILE_EXT) || isDir ? '' : FILE_EXT)
|
||||
)
|
||||
|
@ -24,9 +24,7 @@ import {
|
||||
StateFrom,
|
||||
} from 'xstate'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { VITE_KC_API_BASE_URL } from 'env'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
|
@ -172,27 +172,38 @@ export const ModelingMachineProvider = ({
|
||||
}
|
||||
}
|
||||
),
|
||||
'AST add line segment': (
|
||||
'AST add line segment': async (
|
||||
{ sketchPathToNode, sketchEnginePathId },
|
||||
{ data: { coords, segmentId } }
|
||||
) => {
|
||||
if (!sketchPathToNode) return
|
||||
const lastCoord = coords[coords.length - 1]
|
||||
|
||||
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
|
||||
kclManager.ast,
|
||||
sketchPathToNode,
|
||||
'VariableDeclarator'
|
||||
const pathInfo = await engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
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 sketchGroup = kclManager.programMemory.root[variableName]
|
||||
if (!sketchGroup || sketchGroup.type !== 'SketchGroup') return
|
||||
const initialCoords = sketchGroup.value[0].from
|
||||
const firstSegCoords = await engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'curve_get_control_points',
|
||||
curve_id: firstSegment.command_id,
|
||||
},
|
||||
})
|
||||
const startPathCoord = firstSegCoords?.data?.data?.control_points[0]
|
||||
|
||||
const isClose = compareVec2Epsilon(initialCoords, [
|
||||
lastCoord.x,
|
||||
lastCoord.y,
|
||||
])
|
||||
const isClose = compareVec2Epsilon(
|
||||
[startPathCoord.x, startPathCoord.y],
|
||||
[lastCoord.x, lastCoord.y]
|
||||
)
|
||||
|
||||
let _modifiedAst: Program
|
||||
if (!isClose) {
|
||||
@ -200,6 +211,7 @@ export const ModelingMachineProvider = ({
|
||||
node: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
to: [lastCoord.x, lastCoord.y],
|
||||
from: [coords[0].x, coords[0].y],
|
||||
fnName: 'line',
|
||||
pathToNode: sketchPathToNode,
|
||||
})
|
||||
@ -413,6 +425,12 @@ export const ModelingMachineProvider = ({
|
||||
})
|
||||
}, [modelingSend, modelingState.nextEvents])
|
||||
|
||||
useEffect(() => {
|
||||
kclManager.registerExecuteCallback(() => {
|
||||
modelingSend({ type: 'Re-execute' })
|
||||
})
|
||||
}, [modelingSend])
|
||||
|
||||
// useStateMachineCommands({
|
||||
// state: settingsState,
|
||||
// send: settingsSend,
|
||||
|
@ -7,6 +7,7 @@ import { Link } from 'react-router-dom'
|
||||
import { ExportButton } from './ExportButton'
|
||||
import { Fragment } from 'react'
|
||||
import { FileTree } from './FileTree'
|
||||
import { sep } from '@tauri-apps/api/path'
|
||||
|
||||
const ProjectSidebarMenu = ({
|
||||
project,
|
||||
@ -26,10 +27,10 @@ const ProjectSidebarMenu = ({
|
||||
<img
|
||||
src="/kitt-8bit-winking.svg"
|
||||
alt="KittyCAD App"
|
||||
className="h-9 w-auto"
|
||||
className="w-auto h-9"
|
||||
/>
|
||||
<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"
|
||||
>
|
||||
{project?.name ? project.name : 'KittyCAD Modeling App'}
|
||||
@ -44,16 +45,16 @@ const ProjectSidebarMenu = ({
|
||||
<img
|
||||
src="/kitt-8bit-winking.svg"
|
||||
alt="KittyCAD App"
|
||||
className="h-full w-auto"
|
||||
className="w-auto h-full"
|
||||
/>
|
||||
<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
|
||||
? file.name.slice(file.name.lastIndexOf('/') + 1)
|
||||
? file.name.slice(file.name.lastIndexOf(sep) + 1)
|
||||
: 'KittyCAD Modeling App'}
|
||||
</span>
|
||||
{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}
|
||||
</span>
|
||||
)}
|
||||
@ -68,7 +69,7 @@ const ProjectSidebarMenu = ({
|
||||
leaveTo="opacity-0"
|
||||
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
|
||||
@ -81,7 +82,7 @@ const ProjectSidebarMenu = ({
|
||||
as={Fragment}
|
||||
>
|
||||
<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' }}
|
||||
>
|
||||
{({ close }) => (
|
||||
@ -90,7 +91,7 @@ const ProjectSidebarMenu = ({
|
||||
<img
|
||||
src="/kitt-8bit-winking.svg"
|
||||
alt="KittyCAD App"
|
||||
className="h-9 w-auto"
|
||||
className="w-auto h-9"
|
||||
/>
|
||||
|
||||
<div>
|
||||
@ -102,7 +103,7 @@ const ProjectSidebarMenu = ({
|
||||
</p>
|
||||
{project?.entrypointMetadata && (
|
||||
<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"
|
||||
>
|
||||
Created{' '}
|
||||
@ -120,7 +121,7 @@ const ProjectSidebarMenu = ({
|
||||
) : (
|
||||
<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
|
||||
className={{
|
||||
button:
|
||||
|
@ -20,14 +20,13 @@ import kclLanguage from 'editor/lsp/language'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { writeTextFile } from '@tauri-apps/api/fs'
|
||||
import { PROJECT_ENTRYPOINT } from 'lib/tauriFS'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import {
|
||||
EditorView,
|
||||
addLineHighlight,
|
||||
lineHighlightField,
|
||||
} from 'editor/highlightextension'
|
||||
import { isOverlap, roundOff } from 'lib/utils'
|
||||
import { roundOff } from 'lib/utils'
|
||||
import { kclErrToDiagnostic } from 'lang/errors'
|
||||
import { CSSRuleObject } from 'tailwindcss/types/config'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
@ -112,7 +111,7 @@ export const TextEditor = ({
|
||||
}, [lspClient, isLSPServerReady])
|
||||
|
||||
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
|
||||
const onChange = (newCode: string, viewUpdate: ViewUpdate) => {
|
||||
const onChange = (newCode: string) => {
|
||||
kclManager.setCodeAndExecute(newCode)
|
||||
if (isTauri() && pathParams.id) {
|
||||
// Save the file to disk
|
||||
|
63
src/components/WasmErrBanner.tsx
Normal file
63
src/components/WasmErrBanner.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -7,6 +7,6 @@ export function useAbsoluteFilePath() {
|
||||
return (
|
||||
paths.FILE +
|
||||
'/' +
|
||||
encodeURIComponent(routeData?.project?.path || BROWSER_FILE_NAME)
|
||||
encodeURIComponent(routeData?.file?.path || BROWSER_FILE_NAME)
|
||||
)
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ class KclManager {
|
||||
private _logs: string[] = []
|
||||
private _kclErrors: KCLError[] = []
|
||||
private _isExecuting = false
|
||||
private _wasmInitFailed = true
|
||||
|
||||
engineCommandManager: EngineCommandManager
|
||||
private _defferer = deferExecution((code: string) => {
|
||||
@ -47,12 +48,14 @@ class KclManager {
|
||||
this.executeAst(ast)
|
||||
}, 600)
|
||||
|
||||
private _isExecutingCallback: (a: boolean) => void = () => {}
|
||||
private _isExecutingCallback: (arg: boolean) => void = () => {}
|
||||
private _codeCallBack: (arg: string) => void = () => {}
|
||||
private _astCallBack: (arg: Program) => void = () => {}
|
||||
private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {}
|
||||
private _logsCallBack: (arg: string[]) => void = () => {}
|
||||
private _kclErrorsCallBack: (arg: KCLError[]) => void = () => {}
|
||||
private _wasmInitFailedCallback: (arg: boolean) => void = () => {}
|
||||
private _executeCallback: () => void = () => {}
|
||||
|
||||
get ast() {
|
||||
return this._ast
|
||||
@ -106,6 +109,14 @@ class KclManager {
|
||||
this._isExecutingCallback(isExecuting)
|
||||
}
|
||||
|
||||
get wasmInitFailed() {
|
||||
return this._wasmInitFailed
|
||||
}
|
||||
set wasmInitFailed(wasmInitFailed) {
|
||||
this._wasmInitFailed = wasmInitFailed
|
||||
this._wasmInitFailedCallback(wasmInitFailed)
|
||||
}
|
||||
|
||||
constructor(engineCommandManager: EngineCommandManager) {
|
||||
this.engineCommandManager = engineCommandManager
|
||||
const storedCode = localStorage.getItem(PERSIST_CODE_TOKEN)
|
||||
@ -131,6 +142,7 @@ class KclManager {
|
||||
setLogs,
|
||||
setKclErrors,
|
||||
setIsExecuting,
|
||||
setWasmInitFailed,
|
||||
}: {
|
||||
setCode: (arg: string) => void
|
||||
setProgramMemory: (arg: ProgramMemory) => void
|
||||
@ -138,6 +150,7 @@ class KclManager {
|
||||
setLogs: (arg: string[]) => void
|
||||
setKclErrors: (arg: KCLError[]) => void
|
||||
setIsExecuting: (arg: boolean) => void
|
||||
setWasmInitFailed: (arg: boolean) => void
|
||||
}) {
|
||||
this._codeCallBack = setCode
|
||||
this._programMemoryCallBack = setProgramMemory
|
||||
@ -145,11 +158,26 @@ class KclManager {
|
||||
this._logsCallBack = setLogs
|
||||
this._kclErrorsCallBack = setKclErrors
|
||||
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) {
|
||||
await this.ensureWasmInit()
|
||||
this.isExecuting = true
|
||||
await initPromise
|
||||
const { logs, errors, programMemory } = await executeAst({
|
||||
ast,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
@ -164,9 +192,10 @@ class KclManager {
|
||||
this._code = recast(ast)
|
||||
this._codeCallBack(this._code)
|
||||
}
|
||||
this._executeCallback()
|
||||
}
|
||||
async executeAstMock(ast: Program = this._ast, updateCode = false) {
|
||||
await initPromise
|
||||
await this.ensureWasmInit()
|
||||
const newCode = recast(ast)
|
||||
const newAst = parse(newCode)
|
||||
await this?.engineCommandManager?.waitForReady
|
||||
@ -186,7 +215,7 @@ class KclManager {
|
||||
this._programMemory = programMemory
|
||||
}
|
||||
async executeCode(code?: string) {
|
||||
await initPromise
|
||||
await this.ensureWasmInit()
|
||||
await this?.engineCommandManager?.waitForReady
|
||||
if (!this?.engineCommandManager?.planesInitialized()) return
|
||||
const result = await executeCode({
|
||||
@ -306,6 +335,7 @@ const KclContext = createContext({
|
||||
isExecuting: kclManager.isExecuting,
|
||||
errors: kclManager.kclErrors,
|
||||
logs: kclManager.logs,
|
||||
wasmInitFailed: kclManager.wasmInitFailed,
|
||||
})
|
||||
|
||||
export function useKclContext() {
|
||||
@ -326,6 +356,7 @@ export function KclContextProvider({
|
||||
const [isExecuting, setIsExecuting] = useState(false)
|
||||
const [errors, setErrors] = useState<KCLError[]>([])
|
||||
const [logs, setLogs] = useState<string[]>([])
|
||||
const [wasmInitFailed, setWasmInitFailed] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
kclManager.registerCallBacks({
|
||||
@ -335,6 +366,7 @@ export function KclContextProvider({
|
||||
setLogs,
|
||||
setKclErrors: setErrors,
|
||||
setIsExecuting,
|
||||
setWasmInitFailed,
|
||||
})
|
||||
}, [])
|
||||
return (
|
||||
@ -346,6 +378,7 @@ export function KclContextProvider({
|
||||
isExecuting,
|
||||
errors,
|
||||
logs,
|
||||
wasmInitFailed,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
@ -450,18 +450,18 @@ export class EngineConnection {
|
||||
videoTrackStats.forEach((videoTrackReport) => {
|
||||
if (videoTrackReport.type === 'inbound-rtp') {
|
||||
client_metrics.rtc_frames_decoded =
|
||||
videoTrackReport.framesDecoded
|
||||
videoTrackReport.framesDecoded || 0
|
||||
client_metrics.rtc_frames_dropped =
|
||||
videoTrackReport.framesDropped
|
||||
videoTrackReport.framesDropped || 0
|
||||
client_metrics.rtc_frames_received =
|
||||
videoTrackReport.framesReceived
|
||||
videoTrackReport.framesReceived || 0
|
||||
client_metrics.rtc_frames_per_second =
|
||||
videoTrackReport.framesPerSecond || 0
|
||||
client_metrics.rtc_freeze_count =
|
||||
videoTrackReport.freezeCount || 0
|
||||
client_metrics.rtc_jitter_sec = videoTrackReport.jitter
|
||||
client_metrics.rtc_jitter_sec = videoTrackReport.jitter || 0.0
|
||||
client_metrics.rtc_keyframes_decoded =
|
||||
videoTrackReport.keyFramesDecoded
|
||||
videoTrackReport.keyFramesDecoded || 0
|
||||
client_metrics.rtc_total_freezes_duration_sec =
|
||||
videoTrackReport.totalFreezesDuration || 0
|
||||
} else if (videoTrackReport.type === 'transport') {
|
||||
|
@ -138,6 +138,7 @@ show(mySketch001)`
|
||||
node: ast,
|
||||
programMemory,
|
||||
to: [2, 3],
|
||||
from: [0, 0],
|
||||
fnName: 'lineTo',
|
||||
pathToNode: [
|
||||
['body', ''],
|
||||
|
@ -193,9 +193,6 @@ export const line: SketchLineHelper = {
|
||||
pathToNode,
|
||||
'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 newYVal = createLiteral(roundOff(to[1] - from[1], 2))
|
||||
@ -969,7 +966,8 @@ export function addNewSketchLn({
|
||||
to,
|
||||
fnName,
|
||||
pathToNode,
|
||||
}: Omit<CreateLineFnCallArgs, 'from'>): {
|
||||
from,
|
||||
}: CreateLineFnCallArgs): {
|
||||
modifiedAst: Program
|
||||
pathToNode: PathToNode
|
||||
} {
|
||||
@ -984,12 +982,6 @@ export function addNewSketchLn({
|
||||
const { node: pipeExp, shallowPath: pipePath } = getNodeFromPath<
|
||||
PipeExpression | CallExpression
|
||||
>(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({
|
||||
node,
|
||||
previousProgramMemory,
|
||||
|
@ -24,6 +24,7 @@ export interface PathReturn {
|
||||
|
||||
export interface ModifyAstBase {
|
||||
node: Program
|
||||
// TODO #896: Remove ProgramMemory from this interface
|
||||
previousProgramMemory: ProgramMemory
|
||||
pathToNode: PathToNode
|
||||
}
|
||||
|
@ -66,13 +66,16 @@ const initialise = async () => {
|
||||
typeof window === 'undefined'
|
||||
? 'http://127.0.0.1:3000'
|
||||
: 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')
|
||||
? 'http://localhost:3000'
|
||||
: window.location.origin && window.location.origin !== 'null'
|
||||
? window.location.origin
|
||||
: 'http://localhost:3000'
|
||||
const fullUrl = baseUrl + '/wasm_lib_bg.wasm'
|
||||
console.log(`Full URL for WASM: ${fullUrl}`)
|
||||
const input = await fetch(fullUrl)
|
||||
const buffer = await input.arrayBuffer()
|
||||
return init(buffer)
|
||||
|
@ -5,10 +5,11 @@ import {
|
||||
readDir,
|
||||
writeTextFile,
|
||||
} 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 { ProjectWithEntryPointMetadata } from '../Router'
|
||||
import { metadata } from 'tauri-plugin-fs-extra-api'
|
||||
import { bracket } from './exampleKcl'
|
||||
|
||||
const PROJECT_FOLDER = 'kittycad-modeling-projects'
|
||||
export const FILE_EXT = '.kcl'
|
||||
@ -70,7 +71,7 @@ export async function getProjectsInDir(projectDir: string) {
|
||||
|
||||
const projectsWithMetadata = await Promise.all(
|
||||
readProjects.map(async (p) => ({
|
||||
entrypointMetadata: await metadata(p.path + '/' + PROJECT_ENTRYPOINT),
|
||||
entrypointMetadata: await metadata(p.path + sep + PROJECT_ENTRYPOINT),
|
||||
...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)
|
||||
throw err
|
||||
})
|
||||
@ -232,13 +233,13 @@ export async function createNewProject(
|
||||
const m = await metadata(path)
|
||||
|
||||
return {
|
||||
name: path.slice(path.lastIndexOf('/') + 1),
|
||||
name: path.slice(path.lastIndexOf(sep) + 1),
|
||||
path: path,
|
||||
entrypointMetadata: m,
|
||||
children: [
|
||||
{
|
||||
name: PROJECT_ENTRYPOINT,
|
||||
path: path + '/' + PROJECT_ENTRYPOINT,
|
||||
path: path + sep + PROJECT_ENTRYPOINT,
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
|
File diff suppressed because one or more lines are too long
@ -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";
|
||||
"create path": "Select default plane";
|
||||
"default_camera_disable_sketch_mode": "Cancel";
|
||||
"edit mode enter": "Enter sketch";
|
||||
"edit mode enter": "Enter sketch" | "Re-execute";
|
||||
"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";
|
||||
"reset sketch metadata": "Cancel" | "Select default plane";
|
||||
"set default plane id": "Select default plane";
|
||||
"set sketch metadata": "Enter sketch";
|
||||
"set sketchMetadata from pathToNode": "Re-execute";
|
||||
"set tool": "Equip new 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";
|
||||
"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": "";
|
||||
};
|
||||
eventsCausingDelays: {
|
||||
|
@ -29,6 +29,7 @@ import useStateMachineCommands from '../hooks/useStateMachineCommands'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
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,
|
||||
// as defined in Router.tsx, so we can use the Tauri APIs and types.
|
||||
@ -58,7 +59,7 @@ const Home = () => {
|
||||
setCommandBarOpen(false)
|
||||
navigate(
|
||||
`${paths.FILE}/${encodeURIComponent(
|
||||
context.defaultDirectory + '/' + event.data.name
|
||||
context.defaultDirectory + sep + event.data.name
|
||||
)}`
|
||||
)
|
||||
}
|
||||
@ -91,7 +92,7 @@ const Home = () => {
|
||||
name = interpolateProjectNameWithIndex(name, nextIndex)
|
||||
}
|
||||
|
||||
await createNewProject(context.defaultDirectory + '/' + name)
|
||||
await createNewProject(context.defaultDirectory + sep + name)
|
||||
|
||||
if (shouldUpdateDefaultProjectName) {
|
||||
sendToSettings({
|
||||
@ -114,8 +115,8 @@ const Home = () => {
|
||||
}
|
||||
|
||||
await renameFile(
|
||||
context.defaultDirectory + '/' + oldName,
|
||||
context.defaultDirectory + '/' + name
|
||||
context.defaultDirectory + sep + oldName,
|
||||
context.defaultDirectory + sep + name
|
||||
)
|
||||
return `Successfully renamed "${oldName}" to "${name}"`
|
||||
},
|
||||
@ -123,7 +124,7 @@ const Home = () => {
|
||||
context: ContextFrom<typeof homeMachine>,
|
||||
event: EventFrom<typeof homeMachine, 'Delete project'>
|
||||
) => {
|
||||
await removeDir(context.defaultDirectory + '/' + event.data.name, {
|
||||
await removeDir(context.defaultDirectory + sep + event.data.name, {
|
||||
recursive: true,
|
||||
})
|
||||
return `Successfully deleted "${event.data.name}"`
|
||||
@ -172,9 +173,9 @@ const Home = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-screen overflow-hidden relative flex flex-col">
|
||||
<div className="relative flex flex-col h-screen overflow-hidden">
|
||||
<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">
|
||||
<h1 className="text-3xl text-bold">Your Projects</h1>
|
||||
<div className="flex">
|
||||
@ -235,7 +236,7 @@ const Home = () => {
|
||||
) : (
|
||||
<>
|
||||
{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) => (
|
||||
<ProjectCard
|
||||
key={project.name}
|
||||
@ -246,7 +247,7 @@ const Home = () => {
|
||||
))}
|
||||
</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?
|
||||
</p>
|
||||
)}
|
||||
|
@ -24,8 +24,15 @@ export default function Export() {
|
||||
Try opening the project menu and clicking "Export Model".
|
||||
</p>
|
||||
<p className="my-4">
|
||||
KittyCAD Modeling App uses our open-source extension proposal for
|
||||
the GLTF file format.{' '}
|
||||
KittyCAD Modeling App uses{' '}
|
||||
<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
|
||||
href="https://kittycad.io/docs/api/convert-cad-file"
|
||||
rel="noopener noreferrer"
|
||||
|
@ -4,13 +4,23 @@ import { useDismiss } from '.'
|
||||
import { useEffect } from 'react'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
import { kclManager } from 'lang/KclSinglton'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
|
||||
export default function FutureWork() {
|
||||
const { send } = useModelingContext()
|
||||
const dismiss = useDismiss()
|
||||
|
||||
useEffect(() => {
|
||||
kclManager.setCode(bracket)
|
||||
}, [kclManager.setCode])
|
||||
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)
|
||||
}
|
||||
|
||||
send({ type: 'Cancel' }) // in case the user hit 'Next' while still in sketch mode
|
||||
}, [send])
|
||||
|
||||
return (
|
||||
<div className="fixed grid justify-center items-center inset-0 bg-chalkboard-100/50 z-50">
|
||||
|
@ -10,6 +10,7 @@ import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
import { Themes, getSystemTheme } from 'lib/theme'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
import {
|
||||
PROJECT_ENTRYPOINT,
|
||||
createNewProject,
|
||||
getNextProjectIndex,
|
||||
getProjectsInDir,
|
||||
@ -20,6 +21,7 @@ import { useNavigate } from 'react-router-dom'
|
||||
import { paths } from 'Router'
|
||||
import { useEffect } from 'react'
|
||||
import { kclManager } from 'lang/KclSinglton'
|
||||
import { sep } from '@tauri-apps/api/path'
|
||||
|
||||
function OnboardingWithNewFile() {
|
||||
const navigate = useNavigate()
|
||||
@ -41,12 +43,16 @@ function OnboardingWithNewFile() {
|
||||
ONBOARDING_PROJECT_NAME,
|
||||
nextIndex
|
||||
)
|
||||
const newFile = await createNewProject(defaultDirectory + '/' + name)
|
||||
navigate(`${paths.FILE}/${encodeURIComponent(newFile.path)}`)
|
||||
const newFile = await createNewProject(defaultDirectory + sep + name)
|
||||
navigate(
|
||||
`${paths.FILE}/${encodeURIComponent(
|
||||
newFile.path + sep + PROJECT_ENTRYPOINT
|
||||
)}${paths.ONBOARDING.INDEX}`
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
|
||||
<div className="max-w-3xl bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
|
||||
<div className="fixed inset-0 z-50 grid place-content-center bg-chalkboard-110/50">
|
||||
<div className="max-w-3xl p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
|
||||
{!isTauri() ? (
|
||||
<>
|
||||
<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?
|
||||
</h1>
|
||||
<section className="my-12">
|
||||
@ -110,7 +116,11 @@ function OnboardingWithNewFile() {
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={createAndOpenNewProject}
|
||||
onClick={() => {
|
||||
createAndOpenNewProject()
|
||||
kclManager.setCode(bracket)
|
||||
dismiss()
|
||||
}}
|
||||
icon={{ icon: faArrowRight }}
|
||||
>
|
||||
Make a new project
|
||||
@ -138,21 +148,22 @@ export default function Introduction() {
|
||||
: ''
|
||||
const dismiss = useDismiss()
|
||||
const next = useNextClick(onboardingPaths.CAMERA)
|
||||
const isStarterCode = kclManager.code === '' || kclManager.code === bracket
|
||||
|
||||
useEffect(() => {
|
||||
if (kclManager.code === '') kclManager.setCode(bracket)
|
||||
}, [kclManager.code, kclManager.setCode])
|
||||
}, [])
|
||||
|
||||
return !(kclManager.code !== '' && kclManager.code !== bracket) ? (
|
||||
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
|
||||
<div className="max-w-3xl bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
|
||||
<h1 className="text-2xl font-bold flex gap-4 flex-wrap items-center">
|
||||
return isStarterCode ? (
|
||||
<div className="fixed inset-0 z-50 grid place-content-center bg-chalkboard-110/50">
|
||||
<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-2xl font-bold">
|
||||
<img
|
||||
src={`/kcma-logomark${getLogoTheme()}.svg`}
|
||||
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
|
||||
</span>
|
||||
</h1>
|
||||
|
@ -11,7 +11,13 @@ export default function Sketching() {
|
||||
const next = useNextClick(onboardingPaths.FUTURE_WORK)
|
||||
|
||||
useEffect(() => {
|
||||
kclManager.setCode('')
|
||||
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('')
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
|
@ -31,6 +31,7 @@ import {
|
||||
interpolateProjectNameWithIndex,
|
||||
} from 'lib/tauriFS'
|
||||
import { ONBOARDING_PROJECT_NAME } from './Onboarding'
|
||||
import { sep } from '@tauri-apps/api/path'
|
||||
|
||||
export const Settings = () => {
|
||||
const loaderData =
|
||||
@ -95,7 +96,7 @@ export const Settings = () => {
|
||||
ONBOARDING_PROJECT_NAME,
|
||||
nextIndex
|
||||
)
|
||||
const newFile = await createNewProject(defaultDirectory + '/' + name)
|
||||
const newFile = await createNewProject(defaultDirectory + sep + name)
|
||||
navigate(`${paths.FILE}/${encodeURIComponent(newFile.path)}`)
|
||||
}
|
||||
|
||||
@ -116,7 +117,7 @@ export const Settings = () => {
|
||||
Close
|
||||
</ActionButton>
|
||||
</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>
|
||||
<p className="max-w-2xl mt-6">
|
||||
Don't see the feature you want? Check to see if it's on{' '}
|
||||
|
@ -2,12 +2,10 @@ import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
import { addLineHighlight, EditorView } from './editor/highlightextension'
|
||||
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 { EditorSelection } from '@codemirror/state'
|
||||
import { EngineCommandManager } from './lang/std/engineConnection'
|
||||
import { KCLError } from './lang/errors'
|
||||
import { kclManager } from 'lang/KclSinglton'
|
||||
import { DefaultPlanes } from './wasm-lib/kcl/bindings/DefaultPlanes'
|
||||
|
||||
export type ToolTip =
|
||||
|
Reference in New Issue
Block a user