Compare commits
6 Commits
achalmers/
...
v0.40.0
Author | SHA1 | Date | |
---|---|---|---|
5b207d7d1a | |||
2fac213c58 | |||
2f72a8ef14 | |||
27ce9f8aa4 | |||
b0426e3f94 | |||
d707c66e53 |
@ -1078,7 +1078,7 @@ sketch002 = startSketchOn('XZ')
|
|||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText('Unable to sweep with the provided selection')
|
page.getByText('Unable to sweep with the current selection. Reason:')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -1846,7 +1846,7 @@ sweep001 = sweep({ path = sketch002 }, sketch001)
|
|||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText('Unable to shell with the provided selection')
|
page.getByText('Unable to shell with the current selection. Reason:')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
})
|
})
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Binary file not shown.
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
@ -69,7 +69,6 @@ test.describe('Testing in-app sample loading', () => {
|
|||||||
await confirmButton.click()
|
await confirmButton.click()
|
||||||
|
|
||||||
await editor.expectEditor.toContain('// ' + newSample.title)
|
await editor.expectEditor.toContain('// ' + newSample.title)
|
||||||
await expect(unitsToast('in')).toBeVisible()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -158,7 +157,6 @@ test.describe('Testing in-app sample loading', () => {
|
|||||||
await editor.expectEditor.toContain('// ' + sampleOne.title)
|
await editor.expectEditor.toContain('// ' + sampleOne.title)
|
||||||
await expect(newlyCreatedFile(sampleOne.file)).toBeVisible()
|
await expect(newlyCreatedFile(sampleOne.file)).toBeVisible()
|
||||||
await expect(projectMenuButton).toContainText(sampleOne.file)
|
await expect(projectMenuButton).toContainText(sampleOne.file)
|
||||||
await expect(unitsToast('in')).toBeVisible()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Now overwrite the current file`, async () => {
|
await test.step(`Now overwrite the current file`, async () => {
|
||||||
@ -188,7 +186,6 @@ test.describe('Testing in-app sample loading', () => {
|
|||||||
await expect(newlyCreatedFile(sampleOne.file)).toBeVisible()
|
await expect(newlyCreatedFile(sampleOne.file)).toBeVisible()
|
||||||
await expect(newlyCreatedFile(sampleTwo.file)).not.toBeVisible()
|
await expect(newlyCreatedFile(sampleTwo.file)).not.toBeVisible()
|
||||||
await expect(projectMenuButton).toContainText(sampleOne.file)
|
await expect(projectMenuButton).toContainText(sampleOne.file)
|
||||||
await expect(unitsToast('mm')).toBeVisible()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -25,6 +25,7 @@ import { CameraProjectionToggle } from 'components/CameraProjectionToggle'
|
|||||||
import { useCreateFileLinkQuery } from 'hooks/useCreateFileLinkQueryWatcher'
|
import { useCreateFileLinkQuery } from 'hooks/useCreateFileLinkQueryWatcher'
|
||||||
import { maybeWriteToDisk } from 'lib/telemetry'
|
import { maybeWriteToDisk } from 'lib/telemetry'
|
||||||
import { commandBarActor } from 'machines/commandBarMachine'
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
|
import { useToken } from 'machines/appMachine'
|
||||||
maybeWriteToDisk()
|
maybeWriteToDisk()
|
||||||
.then(() => {})
|
.then(() => {})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
@ -60,8 +61,8 @@ export function App() {
|
|||||||
|
|
||||||
useHotKeyListener()
|
useHotKeyListener()
|
||||||
|
|
||||||
const { auth, settings } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
const token = auth?.context?.token
|
const token = useToken()
|
||||||
|
|
||||||
const coreDumpManager = useMemo(
|
const coreDumpManager = useMemo(
|
||||||
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
|
import { useAuthState } from 'machines/appMachine'
|
||||||
import Loading from './components/Loading'
|
import Loading from './components/Loading'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
|
||||||
|
|
||||||
// Wrapper around protected routes, used in src/Router.tsx
|
// Wrapper around protected routes, used in src/Router.tsx
|
||||||
export const Auth = ({ children }: React.PropsWithChildren) => {
|
export const Auth = ({ children }: React.PropsWithChildren) => {
|
||||||
const { auth } = useSettingsAuthContext()
|
const authState = useAuthState()
|
||||||
const isLoggingIn = auth?.state.matches('checkIfLoggedIn')
|
const isLoggingIn = authState.matches('checkIfLoggedIn')
|
||||||
|
|
||||||
return isLoggingIn ? (
|
return isLoggingIn ? (
|
||||||
<Loading>
|
<Loading>
|
||||||
|
@ -37,7 +37,6 @@ import { KclContextProvider } from 'lang/KclProvider'
|
|||||||
import { ASK_TO_OPEN_QUERY_PARAM, BROWSER_PROJECT_NAME } from 'lib/constants'
|
import { ASK_TO_OPEN_QUERY_PARAM, BROWSER_PROJECT_NAME } from 'lib/constants'
|
||||||
import { CoreDumpManager } from 'lib/coredump'
|
import { CoreDumpManager } from 'lib/coredump'
|
||||||
import { codeManager, engineCommandManager } from 'lib/singletons'
|
import { codeManager, engineCommandManager } from 'lib/singletons'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
|
||||||
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { coreDump } from 'lang/wasm'
|
import { coreDump } from 'lang/wasm'
|
||||||
@ -47,6 +46,7 @@ import { reportRejection } from 'lib/trap'
|
|||||||
import { RouteProvider } from 'components/RouteProvider'
|
import { RouteProvider } from 'components/RouteProvider'
|
||||||
import { ProjectsContextProvider } from 'components/ProjectsContextProvider'
|
import { ProjectsContextProvider } from 'components/ProjectsContextProvider'
|
||||||
import { OpenInDesktopAppHandler } from 'components/OpenInDesktopAppHandler'
|
import { OpenInDesktopAppHandler } from 'components/OpenInDesktopAppHandler'
|
||||||
|
import { useToken } from 'machines/appMachine'
|
||||||
|
|
||||||
const createRouter = isDesktop() ? createHashRouter : createBrowserRouter
|
const createRouter = isDesktop() ? createHashRouter : createBrowserRouter
|
||||||
|
|
||||||
@ -203,8 +203,7 @@ export const Router = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function CoreDump() {
|
function CoreDump() {
|
||||||
const { auth } = useSettingsAuthContext()
|
const token = useToken()
|
||||||
const token = auth?.context?.token
|
|
||||||
const coreDumpManager = useMemo(
|
const coreDumpManager = useMemo(
|
||||||
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
||||||
[]
|
[]
|
||||||
|
@ -2,11 +2,11 @@ import { Toolbar } from '../Toolbar'
|
|||||||
import UserSidebarMenu from 'components/UserSidebarMenu'
|
import UserSidebarMenu from 'components/UserSidebarMenu'
|
||||||
import { type IndexLoaderData } from 'lib/types'
|
import { type IndexLoaderData } from 'lib/types'
|
||||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
|
||||||
import styles from './AppHeader.module.css'
|
import styles from './AppHeader.module.css'
|
||||||
import { RefreshButton } from 'components/RefreshButton'
|
import { RefreshButton } from 'components/RefreshButton'
|
||||||
import { CommandBarOpenButton } from './CommandBarOpenButton'
|
import { CommandBarOpenButton } from './CommandBarOpenButton'
|
||||||
import { isDesktop } from 'lib/isDesktop'
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
|
import { useUser } from 'machines/appMachine'
|
||||||
|
|
||||||
interface AppHeaderProps extends React.PropsWithChildren {
|
interface AppHeaderProps extends React.PropsWithChildren {
|
||||||
showToolbar?: boolean
|
showToolbar?: boolean
|
||||||
@ -24,8 +24,7 @@ export const AppHeader = ({
|
|||||||
style,
|
style,
|
||||||
enableMenu = false,
|
enableMenu = false,
|
||||||
}: AppHeaderProps) => {
|
}: AppHeaderProps) => {
|
||||||
const { auth } = useSettingsAuthContext()
|
const user = useUser()
|
||||||
const user = auth?.context?.user
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
|
@ -30,6 +30,7 @@ import {
|
|||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { markOnce } from 'lib/performance'
|
import { markOnce } from 'lib/performance'
|
||||||
import { commandBarActor } from 'machines/commandBarMachine'
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
|
import { useToken } from 'machines/appMachine'
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -47,7 +48,8 @@ export const FileMachineProvider = ({
|
|||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) => {
|
}) => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { settings, auth } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
|
const token = useToken()
|
||||||
const projectData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
const projectData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
||||||
const { project, file } = projectData
|
const { project, file } = projectData
|
||||||
const [kclSamples, setKclSamples] = React.useState<KclSamplesManifestItem[]>(
|
const [kclSamples, setKclSamples] = React.useState<KclSamplesManifestItem[]>(
|
||||||
@ -297,7 +299,7 @@ export const FileMachineProvider = ({
|
|||||||
const kclCommandMemo = useMemo(
|
const kclCommandMemo = useMemo(
|
||||||
() =>
|
() =>
|
||||||
kclCommands({
|
kclCommands({
|
||||||
authToken: auth?.context?.token ?? '',
|
authToken: token ?? '',
|
||||||
projectData,
|
projectData,
|
||||||
settings: {
|
settings: {
|
||||||
defaultUnit: settings?.context?.modeling.defaultUnit.current ?? 'mm',
|
defaultUnit: settings?.context?.modeling.defaultUnit.current ?? 'mm',
|
||||||
|
@ -27,6 +27,7 @@ import { PROJECT_ENTRYPOINT } from 'lib/constants'
|
|||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { isDesktop } from 'lib/isDesktop'
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
import { codeManager } from 'lib/singletons'
|
import { codeManager } from 'lib/singletons'
|
||||||
|
import { useToken } from 'machines/appMachine'
|
||||||
|
|
||||||
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
|
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
|
||||||
return []
|
return []
|
||||||
@ -69,8 +70,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
const [isKclLspReady, setIsKclLspReady] = useState(false)
|
const [isKclLspReady, setIsKclLspReady] = useState(false)
|
||||||
const [isCopilotLspReady, setIsCopilotLspReady] = useState(false)
|
const [isCopilotLspReady, setIsCopilotLspReady] = useState(false)
|
||||||
|
|
||||||
const { auth } = useSettingsAuthContext()
|
const token = useToken()
|
||||||
const token = auth?.context.token
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
// So this is a bit weird, we need to initialize the lsp server and client.
|
// So this is a bit weird, we need to initialize the lsp server and client.
|
||||||
|
@ -89,6 +89,7 @@ import { Node } from 'wasm-lib/kcl/bindings/Node'
|
|||||||
import { promptToEditFlow } from 'lib/promptToEdit'
|
import { promptToEditFlow } from 'lib/promptToEdit'
|
||||||
import { kclEditorActor } from 'machines/kclEditorMachine'
|
import { kclEditorActor } from 'machines/kclEditorMachine'
|
||||||
import { commandBarActor } from 'machines/commandBarMachine'
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
|
import { useToken } from 'machines/appMachine'
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -110,7 +111,6 @@ export const ModelingMachineProvider = ({
|
|||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
auth,
|
|
||||||
settings: {
|
settings: {
|
||||||
context: {
|
context: {
|
||||||
app: { theme, enableSSAO, allowOrbitInSketchMode },
|
app: { theme, enableSSAO, allowOrbitInSketchMode },
|
||||||
@ -127,7 +127,7 @@ export const ModelingMachineProvider = ({
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { context, send: fileMachineSend } = useFileContext()
|
const { context, send: fileMachineSend } = useFileContext()
|
||||||
const { file } = useLoaderData() as IndexLoaderData
|
const { file } = useLoaderData() as IndexLoaderData
|
||||||
const token = auth?.context?.token
|
const token = useToken()
|
||||||
const streamRef = useRef<HTMLDivElement>(null)
|
const streamRef = useRef<HTMLDivElement>(null)
|
||||||
const persistedContext = useMemo(() => getPersistedContext(), [])
|
const persistedContext = useMemo(() => getPersistedContext(), [])
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import { useSelector } from '@xstate/react'
|
|||||||
import { copyFileShareLink } from 'lib/links'
|
import { copyFileShareLink } from 'lib/links'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { DEV } from 'env'
|
import { DEV } from 'env'
|
||||||
|
import { useToken } from 'machines/appMachine'
|
||||||
|
|
||||||
const ProjectSidebarMenu = ({
|
const ProjectSidebarMenu = ({
|
||||||
project,
|
project,
|
||||||
@ -103,7 +104,8 @@ function ProjectMenuPopover({
|
|||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const filePath = useAbsoluteFilePath()
|
const filePath = useAbsoluteFilePath()
|
||||||
const { settings, auth } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
|
const token = useToken()
|
||||||
const machineManager = useContext(MachineManagerContext)
|
const machineManager = useContext(MachineManagerContext)
|
||||||
const commands = useSelector(commandBarActor, commandsSelector)
|
const commands = useSelector(commandBarActor, commandsSelector)
|
||||||
|
|
||||||
@ -194,7 +196,7 @@ function ProjectMenuPopover({
|
|||||||
disabled: !DEV,
|
disabled: !DEV,
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
await copyFileShareLink({
|
await copyFileShareLink({
|
||||||
token: auth?.context.token || '',
|
token: token ?? '',
|
||||||
code: codeManager.code,
|
code: codeManager.code,
|
||||||
name: project?.name || '',
|
name: project?.name || '',
|
||||||
units: settings.context.modeling.defaultUnit.current,
|
units: settings.context.modeling.defaultUnit.current,
|
||||||
|
@ -8,10 +8,10 @@ import Tooltip from './Tooltip'
|
|||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
import { toSync } from 'lib/utils'
|
import { toSync } from 'lib/utils'
|
||||||
|
import { useToken } from 'machines/appMachine'
|
||||||
|
|
||||||
export const RefreshButton = ({ children }: React.PropsWithChildren) => {
|
export const RefreshButton = ({ children }: React.PropsWithChildren) => {
|
||||||
const { auth } = useSettingsAuthContext()
|
const token = useToken()
|
||||||
const token = auth?.context?.token
|
|
||||||
const coreDumpManager = useMemo(
|
const coreDumpManager = useMemo(
|
||||||
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
() => new CoreDumpManager(engineCommandManager, codeManager, token),
|
||||||
[]
|
[]
|
||||||
|
@ -2,10 +2,12 @@ import { useEffect, useState, createContext, ReactNode } from 'react'
|
|||||||
import { useNavigation, useLocation } from 'react-router-dom'
|
import { useNavigation, useLocation } from 'react-router-dom'
|
||||||
import { PATHS } from 'lib/paths'
|
import { PATHS } from 'lib/paths'
|
||||||
import { markOnce } from 'lib/performance'
|
import { markOnce } from 'lib/performance'
|
||||||
|
import { useAuthNavigation } from 'hooks/useAuthNavigation'
|
||||||
|
|
||||||
export const RouteProviderContext = createContext({})
|
export const RouteProviderContext = createContext({})
|
||||||
|
|
||||||
export function RouteProvider({ children }: { children: ReactNode }) {
|
export function RouteProvider({ children }: { children: ReactNode }) {
|
||||||
|
useAuthNavigation()
|
||||||
const [first, setFirstState] = useState(true)
|
const [first, setFirstState] = useState(true)
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
@ -2,10 +2,7 @@ import { trap } from 'lib/trap'
|
|||||||
import { useMachine, useSelector } from '@xstate/react'
|
import { useMachine, useSelector } from '@xstate/react'
|
||||||
import { useNavigate, useRouteLoaderData, useLocation } from 'react-router-dom'
|
import { useNavigate, useRouteLoaderData, useLocation } from 'react-router-dom'
|
||||||
import { PATHS, BROWSER_PATH } from 'lib/paths'
|
import { PATHS, BROWSER_PATH } from 'lib/paths'
|
||||||
import { authMachine, TOKEN_PERSIST_KEY } from '../machines/authMachine'
|
|
||||||
import withBaseUrl from '../lib/withBaseURL'
|
|
||||||
import React, { createContext, useEffect, useState } from 'react'
|
import React, { createContext, useEffect, useState } from 'react'
|
||||||
import useStateMachineCommands from '../hooks/useStateMachineCommands'
|
|
||||||
import { settingsMachine } from 'machines/settingsMachine'
|
import { settingsMachine } from 'machines/settingsMachine'
|
||||||
import { toast } from 'react-hot-toast'
|
import { toast } from 'react-hot-toast'
|
||||||
import {
|
import {
|
||||||
@ -16,7 +13,6 @@ import {
|
|||||||
} from 'lib/theme'
|
} from 'lib/theme'
|
||||||
import decamelize from 'decamelize'
|
import decamelize from 'decamelize'
|
||||||
import { Actor, AnyStateMachine, ContextFrom, Prop, StateFrom } from 'xstate'
|
import { Actor, AnyStateMachine, ContextFrom, Prop, StateFrom } from 'xstate'
|
||||||
import { authCommandBarConfig } from 'lib/commandBarConfigs/authCommandConfig'
|
|
||||||
import {
|
import {
|
||||||
kclManager,
|
kclManager,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
@ -50,7 +46,6 @@ type MachineContext<T extends AnyStateMachine> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SettingsAuthContextType = {
|
type SettingsAuthContextType = {
|
||||||
auth: MachineContext<typeof authMachine>
|
|
||||||
settings: MachineContext<typeof settingsMachine>
|
settings: MachineContext<typeof settingsMachine>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,40 +365,9 @@ export const SettingsAuthProviderBase = ({
|
|||||||
)
|
)
|
||||||
}, [settingsState.context.textEditor.blinkingCursor.current])
|
}, [settingsState.context.textEditor.blinkingCursor.current])
|
||||||
|
|
||||||
// Auth machine setup
|
|
||||||
const [authState, authSend, authActor] = useMachine(
|
|
||||||
authMachine.provide({
|
|
||||||
actions: {
|
|
||||||
goToSignInPage: () => {
|
|
||||||
navigate(PATHS.SIGN_IN)
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
logout()
|
|
||||||
},
|
|
||||||
goToIndexPage: () => {
|
|
||||||
if (location.pathname.includes(PATHS.SIGN_IN)) {
|
|
||||||
navigate(PATHS.INDEX)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
useStateMachineCommands({
|
|
||||||
machineId: 'auth',
|
|
||||||
state: authState,
|
|
||||||
send: authSend,
|
|
||||||
commandBarConfig: authCommandBarConfig,
|
|
||||||
actor: authActor,
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsAuthContext.Provider
|
<SettingsAuthContext.Provider
|
||||||
value={{
|
value={{
|
||||||
auth: {
|
|
||||||
state: authState,
|
|
||||||
context: authState.context,
|
|
||||||
send: authSend,
|
|
||||||
},
|
|
||||||
settings: {
|
settings: {
|
||||||
state: settingsState,
|
state: settingsState,
|
||||||
context: settingsState.context,
|
context: settingsState.context,
|
||||||
@ -417,12 +381,3 @@ export const SettingsAuthProviderBase = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default SettingsAuthProvider
|
export default SettingsAuthProvider
|
||||||
|
|
||||||
export async function logout() {
|
|
||||||
localStorage.removeItem(TOKEN_PERSIST_KEY)
|
|
||||||
if (isDesktop()) return Promise.resolve(null)
|
|
||||||
return fetch(withBaseUrl('/logout'), {
|
|
||||||
method: 'POST',
|
|
||||||
credentials: 'include',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
@ -4,12 +4,12 @@ import { useLocation, useNavigate } from 'react-router-dom'
|
|||||||
import { Fragment, useMemo, useState } from 'react'
|
import { Fragment, useMemo, useState } from 'react'
|
||||||
import { PATHS } from 'lib/paths'
|
import { PATHS } from 'lib/paths'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
|
||||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||||
import Tooltip from './Tooltip'
|
import Tooltip from './Tooltip'
|
||||||
import usePlatform from 'hooks/usePlatform'
|
import usePlatform from 'hooks/usePlatform'
|
||||||
import { isDesktop } from 'lib/isDesktop'
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
import { CustomIcon } from './CustomIcon'
|
import { CustomIcon } from './CustomIcon'
|
||||||
|
import { authActor } from 'machines/appMachine'
|
||||||
|
|
||||||
type User = Models['User_type']
|
type User = Models['User_type']
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
|||||||
const displayedName = getDisplayName(user)
|
const displayedName = getDisplayName(user)
|
||||||
const [imageLoadFailed, setImageLoadFailed] = useState(false)
|
const [imageLoadFailed, setImageLoadFailed] = useState(false)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const send = useSettingsAuthContext()?.auth?.send
|
const send = authActor.send
|
||||||
|
|
||||||
// We filter this memoized list so that no orphan "break" elements are rendered.
|
// We filter this memoized list so that no orphan "break" elements are rendered.
|
||||||
const userMenuItems = useMemo<(ActionButtonProps | 'break')[]>(
|
const userMenuItems = useMemo<(ActionButtonProps | 'break')[]>(
|
||||||
|
29
src/hooks/useAuthNavigation.tsx
Normal file
29
src/hooks/useAuthNavigation.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { PATHS } from 'lib/paths'
|
||||||
|
import { useAuthState } from 'machines/appMachine'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple hook that listens to the auth state of the app and navigates
|
||||||
|
* accordingly.
|
||||||
|
*/
|
||||||
|
export function useAuthNavigation() {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const location = useLocation()
|
||||||
|
const authState = useAuthState()
|
||||||
|
|
||||||
|
// Subscribe to the auth state of the app and navigate accordingly.
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
authState.matches('loggedIn') &&
|
||||||
|
location.pathname.includes(PATHS.SIGN_IN)
|
||||||
|
) {
|
||||||
|
navigate(PATHS.INDEX)
|
||||||
|
} else if (
|
||||||
|
authState.matches('loggedOut') &&
|
||||||
|
!location.pathname.includes(PATHS.SIGN_IN)
|
||||||
|
) {
|
||||||
|
navigate(PATHS.SIGN_IN)
|
||||||
|
}
|
||||||
|
}, [authState])
|
||||||
|
}
|
@ -5,7 +5,11 @@ import {
|
|||||||
PathToNode,
|
PathToNode,
|
||||||
Identifier,
|
Identifier,
|
||||||
topLevelRange,
|
topLevelRange,
|
||||||
|
PipeExpression,
|
||||||
|
CallExpression,
|
||||||
|
VariableDeclarator,
|
||||||
} from './wasm'
|
} from './wasm'
|
||||||
|
import { ProgramMemory } from 'lang/wasm'
|
||||||
import {
|
import {
|
||||||
findAllPreviousVariables,
|
findAllPreviousVariables,
|
||||||
isNodeSafeToReplace,
|
isNodeSafeToReplace,
|
||||||
@ -25,9 +29,11 @@ import {
|
|||||||
createCallExpression,
|
createCallExpression,
|
||||||
createLiteral,
|
createLiteral,
|
||||||
createPipeSubstitution,
|
createPipeSubstitution,
|
||||||
|
createCallExpressionStdLib,
|
||||||
} from './modifyAst'
|
} from './modifyAst'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { codeRefFromRange } from './std/artifactGraph'
|
import { codeRefFromRange } from './std/artifactGraph'
|
||||||
|
import { addCallExpressionsToPipe, addCloseToPipe } from 'lang/std/sketch'
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await initPromise
|
await initPromise
|
||||||
@ -680,3 +686,115 @@ myNestedVar = [
|
|||||||
expect(pathToNode).toEqual(pathToNode2)
|
expect(pathToNode).toEqual(pathToNode2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('Testing specific sketch getNodeFromPath workflow', () => {
|
||||||
|
it('should parse the code', () => {
|
||||||
|
const openSketch = `sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([0.02, 0.22], %)
|
||||||
|
|> xLine(0.39, %)
|
||||||
|
|> line([0.02, -0.17], %)
|
||||||
|
|> yLine(-0.15, %)
|
||||||
|
|> line([-0.21, -0.02], %)
|
||||||
|
|> xLine(-0.15, %)
|
||||||
|
|> line([-0.02, 0.21], %)
|
||||||
|
|> line([-0.08, 0.05], %)`
|
||||||
|
const ast = assertParse(openSketch)
|
||||||
|
expect(ast.start).toEqual(0)
|
||||||
|
expect(ast.end).toEqual(227)
|
||||||
|
})
|
||||||
|
it('should find the location to add new lineTo', () => {
|
||||||
|
const openSketch = `sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([0.02, 0.22], %)
|
||||||
|
|> xLine(0.39, %)
|
||||||
|
|> line([0.02, -0.17], %)
|
||||||
|
|> yLine(-0.15, %)
|
||||||
|
|> line([-0.21, -0.02], %)
|
||||||
|
|> xLine(-0.15, %)
|
||||||
|
|> line([-0.02, 0.21], %)
|
||||||
|
|> line([-0.08, 0.05], %)`
|
||||||
|
const ast = assertParse(openSketch)
|
||||||
|
|
||||||
|
const sketchSnippet = `startProfileAt([0.02, 0.22], %)`
|
||||||
|
const sketchRange = topLevelRange(
|
||||||
|
openSketch.indexOf(sketchSnippet),
|
||||||
|
openSketch.indexOf(sketchSnippet) + sketchSnippet.length
|
||||||
|
)
|
||||||
|
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
|
||||||
|
const modifiedAst = addCallExpressionsToPipe({
|
||||||
|
node: ast,
|
||||||
|
programMemory: ProgramMemory.empty(),
|
||||||
|
pathToNode: sketchPathToNode,
|
||||||
|
expressions: [
|
||||||
|
createCallExpressionStdLib(
|
||||||
|
'lineTo', // We are forcing lineTo!
|
||||||
|
[
|
||||||
|
createArrayExpression([
|
||||||
|
createCallExpressionStdLib('profileStartX', [
|
||||||
|
createPipeSubstitution(),
|
||||||
|
]),
|
||||||
|
createCallExpressionStdLib('profileStartY', [
|
||||||
|
createPipeSubstitution(),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
createPipeSubstitution(),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
if (err(modifiedAst)) throw modifiedAst
|
||||||
|
const recasted = recast(modifiedAst)
|
||||||
|
const expectedCode = `sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([0.02, 0.22], %)
|
||||||
|
|> xLine(0.39, %)
|
||||||
|
|> line([0.02, -0.17], %)
|
||||||
|
|> yLine(-0.15, %)
|
||||||
|
|> line([-0.21, -0.02], %)
|
||||||
|
|> xLine(-0.15, %)
|
||||||
|
|> line([-0.02, 0.21], %)
|
||||||
|
|> line([-0.08, 0.05], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
`
|
||||||
|
expect(recasted).toEqual(expectedCode)
|
||||||
|
})
|
||||||
|
it('it should find the location to add close', () => {
|
||||||
|
const openSketch = `sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([0.02, 0.22], %)
|
||||||
|
|> xLine(0.39, %)
|
||||||
|
|> line([0.02, -0.17], %)
|
||||||
|
|> yLine(-0.15, %)
|
||||||
|
|> line([-0.21, -0.02], %)
|
||||||
|
|> xLine(-0.15, %)
|
||||||
|
|> line([-0.02, 0.21], %)
|
||||||
|
|> line([-0.08, 0.05], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
`
|
||||||
|
const ast = assertParse(openSketch)
|
||||||
|
const sketchSnippet = `startProfileAt([0.02, 0.22], %)`
|
||||||
|
const sketchRange = topLevelRange(
|
||||||
|
openSketch.indexOf(sketchSnippet),
|
||||||
|
openSketch.indexOf(sketchSnippet) + sketchSnippet.length
|
||||||
|
)
|
||||||
|
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
|
||||||
|
const modifiedAst = addCloseToPipe({
|
||||||
|
node: ast,
|
||||||
|
programMemory: ProgramMemory.empty(),
|
||||||
|
pathToNode: sketchPathToNode,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (err(modifiedAst)) throw modifiedAst
|
||||||
|
const recasted = recast(modifiedAst)
|
||||||
|
const expectedCode = `sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([0.02, 0.22], %)
|
||||||
|
|> xLine(0.39, %)
|
||||||
|
|> line([0.02, -0.17], %)
|
||||||
|
|> yLine(-0.15, %)
|
||||||
|
|> line([-0.21, -0.02], %)
|
||||||
|
|> xLine(-0.15, %)
|
||||||
|
|> line([-0.02, 0.21], %)
|
||||||
|
|> line([-0.08, 0.05], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
`
|
||||||
|
expect(recasted).toEqual(expectedCode)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
topLevelRange,
|
topLevelRange,
|
||||||
VariableDeclaration,
|
VariableDeclaration,
|
||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
|
recast,
|
||||||
} from './wasm'
|
} from './wasm'
|
||||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
|
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
|
||||||
@ -68,7 +69,28 @@ export function getNodeFromPath<T>(
|
|||||||
deepPath: successfulPaths,
|
deepPath: successfulPaths,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new Error('not an object')
|
const stackTraceError = new Error()
|
||||||
|
const sourceCode = recast(node)
|
||||||
|
const levels = stackTraceError.stack?.split('\n')
|
||||||
|
const aFewFunctionNames: string[] = []
|
||||||
|
let tree = ''
|
||||||
|
levels?.forEach((val, index) => {
|
||||||
|
const fnName = val.trim().split(' ')[1]
|
||||||
|
const ending = index === levels.length - 1 ? ' ' : ' > '
|
||||||
|
tree += fnName + ending
|
||||||
|
if (index < 3) {
|
||||||
|
aFewFunctionNames.push(fnName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const error = new Error(
|
||||||
|
`Failed to stopAt ${stopAt}, ${aFewFunctionNames
|
||||||
|
.filter((a) => a)
|
||||||
|
.join(' > ')}`
|
||||||
|
)
|
||||||
|
console.error(tree)
|
||||||
|
console.error(sourceCode)
|
||||||
|
console.error(error.stack)
|
||||||
|
return error
|
||||||
}
|
}
|
||||||
currentNode = currentNode?.[pathItem[0]]
|
currentNode = currentNode?.[pathItem[0]]
|
||||||
successfulPaths.push(pathItem)
|
successfulPaths.push(pathItem)
|
||||||
|
@ -1999,7 +1999,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
// TODO: Previously was never caught, we are not rejecting these pendingCommands but this needs to be handled at some point.
|
// TODO: Previously was never caught, we are not rejecting these pendingCommands but this needs to be handled at some point.
|
||||||
/*noop*/
|
/*noop*/
|
||||||
return null
|
return e
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
@ -18,6 +18,7 @@ import {
|
|||||||
default_project_settings,
|
default_project_settings,
|
||||||
base64_decode,
|
base64_decode,
|
||||||
clear_scene_and_bust_cache,
|
clear_scene_and_bust_cache,
|
||||||
|
change_kcl_settings,
|
||||||
reloadModule,
|
reloadModule,
|
||||||
} from 'lib/wasm_lib_wrapper'
|
} from 'lib/wasm_lib_wrapper'
|
||||||
|
|
||||||
@ -56,6 +57,7 @@ import { ArtifactGraph as RustArtifactGraph } from 'wasm-lib/kcl/bindings/Artifa
|
|||||||
import { Artifact } from './std/artifactGraph'
|
import { Artifact } from './std/artifactGraph'
|
||||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import { NumericSuffix } from 'wasm-lib/kcl/bindings/NumericSuffix'
|
import { NumericSuffix } from 'wasm-lib/kcl/bindings/NumericSuffix'
|
||||||
|
import { MetaSettings } from 'wasm-lib/kcl/bindings/MetaSettings'
|
||||||
|
|
||||||
export type { Artifact } from 'wasm-lib/kcl/bindings/Artifact'
|
export type { Artifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
export type { ArtifactCommand } from 'wasm-lib/kcl/bindings/Artifact'
|
export type { ArtifactCommand } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
@ -844,3 +846,17 @@ export function base64Decode(base64: string): ArrayBuffer | Error {
|
|||||||
return new Error('Caught error decoding base64 string: ' + e)
|
return new Error('Caught error decoding base64 string: ' + e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Change the meta settings for the kcl file.
|
||||||
|
/// Returns the new kcl string with the updated settings.
|
||||||
|
export function changeKclSettings(
|
||||||
|
kcl: string,
|
||||||
|
settings: MetaSettings
|
||||||
|
): string | Error {
|
||||||
|
try {
|
||||||
|
return change_kcl_settings(kcl, JSON.stringify(settings))
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Caught error changing kcl settings: ' + e)
|
||||||
|
return new Error('Caught error changing kcl settings: ' + e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
import { StateMachineCommandSetConfig } from 'lib/commandTypes'
|
import { Command } from 'lib/commandTypes'
|
||||||
import { authMachine } from 'machines/authMachine'
|
import { authActor } from 'machines/appMachine'
|
||||||
|
import { ACTOR_IDS } from 'machines/machineConstants'
|
||||||
|
|
||||||
type AuthCommandSchema = {}
|
export const authCommands: Command[] = [
|
||||||
|
{
|
||||||
export const authCommandBarConfig: StateMachineCommandSetConfig<
|
groupId: ACTOR_IDS.AUTH,
|
||||||
typeof authMachine,
|
name: 'log-out',
|
||||||
AuthCommandSchema
|
displayName: 'Log out',
|
||||||
> = {
|
|
||||||
'Log in': {
|
|
||||||
hide: 'both',
|
|
||||||
},
|
|
||||||
'Log out': {
|
|
||||||
args: [],
|
|
||||||
icon: 'arrowLeft',
|
icon: 'arrowLeft',
|
||||||
|
needsReview: false,
|
||||||
|
onSubmit: () => authActor.send({ type: 'Log out' }),
|
||||||
},
|
},
|
||||||
}
|
]
|
||||||
|
19
src/lib/commandBarConfigs/validators.test.ts
Normal file
19
src/lib/commandBarConfigs/validators.test.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { parseEngineErrorMessage } from './validators'
|
||||||
|
|
||||||
|
describe('parseEngineErrorMessage', () => {
|
||||||
|
it('takes an engine error string and parses its json message', () => {
|
||||||
|
const engineError =
|
||||||
|
'engine error: [{"error_code":"internal_engine","message":"Trajectory curve must be G1 continuous (with continuous tangents)"}]'
|
||||||
|
const message = parseEngineErrorMessage(engineError)
|
||||||
|
expect(message).toEqual(
|
||||||
|
'Trajectory curve must be G1 continuous (with continuous tangents)'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('retuns undefined on strings with different formats', () => {
|
||||||
|
const s1 = 'engine error: []'
|
||||||
|
const s2 = 'blabla'
|
||||||
|
expect(parseEngineErrorMessage(s1)).toBeUndefined()
|
||||||
|
expect(parseEngineErrorMessage(s2)).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
@ -3,6 +3,7 @@ import { engineCommandManager } from 'lib/singletons'
|
|||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
import { CommandBarContext } from 'machines/commandBarMachine'
|
import { CommandBarContext } from 'machines/commandBarMachine'
|
||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
|
import { ApiError_type } from '@kittycad/lib/dist/types/src/models'
|
||||||
|
|
||||||
export const disableDryRunWithRetry = async (numberOfRetries = 3) => {
|
export const disableDryRunWithRetry = async (numberOfRetries = 3) => {
|
||||||
for (let tries = 0; tries < numberOfRetries; tries++) {
|
for (let tries = 0; tries < numberOfRetries; tries++) {
|
||||||
@ -46,6 +47,20 @@ function isSelections(selections: unknown): selections is Selections {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseEngineErrorMessage(engineError: string) {
|
||||||
|
const parts = engineError.split('engine error: ')
|
||||||
|
if (parts.length < 2) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const errors = JSON.parse(parts[1]) as ApiError_type[]
|
||||||
|
if (!errors[0]) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors[0].message
|
||||||
|
}
|
||||||
|
|
||||||
export const revolveAxisValidator = async ({
|
export const revolveAxisValidator = async ({
|
||||||
data,
|
data,
|
||||||
context,
|
context,
|
||||||
@ -83,7 +98,7 @@ export const revolveAxisValidator = async ({
|
|||||||
value: 360,
|
value: 360,
|
||||||
}
|
}
|
||||||
|
|
||||||
const revolveAboutEdgeCommand = async () => {
|
const command = async () => {
|
||||||
return await engineCommandManager.sendSceneCommand({
|
return await engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
@ -96,13 +111,13 @@ export const revolveAxisValidator = async ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const attemptRevolve = await dryRunWrapper(revolveAboutEdgeCommand)
|
const result = await dryRunWrapper(command)
|
||||||
if (attemptRevolve?.success) {
|
if (result?.success) {
|
||||||
return true
|
return true
|
||||||
} else {
|
|
||||||
// return error message for the toast
|
|
||||||
return 'Unable to revolve with selected edge'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reason = parseEngineErrorMessage(result) || 'unknown'
|
||||||
|
return `Unable to revolve with the current selection. Reason: ${reason}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loftValidator = async ({
|
export const loftValidator = async ({
|
||||||
@ -128,7 +143,7 @@ export const loftValidator = async ({
|
|||||||
return 'Unable to loft, selection contains less than two solid2ds'
|
return 'Unable to loft, selection contains less than two solid2ds'
|
||||||
}
|
}
|
||||||
|
|
||||||
const loftCommand = async () => {
|
const command = async () => {
|
||||||
// TODO: check what to do with these
|
// TODO: check what to do with these
|
||||||
const DEFAULT_V_DEGREE = 2
|
const DEFAULT_V_DEGREE = 2
|
||||||
const DEFAULT_TOLERANCE = 2
|
const DEFAULT_TOLERANCE = 2
|
||||||
@ -145,13 +160,13 @@ export const loftValidator = async ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const attempt = await dryRunWrapper(loftCommand)
|
const result = await dryRunWrapper(command)
|
||||||
if (attempt?.success) {
|
if (result?.success) {
|
||||||
return true
|
return true
|
||||||
} else {
|
|
||||||
// return error message for the toast
|
|
||||||
return 'Unable to loft with selected sketches'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reason = parseEngineErrorMessage(result) || 'unknown'
|
||||||
|
return `Unable to loft with the current selection. Reason: ${reason}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const shellValidator = async ({
|
export const shellValidator = async ({
|
||||||
@ -180,7 +195,7 @@ export const shellValidator = async ({
|
|||||||
return "Unable to shell, couldn't find the solid"
|
return "Unable to shell, couldn't find the solid"
|
||||||
}
|
}
|
||||||
|
|
||||||
const shellCommand = async () => {
|
const command = async () => {
|
||||||
// TODO: figure out something better than an arbitrarily small value
|
// TODO: figure out something better than an arbitrarily small value
|
||||||
const DEFAULT_THICKNESS: Models['LengthUnit_type'] = 1e-9
|
const DEFAULT_THICKNESS: Models['LengthUnit_type'] = 1e-9
|
||||||
const DEFAULT_HOLLOW = false
|
const DEFAULT_HOLLOW = false
|
||||||
@ -200,12 +215,13 @@ export const shellValidator = async ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const attemptShell = await dryRunWrapper(shellCommand)
|
const result = await dryRunWrapper(command)
|
||||||
if (attemptShell?.success) {
|
if (result?.success) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'Unable to shell with the provided selection'
|
const reason = parseEngineErrorMessage(result) || 'unknown'
|
||||||
|
return `Unable to shell with the current selection. Reason: ${reason}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sweepValidator = async ({
|
export const sweepValidator = async ({
|
||||||
@ -241,7 +257,7 @@ export const sweepValidator = async ({
|
|||||||
}
|
}
|
||||||
const target = targetArtifact.pathId
|
const target = targetArtifact.pathId
|
||||||
|
|
||||||
const sweepCommand = async () => {
|
const command = async () => {
|
||||||
// TODO: second look on defaults here
|
// TODO: second look on defaults here
|
||||||
const DEFAULT_TOLERANCE: Models['LengthUnit_type'] = 1e-7
|
const DEFAULT_TOLERANCE: Models['LengthUnit_type'] = 1e-7
|
||||||
const DEFAULT_SECTIONAL = false
|
const DEFAULT_SECTIONAL = false
|
||||||
@ -261,10 +277,11 @@ export const sweepValidator = async ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const attemptSweep = await dryRunWrapper(sweepCommand)
|
const result = await dryRunWrapper(command)
|
||||||
if (attemptSweep?.success) {
|
if (result?.success) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'Unable to sweep with the provided selection'
|
const reason = parseEngineErrorMessage(result) || 'unknown'
|
||||||
|
return `Unable to sweep with the current selection. Reason: ${reason}`
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
import { CommandBarOverwriteWarning } from 'components/CommandBarOverwriteWarning'
|
import { CommandBarOverwriteWarning } from 'components/CommandBarOverwriteWarning'
|
||||||
import { Command, CommandArgumentOption } from './commandTypes'
|
import { Command, CommandArgumentOption } from './commandTypes'
|
||||||
import { codeManager, kclManager } from './singletons'
|
import { kclManager } from './singletons'
|
||||||
import { isDesktop } from './isDesktop'
|
import { isDesktop } from './isDesktop'
|
||||||
import { FILE_EXT, PROJECT_SETTINGS_FILE_NAME } from './constants'
|
import { FILE_EXT } from './constants'
|
||||||
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
|
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
|
||||||
import { parseProjectSettings } from 'lang/wasm'
|
import { reportRejection } from './trap'
|
||||||
import { err, reportRejection } from './trap'
|
|
||||||
import { projectConfigurationToSettingsPayload } from './settings/settingsUtils'
|
|
||||||
import { copyFileShareLink } from './links'
|
|
||||||
import { IndexLoaderData } from './types'
|
import { IndexLoaderData } from './types'
|
||||||
|
|
||||||
interface OnSubmitProps {
|
interface OnSubmitProps {
|
||||||
@ -68,56 +65,23 @@ export function kclCommands(commandProps: KclCommandConfig): Command[] {
|
|||||||
const sampleCodeUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent(
|
const sampleCodeUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent(
|
||||||
projectPathPart
|
projectPathPart
|
||||||
)}/${encodeURIComponent(primaryKclFile)}`
|
)}/${encodeURIComponent(primaryKclFile)}`
|
||||||
const sampleSettingsFileUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent(
|
|
||||||
projectPathPart
|
|
||||||
)}/${PROJECT_SETTINGS_FILE_NAME}`
|
|
||||||
|
|
||||||
Promise.allSettled([fetch(sampleCodeUrl), fetch(sampleSettingsFileUrl)])
|
fetch(sampleCodeUrl)
|
||||||
.then((results) => {
|
.then(async (codeResponse): Promise<OnSubmitProps> => {
|
||||||
const a =
|
if (!codeResponse.ok) {
|
||||||
'value' in results[0] ? results[0].value : results[0].reason
|
console.error(
|
||||||
const b =
|
'Failed to fetch sample code:',
|
||||||
'value' in results[1] ? results[1].value : results[1].reason
|
codeResponse.statusText
|
||||||
return [a, b]
|
)
|
||||||
})
|
return Promise.reject(new Error('Failed to fetch sample code'))
|
||||||
.then(
|
|
||||||
async ([
|
|
||||||
codeResponse,
|
|
||||||
settingsResponse,
|
|
||||||
]): Promise<OnSubmitProps> => {
|
|
||||||
if (!codeResponse.ok) {
|
|
||||||
console.error(
|
|
||||||
'Failed to fetch sample code:',
|
|
||||||
codeResponse.statusText
|
|
||||||
)
|
|
||||||
return Promise.reject(new Error('Failed to fetch sample code'))
|
|
||||||
}
|
|
||||||
const code = await codeResponse.text()
|
|
||||||
|
|
||||||
// It's possible that a sample doesn't have a project.toml
|
|
||||||
// associated with it.
|
|
||||||
let projectSettingsPayload: ReturnType<
|
|
||||||
typeof projectConfigurationToSettingsPayload
|
|
||||||
> = {}
|
|
||||||
if (settingsResponse.ok) {
|
|
||||||
const parsedProjectSettings = parseProjectSettings(
|
|
||||||
await settingsResponse.text()
|
|
||||||
)
|
|
||||||
if (!err(parsedProjectSettings)) {
|
|
||||||
projectSettingsPayload =
|
|
||||||
projectConfigurationToSettingsPayload(parsedProjectSettings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
sampleName: data.sample.split('/')[0] + FILE_EXT,
|
|
||||||
code,
|
|
||||||
method: data.method,
|
|
||||||
sampleUnits:
|
|
||||||
projectSettingsPayload.modeling?.defaultUnit || 'mm',
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
const code = await codeResponse.text()
|
||||||
|
return {
|
||||||
|
sampleName: data.sample.split('/')[0] + FILE_EXT,
|
||||||
|
code,
|
||||||
|
method: data.method,
|
||||||
|
}
|
||||||
|
})
|
||||||
.then((props) => {
|
.then((props) => {
|
||||||
if (props?.code) {
|
if (props?.code) {
|
||||||
commandProps.specialPropsForSampleCommand
|
commandProps.specialPropsForSampleCommand
|
||||||
|
@ -26,6 +26,7 @@ import {
|
|||||||
default_project_settings as DefaultProjectSettings,
|
default_project_settings as DefaultProjectSettings,
|
||||||
base64_decode as Base64Decode,
|
base64_decode as Base64Decode,
|
||||||
clear_scene_and_bust_cache as ClearSceneAndBustCache,
|
clear_scene_and_bust_cache as ClearSceneAndBustCache,
|
||||||
|
change_kcl_settings as ChangeKclSettings,
|
||||||
} from '../wasm-lib/pkg/wasm_lib'
|
} from '../wasm-lib/pkg/wasm_lib'
|
||||||
|
|
||||||
type ModuleType = typeof import('../wasm-lib/pkg/wasm_lib')
|
type ModuleType = typeof import('../wasm-lib/pkg/wasm_lib')
|
||||||
@ -110,3 +111,6 @@ export const clear_scene_and_bust_cache: typeof ClearSceneAndBustCache = (
|
|||||||
) => {
|
) => {
|
||||||
return getModule().clear_scene_and_bust_cache(...args)
|
return getModule().clear_scene_and_bust_cache(...args)
|
||||||
}
|
}
|
||||||
|
export const change_kcl_settings: typeof ChangeKclSettings = (...args) => {
|
||||||
|
return getModule().change_kcl_settings(...args)
|
||||||
|
}
|
||||||
|
30
src/machines/appMachine.ts
Normal file
30
src/machines/appMachine.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { ActorRefFrom, createActor, setup } from 'xstate'
|
||||||
|
import { authMachine } from './authMachine'
|
||||||
|
import { useSelector } from '@xstate/react'
|
||||||
|
import { ACTOR_IDS } from './machineConstants'
|
||||||
|
|
||||||
|
const appMachine = setup({
|
||||||
|
actors: {
|
||||||
|
[ACTOR_IDS.AUTH]: authMachine,
|
||||||
|
},
|
||||||
|
}).createMachine({
|
||||||
|
/** @xstate-layout N4IgpgJg5mDOIC5gF8A0IB2B7CdGgAoBbAQwGMALASwzAEp8QAHLWKgFyqw0YA9EAjACZ0AT0FDkU5EA */
|
||||||
|
id: 'modeling-app',
|
||||||
|
invoke: [
|
||||||
|
{
|
||||||
|
src: ACTOR_IDS.AUTH,
|
||||||
|
systemId: ACTOR_IDS.AUTH,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
export const appActor = createActor(appMachine).start()
|
||||||
|
|
||||||
|
export const authActor = appActor.system.get(ACTOR_IDS.AUTH) as ActorRefFrom<
|
||||||
|
typeof authMachine
|
||||||
|
>
|
||||||
|
export const useAuthState = () => useSelector(authActor, (state) => state)
|
||||||
|
export const useToken = () =>
|
||||||
|
useSelector(authActor, (state) => state.context.token)
|
||||||
|
export const useUser = () =>
|
||||||
|
useSelector(authActor, (state) => state.context.user)
|
@ -15,6 +15,8 @@ import {
|
|||||||
} from 'lib/desktop'
|
} from 'lib/desktop'
|
||||||
import { COOKIE_NAME } from 'lib/constants'
|
import { COOKIE_NAME } from 'lib/constants'
|
||||||
import { markOnce } from 'lib/performance'
|
import { markOnce } from 'lib/performance'
|
||||||
|
import { ACTOR_IDS } from './machineConstants'
|
||||||
|
import withBaseUrl from '../lib/withBaseURL'
|
||||||
|
|
||||||
const SKIP_AUTH = VITE_KC_SKIP_AUTH === 'true' && DEV
|
const SKIP_AUTH = VITE_KC_SKIP_AUTH === 'true' && DEV
|
||||||
|
|
||||||
@ -50,7 +52,7 @@ export type Events =
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const TOKEN_PERSIST_KEY = 'TOKEN_PERSIST_KEY'
|
export const TOKEN_PERSIST_KEY = 'TOKEN_PERSIST_KEY'
|
||||||
const persistedToken =
|
export const persistedToken =
|
||||||
VITE_KC_DEV_TOKEN ||
|
VITE_KC_DEV_TOKEN ||
|
||||||
getCookie(COOKIE_NAME) ||
|
getCookie(COOKIE_NAME) ||
|
||||||
localStorage?.getItem(TOKEN_PERSIST_KEY) ||
|
localStorage?.getItem(TOKEN_PERSIST_KEY) ||
|
||||||
@ -69,18 +71,17 @@ export const authMachine = setup({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
|
||||||
goToIndexPage: () => {},
|
|
||||||
goToSignInPage: () => {},
|
|
||||||
},
|
|
||||||
actors: {
|
actors: {
|
||||||
getUser: fromPromise(({ input }: { input: { token?: string } }) =>
|
getUser: fromPromise(({ input }: { input: { token?: string } }) =>
|
||||||
getUser(input)
|
getUser(input)
|
||||||
),
|
),
|
||||||
|
logout: fromPromise(async () =>
|
||||||
|
isDesktop() ? writeTokenFile('') : logout()
|
||||||
|
),
|
||||||
},
|
},
|
||||||
}).createMachine({
|
}).createMachine({
|
||||||
/** @xstate-layout N4IgpgJg5mDOIC5QEECuAXAFgOgMabFwGsBJAMwBkB7KGCEgOwGIIqGxsBLBgNyqI75CRALQAbGnRHcA2gAYAuolAAHKrE7pObZSAAeiAIwAWQ9gBspuQCYAnAGYAHPYCsx+4ccAaEAE9E1q7YcoZyxrYR1m7mcrYAvnE+aFh4BMTk1LSQjExgAE55VHnYKmIAhuhkRQC2qcLikpDSDPJKSCBqGlo67QYI9gDs5tge5o6h5vau7oY+-v3mA9jWco4u5iu21ua2YcYJSRg4Eln0zJkABFQYrbqdmtoMun2GA7YjxuPmLqvGNh5zRCfJaOcyLUzuAYuFyGcwHEDJY6NCAAeQwTEuskUd3UDx6oD6Im2wUcAzkMJ2cjBxlMgIWLmwZLWljecjJTjh8IYVAgcF0iJxXUez0QIgGxhJZIpu2ptL8AWwtje1nCW2iq1shns8MRdXSlGRjEFeKevUQjkcy3sqwGHimbg83nlCF22GMytVUWMMUc8USCKO2BOdCN7Xu3VNBKMKsVFp2hm2vu+1id83slkVrgTxhcW0pNJ1geDkDR6GNEZFCAT1kZZLk9cMLltb0WdPMjewjjC1mzOZCtk5CSAA */
|
/** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAOhzEwGsBJAMwBkB7KGCa-AYgkcJIIDdGlMGWwVKAWgA2zVhIIBtAAwBdRKAAOjWLgAuuHupAAPRAGYArAEYSADgu2AnGYBMLpVYBsZz7YA0IACeiG6OJM62tmZKLgDsno5KtvEAvikBaFh4hKTkVHRMLJDsHGAATmWMZSQaUui6tFWoouLSspDy+MpqSCBaOvqGvaYIljb2Tq7uXj7+QYgALFYW4clWy1ZmVgsWsZtpGRg4BMQkMkVsnIUABIwArrrdRv16BvhGI74LJBYW7o5WKJmKILObBUZeEgJP4LTxKMwIhZmBYLA4gTLHHJnWQEKAAeQeXB4IgEQhEGOyp3OUFxBN0CFJmHqb26T16L0G72GiCsSg8PyszkBCViTiUjgC4Jcnhc4SUsQcvgsoL2VjRFJOpGptMJ5Uq1Vq9UaZWaGqx2vw+IeDPwgiZnNZqme2leQ1An1s31+-0BCJBYJCLm+lk8CRl9hRyos6qOlK17QgdI4N0UTvZLs5Hx58NsJARuys0tDSl+AYQthsgNi0TMqt2LjVaPwjAgcCMZuIzoGbyzCAknkliH7Maympa+QYCfYXddXPdixcg4QvKUdk2u2iLkcsXhCRHmKpU7nfQzPe5CAsMpIXi8MvFKM8VliS5c1jzj53W3isNFqPS6NjMcLStXQZ0zc8ohsJI-kcFxXEcR9HAWF9gTzDxbCUXxAQWEsdn3ONsQuOkwLPedl22MIzFg3YP1gl9PG+bYvGsSxlUcRJozSFIgA */
|
||||||
id: 'Auth',
|
id: ACTOR_IDS.AUTH,
|
||||||
initial: 'checkIfLoggedIn',
|
initial: 'checkIfLoggedIn',
|
||||||
context: {
|
context: {
|
||||||
token: persistedToken,
|
token: persistedToken,
|
||||||
@ -112,19 +113,30 @@ export const authMachine = setup({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
loggedIn: {
|
loggedIn: {
|
||||||
entry: ['goToIndexPage'],
|
|
||||||
on: {
|
on: {
|
||||||
'Log out': {
|
'Log out': {
|
||||||
target: 'loggedOut',
|
target: 'loggingOut',
|
||||||
actions: () => {
|
},
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
},
|
||||||
if (isDesktop()) writeTokenFile('')
|
},
|
||||||
},
|
loggingOut: {
|
||||||
|
invoke: {
|
||||||
|
src: 'logout',
|
||||||
|
onDone: 'loggedOut',
|
||||||
|
onError: {
|
||||||
|
target: 'loggedIn',
|
||||||
|
actions: [
|
||||||
|
({ event }) => {
|
||||||
|
console.error(
|
||||||
|
'Error while logging out',
|
||||||
|
'error' in event ? `: ${event.error}` : ''
|
||||||
|
)
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
loggedOut: {
|
loggedOut: {
|
||||||
entry: ['goToSignInPage'],
|
|
||||||
on: {
|
on: {
|
||||||
'Log in': {
|
'Log in': {
|
||||||
target: 'checkIfLoggedIn',
|
target: 'checkIfLoggedIn',
|
||||||
@ -235,3 +247,12 @@ async function getAndSyncStoredToken(input: {
|
|||||||
localStorage.setItem(TOKEN_PERSIST_KEY, fileToken)
|
localStorage.setItem(TOKEN_PERSIST_KEY, fileToken)
|
||||||
return fileToken
|
return fileToken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function logout() {
|
||||||
|
localStorage.removeItem(TOKEN_PERSIST_KEY)
|
||||||
|
if (isDesktop()) return Promise.resolve(null)
|
||||||
|
return fetch(withBaseUrl('/logout'), {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -10,6 +10,7 @@ import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils'
|
|||||||
import { MachineManager } from 'components/MachineManagerProvider'
|
import { MachineManager } from 'components/MachineManagerProvider'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { useSelector } from '@xstate/react'
|
import { useSelector } from '@xstate/react'
|
||||||
|
import { authCommands } from 'lib/commandBarConfigs/authCommandConfig'
|
||||||
|
|
||||||
export type CommandBarContext = {
|
export type CommandBarContext = {
|
||||||
commands: Command[]
|
commands: Command[]
|
||||||
@ -80,6 +81,7 @@ export type CommandBarMachineEvent =
|
|||||||
export const commandBarMachine = setup({
|
export const commandBarMachine = setup({
|
||||||
types: {
|
types: {
|
||||||
context: {} as CommandBarContext,
|
context: {} as CommandBarContext,
|
||||||
|
input: {} as { commands: Command[] },
|
||||||
events: {} as CommandBarMachineEvent,
|
events: {} as CommandBarMachineEvent,
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
@ -409,8 +411,8 @@ export const commandBarMachine = setup({
|
|||||||
},
|
},
|
||||||
}).createMachine({
|
}).createMachine({
|
||||||
/** @xstate-layout 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 */
|
/** @xstate-layout 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 */
|
||||||
context: {
|
context: ({ input }) => ({
|
||||||
commands: [],
|
commands: input.commands || [],
|
||||||
selectedCommand: undefined,
|
selectedCommand: undefined,
|
||||||
currentArgument: undefined,
|
currentArgument: undefined,
|
||||||
selectionRanges: {
|
selectionRanges: {
|
||||||
@ -425,7 +427,7 @@ export const commandBarMachine = setup({
|
|||||||
setCurrentMachine: () => {},
|
setCurrentMachine: () => {},
|
||||||
noMachinesReason: () => undefined,
|
noMachinesReason: () => undefined,
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
id: 'Command Bar',
|
id: 'Command Bar',
|
||||||
initial: 'Closed',
|
initial: 'Closed',
|
||||||
states: {
|
states: {
|
||||||
@ -631,7 +633,11 @@ function sortCommands(a: Command, b: Command) {
|
|||||||
return a.name.localeCompare(b.name)
|
return a.name.localeCompare(b.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const commandBarActor = createActor(commandBarMachine).start()
|
export const commandBarActor = createActor(commandBarMachine, {
|
||||||
|
input: {
|
||||||
|
commands: [...authCommands],
|
||||||
|
},
|
||||||
|
}).start()
|
||||||
|
|
||||||
/** Basic state snapshot selector */
|
/** Basic state snapshot selector */
|
||||||
const cmdBarStateSelector = (state: SnapshotFrom<typeof commandBarActor>) =>
|
const cmdBarStateSelector = (state: SnapshotFrom<typeof commandBarActor>) =>
|
||||||
|
3
src/machines/machineConstants.ts
Normal file
3
src/machines/machineConstants.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const ACTOR_IDS = {
|
||||||
|
AUTH: 'auth',
|
||||||
|
}
|
@ -583,6 +583,13 @@ export const modelingMachine = setup({
|
|||||||
},
|
},
|
||||||
// end guards
|
// end guards
|
||||||
actions: {
|
actions: {
|
||||||
|
toastError: ({ event }) => {
|
||||||
|
if ('output' in event && event.output instanceof Error) {
|
||||||
|
toast.error(event.output.message)
|
||||||
|
} else if ('data' in event && event.data instanceof Error) {
|
||||||
|
toast.error(event.data.message)
|
||||||
|
}
|
||||||
|
},
|
||||||
'assign tool in context': assign({
|
'assign tool in context': assign({
|
||||||
currentTool: ({ event }) =>
|
currentTool: ({ event }) =>
|
||||||
'data' in event && event.data && 'tool' in event.data
|
'data' in event && event.data && 'tool' in event.data
|
||||||
@ -637,56 +644,6 @@ export const modelingMachine = setup({
|
|||||||
sketchDetails: event.output,
|
sketchDetails: event.output,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
'AST extrude': ({ context: { store }, event }) => {
|
|
||||||
if (event.type !== 'Extrude') return
|
|
||||||
;(async () => {
|
|
||||||
if (!event.data) return
|
|
||||||
const { selection, distance } = event.data
|
|
||||||
let ast = kclManager.ast
|
|
||||||
if (
|
|
||||||
'variableName' in distance &&
|
|
||||||
distance.variableName &&
|
|
||||||
distance.insertIndex !== undefined
|
|
||||||
) {
|
|
||||||
const newBody = [...ast.body]
|
|
||||||
newBody.splice(
|
|
||||||
distance.insertIndex,
|
|
||||||
0,
|
|
||||||
distance.variableDeclarationAst
|
|
||||||
)
|
|
||||||
ast.body = newBody
|
|
||||||
}
|
|
||||||
const pathToNode = getNodePathFromSourceRange(
|
|
||||||
ast,
|
|
||||||
selection.graphSelections[0]?.codeRef.range
|
|
||||||
)
|
|
||||||
const extrudeSketchRes = extrudeSketch(
|
|
||||||
ast,
|
|
||||||
pathToNode,
|
|
||||||
false,
|
|
||||||
'variableName' in distance
|
|
||||||
? distance.variableIdentifierAst
|
|
||||||
: distance.valueAst
|
|
||||||
)
|
|
||||||
if (trap(extrudeSketchRes)) return
|
|
||||||
const { modifiedAst, pathToExtrudeArg } = extrudeSketchRes
|
|
||||||
|
|
||||||
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
|
|
||||||
focusPath: [pathToExtrudeArg],
|
|
||||||
zoomToFit: true,
|
|
||||||
zoomOnRangeAndType: {
|
|
||||||
range: selection.graphSelections[0]?.codeRef.range,
|
|
||||||
type: 'path',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
|
|
||||||
|
|
||||||
if (updatedAst?.selections) {
|
|
||||||
editorManager.selectRange(updatedAst?.selections)
|
|
||||||
}
|
|
||||||
})().catch(reportRejection)
|
|
||||||
},
|
|
||||||
'AST revolve': ({ context: { store }, event }) => {
|
'AST revolve': ({ context: { store }, event }) => {
|
||||||
if (event.type !== 'Revolve') return
|
if (event.type !== 'Revolve') return
|
||||||
;(async () => {
|
;(async () => {
|
||||||
@ -1491,6 +1448,63 @@ export const modelingMachine = setup({
|
|||||||
return {} as SetSelections
|
return {} as SetSelections
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
extrudeAstMod: fromPromise<
|
||||||
|
void,
|
||||||
|
ModelingCommandSchema['Extrude'] | undefined
|
||||||
|
>(async ({ input }) => {
|
||||||
|
if (!input) return Promise.reject('No input provided')
|
||||||
|
const { selection, distance } = input
|
||||||
|
let ast = structuredClone(kclManager.ast)
|
||||||
|
let extrudeName: string | undefined = undefined
|
||||||
|
|
||||||
|
const pathToNode = getNodePathFromSourceRange(
|
||||||
|
ast,
|
||||||
|
selection.graphSelections[0]?.codeRef.range
|
||||||
|
)
|
||||||
|
// Add an extrude statement to the AST
|
||||||
|
const extrudeSketchRes = extrudeSketch(
|
||||||
|
ast,
|
||||||
|
pathToNode,
|
||||||
|
false,
|
||||||
|
'variableName' in distance
|
||||||
|
? distance.variableIdentifierAst
|
||||||
|
: distance.valueAst
|
||||||
|
)
|
||||||
|
if (err(extrudeSketchRes)) return Promise.reject(extrudeSketchRes)
|
||||||
|
const { modifiedAst, pathToExtrudeArg } = extrudeSketchRes
|
||||||
|
|
||||||
|
// Insert the distance variable if the user has provided a variable name
|
||||||
|
if (
|
||||||
|
'variableName' in distance &&
|
||||||
|
distance.variableName &&
|
||||||
|
typeof pathToExtrudeArg[1][0] === 'number'
|
||||||
|
) {
|
||||||
|
const insertIndex = Math.min(
|
||||||
|
pathToExtrudeArg[1][0],
|
||||||
|
distance.insertIndex
|
||||||
|
)
|
||||||
|
const newBody = [...modifiedAst.body]
|
||||||
|
newBody.splice(insertIndex, 0, distance.variableDeclarationAst)
|
||||||
|
modifiedAst.body = newBody
|
||||||
|
// Since we inserted a new variable, we need to update the path to the extrude argument
|
||||||
|
pathToExtrudeArg[1][0]++
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
|
||||||
|
focusPath: [pathToExtrudeArg],
|
||||||
|
zoomToFit: true,
|
||||||
|
zoomOnRangeAndType: {
|
||||||
|
range: selection.graphSelections[0]?.codeRef.range,
|
||||||
|
type: 'path',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
|
||||||
|
|
||||||
|
if (updatedAst?.selections) {
|
||||||
|
editorManager.selectRange(updatedAst?.selections)
|
||||||
|
}
|
||||||
|
}),
|
||||||
offsetPlaneAstMod: fromPromise(
|
offsetPlaneAstMod: fromPromise(
|
||||||
async ({
|
async ({
|
||||||
input,
|
input,
|
||||||
@ -1821,9 +1835,8 @@ export const modelingMachine = setup({
|
|||||||
],
|
],
|
||||||
|
|
||||||
Extrude: {
|
Extrude: {
|
||||||
target: 'idle',
|
target: 'Applying extrude',
|
||||||
actions: ['AST extrude'],
|
reenter: true,
|
||||||
reenter: false,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Revolve: {
|
Revolve: {
|
||||||
@ -2548,7 +2561,7 @@ export const modelingMachine = setup({
|
|||||||
|
|
||||||
'Delete segment': {
|
'Delete segment': {
|
||||||
reenter: false,
|
reenter: false,
|
||||||
actions: ['Delete segment', 'Set sketchDetails'],
|
actions: ['Delete segment', 'Set sketchDetails', 'reset selections'],
|
||||||
},
|
},
|
||||||
'code edit during sketch': '.clean slate',
|
'code edit during sketch': '.clean slate',
|
||||||
},
|
},
|
||||||
@ -2620,6 +2633,22 @@ export const modelingMachine = setup({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'Applying extrude': {
|
||||||
|
invoke: {
|
||||||
|
src: 'extrudeAstMod',
|
||||||
|
id: 'extrudeAstMod',
|
||||||
|
input: ({ event }) => {
|
||||||
|
if (event.type !== 'Extrude') return undefined
|
||||||
|
return event.data
|
||||||
|
},
|
||||||
|
onDone: ['idle'],
|
||||||
|
onError: {
|
||||||
|
target: 'idle',
|
||||||
|
actions: 'toastError',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
'Applying offset plane': {
|
'Applying offset plane': {
|
||||||
invoke: {
|
invoke: {
|
||||||
src: 'offsetPlaneAstMod',
|
src: 'offsetPlaneAstMod',
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useUser } from 'machines/appMachine'
|
||||||
|
|
||||||
export default function UserMenu() {
|
export default function UserMenu() {
|
||||||
const { auth } = useSettingsAuthContext()
|
const user = useUser()
|
||||||
const dismiss = useDismiss()
|
const dismiss = useDismiss()
|
||||||
const next = useNextClick(onboardingPaths.PROJECT_MENU)
|
const next = useNextClick(onboardingPaths.PROJECT_MENU)
|
||||||
const [avatarErrored, setAvatarErrored] = useState(false)
|
const [avatarErrored, setAvatarErrored] = useState(false)
|
||||||
|
|
||||||
const user = auth?.context?.user
|
|
||||||
const errorOrNoImage = !user?.image || avatarErrored
|
const errorOrNoImage = !user?.image || avatarErrored
|
||||||
const buttonDescription = errorOrNoImage ? 'the menu button' : 'your avatar'
|
const buttonDescription = errorOrNoImage ? 'the menu button' : 'your avatar'
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
|||||||
import { toSync } from 'lib/utils'
|
import { toSync } from 'lib/utils'
|
||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
|
import { authActor } from 'machines/appMachine'
|
||||||
|
|
||||||
const subtleBorder =
|
const subtleBorder =
|
||||||
'border border-solid border-chalkboard-30 dark:border-chalkboard-80'
|
'border border-solid border-chalkboard-30 dark:border-chalkboard-80'
|
||||||
@ -22,7 +23,6 @@ const cardArea = `${subtleBorder} rounded-lg px-6 py-3 text-chalkboard-70 dark:t
|
|||||||
const SignIn = () => {
|
const SignIn = () => {
|
||||||
const [userCode, setUserCode] = useState('')
|
const [userCode, setUserCode] = useState('')
|
||||||
const {
|
const {
|
||||||
auth: { send },
|
|
||||||
settings: {
|
settings: {
|
||||||
state: {
|
state: {
|
||||||
context: {
|
context: {
|
||||||
@ -70,7 +70,7 @@ const SignIn = () => {
|
|||||||
toast.error('Error while trying to log in')
|
toast.error('Error while trying to log in')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
send({ type: 'Log in', token })
|
authActor.send({ type: 'Log in', token })
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
2
src/wasm-lib/Cargo.lock
generated
2
src/wasm-lib/Cargo.lock
generated
@ -1710,7 +1710,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
version = "0.2.32"
|
version = "0.2.33"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx 0.5.1",
|
"approx 0.5.1",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
description = "KittyCAD Language implementation and tools"
|
description = "KittyCAD Language implementation and tools"
|
||||||
version = "0.2.32"
|
version = "0.2.33"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
|
@ -13,9 +13,7 @@ use tower_lsp::lsp_types::{
|
|||||||
MarkupKind, ParameterInformation, ParameterLabel, SignatureHelp, SignatureInformation,
|
MarkupKind, ParameterInformation, ParameterLabel, SignatureHelp, SignatureInformation,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::execution::Sketch;
|
use crate::{execution::Sketch, std::Primitive};
|
||||||
|
|
||||||
use crate::std::Primitive;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
|
@ -7,9 +7,9 @@ use crate::{
|
|||||||
KclError, SourceRange,
|
KclError, SourceRange,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(super) const SETTINGS: &str = "settings";
|
pub(crate) const SETTINGS: &str = "settings";
|
||||||
pub(super) const SETTINGS_UNIT_LENGTH: &str = "defaultLengthUnit";
|
pub(crate) const SETTINGS_UNIT_LENGTH: &str = "defaultLengthUnit";
|
||||||
pub(super) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit";
|
pub(crate) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit";
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
pub(super) enum AnnotationScope {
|
pub(super) enum AnnotationScope {
|
||||||
|
@ -2,6 +2,7 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use async_recursion::async_recursion;
|
use async_recursion::async_recursion;
|
||||||
|
|
||||||
|
use super::cad_op::{OpArg, Operation};
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
execution::{
|
execution::{
|
||||||
@ -19,8 +20,6 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::cad_op::{OpArg, Operation};
|
|
||||||
|
|
||||||
impl BinaryPart {
|
impl BinaryPart {
|
||||||
#[async_recursion]
|
#[async_recursion]
|
||||||
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||||
|
@ -15,6 +15,7 @@ use kittycad_modeling_cmds as kcmc;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use super::ExecutorContext;
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
execution::{ExecState, ImportedGeometry},
|
execution::{ExecState, ImportedGeometry},
|
||||||
@ -22,8 +23,6 @@ use crate::{
|
|||||||
source_range::SourceRange,
|
source_range::SourceRange,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::ExecutorContext;
|
|
||||||
|
|
||||||
// Zoo co-ordinate system.
|
// Zoo co-ordinate system.
|
||||||
//
|
//
|
||||||
// * Forward: -Y
|
// * Forward: -Y
|
||||||
|
@ -581,10 +581,11 @@ impl KclValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO called UnitLen so as not to clash with UnitLength in settings)
|
// TODO called UnitLen so as not to clash with UnitLength in settings)
|
||||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
|
#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum UnitLen {
|
pub enum UnitLen {
|
||||||
|
#[default]
|
||||||
Mm,
|
Mm,
|
||||||
Cm,
|
Cm,
|
||||||
M,
|
M,
|
||||||
@ -593,6 +594,19 @@ pub enum UnitLen {
|
|||||||
Yards,
|
Yards,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for UnitLen {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
UnitLen::Mm => write!(f, "mm"),
|
||||||
|
UnitLen::Cm => write!(f, "cm"),
|
||||||
|
UnitLen::M => write!(f, "m"),
|
||||||
|
UnitLen::Inches => write!(f, "in"),
|
||||||
|
UnitLen::Feet => write!(f, "ft"),
|
||||||
|
UnitLen::Yards => write!(f, "yd"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<NumericSuffix> for UnitLen {
|
impl TryFrom<NumericSuffix> for UnitLen {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
@ -644,6 +658,15 @@ pub enum UnitAngle {
|
|||||||
Radians,
|
Radians,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for UnitAngle {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
UnitAngle::Degrees => write!(f, "deg"),
|
||||||
|
UnitAngle::Radians => write!(f, "rad"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<NumericSuffix> for UnitAngle {
|
impl TryFrom<NumericSuffix> for UnitAngle {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
|
@ -13,8 +13,7 @@ use kcmc::{
|
|||||||
websocket::{ModelingSessionData, OkWebSocketResponseData},
|
websocket::{ModelingSessionData, OkWebSocketResponseData},
|
||||||
ImageFormat, ModelingCmd,
|
ImageFormat, ModelingCmd,
|
||||||
};
|
};
|
||||||
use kittycad_modeling_cmds::length_unit::LengthUnit;
|
use kittycad_modeling_cmds::{self as kcmc, length_unit::LengthUnit, websocket::WebSocketResponse};
|
||||||
use kittycad_modeling_cmds::{self as kcmc, websocket::WebSocketResponse};
|
|
||||||
use parse_display::{Display, FromStr};
|
use parse_display::{Display, FromStr};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -27,14 +26,18 @@ pub(crate) use import::{import_foreign, send_to_engine as send_import_to_engine,
|
|||||||
pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen};
|
pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
mod annotations;
|
pub(crate) mod annotations;
|
||||||
mod artifact;
|
mod artifact;
|
||||||
pub(crate) mod cache;
|
pub(crate) mod cache;
|
||||||
mod cad_op;
|
mod cad_op;
|
||||||
mod exec_ast;
|
mod exec_ast;
|
||||||
mod function_param;
|
mod function_param;
|
||||||
mod import;
|
mod import;
|
||||||
mod kcl_value;
|
pub(crate) mod kcl_value;
|
||||||
|
|
||||||
|
// Re-exports.
|
||||||
|
pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId};
|
||||||
|
pub use cad_op::Operation;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
engine::{EngineManager, ExecutionKind},
|
engine::{EngineManager, ExecutionKind},
|
||||||
@ -52,10 +55,6 @@ use crate::{
|
|||||||
ExecError, KclErrorWithOutputs, Program,
|
ExecError, KclErrorWithOutputs, Program,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Re-exports.
|
|
||||||
pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId};
|
|
||||||
pub use cad_op::Operation;
|
|
||||||
|
|
||||||
/// State for executing a program.
|
/// State for executing a program.
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@ -247,7 +246,7 @@ impl ModuleState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MetaSettings {
|
pub struct MetaSettings {
|
||||||
@ -256,7 +255,11 @@ pub struct MetaSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MetaSettings {
|
impl MetaSettings {
|
||||||
fn update_from_annotation(&mut self, annotation: &NonCodeValue, source_range: SourceRange) -> Result<(), KclError> {
|
pub fn update_from_annotation(
|
||||||
|
&mut self,
|
||||||
|
annotation: &NonCodeValue,
|
||||||
|
source_range: SourceRange,
|
||||||
|
) -> Result<(), KclError> {
|
||||||
let properties = annotations::expect_properties(annotations::SETTINGS, annotation, source_range)?;
|
let properties = annotations::expect_properties(annotations::SETTINGS, annotation, source_range)?;
|
||||||
|
|
||||||
for p in properties {
|
for p in properties {
|
||||||
|
@ -84,7 +84,7 @@ pub use engine::{EngineManager, ExecutionKind};
|
|||||||
pub use errors::{CompilationError, ConnectionError, ExecError, KclError, KclErrorWithOutputs};
|
pub use errors::{CompilationError, ConnectionError, ExecError, KclError, KclErrorWithOutputs};
|
||||||
pub use execution::{
|
pub use execution::{
|
||||||
cache::{CacheInformation, OldAstState},
|
cache::{CacheInformation, OldAstState},
|
||||||
ExecState, ExecutorContext, ExecutorSettings, Point2d,
|
ExecState, ExecutorContext, ExecutorSettings, MetaSettings, Point2d,
|
||||||
};
|
};
|
||||||
pub use lsp::{
|
pub use lsp::{
|
||||||
copilot::Backend as CopilotLspBackend,
|
copilot::Backend as CopilotLspBackend,
|
||||||
@ -121,8 +121,7 @@ pub mod std_utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod pretty {
|
pub mod pretty {
|
||||||
pub use crate::parsing::token::NumericSuffix;
|
pub use crate::{parsing::token::NumericSuffix, unparser::format_number};
|
||||||
pub use crate::unparser::format_number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -162,6 +161,18 @@ impl Program {
|
|||||||
self.ast.compute_digest()
|
self.ast.compute_digest()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the meta settings for the kcl file from the annotations.
|
||||||
|
pub fn get_meta_settings(&self) -> Result<Option<crate::MetaSettings>, KclError> {
|
||||||
|
self.ast.get_meta_settings()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change the meta settings for the kcl file.
|
||||||
|
pub fn change_meta_settings(&mut self, settings: crate::MetaSettings) -> Result<Self, KclError> {
|
||||||
|
Ok(Self {
|
||||||
|
ast: self.ast.change_meta_settings(settings)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn lint_all(&self) -> Result<Vec<lint::Discovered>, anyhow::Error> {
|
pub fn lint_all(&self) -> Result<Vec<lint::Discovered>, anyhow::Error> {
|
||||||
self.ast.lint_all()
|
self.ast.lint_all()
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ use kcmc::{
|
|||||||
};
|
};
|
||||||
use kittycad_modeling_cmds as kcmc;
|
use kittycad_modeling_cmds as kcmc;
|
||||||
|
|
||||||
|
use super::types::LiteralValue;
|
||||||
use crate::{
|
use crate::{
|
||||||
engine::EngineManager,
|
engine::EngineManager,
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
@ -18,8 +19,6 @@ use crate::{
|
|||||||
Program,
|
Program,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::types::LiteralValue;
|
|
||||||
|
|
||||||
type Point3d = kcmc::shared::Point3d<f64>;
|
type Point3d = kcmc::shared::Point3d<f64>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -17,7 +17,6 @@ use tower_lsp::lsp_types::{
|
|||||||
CompletionItem, CompletionItemKind, DocumentSymbol, FoldingRange, FoldingRangeKind, Range as LspRange, SymbolKind,
|
CompletionItem, CompletionItemKind, DocumentSymbol, FoldingRange, FoldingRangeKind, Range as LspRange, SymbolKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::digest::Digest;
|
|
||||||
pub use crate::parsing::ast::types::{
|
pub use crate::parsing::ast::types::{
|
||||||
condition::{ElseIf, IfExpression},
|
condition::{ElseIf, IfExpression},
|
||||||
literal_value::LiteralValue,
|
literal_value::LiteralValue,
|
||||||
@ -26,7 +25,8 @@ pub use crate::parsing::ast::types::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
docs::StdLibFn,
|
docs::StdLibFn,
|
||||||
errors::KclError,
|
errors::KclError,
|
||||||
execution::{KclValue, Metadata, TagIdentifier},
|
execution::{annotations, KclValue, Metadata, TagIdentifier},
|
||||||
|
parsing::ast::digest::Digest,
|
||||||
parsing::PIPE_OPERATOR,
|
parsing::PIPE_OPERATOR,
|
||||||
source_range::{ModuleId, SourceRange},
|
source_range::{ModuleId, SourceRange},
|
||||||
};
|
};
|
||||||
@ -254,6 +254,52 @@ impl Node<Program> {
|
|||||||
}
|
}
|
||||||
Ok(findings)
|
Ok(findings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the annotations for the meta settings from the kcl file.
|
||||||
|
pub fn get_meta_settings(&self) -> Result<Option<crate::execution::MetaSettings>, KclError> {
|
||||||
|
let annotations = self
|
||||||
|
.non_code_meta
|
||||||
|
.start_nodes
|
||||||
|
.iter()
|
||||||
|
.filter_map(|n| n.annotation().map(|result| (result, n.as_source_range())));
|
||||||
|
for (annotation, source_range) in annotations {
|
||||||
|
if annotation.annotation_name() == Some(annotations::SETTINGS) {
|
||||||
|
let mut meta_settings = crate::execution::MetaSettings::default();
|
||||||
|
meta_settings.update_from_annotation(annotation, source_range)?;
|
||||||
|
return Ok(Some(meta_settings));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn change_meta_settings(&mut self, settings: crate::execution::MetaSettings) -> Result<Self, KclError> {
|
||||||
|
let mut new_program = self.clone();
|
||||||
|
let mut found = false;
|
||||||
|
for node in &mut new_program.non_code_meta.start_nodes {
|
||||||
|
if let Some(annotation) = node.annotation() {
|
||||||
|
if annotation.annotation_name() == Some(annotations::SETTINGS) {
|
||||||
|
let annotation = NonCodeValue::new_from_meta_settings(&settings);
|
||||||
|
*node = Node::no_src(NonCodeNode {
|
||||||
|
value: annotation,
|
||||||
|
digest: None,
|
||||||
|
});
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
let annotation = NonCodeValue::new_from_meta_settings(&settings);
|
||||||
|
new_program.non_code_meta.start_nodes.push(Node::no_src(NonCodeNode {
|
||||||
|
value: annotation,
|
||||||
|
digest: None,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(new_program)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Program {
|
impl Program {
|
||||||
@ -1078,6 +1124,24 @@ impl NonCodeValue {
|
|||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_from_meta_settings(settings: &crate::execution::MetaSettings) -> NonCodeValue {
|
||||||
|
let mut properties: Vec<Node<ObjectProperty>> = vec![ObjectProperty::new(
|
||||||
|
Identifier::new(annotations::SETTINGS_UNIT_LENGTH),
|
||||||
|
Expr::Identifier(Box::new(Identifier::new(&settings.default_length_units.to_string()))),
|
||||||
|
)];
|
||||||
|
|
||||||
|
if settings.default_angle_units != Default::default() {
|
||||||
|
properties.push(ObjectProperty::new(
|
||||||
|
Identifier::new(annotations::SETTINGS_UNIT_ANGLE),
|
||||||
|
Expr::Identifier(Box::new(Identifier::new(&settings.default_angle_units.to_string()))),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
NonCodeValue::Annotation {
|
||||||
|
name: Identifier::new(annotations::SETTINGS),
|
||||||
|
properties: Some(properties),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Default, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
@ -2337,6 +2401,14 @@ impl Node<ObjectProperty> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectProperty {
|
impl ObjectProperty {
|
||||||
|
pub fn new(key: Node<Identifier>, value: Expr) -> Node<Self> {
|
||||||
|
Node::no_src(Self {
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
digest: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a hover value that includes the given character position.
|
/// Returns a hover value that includes the given character position.
|
||||||
pub fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option<Hover> {
|
pub fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option<Hover> {
|
||||||
let value_source_range: SourceRange = self.value.clone().into();
|
let value_source_range: SourceRange = self.value.clone().into();
|
||||||
@ -3756,4 +3828,98 @@ const cylinder = startSketchOn('-XZ')
|
|||||||
|
|
||||||
assert_eq!(l.raw, "false");
|
assert_eq!(l.raw, "false");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_parse_get_meta_settings_inch() {
|
||||||
|
let some_program_string = r#"@settings(defaultLengthUnit = inch)
|
||||||
|
|
||||||
|
startSketchOn('XY')"#;
|
||||||
|
let program = crate::parsing::top_level_parse(some_program_string).unwrap();
|
||||||
|
let result = program.get_meta_settings().unwrap();
|
||||||
|
assert!(result.is_some());
|
||||||
|
let meta_settings = result.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
meta_settings.default_length_units,
|
||||||
|
crate::execution::kcl_value::UnitLen::Inches
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_parse_get_meta_settings_inch_to_mm() {
|
||||||
|
let some_program_string = r#"@settings(defaultLengthUnit = inch)
|
||||||
|
|
||||||
|
startSketchOn('XY')"#;
|
||||||
|
let mut program = crate::parsing::top_level_parse(some_program_string).unwrap();
|
||||||
|
let result = program.get_meta_settings().unwrap();
|
||||||
|
assert!(result.is_some());
|
||||||
|
let meta_settings = result.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
meta_settings.default_length_units,
|
||||||
|
crate::execution::kcl_value::UnitLen::Inches
|
||||||
|
);
|
||||||
|
|
||||||
|
// Edit the ast.
|
||||||
|
let new_program = program
|
||||||
|
.change_meta_settings(crate::execution::MetaSettings {
|
||||||
|
default_length_units: crate::execution::kcl_value::UnitLen::Mm,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let result = new_program.get_meta_settings().unwrap();
|
||||||
|
assert!(result.is_some());
|
||||||
|
let meta_settings = result.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
meta_settings.default_length_units,
|
||||||
|
crate::execution::kcl_value::UnitLen::Mm
|
||||||
|
);
|
||||||
|
|
||||||
|
let formatted = new_program.recast(&Default::default(), 0);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
formatted,
|
||||||
|
r#"@settings(defaultLengthUnit = mm)
|
||||||
|
|
||||||
|
|
||||||
|
startSketchOn('XY')
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_parse_get_meta_settings_nothing_to_mm() {
|
||||||
|
let some_program_string = r#"startSketchOn('XY')"#;
|
||||||
|
let mut program = crate::parsing::top_level_parse(some_program_string).unwrap();
|
||||||
|
let result = program.get_meta_settings().unwrap();
|
||||||
|
assert!(result.is_none());
|
||||||
|
|
||||||
|
// Edit the ast.
|
||||||
|
let new_program = program
|
||||||
|
.change_meta_settings(crate::execution::MetaSettings {
|
||||||
|
default_length_units: crate::execution::kcl_value::UnitLen::Mm,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let result = new_program.get_meta_settings().unwrap();
|
||||||
|
assert!(result.is_some());
|
||||||
|
let meta_settings = result.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
meta_settings.default_length_units,
|
||||||
|
crate::execution::kcl_value::UnitLen::Mm
|
||||||
|
);
|
||||||
|
|
||||||
|
let formatted = new_program.recast(&Default::default(), 0);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
formatted,
|
||||||
|
r#"@settings(defaultLengthUnit = mm)
|
||||||
|
startSketchOn('XY')
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,14 +3,13 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use derive_docs::stdlib;
|
use derive_docs::stdlib;
|
||||||
|
|
||||||
|
use super::args::FromArgs;
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
execution::{ExecState, KclValue},
|
execution::{ExecState, KclValue},
|
||||||
std::Args,
|
std::Args,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::args::FromArgs;
|
|
||||||
|
|
||||||
/// Compute the remainder after dividing `num` by `div`.
|
/// Compute the remainder after dividing `num` by `div`.
|
||||||
/// If `num` is negative, the result will be too.
|
/// If `num` is negative, the result will be too.
|
||||||
pub async fn rem(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn rem(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
|
@ -11,12 +11,11 @@ use parse_display::{Display, FromStr};
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::execution::{Artifact, ArtifactId};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
execution::{
|
execution::{
|
||||||
BasePath, ExecState, Face, GeoMeta, KclValue, Path, Plane, Point2d, Point3d, Sketch, SketchSet, SketchSurface,
|
Artifact, ArtifactId, BasePath, ExecState, Face, GeoMeta, KclValue, Path, Plane, Point2d, Point3d, Sketch,
|
||||||
Solid, TagEngineInfo, TagIdentifier,
|
SketchSet, SketchSurface, Solid, TagEngineInfo, TagIdentifier,
|
||||||
},
|
},
|
||||||
parsing::ast::types::TagNode,
|
parsing::ast::types::TagNode,
|
||||||
std::{
|
std::{
|
||||||
@ -2250,7 +2249,10 @@ mod tests {
|
|||||||
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
use crate::{execution::TagIdentifier, std::sketch::PlaneData, std::utils::calculate_circle_center};
|
use crate::{
|
||||||
|
execution::TagIdentifier,
|
||||||
|
std::{sketch::PlaneData, utils::calculate_circle_center},
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_deserialize_plane_data() {
|
fn test_deserialize_plane_data() {
|
||||||
|
@ -614,3 +614,18 @@ pub fn calculate_circle_from_3_points(ax: f64, ay: f64, bx: f64, by: f64, cx: f6
|
|||||||
radius: result.radius,
|
radius: result.radius,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Takes a kcl string and Meta settings and changes the meta settings in the kcl string.
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn change_kcl_settings(code: &str, settings_str: &str) -> Result<String, String> {
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
|
let settings: kcl_lib::MetaSettings = serde_json::from_str(settings_str).map_err(|e| e.to_string())?;
|
||||||
|
let mut program = Program::parse_no_errs(code).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let new_program = program.change_meta_settings(settings).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let formatted = new_program.recast();
|
||||||
|
|
||||||
|
Ok(formatted)
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user