Compare commits
8 Commits
jtran/json
...
franknoiro
Author | SHA1 | Date | |
---|---|---|---|
727a7e87f1 | |||
00b779a767 | |||
124ecc11ba | |||
ea330c3b2e | |||
dfe7a32f1e | |||
3600354c13 | |||
1f705ca6df | |||
5a1b0777b7 |
@ -1,3 +1,3 @@
|
|||||||
[codespell]
|
[codespell]
|
||||||
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,atleast,ue,afterall,ser,fromM,FromM
|
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,atleast,ue,afterall,ser,fromM,FromM
|
||||||
skip: **/target,node_modules,build,dist,./out,**/Cargo.lock,./docs/kcl/*.md,./e2e/playwright/lib/console-error-whitelist.ts,.package-lock.json,**/package-lock.json,./openapi/*.json,./packages/codemirror-lang-kcl/test/all.test.ts,./public/kcl-samples,./rust/kcl-lib/tests/kcl_samples,tsconfig.tsbuildinfo,./src/lib/machine-api.d.ts
|
skip: **/target,node_modules,build,dist,./out,**/Cargo.lock,./docs/kcl/*.md,.package-lock.json,**/package-lock.json,./openapi/*.json,./packages/codemirror-lang-kcl/test/all.test.ts,./public/kcl-samples,./rust/kcl-lib/tests/kcl_samples,tsconfig.tsbuildinfo,./src/lib/machine-api.d.ts
|
||||||
|
4
.github/workflows/e2e-tests.yml
vendored
4
.github/workflows/e2e-tests.yml
vendored
@ -226,8 +226,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
shell: bash
|
shell: bash
|
||||||
command: npm run test:snapshots
|
command: npm run test:snapshots
|
||||||
timeout_minutes: 5
|
timeout_minutes: 30
|
||||||
max_attempts: 5
|
max_attempts: 3
|
||||||
env:
|
env:
|
||||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||||
snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
|
snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
|
||||||
|
4
Makefile
4
Makefile
@ -15,8 +15,8 @@ ifdef WINDOWS
|
|||||||
CARGO ?= $(USERPROFILE)/.cargo/bin/cargo.exe
|
CARGO ?= $(USERPROFILE)/.cargo/bin/cargo.exe
|
||||||
WASM_PACK ?= $(USERPROFILE)/.cargo/bin/wasm-pack.exe
|
WASM_PACK ?= $(USERPROFILE)/.cargo/bin/wasm-pack.exe
|
||||||
else
|
else
|
||||||
CARGO ?= $(shell which cargo || echo ~/.cargo/bin/cargo)
|
CARGO ?= ~/.cargo/bin/cargo
|
||||||
WASM_PACK ?= $(shell which wasm-pack || echo ~/.cargo/bin/wasm-pack)
|
WASM_PACK ?= ~/.cargo/bin/wasm-pack
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: install
|
.PHONY: install
|
||||||
|
@ -5,10 +5,7 @@ import type { Locator, Page } from '@playwright/test'
|
|||||||
import type { EditorFixture } from '@e2e/playwright/fixtures/editorFixture'
|
import type { EditorFixture } from '@e2e/playwright/fixtures/editorFixture'
|
||||||
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
|
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
|
||||||
import type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
|
import type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
|
||||||
import {
|
import { orRunWhenFullSuiteEnabled } from '@e2e/playwright/test-utils'
|
||||||
orRunWhenFullSuiteEnabled,
|
|
||||||
runningOnWindows,
|
|
||||||
} from '@e2e/playwright/test-utils'
|
|
||||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||||
|
|
||||||
// test file is for testing point an click code gen functionality that's not sketch mode related
|
// test file is for testing point an click code gen functionality that's not sketch mode related
|
||||||
@ -3547,9 +3544,6 @@ tag=$rectangleSegmentC002,
|
|||||||
toolbar,
|
toolbar,
|
||||||
cmdBar,
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
if (runningOnWindows()) {
|
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
|
||||||
}
|
|
||||||
const initialCode = `sketch001 = startSketchOn(XZ)
|
const initialCode = `sketch001 = startSketchOn(XZ)
|
||||||
|> startProfileAt([-102.57, 101.72], %)
|
|> startProfileAt([-102.57, 101.72], %)
|
||||||
|> angledLine(angle = 0, length = 202.6, tag = $rectangleSegmentA001)
|
|> angledLine(angle = 0, length = 202.6, tag = $rectangleSegmentA001)
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||||
import { orRunWhenFullSuiteEnabled } from '@e2e/playwright/test-utils'
|
|
||||||
|
|
||||||
/* eslint-disable jest/no-conditional-expect */
|
/* eslint-disable jest/no-conditional-expect */
|
||||||
|
|
||||||
@ -58,7 +57,6 @@ test.describe('edit with AI example snapshots', () => {
|
|||||||
`change colour`,
|
`change colour`,
|
||||||
{ tag: '@snapshot' },
|
{ tag: '@snapshot' },
|
||||||
async ({ context, homePage, cmdBar, editor, page, scene }) => {
|
async ({ context, homePage, cmdBar, editor, page, scene }) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
|
||||||
await context.addInitScript((file) => {
|
await context.addInitScript((file) => {
|
||||||
localStorage.setItem('persistCode', file)
|
localStorage.setItem('persistCode', file)
|
||||||
}, file)
|
}, file)
|
||||||
|
@ -377,7 +377,8 @@ test.describe(
|
|||||||
'extrude on default planes should be stable',
|
'extrude on default planes should be stable',
|
||||||
{ tag: '@snapshot' },
|
{ tag: '@snapshot' },
|
||||||
() => {
|
() => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
// FIXME: Skip on macos its being weird.
|
||||||
|
test.skip(process.platform === 'darwin', 'Skip on macos')
|
||||||
|
|
||||||
test('XY', async ({ page, context, cmdBar, scene }) => {
|
test('XY', async ({ page, context, cmdBar, scene }) => {
|
||||||
await extrudeDefaultPlane(context, page, cmdBar, scene, 'XY')
|
await extrudeDefaultPlane(context, page, cmdBar, scene, 'XY')
|
||||||
@ -409,7 +410,6 @@ test(
|
|||||||
'Draft segments should look right',
|
'Draft segments should look right',
|
||||||
{ tag: '@snapshot' },
|
{ tag: '@snapshot' },
|
||||||
async ({ page, scene, toolbar }) => {
|
async ({ page, scene, toolbar }) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
@ -534,7 +534,8 @@ test(
|
|||||||
'Draft rectangles should look right',
|
'Draft rectangles should look right',
|
||||||
{ tag: '@snapshot' },
|
{ tag: '@snapshot' },
|
||||||
async ({ page, context, cmdBar, scene }) => {
|
async ({ page, context, cmdBar, scene }) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
// FIXME: Skip on macos its being weird.
|
||||||
|
test.skip(process.platform === 'darwin', 'Skip on macos')
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
@ -628,7 +629,8 @@ test.describe(
|
|||||||
'Client side scene scale should match engine scale',
|
'Client side scene scale should match engine scale',
|
||||||
{ tag: '@snapshot' },
|
{ tag: '@snapshot' },
|
||||||
() => {
|
() => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
// FIXME: Skip on macos its being weird.
|
||||||
|
test.skip(process.platform === 'darwin', 'Skip on macos')
|
||||||
|
|
||||||
test('Inch scale', async ({ page, cmdBar, scene }) => {
|
test('Inch scale', async ({ page, cmdBar, scene }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
@ -805,7 +807,8 @@ test(
|
|||||||
'Sketch on face with none z-up',
|
'Sketch on face with none z-up',
|
||||||
{ tag: '@snapshot' },
|
{ tag: '@snapshot' },
|
||||||
async ({ page, context, cmdBar, scene }) => {
|
async ({ page, context, cmdBar, scene }) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
// FIXME: Skip on macos its being weird.
|
||||||
|
test.skip(process.platform === 'darwin', 'Skip on macos')
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await context.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
await context.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
||||||
@ -865,7 +868,8 @@ test(
|
|||||||
'Zoom to fit on load - solid 2d',
|
'Zoom to fit on load - solid 2d',
|
||||||
{ tag: '@snapshot' },
|
{ tag: '@snapshot' },
|
||||||
async ({ page, context, cmdBar, scene }) => {
|
async ({ page, context, cmdBar, scene }) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
// FIXME: Skip on macos its being weird.
|
||||||
|
test.skip(process.platform === 'darwin', 'Skip on macos')
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await context.addInitScript(async () => {
|
await context.addInitScript(async () => {
|
||||||
@ -903,7 +907,8 @@ test(
|
|||||||
'Zoom to fit on load - solid 3d',
|
'Zoom to fit on load - solid 3d',
|
||||||
{ tag: '@snapshot' },
|
{ tag: '@snapshot' },
|
||||||
async ({ page, context, cmdBar, scene }) => {
|
async ({ page, context, cmdBar, scene }) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
// FIXME: Skip on macos its being weird.
|
||||||
|
test.skip(process.platform === 'darwin', 'Skip on macos')
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await context.addInitScript(async () => {
|
await context.addInitScript(async () => {
|
||||||
@ -944,7 +949,6 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
|
|||||||
cmdBar,
|
cmdBar,
|
||||||
scene,
|
scene,
|
||||||
}) => {
|
}) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const stream = page.getByTestId('stream')
|
const stream = page.getByTestId('stream')
|
||||||
|
|
||||||
@ -1004,7 +1008,6 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('Grid turned off', async ({ page, cmdBar, scene }) => {
|
test('Grid turned off', async ({ page, cmdBar, scene }) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const stream = page.getByTestId('stream')
|
const stream = page.getByTestId('stream')
|
||||||
|
|
||||||
@ -1026,7 +1029,6 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('Grid turned on', async ({ page, context, cmdBar, scene }) => {
|
test('Grid turned on', async ({ page, context, cmdBar, scene }) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
|
||||||
await context.addInitScript(
|
await context.addInitScript(
|
||||||
async ({ settingsKey, settings }) => {
|
async ({ settingsKey, settings }) => {
|
||||||
localStorage.setItem(settingsKey, settings)
|
localStorage.setItem(settingsKey, settings)
|
||||||
@ -1136,7 +1138,6 @@ test('theme persists', async ({ page, context }) => {
|
|||||||
|
|
||||||
test.describe('code color goober', { tag: '@snapshot' }, () => {
|
test.describe('code color goober', { tag: '@snapshot' }, () => {
|
||||||
test('code color goober', async ({ page, context, scene, cmdBar }) => {
|
test('code color goober', async ({ page, context, scene, cmdBar }) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await context.addInitScript(async () => {
|
await context.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
|
4
rust/Cargo.lock
generated
4
rust/Cargo.lock
generated
@ -665,9 +665,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.5.15"
|
version = "0.5.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
@ -43,6 +43,7 @@ import {
|
|||||||
import { commandBarActor } from '@src/machines/commandBarMachine'
|
import { commandBarActor } from '@src/machines/commandBarMachine'
|
||||||
import { EngineStreamTransition } from '@src/machines/engineStreamMachine'
|
import { EngineStreamTransition } from '@src/machines/engineStreamMachine'
|
||||||
import { onboardingPaths } from '@src/routes/Onboarding/paths'
|
import { onboardingPaths } from '@src/routes/Onboarding/paths'
|
||||||
|
import { ActionSidebar } from './components/ModelingSidebar/ActionSidebar'
|
||||||
|
|
||||||
// CYCLIC REF
|
// CYCLIC REF
|
||||||
sceneInfra.camControls.engineStreamActor = engineStreamActor
|
sceneInfra.camControls.engineStreamActor = engineStreamActor
|
||||||
@ -168,7 +169,10 @@ export function App() {
|
|||||||
enableMenu={true}
|
enableMenu={true}
|
||||||
/>
|
/>
|
||||||
<ModalContainer />
|
<ModalContainer />
|
||||||
<ModelingSidebar paneOpacity={paneOpacity} />
|
<div className="relative flex-1 flex flex-col">
|
||||||
|
<ModelingSidebar paneOpacity={paneOpacity} />
|
||||||
|
<ActionSidebar />
|
||||||
|
</div>
|
||||||
<EngineStream pool={pool} authToken={authToken} />
|
<EngineStream pool={pool} authToken={authToken} />
|
||||||
{/* <CamToggle /> */}
|
{/* <CamToggle /> */}
|
||||||
<LowerRightControls coreDumpManager={coreDumpManager}>
|
<LowerRightControls coreDumpManager={coreDumpManager}>
|
||||||
|
@ -127,7 +127,7 @@ export const CreateNewVariable = ({
|
|||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
autoCapitalize="off"
|
autoCapitalize="off"
|
||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
className={`font-mono flex-1 sm:text-sm px-2 py-1 rounded-sm bg-chalkboard-10 dark:bg-chalkboard-90 text-chalkboard-90 dark:text-chalkboard-10 ${
|
className={`flex-1 sm:text-sm px-2 py-1 rounded-sm bg-chalkboard-10 dark:bg-chalkboard-90 text-chalkboard-90 dark:text-chalkboard-10 ${
|
||||||
!shouldCreateVariable ? 'opacity-50' : ''
|
!shouldCreateVariable ? 'opacity-50' : ''
|
||||||
}`}
|
}`}
|
||||||
value={newVariableName}
|
value={newVariableName}
|
||||||
|
@ -628,8 +628,8 @@ const CustomIconMap = {
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fillRule="evenodd"
|
||||||
clip-rule="evenodd"
|
clipRule="evenodd"
|
||||||
d="M4 3H4.5H11H11.2071L11.3536 3.14645L15.8536 7.64646L16 7.7929V8.00001V11.3773C15.6992 11.1362 15.3628 10.9376 15 10.7908V8.50001H11H10.5V8.00001V4H5V16H9.79076C9.93763 16.3628 10.1362 16.6992 10.3773 17H4.5H4V16.5V3.5V3ZM11.5 4.70711L14.2929 7.50001H11.5V4.70711ZM13.8123 17.3904L16.3123 15.3904L15.6877 14.6096L14 15.9597V12H13V15.9597L11.3123 14.6096L10.6877 15.3904L13.1877 17.3904L13.5 17.6403L13.8123 17.3904Z"
|
d="M4 3H4.5H11H11.2071L11.3536 3.14645L15.8536 7.64646L16 7.7929V8.00001V11.3773C15.6992 11.1362 15.3628 10.9376 15 10.7908V8.50001H11H10.5V8.00001V4H5V16H9.79076C9.93763 16.3628 10.1362 16.6992 10.3773 17H4.5H4V16.5V3.5V3ZM11.5 4.70711L14.2929 7.50001H11.5V4.70711ZM13.8123 17.3904L16.3123 15.3904L15.6877 14.6096L14 15.9597V12H13V15.9597L11.3123 14.6096L10.6877 15.3904L13.1877 17.3904L13.5 17.6403L13.8123 17.3904Z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
|
@ -3,7 +3,6 @@ import { useEffect, useState } from 'react'
|
|||||||
import type { CommandLog } from '@src/lang/std/commandLog'
|
import type { CommandLog } from '@src/lang/std/commandLog'
|
||||||
import { engineCommandManager } from '@src/lib/singletons'
|
import { engineCommandManager } from '@src/lib/singletons'
|
||||||
import { reportRejection } from '@src/lib/trap'
|
import { reportRejection } from '@src/lib/trap'
|
||||||
import { parseJson } from '@src/lib/utils'
|
|
||||||
|
|
||||||
export function useEngineCommands(): [CommandLog[], () => void] {
|
export function useEngineCommands(): [CommandLog[], () => void] {
|
||||||
const [engineCommands, setEngineCommands] = useState<CommandLog[]>(
|
const [engineCommands, setEngineCommands] = useState<CommandLog[]>(
|
||||||
@ -85,7 +84,7 @@ export const EngineCommands = () => {
|
|||||||
data-testid="custom-cmd-send-button"
|
data-testid="custom-cmd-send-button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
engineCommandManager
|
engineCommandManager
|
||||||
.sendSceneCommand(parseJson(customCmd))
|
.sendSceneCommand(JSON.parse(customCmd))
|
||||||
.catch(reportRejection)
|
.catch(reportRejection)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
140
src/components/ModelingSidebar/ActionSidebar.tsx
Normal file
140
src/components/ModelingSidebar/ActionSidebar.tsx
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import { isDesktop } from '@src/lib/isDesktop'
|
||||||
|
import { commandBarActor } from '@src/machines/commandBarMachine'
|
||||||
|
import { SidebarAction } from './ModelingPanes'
|
||||||
|
import { useAppState } from '@src/AppState'
|
||||||
|
import { useNetworkContext } from '@src/hooks/useNetworkContext'
|
||||||
|
import { NetworkHealthState } from '@src/hooks/useNetworkStatus'
|
||||||
|
import { useKclContext } from '@src/lang/KclProvider'
|
||||||
|
import { EngineConnectionStateType } from '@src/lang/std/engineConnection'
|
||||||
|
import { useContext, useMemo } from 'react'
|
||||||
|
import { MachineManagerContext } from '../MachineManagerProvider'
|
||||||
|
import { useSettings } from '@src/machines/appMachine'
|
||||||
|
import { getPlatformString } from '@src/lib/utils'
|
||||||
|
import { ModelingPaneButton } from './ModelingSidebarButton'
|
||||||
|
|
||||||
|
export function ActionSidebar() {
|
||||||
|
const machineManager = useContext(MachineManagerContext)
|
||||||
|
const kclContext = useKclContext()
|
||||||
|
const settings = useSettings()
|
||||||
|
const { overallState, immediateState } = useNetworkContext()
|
||||||
|
const { isExecuting } = useKclContext()
|
||||||
|
const { isStreamReady } = useAppState()
|
||||||
|
const reliesOnEngine =
|
||||||
|
(overallState !== NetworkHealthState.Ok &&
|
||||||
|
overallState !== NetworkHealthState.Weak) ||
|
||||||
|
isExecuting ||
|
||||||
|
immediateState.type !== EngineConnectionStateType.ConnectionEstablished ||
|
||||||
|
!isStreamReady
|
||||||
|
const paneCallbackProps = useMemo(
|
||||||
|
() => ({
|
||||||
|
kclContext,
|
||||||
|
settings,
|
||||||
|
platform: getPlatformString(),
|
||||||
|
}),
|
||||||
|
[kclContext.diagnostics, settings]
|
||||||
|
)
|
||||||
|
|
||||||
|
const sidebarActions: SidebarAction[] = [
|
||||||
|
{
|
||||||
|
id: 'load-external-model',
|
||||||
|
title: 'Load external model',
|
||||||
|
sidebarName: 'Load external model',
|
||||||
|
icon: 'importFile',
|
||||||
|
keybinding: 'Ctrl + Shift + I',
|
||||||
|
action: () =>
|
||||||
|
commandBarActor.send({
|
||||||
|
type: 'Find and select command',
|
||||||
|
data: { name: 'load-external-model', groupId: 'code' },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'share-link',
|
||||||
|
title: 'Create share link',
|
||||||
|
sidebarName: 'Create share link',
|
||||||
|
icon: 'link',
|
||||||
|
keybinding: 'Mod + Alt + S',
|
||||||
|
action: () =>
|
||||||
|
commandBarActor.send({
|
||||||
|
type: 'Find and select command',
|
||||||
|
data: { name: 'share-file-link', groupId: 'code' },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'export',
|
||||||
|
title: 'Export part',
|
||||||
|
sidebarName: 'Export part',
|
||||||
|
icon: 'floppyDiskArrow',
|
||||||
|
keybinding: 'Ctrl + Shift + E',
|
||||||
|
disable: () =>
|
||||||
|
reliesOnEngine ? 'Need engine connection to export' : undefined,
|
||||||
|
action: () =>
|
||||||
|
commandBarActor.send({
|
||||||
|
type: 'Find and select command',
|
||||||
|
data: { name: 'Export', groupId: 'modeling' },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'make',
|
||||||
|
title: 'Make part',
|
||||||
|
sidebarName: 'Make part',
|
||||||
|
icon: 'printer3d',
|
||||||
|
keybinding: 'Ctrl + Shift + M',
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
action: async () => {
|
||||||
|
commandBarActor.send({
|
||||||
|
type: 'Find and select command',
|
||||||
|
data: { name: 'Make', groupId: 'modeling' },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
hide: () => !isDesktop(),
|
||||||
|
disable: () => {
|
||||||
|
return machineManager.noMachinesReason()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const filteredActions = sidebarActions.filter(
|
||||||
|
(action) =>
|
||||||
|
!action.hide ||
|
||||||
|
(action.hide instanceof Function && !action.hide(paneCallbackProps))
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<aside
|
||||||
|
id="action-sidebar"
|
||||||
|
className="absolute right-0 top-0 bottom-0 flex flex-col z-10 my-2"
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
className={
|
||||||
|
'relative rounded-l z-[2] pointer-events-auto p-0 col-start-1 col-span-1 h-fit w-fit flex flex-col ' +
|
||||||
|
'bg-chalkboard-10 border border-solid border-chalkboard-30 dark:bg-chalkboard-90 dark:border-chalkboard-80 group-focus-within:border-primary dark:group-focus-within:border-chalkboard-50 shadow-sm '
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{filteredActions.length > 0 && (
|
||||||
|
<>
|
||||||
|
<ul id="sidebar-actions" className="w-fit p-2 flex flex-col gap-2">
|
||||||
|
{filteredActions.map((action) => (
|
||||||
|
<li className="contents">
|
||||||
|
<ModelingPaneButton
|
||||||
|
key={action.id}
|
||||||
|
paneConfig={{
|
||||||
|
id: action.id,
|
||||||
|
sidebarName: action.sidebarName,
|
||||||
|
icon: action.icon,
|
||||||
|
keybinding: action.keybinding,
|
||||||
|
iconClassName: action.iconClassName,
|
||||||
|
iconSize: 'md',
|
||||||
|
}}
|
||||||
|
onClick={action.action}
|
||||||
|
disabledText={action.disable?.()}
|
||||||
|
tooltipPosition="left"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</aside>
|
||||||
|
)
|
||||||
|
}
|
@ -18,7 +18,7 @@
|
|||||||
.header {
|
.header {
|
||||||
@apply z-10 relative rounded-tr;
|
@apply z-10 relative rounded-tr;
|
||||||
@apply flex h-[41px] items-center justify-between gap-2 px-2;
|
@apply flex h-[41px] items-center justify-between gap-2 px-2;
|
||||||
@apply font-mono text-xs font-bold select-none text-chalkboard-90;
|
@apply text-xs select-none text-chalkboard-90;
|
||||||
@apply bg-chalkboard-10 border-b border-chalkboard-30;
|
@apply bg-chalkboard-10 border-b border-chalkboard-30;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
.button {
|
.button {
|
||||||
@apply flex justify-between items-center gap-2 px-2 py-1 text-left border-none rounded-sm;
|
@apply flex justify-between items-center gap-2 px-2 py-1 text-left border-none rounded-sm;
|
||||||
@apply font-mono !no-underline text-xs font-bold select-none text-chalkboard-90;
|
@apply !no-underline text-xs select-none text-chalkboard-90;
|
||||||
@apply ui-active:bg-primary/10 ui-active:text-primary ui-active:text-inherit;
|
@apply ui-active:bg-primary/10 ui-active:text-primary ui-active:text-inherit;
|
||||||
@apply transition-colors ease-out;
|
@apply transition-colors ease-out;
|
||||||
@apply m-0;
|
@apply m-0;
|
||||||
|
@ -1,48 +1,28 @@
|
|||||||
import type { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
import type { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { Resizable } from 're-resizable'
|
import { Resizable } from 're-resizable'
|
||||||
import type { MouseEventHandler } from 'react'
|
import type { MouseEventHandler } from 'react'
|
||||||
import { useCallback, useContext, useEffect, useMemo } from 'react'
|
import { useCallback, useEffect, useMemo } from 'react'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
|
|
||||||
import { useAppState } from '@src/AppState'
|
|
||||||
import { ActionIcon } from '@src/components/ActionIcon'
|
import { ActionIcon } from '@src/components/ActionIcon'
|
||||||
import type { CustomIconName } from '@src/components/CustomIcon'
|
import type { CustomIconName } from '@src/components/CustomIcon'
|
||||||
import { MachineManagerContext } from '@src/components/MachineManagerProvider'
|
|
||||||
import { ModelingPane } from '@src/components/ModelingSidebar/ModelingPane'
|
import { ModelingPane } from '@src/components/ModelingSidebar/ModelingPane'
|
||||||
import type {
|
import type { SidebarType } from '@src/components/ModelingSidebar/ModelingPanes'
|
||||||
SidebarAction,
|
|
||||||
SidebarType,
|
|
||||||
} from '@src/components/ModelingSidebar/ModelingPanes'
|
|
||||||
import { sidebarPanes } from '@src/components/ModelingSidebar/ModelingPanes'
|
import { sidebarPanes } from '@src/components/ModelingSidebar/ModelingPanes'
|
||||||
import Tooltip from '@src/components/Tooltip'
|
import Tooltip from '@src/components/Tooltip'
|
||||||
import { useModelingContext } from '@src/hooks/useModelingContext'
|
import { useModelingContext } from '@src/hooks/useModelingContext'
|
||||||
import { useNetworkContext } from '@src/hooks/useNetworkContext'
|
|
||||||
import { NetworkHealthState } from '@src/hooks/useNetworkStatus'
|
|
||||||
import { useKclContext } from '@src/lang/KclProvider'
|
import { useKclContext } from '@src/lang/KclProvider'
|
||||||
import { EngineConnectionStateType } from '@src/lang/std/engineConnection'
|
|
||||||
import { SIDEBAR_BUTTON_SUFFIX } from '@src/lib/constants'
|
import { SIDEBAR_BUTTON_SUFFIX } from '@src/lib/constants'
|
||||||
import { isDesktop } from '@src/lib/isDesktop'
|
|
||||||
import { useSettings } from '@src/machines/appMachine'
|
import { useSettings } from '@src/machines/appMachine'
|
||||||
import { commandBarActor } from '@src/machines/commandBarMachine'
|
|
||||||
import { onboardingPaths } from '@src/routes/Onboarding/paths'
|
import { onboardingPaths } from '@src/routes/Onboarding/paths'
|
||||||
|
import { getPlatformString } from '@src/lib/utils'
|
||||||
|
import { BadgeInfoComputed, ModelingPaneButton } from './ModelingSidebarButton'
|
||||||
|
|
||||||
interface ModelingSidebarProps {
|
interface ModelingSidebarProps {
|
||||||
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BadgeInfoComputed {
|
|
||||||
value: number | boolean | string
|
|
||||||
onClick?: MouseEventHandler<any>
|
|
||||||
className?: string
|
|
||||||
title?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPlatformString(): 'web' | 'desktop' {
|
|
||||||
return isDesktop() ? 'desktop' : 'web'
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||||
const machineManager = useContext(MachineManagerContext)
|
|
||||||
const kclContext = useKclContext()
|
const kclContext = useKclContext()
|
||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
const onboardingStatus = settings.app.onboardingStatus
|
const onboardingStatus = settings.app.onboardingStatus
|
||||||
@ -54,16 +34,6 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
|||||||
: 'pointer-events-auto '
|
: 'pointer-events-auto '
|
||||||
const showDebugPanel = settings.app.showDebugPanel
|
const showDebugPanel = settings.app.showDebugPanel
|
||||||
|
|
||||||
const { overallState, immediateState } = useNetworkContext()
|
|
||||||
const { isExecuting } = useKclContext()
|
|
||||||
const { isStreamReady } = useAppState()
|
|
||||||
const reliesOnEngine =
|
|
||||||
(overallState !== NetworkHealthState.Ok &&
|
|
||||||
overallState !== NetworkHealthState.Weak) ||
|
|
||||||
isExecuting ||
|
|
||||||
immediateState.type !== EngineConnectionStateType.ConnectionEstablished ||
|
|
||||||
!isStreamReady
|
|
||||||
|
|
||||||
const paneCallbackProps = useMemo(
|
const paneCallbackProps = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
kclContext,
|
kclContext,
|
||||||
@ -73,70 +43,6 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
|||||||
[kclContext.diagnostics, settings]
|
[kclContext.diagnostics, settings]
|
||||||
)
|
)
|
||||||
|
|
||||||
const sidebarActions: SidebarAction[] = [
|
|
||||||
{
|
|
||||||
id: 'load-external-model',
|
|
||||||
title: 'Load external model',
|
|
||||||
sidebarName: 'Load external model',
|
|
||||||
icon: 'importFile',
|
|
||||||
keybinding: 'Ctrl + Shift + I',
|
|
||||||
action: () =>
|
|
||||||
commandBarActor.send({
|
|
||||||
type: 'Find and select command',
|
|
||||||
data: { name: 'load-external-model', groupId: 'code' },
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'share-link',
|
|
||||||
title: 'Create share link',
|
|
||||||
sidebarName: 'Create share link',
|
|
||||||
icon: 'link',
|
|
||||||
keybinding: 'Mod + Alt + S',
|
|
||||||
action: () =>
|
|
||||||
commandBarActor.send({
|
|
||||||
type: 'Find and select command',
|
|
||||||
data: { name: 'share-file-link', groupId: 'code' },
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'export',
|
|
||||||
title: 'Export part',
|
|
||||||
sidebarName: 'Export part',
|
|
||||||
icon: 'floppyDiskArrow',
|
|
||||||
keybinding: 'Ctrl + Shift + E',
|
|
||||||
disable: () =>
|
|
||||||
reliesOnEngine ? 'Need engine connection to export' : undefined,
|
|
||||||
action: () =>
|
|
||||||
commandBarActor.send({
|
|
||||||
type: 'Find and select command',
|
|
||||||
data: { name: 'Export', groupId: 'modeling' },
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'make',
|
|
||||||
title: 'Make part',
|
|
||||||
sidebarName: 'Make part',
|
|
||||||
icon: 'printer3d',
|
|
||||||
keybinding: 'Ctrl + Shift + M',
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
action: async () => {
|
|
||||||
commandBarActor.send({
|
|
||||||
type: 'Find and select command',
|
|
||||||
data: { name: 'Make', groupId: 'modeling' },
|
|
||||||
})
|
|
||||||
},
|
|
||||||
hide: () => !isDesktop(),
|
|
||||||
disable: () => {
|
|
||||||
return machineManager.noMachinesReason()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
const filteredActions: SidebarAction[] = sidebarActions.filter(
|
|
||||||
(action) =>
|
|
||||||
!action.hide ||
|
|
||||||
(action.hide instanceof Function && !action.hide(paneCallbackProps))
|
|
||||||
)
|
|
||||||
|
|
||||||
// // Filter out the debug panel if it's not supposed to be shown
|
// // Filter out the debug panel if it's not supposed to be shown
|
||||||
// // TODO: abstract out for allowing user to configure which panes to show
|
// // TODO: abstract out for allowing user to configure which panes to show
|
||||||
const filteredPanes = useMemo(
|
const filteredPanes = useMemo(
|
||||||
@ -257,31 +163,6 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
{filteredActions.length > 0 && (
|
|
||||||
<>
|
|
||||||
<hr className="w-full border-chalkboard-30 dark:border-chalkboard-80" />
|
|
||||||
<ul
|
|
||||||
id="sidebar-actions"
|
|
||||||
className="w-fit p-2 flex flex-col gap-2"
|
|
||||||
>
|
|
||||||
{filteredActions.map((action) => (
|
|
||||||
<ModelingPaneButton
|
|
||||||
key={action.id}
|
|
||||||
paneConfig={{
|
|
||||||
id: action.id,
|
|
||||||
sidebarName: action.sidebarName,
|
|
||||||
icon: action.icon,
|
|
||||||
keybinding: action.keybinding,
|
|
||||||
iconClassName: action.iconClassName,
|
|
||||||
iconSize: 'md',
|
|
||||||
}}
|
|
||||||
onClick={action.action}
|
|
||||||
disabledText={action.disable?.()}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</ul>
|
</ul>
|
||||||
<ul
|
<ul
|
||||||
id="pane-section"
|
id="pane-section"
|
||||||
@ -315,105 +196,3 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
|||||||
</Resizable>
|
</Resizable>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ModelingPaneButtonProps
|
|
||||||
extends React.HTMLAttributes<HTMLButtonElement> {
|
|
||||||
paneConfig: {
|
|
||||||
id: string
|
|
||||||
sidebarName: string
|
|
||||||
icon: CustomIconName | IconDefinition
|
|
||||||
keybinding: string
|
|
||||||
iconClassName?: string
|
|
||||||
iconSize?: 'sm' | 'md' | 'lg'
|
|
||||||
}
|
|
||||||
onClick: () => void
|
|
||||||
paneIsOpen?: boolean
|
|
||||||
showBadge?: BadgeInfoComputed
|
|
||||||
disabledText?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
function ModelingPaneButton({
|
|
||||||
paneConfig,
|
|
||||||
onClick,
|
|
||||||
paneIsOpen,
|
|
||||||
showBadge,
|
|
||||||
disabledText,
|
|
||||||
...props
|
|
||||||
}: ModelingPaneButtonProps) {
|
|
||||||
useHotkeys(paneConfig.keybinding, onClick, {
|
|
||||||
scopes: ['modeling'],
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div id={paneConfig.id + '-button-holder'} className="relative">
|
|
||||||
<button
|
|
||||||
className="group pointer-events-auto flex items-center justify-center border-transparent dark:border-transparent disabled:!border-transparent p-0 m-0 rounded-sm !outline-0 focus-visible:border-primary"
|
|
||||||
onClick={onClick}
|
|
||||||
name={paneConfig.sidebarName}
|
|
||||||
data-testid={paneConfig.id + SIDEBAR_BUTTON_SUFFIX}
|
|
||||||
disabled={disabledText !== undefined}
|
|
||||||
aria-disabled={disabledText !== undefined}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<ActionIcon
|
|
||||||
icon={paneConfig.icon}
|
|
||||||
className={paneConfig.iconClassName || ''}
|
|
||||||
size={paneConfig.iconSize || 'md'}
|
|
||||||
iconClassName={paneIsOpen ? ' !text-chalkboard-10' : ''}
|
|
||||||
bgClassName={
|
|
||||||
'rounded-sm ' + (paneIsOpen ? '!bg-primary' : '!bg-transparent')
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<span className="sr-only">
|
|
||||||
{paneConfig.sidebarName}
|
|
||||||
{paneIsOpen !== undefined ? ` pane` : ''}
|
|
||||||
</span>
|
|
||||||
<Tooltip
|
|
||||||
position="right"
|
|
||||||
contentClassName="max-w-none flex items-center gap-4"
|
|
||||||
hoverOnly
|
|
||||||
>
|
|
||||||
<span className="flex-1">
|
|
||||||
{paneConfig.sidebarName}
|
|
||||||
{disabledText !== undefined ? ` (${disabledText})` : ''}
|
|
||||||
{paneIsOpen !== undefined ? ` pane` : ''}
|
|
||||||
</span>
|
|
||||||
<kbd className="hotkey text-xs capitalize">
|
|
||||||
{paneConfig.keybinding}
|
|
||||||
</kbd>
|
|
||||||
</Tooltip>
|
|
||||||
</button>
|
|
||||||
{!!showBadge?.value && (
|
|
||||||
<p
|
|
||||||
id={`${paneConfig.id}-badge`}
|
|
||||||
className={
|
|
||||||
showBadge.className
|
|
||||||
? showBadge.className
|
|
||||||
: 'absolute m-0 p-0 bottom-4 left-4 w-3 h-3 flex items-center justify-center text-[10px] font-semibold text-white bg-primary hue-rotate-90 rounded-full border border-chalkboard-10 dark:border-chalkboard-80 z-50 hover:cursor-pointer hover:scale-[2] transition-transform duration-200'
|
|
||||||
}
|
|
||||||
onClick={showBadge.onClick}
|
|
||||||
title={
|
|
||||||
showBadge.title
|
|
||||||
? showBadge.title
|
|
||||||
: `Click to view ${showBadge.value} notification${
|
|
||||||
Number(showBadge.value) > 1 ? 's' : ''
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span className="sr-only"> has </span>
|
|
||||||
{typeof showBadge.value === 'number' ||
|
|
||||||
typeof showBadge.value === 'string' ? (
|
|
||||||
<span>{showBadge.value}</span>
|
|
||||||
) : (
|
|
||||||
<span className="sr-only">a</span>
|
|
||||||
)}
|
|
||||||
{typeof showBadge.value === 'number' && (
|
|
||||||
<span className="sr-only">
|
|
||||||
notification{Number(showBadge.value) > 1 ? 's' : ''}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
118
src/components/ModelingSidebar/ModelingSidebarButton.tsx
Normal file
118
src/components/ModelingSidebar/ModelingSidebarButton.tsx
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { SIDEBAR_BUTTON_SUFFIX } from '@src/lib/constants'
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
|
import { ActionIcon } from '../ActionIcon'
|
||||||
|
import { CustomIconName } from '../CustomIcon'
|
||||||
|
import Tooltip from '../Tooltip'
|
||||||
|
import { MouseEventHandler } from 'react'
|
||||||
|
|
||||||
|
export interface BadgeInfoComputed {
|
||||||
|
value: number | boolean | string
|
||||||
|
onClick?: MouseEventHandler<any>
|
||||||
|
className?: string
|
||||||
|
title?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ModelingPaneButtonProps
|
||||||
|
extends React.HTMLAttributes<HTMLButtonElement> {
|
||||||
|
paneConfig: {
|
||||||
|
id: string
|
||||||
|
sidebarName: string
|
||||||
|
icon: CustomIconName | IconDefinition
|
||||||
|
keybinding: string
|
||||||
|
iconClassName?: string
|
||||||
|
iconSize?: 'sm' | 'md' | 'lg'
|
||||||
|
}
|
||||||
|
onClick: () => void
|
||||||
|
paneIsOpen?: boolean
|
||||||
|
showBadge?: BadgeInfoComputed
|
||||||
|
disabledText?: string
|
||||||
|
tooltipPosition?: 'right' | 'left'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ModelingPaneButton({
|
||||||
|
paneConfig,
|
||||||
|
onClick,
|
||||||
|
paneIsOpen,
|
||||||
|
showBadge,
|
||||||
|
disabledText,
|
||||||
|
tooltipPosition = 'right',
|
||||||
|
...props
|
||||||
|
}: ModelingPaneButtonProps) {
|
||||||
|
useHotkeys(paneConfig.keybinding, onClick, {
|
||||||
|
scopes: ['modeling'],
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id={paneConfig.id + '-button-holder'} className="relative">
|
||||||
|
<button
|
||||||
|
className="group pointer-events-auto flex items-center justify-center border-transparent dark:border-transparent disabled:!border-transparent p-0 m-0 rounded-sm !outline-0 focus-visible:border-primary"
|
||||||
|
onClick={onClick}
|
||||||
|
name={paneConfig.sidebarName}
|
||||||
|
data-testid={paneConfig.id + SIDEBAR_BUTTON_SUFFIX}
|
||||||
|
disabled={disabledText !== undefined}
|
||||||
|
aria-disabled={disabledText !== undefined}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ActionIcon
|
||||||
|
icon={paneConfig.icon}
|
||||||
|
className={paneConfig.iconClassName || ''}
|
||||||
|
size={paneConfig.iconSize || 'md'}
|
||||||
|
iconClassName={paneIsOpen ? ' !text-chalkboard-10' : ''}
|
||||||
|
bgClassName={
|
||||||
|
'rounded-sm ' + (paneIsOpen ? '!bg-primary' : '!bg-transparent')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span className="sr-only">
|
||||||
|
{paneConfig.sidebarName}
|
||||||
|
{paneIsOpen !== undefined ? ` pane` : ''}
|
||||||
|
</span>
|
||||||
|
<Tooltip
|
||||||
|
position={tooltipPosition}
|
||||||
|
contentClassName="max-w-none flex items-center gap-4"
|
||||||
|
hoverOnly
|
||||||
|
>
|
||||||
|
<span className="flex-1">
|
||||||
|
{paneConfig.sidebarName}
|
||||||
|
{disabledText !== undefined ? ` (${disabledText})` : ''}
|
||||||
|
{paneIsOpen !== undefined ? ` pane` : ''}
|
||||||
|
</span>
|
||||||
|
<kbd className="hotkey text-xs capitalize">
|
||||||
|
{paneConfig.keybinding}
|
||||||
|
</kbd>
|
||||||
|
</Tooltip>
|
||||||
|
</button>
|
||||||
|
{!!showBadge?.value && (
|
||||||
|
<p
|
||||||
|
id={`${paneConfig.id}-badge`}
|
||||||
|
className={
|
||||||
|
showBadge.className
|
||||||
|
? showBadge.className
|
||||||
|
: 'absolute m-0 p-0 bottom-4 left-4 w-3 h-3 flex items-center justify-center text-[10px] font-semibold text-white bg-primary hue-rotate-90 rounded-full border border-chalkboard-10 dark:border-chalkboard-80 z-50 hover:cursor-pointer hover:scale-[2] transition-transform duration-200'
|
||||||
|
}
|
||||||
|
onClick={showBadge.onClick}
|
||||||
|
title={
|
||||||
|
showBadge.title
|
||||||
|
? showBadge.title
|
||||||
|
: `Click to view ${showBadge.value} notification${
|
||||||
|
Number(showBadge.value) > 1 ? 's' : ''
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span className="sr-only"> has </span>
|
||||||
|
{typeof showBadge.value === 'number' ||
|
||||||
|
typeof showBadge.value === 'string' ? (
|
||||||
|
<span>{showBadge.value}</span>
|
||||||
|
) : (
|
||||||
|
<span className="sr-only">a</span>
|
||||||
|
)}
|
||||||
|
{typeof showBadge.value === 'number' && (
|
||||||
|
<span className="sr-only">
|
||||||
|
notification{Number(showBadge.value) > 1 ? 's' : ''}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -37,7 +37,7 @@ export class KCLError extends Error {
|
|||||||
filenames: { [x: number]: ModulePath | undefined },
|
filenames: { [x: number]: ModulePath | undefined },
|
||||||
defaultPlanes: DefaultPlanes | null
|
defaultPlanes: DefaultPlanes | null
|
||||||
) {
|
) {
|
||||||
super(`${kind}: ${msg}`)
|
super()
|
||||||
this.kind = kind
|
this.kind = kind
|
||||||
this.msg = msg
|
this.msg = msg
|
||||||
this.sourceRange = sourceRange
|
this.sourceRange = sourceRange
|
||||||
|
@ -4,7 +4,6 @@ import path from 'node:path'
|
|||||||
import { assertParse } from '@src/lang/wasm'
|
import { assertParse } from '@src/lang/wasm'
|
||||||
import { initPromise } from '@src/lang/wasmUtils'
|
import { initPromise } from '@src/lang/wasmUtils'
|
||||||
import { enginelessExecutor } from '@src/lib/testHelpers'
|
import { enginelessExecutor } from '@src/lib/testHelpers'
|
||||||
import { parseJson } from '@src/lib/utils'
|
|
||||||
|
|
||||||
// The purpose of these tests is to act as a first line of defense
|
// The purpose of these tests is to act as a first line of defense
|
||||||
// if something gets real screwy with our KCL ecosystem.
|
// if something gets real screwy with our KCL ecosystem.
|
||||||
@ -28,7 +27,7 @@ const manifestJsonStr = await fs.readFile(
|
|||||||
path.resolve(DIR_KCL_SAMPLES, 'manifest.json'),
|
path.resolve(DIR_KCL_SAMPLES, 'manifest.json'),
|
||||||
'utf-8'
|
'utf-8'
|
||||||
)
|
)
|
||||||
const manifest: KclSampleFile[] = parseJson(manifestJsonStr)
|
const manifest = JSON.parse(manifestJsonStr)
|
||||||
|
|
||||||
process.chdir(DIR_KCL_SAMPLES)
|
process.chdir(DIR_KCL_SAMPLES)
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ import {
|
|||||||
getThemeColorForEngine,
|
getThemeColorForEngine,
|
||||||
} from '@src/lib/theme'
|
} from '@src/lib/theme'
|
||||||
import { reportRejection } from '@src/lib/trap'
|
import { reportRejection } from '@src/lib/trap'
|
||||||
import { binaryToUuid, parseJson, uuidv4 } from '@src/lib/utils'
|
import { binaryToUuid, uuidv4 } from '@src/lib/utils'
|
||||||
|
|
||||||
const pingIntervalMs = 1_000
|
const pingIntervalMs = 1_000
|
||||||
|
|
||||||
@ -390,7 +390,7 @@ class EngineConnection extends EventTarget {
|
|||||||
this.websocket.addEventListener('open', this.onWebSocketOpen)
|
this.websocket.addEventListener('open', this.onWebSocketOpen)
|
||||||
|
|
||||||
this.websocket?.addEventListener('message', ((event: MessageEvent) => {
|
this.websocket?.addEventListener('message', ((event: MessageEvent) => {
|
||||||
const message: Models['WebSocketResponse_type'] = parseJson(event.data)
|
const message: Models['WebSocketResponse_type'] = JSON.parse(event.data)
|
||||||
const pending =
|
const pending =
|
||||||
this.engineCommandManager.pendingCommands[message.request_id || '']
|
this.engineCommandManager.pendingCommands[message.request_id || '']
|
||||||
if (!('resp' in message)) return
|
if (!('resp' in message)) return
|
||||||
@ -862,7 +862,7 @@ class EngineConnection extends EventTarget {
|
|||||||
)
|
)
|
||||||
|
|
||||||
this.onDataChannelMessage = (event) => {
|
this.onDataChannelMessage = (event) => {
|
||||||
const result: UnreliableResponses = parseJson(event.data)
|
const result: UnreliableResponses = JSON.parse(event.data)
|
||||||
Object.values(
|
Object.values(
|
||||||
this.engineCommandManager.unreliableSubscriptions[result.type] ||
|
this.engineCommandManager.unreliableSubscriptions[result.type] ||
|
||||||
{}
|
{}
|
||||||
@ -976,7 +976,7 @@ class EngineConnection extends EventTarget {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const message: Models['WebSocketResponse_type'] = parseJson(
|
const message: Models['WebSocketResponse_type'] = JSON.parse(
|
||||||
event.data
|
event.data
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1564,7 +1564,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
unreliableDataChannel.addEventListener(
|
unreliableDataChannel.addEventListener(
|
||||||
'message',
|
'message',
|
||||||
(event: MessageEvent) => {
|
(event: MessageEvent) => {
|
||||||
const result: UnreliableResponses = parseJson(event.data)
|
const result: UnreliableResponses = JSON.parse(event.data)
|
||||||
Object.values(
|
Object.values(
|
||||||
this.unreliableSubscriptions[result.type] || {}
|
this.unreliableSubscriptions[result.type] || {}
|
||||||
).forEach(
|
).forEach(
|
||||||
@ -1608,7 +1608,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
message.request_id = binaryToUuid(message.request_id)
|
message.request_id = binaryToUuid(message.request_id)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
message = parseJson(event.data)
|
message = JSON.parse(event.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message === null) {
|
if (message === null) {
|
||||||
|
@ -40,7 +40,7 @@ import type { CoreDumpManager } from '@src/lib/coredump'
|
|||||||
import openWindow from '@src/lib/openWindow'
|
import openWindow from '@src/lib/openWindow'
|
||||||
import { Reason, err } from '@src/lib/trap'
|
import { Reason, err } from '@src/lib/trap'
|
||||||
import type { DeepPartial } from '@src/lib/types'
|
import type { DeepPartial } from '@src/lib/types'
|
||||||
import { isArray, parseJson } from '@src/lib/utils'
|
import { isArray } from '@src/lib/utils'
|
||||||
import {
|
import {
|
||||||
base64_decode,
|
base64_decode,
|
||||||
change_kcl_settings,
|
change_kcl_settings,
|
||||||
@ -217,7 +217,9 @@ export const parse = (code: string | Error): ParseResult | Error => {
|
|||||||
let errs = splitErrors(parsed[1])
|
let errs = splitErrors(parsed[1])
|
||||||
return new ParseResult(parsed[0], errs.errors, errs.warnings)
|
return new ParseResult(parsed[0], errs.errors, errs.warnings)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
const parsed: RustKclError = parseJson(e.toString())
|
// throw e
|
||||||
|
console.error(e.toString())
|
||||||
|
const parsed: RustKclError = JSON.parse(e.toString())
|
||||||
return new KCLError(
|
return new KCLError(
|
||||||
parsed.kind,
|
parsed.kind,
|
||||||
parsed.msg,
|
parsed.msg,
|
||||||
@ -379,7 +381,7 @@ export function sketchFromKclValue(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const errFromErrWithOutputs = (e: any): KCLError => {
|
export const errFromErrWithOutputs = (e: any): KCLError => {
|
||||||
const parsed: KclErrorWithOutputs = parseJson(e.toString())
|
const parsed: KclErrorWithOutputs = JSON.parse(e.toString())
|
||||||
return new KCLError(
|
return new KCLError(
|
||||||
parsed.error.kind,
|
parsed.error.kind,
|
||||||
parsed.error.msg,
|
parsed.error.msg,
|
||||||
|
@ -3,7 +3,7 @@ import type { ApiError_type } from '@kittycad/lib/dist/types/src/models'
|
|||||||
|
|
||||||
import type { Selections } from '@src/lib/selections'
|
import type { Selections } from '@src/lib/selections'
|
||||||
import { engineCommandManager, kclManager } from '@src/lib/singletons'
|
import { engineCommandManager, kclManager } from '@src/lib/singletons'
|
||||||
import { parseJson, uuidv4 } from '@src/lib/utils'
|
import { uuidv4 } from '@src/lib/utils'
|
||||||
import type { CommandBarContext } from '@src/machines/commandBarMachine'
|
import type { CommandBarContext } from '@src/machines/commandBarMachine'
|
||||||
|
|
||||||
export const disableDryRunWithRetry = async (numberOfRetries = 3) => {
|
export const disableDryRunWithRetry = async (numberOfRetries = 3) => {
|
||||||
@ -54,7 +54,7 @@ export function parseEngineErrorMessage(engineError: string) {
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const errors = parseJson<ApiError_type[]>(parts[1])
|
const errors = JSON.parse(parts[1]) as ApiError_type[]
|
||||||
if (!errors[0]) {
|
if (!errors[0]) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ import type { DeepPartial } from '@src/lib/types'
|
|||||||
import type { ModuleType } from '@src/lib/wasm_lib_wrapper'
|
import type { ModuleType } from '@src/lib/wasm_lib_wrapper'
|
||||||
import { getModule } from '@src/lib/wasm_lib_wrapper'
|
import { getModule } from '@src/lib/wasm_lib_wrapper'
|
||||||
import type { Models } from '@kittycad/lib/dist/types/src'
|
import type { Models } from '@kittycad/lib/dist/types/src'
|
||||||
import { parseJson } from '@src/lib/utils'
|
|
||||||
|
|
||||||
export default class RustContext {
|
export default class RustContext {
|
||||||
private wasmInitFailed: boolean = true
|
private wasmInitFailed: boolean = true
|
||||||
@ -141,7 +140,7 @@ export default class RustContext {
|
|||||||
JSON.stringify(settings)
|
JSON.stringify(settings)
|
||||||
)
|
)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
const parsed: RustKclError = parseJson(e.toString())
|
const parsed: RustKclError = JSON.parse(e.toString())
|
||||||
toast.error(parsed.msg, { id: toastId })
|
toast.error(parsed.msg, { id: toastId })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,13 @@ import type { AsyncFn } from '@src/lib/types'
|
|||||||
|
|
||||||
export const uuidv4 = v4
|
export const uuidv4 = v4
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current platform as a string.
|
||||||
|
*/
|
||||||
|
export function getPlatformString(): 'web' | 'desktop' {
|
||||||
|
return isDesktop() ? 'desktop' : 'web'
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all labels for a keyword call expression.
|
* Get all labels for a keyword call expression.
|
||||||
*/
|
*/
|
||||||
@ -60,26 +67,6 @@ export function isNonNullable<T>(val: T): val is NonNullable<T> {
|
|||||||
return val !== null && val !== undefined
|
return val !== null && val !== undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as JSON.parse() but if it fails, includes the string that was attempted
|
|
||||||
* to be parsed in the error message. This is useful since a lot of times this
|
|
||||||
* is called on a string that isn't actually JSON, like another error message.
|
|
||||||
*
|
|
||||||
* You can also use the type parameter to assert the expected type of the parsed
|
|
||||||
* object.
|
|
||||||
*/
|
|
||||||
export function parseJson<T>(
|
|
||||||
text: string,
|
|
||||||
reviver?: (this: any, key: string, value: any) => any
|
|
||||||
): T {
|
|
||||||
try {
|
|
||||||
return JSON.parse(text, reviver)
|
|
||||||
} catch (e) {
|
|
||||||
// eslint-disable-next-line suggest-no-throw/suggest-no-throw
|
|
||||||
throw new Error(`Failed to parse JSON: ${text}`, { cause: e })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isOverlap(a: SourceRange, b: SourceRange) {
|
export function isOverlap(a: SourceRange, b: SourceRange) {
|
||||||
const [startingRange, secondRange] = a[0] < b[0] ? [a, b] : [b, a]
|
const [startingRange, secondRange] = a[0] < b[0] ? [a, b] : [b, a]
|
||||||
const [lastOfFirst, firstOfSecond] = [startingRange[1], secondRange[0]]
|
const [lastOfFirst, firstOfSecond] = [startingRange[1], secondRange[0]]
|
||||||
|
Reference in New Issue
Block a user