Compare commits
6 Commits
kcl-0.2.18
...
achalmers/
Author | SHA1 | Date | |
---|---|---|---|
cc145a267c | |||
66e60f2ddb | |||
5f51a0f569 | |||
aee1d66e56 | |||
1d1bb8cee0 | |||
c7dd89e720 |
195
e2e/playwright/testing-samples-loading.spec.ts
Normal file
195
e2e/playwright/testing-samples-loading.spec.ts
Normal file
@ -0,0 +1,195 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
import * as fsp from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
import { FILE_EXT } from 'lib/constants'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('Testing in-app sample loading', () => {
|
||||
/**
|
||||
* Note this test implicitly depends on the KCL sample "flange-with-patterns.kcl"
|
||||
* and its title. https://github.com/KittyCAD/kcl-samples/blob/main/flange-with-patterns/flange-with-patterns.kcl
|
||||
*/
|
||||
test('Web: should overwrite current code, cannot create new file', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await test.step(`Test setup`, async () => {
|
||||
await page.addInitScript((code) => {
|
||||
window.localStorage.setItem('persistCode', code)
|
||||
}, bracket)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
})
|
||||
|
||||
// Locators and constants
|
||||
const newSample = {
|
||||
file: 'flange-with-patterns' + FILE_EXT,
|
||||
title: 'Flange',
|
||||
}
|
||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
const samplesCommandOption = page.getByRole('option', {
|
||||
name: 'Open Sample',
|
||||
})
|
||||
const commandSampleOption = page.getByRole('option', {
|
||||
name: newSample.title,
|
||||
exact: true,
|
||||
})
|
||||
const commandMethodArgButton = page.getByRole('button', {
|
||||
name: 'Method',
|
||||
})
|
||||
const commandMethodOption = (name: 'Overwrite' | 'Create new file') =>
|
||||
page.getByRole('option', {
|
||||
name,
|
||||
})
|
||||
const warningText = page.getByText('Overwrite current file?')
|
||||
const confirmButton = page.getByRole('button', { name: 'Submit command' })
|
||||
const codeLocator = page.locator('.cm-content')
|
||||
|
||||
await test.step(`Precondition: check the initial code`, async () => {
|
||||
await u.openKclCodePanel()
|
||||
await expect(codeLocator).toContainText(bracket.split('\n')[0])
|
||||
})
|
||||
|
||||
await test.step(`Load a KCL sample with the command palette`, async () => {
|
||||
await commandBarButton.click()
|
||||
await samplesCommandOption.click()
|
||||
await commandSampleOption.click()
|
||||
await commandMethodArgButton.click()
|
||||
await expect(commandMethodOption('Create new file')).not.toBeVisible()
|
||||
await commandMethodOption('Overwrite').click()
|
||||
await expect(warningText).toBeVisible()
|
||||
await confirmButton.click()
|
||||
|
||||
await expect(codeLocator).toContainText('// ' + newSample.title)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Note this test implicitly depends on the KCL samples:
|
||||
* "flange-with-patterns.kcl": https://github.com/KittyCAD/kcl-samples/blob/main/flange-with-patterns/flange-with-patterns.kcl
|
||||
* "gear-rack.kcl": https://github.com/KittyCAD/kcl-samples/blob/main/gear-rack/gear-rack.kcl
|
||||
*/
|
||||
test(
|
||||
'Desktop: should create new file by default, optionally overwrite',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName: _ }, testInfo) => {
|
||||
const { electronApp, page, dir } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.writeFile(join(bracketDir, 'main.kcl'), bracket, {
|
||||
encoding: 'utf-8',
|
||||
})
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Locators and constants
|
||||
const sampleOne = {
|
||||
file: 'flange-with-patterns' + FILE_EXT,
|
||||
title: 'Flange',
|
||||
}
|
||||
const sampleTwo = {
|
||||
file: 'gear-rack' + FILE_EXT,
|
||||
title: '100mm Gear Rack',
|
||||
}
|
||||
const projectCard = page.getByRole('link', { name: 'bracket' })
|
||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
const commandOption = page.getByRole('option', { name: 'Open Sample' })
|
||||
const commandSampleOption = (name: string) =>
|
||||
page.getByRole('option', {
|
||||
name,
|
||||
exact: true,
|
||||
})
|
||||
const commandMethodArgButton = page.getByRole('button', {
|
||||
name: 'Method',
|
||||
})
|
||||
const commandMethodOption = page.getByRole('option', {
|
||||
name: 'Overwrite',
|
||||
})
|
||||
const newFileWarning = page.getByText(
|
||||
'Create a new file with the example code?'
|
||||
)
|
||||
const overwriteWarning = page.getByText('Overwrite current file?')
|
||||
const confirmButton = page.getByRole('button', { name: 'Submit command' })
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const newlyCreatedFile = (name: string) =>
|
||||
page.getByRole('listitem').filter({
|
||||
has: page.getByRole('button', { name }),
|
||||
})
|
||||
const codeLocator = page.locator('.cm-content')
|
||||
|
||||
await test.step(`Test setup`, async () => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await projectCard.click()
|
||||
await u.waitForPageLoad()
|
||||
})
|
||||
|
||||
await test.step(`Precondition: check the initial code`, async () => {
|
||||
await u.openKclCodePanel()
|
||||
await expect(codeLocator).toContainText(bracket.split('\n')[0])
|
||||
await u.openFilePanel()
|
||||
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await expect(newlyCreatedFile(sampleOne.file)).not.toBeVisible()
|
||||
})
|
||||
|
||||
await test.step(`Load a KCL sample with the command palette`, async () => {
|
||||
await commandBarButton.click()
|
||||
await commandOption.click()
|
||||
await commandSampleOption(sampleOne.title).click()
|
||||
await expect(overwriteWarning).not.toBeVisible()
|
||||
await expect(newFileWarning).toBeVisible()
|
||||
await confirmButton.click()
|
||||
})
|
||||
|
||||
await test.step(`Ensure we made and opened a new file`, async () => {
|
||||
await expect(codeLocator).toContainText('// ' + sampleOne.title)
|
||||
await expect(newlyCreatedFile(sampleOne.file)).toBeVisible()
|
||||
await expect(projectMenuButton).toContainText(sampleOne.file)
|
||||
})
|
||||
|
||||
await test.step(`Now overwrite the current file`, async () => {
|
||||
await commandBarButton.click()
|
||||
await commandOption.click()
|
||||
await commandSampleOption(sampleTwo.title).click()
|
||||
await commandMethodArgButton.click()
|
||||
await commandMethodOption.click()
|
||||
await expect(commandMethodArgButton).toContainText('overwrite')
|
||||
await expect(newFileWarning).not.toBeVisible()
|
||||
await expect(overwriteWarning).toBeVisible()
|
||||
await confirmButton.click()
|
||||
})
|
||||
|
||||
await test.step(`Ensure we overwrote the current file without navigating`, async () => {
|
||||
await expect(codeLocator).toContainText('// ' + sampleTwo.title)
|
||||
await test.step(`Check actual file contents`, async () => {
|
||||
await expect
|
||||
.poll(async () => {
|
||||
return await fsp.readFile(
|
||||
join(dir, 'bracket', sampleOne.file),
|
||||
'utf-8'
|
||||
)
|
||||
})
|
||||
.toContain('// ' + sampleTwo.title)
|
||||
})
|
||||
await expect(newlyCreatedFile(sampleOne.file)).toBeVisible()
|
||||
await expect(newlyCreatedFile(sampleTwo.file)).not.toBeVisible()
|
||||
await expect(projectMenuButton).toContainText(sampleOne.file)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
})
|
@ -13,9 +13,6 @@ test.afterEach(async ({ page }, testInfo) => {
|
||||
test('Units menu', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const unitsMenuButton = page.getByRole('button', {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "zoo-modeling-app",
|
||||
"version": "0.25.3",
|
||||
"version": "0.25.4",
|
||||
"private": true,
|
||||
"productName": "Zoo Modeling App",
|
||||
"author": {
|
||||
@ -83,6 +83,7 @@
|
||||
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
|
||||
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
|
||||
"fetch:wasm": "./get-latest-wasm-bundle.sh",
|
||||
"fetch:samples": "echo \"Fetching latest KCL samples...\" && curl -o public/kcl-samples-manifest-fallback.json https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/manifest.json",
|
||||
"isomorphic-copy-wasm": "(copy src/wasm-lib/pkg/wasm_lib_bg.wasm public || cp src/wasm-lib/pkg/wasm_lib_bg.wasm public)",
|
||||
"build:wasm-dev": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
|
||||
"build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",
|
||||
@ -90,7 +91,7 @@
|
||||
"wasm-prep": "rimraf src/wasm-lib/pkg && mkdirp src/wasm-lib/pkg && rimraf src/wasm-lib/kcl/bindings",
|
||||
"lint": "eslint --fix src e2e packages/codemirror-lsp-client",
|
||||
"bump-jsons": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
|
||||
"postinstall": "yarn xstate:typegen && ./node_modules/.bin/electron-rebuild",
|
||||
"postinstall": "yarn fetch:samples && yarn xstate:typegen && ./node_modules/.bin/electron-rebuild",
|
||||
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\"",
|
||||
"make:dev": "make dev",
|
||||
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
|
||||
|
152
public/kcl-samples-manifest-fallback.json
Normal file
152
public/kcl-samples-manifest-fallback.json
Normal file
@ -0,0 +1,152 @@
|
||||
[
|
||||
{
|
||||
"file": "80-20-rail.kcl",
|
||||
"title": "80/20 Rail",
|
||||
"description": "An 80/20 extruded aluminum linear rail. T-slot profile adjustable by profile height, rail length, and origin position"
|
||||
},
|
||||
{
|
||||
"file": "a-parametric-bearing-pillow-block.kcl",
|
||||
"title": "A Parametric Bearing Pillow Block",
|
||||
"description": "A bearing pillow block, also known as a plummer block or pillow block bearing, is a pedestal used to provide support for a rotating shaft with the help of compatible bearings and various accessories. Housing a bearing, the pillow block provides a secure and stable foundation that allows the shaft to rotate smoothly within its machinery setup. These components are essential in a wide range of mechanical systems and machinery, playing a key role in reducing friction and supporting radial and axial loads."
|
||||
},
|
||||
{
|
||||
"file": "ball-bearing.kcl",
|
||||
"title": "Ball Bearing",
|
||||
"description": "A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads."
|
||||
},
|
||||
{
|
||||
"file": "bracket.kcl",
|
||||
"title": "Shelf Bracket",
|
||||
"description": "This is a bracket that holds a shelf. It is made of aluminum and is designed to hold a force of 300 lbs. The bracket is 6 inches wide and the force is applied at the end of the shelf, 12 inches from the wall. The bracket has a factor of safety of 1.2. The legs of the bracket are 5 inches and 2 inches long. The thickness of the bracket is calculated from the constraints provided."
|
||||
},
|
||||
{
|
||||
"file": "brake-caliper.kcl",
|
||||
"title": "Brake Caliper",
|
||||
"description": "Brake calipers are used to squeeze the brake pads against the rotor, causing larger and larger amounts of friction depending on how hard the brakes are pressed."
|
||||
},
|
||||
{
|
||||
"file": "car-wheel.kcl",
|
||||
"title": "Car Wheel",
|
||||
"description": "A sports car wheel with a circular lug pattern and spokes."
|
||||
},
|
||||
{
|
||||
"file": "car-wheel-assembly.kcl",
|
||||
"title": "Car Wheel Assembly",
|
||||
"description": "A car wheel assembly with a rotor, tire, and lug nuts."
|
||||
},
|
||||
{
|
||||
"file": "enclosure.kcl",
|
||||
"title": "Enclosure",
|
||||
"description": "An enclosure body and sealing lid for storing items"
|
||||
},
|
||||
{
|
||||
"file": "flange-with-patterns.kcl",
|
||||
"title": "Flange",
|
||||
"description": "A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others."
|
||||
},
|
||||
{
|
||||
"file": "flange-xy.kcl",
|
||||
"title": "Flange with XY coordinates",
|
||||
"description": "A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others."
|
||||
},
|
||||
{
|
||||
"file": "focusrite-scarlett-mounting-bracket.kcl",
|
||||
"title": "A mounting bracket for the Focusrite Scarlett Solo audio interface",
|
||||
"description": "This is a bracket that holds an audio device underneath a desk or shelf. The audio device has dimensions of 144mm wide, 80mm length and 45mm depth with fillets of 6mm. This mounting bracket is designed to be 3D printed with PLA material"
|
||||
},
|
||||
{
|
||||
"file": "french-press.kcl",
|
||||
"title": "French Press",
|
||||
"description": "A french press immersion coffee maker"
|
||||
},
|
||||
{
|
||||
"file": "gear.kcl",
|
||||
"title": "Gear",
|
||||
"description": "A rotating machine part having cut teeth or, in the case of a cogwheel, inserted teeth (called cogs), which mesh with another toothed part to transmit torque. Geared devices can change the speed, torque, and direction of a power source. The two elements that define a gear are its circular shape and the teeth that are integrated into its outer edge, which are designed to fit into the teeth of another gear."
|
||||
},
|
||||
{
|
||||
"file": "gear-rack.kcl",
|
||||
"title": "100mm Gear Rack",
|
||||
"description": "A flat bar or rail that is engraved with teeth along its length. These teeth are designed to mesh with the teeth of a gear, known as a pinion. When the pinion, a small cylindrical gear, rotates, its teeth engage with the teeth on the rack, causing the rack to move linearly. Conversely, linear motion applied to the rack will cause the pinion to rotate."
|
||||
},
|
||||
{
|
||||
"file": "hex-nut.kcl",
|
||||
"title": "Hex nut",
|
||||
"description": "A hex nut is a type of fastener with a threaded hole and a hexagonal outer shape, used in a wide variety of applications to secure parts together. The hexagonal shape allows for a greater torque to be applied with wrenches or tools, making it one of the most common nut types in hardware."
|
||||
},
|
||||
{
|
||||
"file": "kitt.kcl",
|
||||
"title": "Kitt",
|
||||
"description": "The beloved KittyCAD mascot in a voxelized style."
|
||||
},
|
||||
{
|
||||
"file": "lego.kcl",
|
||||
"title": "Lego Brick",
|
||||
"description": "A standard Lego brick. This is a small, plastic construction block toy that can be interlocked with other blocks to build various structures, models, and figures. There are a lot of hacks used in this code."
|
||||
},
|
||||
{
|
||||
"file": "lug-nut.kcl",
|
||||
"title": "Lug Nut",
|
||||
"description": "lug Nuts are essential components used to create secure connections, whether for electrical purposes, like terminating wires or grounding, or for mechanical purposes, such as providing mounting points or reinforcing structural joints."
|
||||
},
|
||||
{
|
||||
"file": "mounting-plate.kcl",
|
||||
"title": "Mounting Plate",
|
||||
"description": "A flat piece of material, often metal or plastic, that serves as a support or base for attaching, securing, or mounting various types of equipment, devices, or components."
|
||||
},
|
||||
{
|
||||
"file": "multi-axis-robot.kcl",
|
||||
"title": "Robot Arm",
|
||||
"description": "A 4 axis robotic arm for industrial use. These machines can be used for assembly, packaging, organization of goods, and quality inspection processes"
|
||||
},
|
||||
{
|
||||
"file": "pipe.kcl",
|
||||
"title": "Pipe",
|
||||
"description": "A tubular section or hollow cylinder, usually but not necessarily of circular cross-section, used mainly to convey substances that can flow."
|
||||
},
|
||||
{
|
||||
"file": "pipe-flange-assembly.kcl",
|
||||
"title": "Pipe and Flange Assembly",
|
||||
"description": "A crucial component in various piping systems, designed to facilitate the connection, disconnection, and access to piping for inspection, cleaning, and modifications. This assembly combines pipes (long cylindrical conduits) with flanges (plate-like fittings) to create a secure yet detachable joint."
|
||||
},
|
||||
{
|
||||
"file": "poopy-shoe.kcl",
|
||||
"title": "Poopy Shoe",
|
||||
"description": "poop shute for bambu labs printer - optimized for printing."
|
||||
},
|
||||
{
|
||||
"file": "router-template-cross-bar.kcl",
|
||||
"title": "Router template for a cross bar",
|
||||
"description": "A guide for routing a notch into a cross bar."
|
||||
},
|
||||
{
|
||||
"file": "router-template-slate.kcl",
|
||||
"title": "Router template for a slate",
|
||||
"description": "A guide for routing a slate for a cross bar."
|
||||
},
|
||||
{
|
||||
"file": "sheet-metal-bracket.kcl",
|
||||
"title": "Sheet Metal Bracket",
|
||||
"description": "A component typically made from flat sheet metal through various manufacturing processes such as bending, punching, cutting, and forming. These brackets are used to support, attach, or mount other hardware components, often providing a structural or functional base for assembly."
|
||||
},
|
||||
{
|
||||
"file": "socket-head-cap-screw.kcl",
|
||||
"title": "Socket Head Cap Screw",
|
||||
"description": "This is for a #10-24 screw that is 1.00 inches long. A socket head cap screw is a type of fastener that is widely used in a variety of applications requiring a high strength fastening solution. It is characterized by its cylindrical head and internal hexagonal drive, which allows for tightening with an Allen wrench or hex key."
|
||||
},
|
||||
{
|
||||
"file": "tire.kcl",
|
||||
"title": "Tire",
|
||||
"description": "A tire is a critical component of a vehicle that provides the necessary traction and grip between the car and the road. It supports the vehicle's weight and absorbs shocks from road irregularities."
|
||||
},
|
||||
{
|
||||
"file": "washer.kcl",
|
||||
"title": "Washer",
|
||||
"description": "A small, typically disk-shaped component with a hole in the middle, used in a wide range of applications, primarily in conjunction with fasteners like bolts and screws. Washers distribute the load of a fastener across a broader area. This is especially important when the fastening surface is soft or uneven, as it helps to prevent damage to the surface and ensures the load is evenly distributed, reducing the risk of the fastener becoming loose over time."
|
||||
},
|
||||
{
|
||||
"file": "wheel-rotor.kcl",
|
||||
"title": "Wheel rotor",
|
||||
"description": "A component of a disc brake system. It provides a surface for brake pads to press against, generating the friction needed to slow or stop the vehicle."
|
||||
}
|
||||
]
|
@ -6,8 +6,8 @@ import { CommandArgument, CommandArgumentOption } from 'lib/commandTypes'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { AnyStateMachine, StateFrom } from 'xstate'
|
||||
|
||||
const contextSelector = (snapshot: StateFrom<AnyStateMachine>) =>
|
||||
snapshot.context
|
||||
const contextSelector = (snapshot: StateFrom<AnyStateMachine> | undefined) =>
|
||||
snapshot?.context
|
||||
|
||||
function CommandArgOptionInput({
|
||||
arg,
|
||||
|
@ -140,7 +140,11 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
||||
JSON.stringify(argValue)
|
||||
)
|
||||
) : (
|
||||
<em>{argValue}</em>
|
||||
<em>
|
||||
{arg.valueSummary
|
||||
? arg.valueSummary(argValue)
|
||||
: argValue}
|
||||
</em>
|
||||
)
|
||||
) : null}
|
||||
</span>
|
||||
|
@ -58,7 +58,17 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
|
||||
|
||||
return (
|
||||
<CommandBarHeader>
|
||||
<p className="px-4">Confirm {selectedCommand?.name}</p>
|
||||
<p className="px-4">
|
||||
{selectedCommand?.reviewMessage ? (
|
||||
selectedCommand.reviewMessage instanceof Function ? (
|
||||
selectedCommand.reviewMessage(commandBarState.context)
|
||||
) : (
|
||||
selectedCommand.reviewMessage
|
||||
)
|
||||
) : (
|
||||
<>Confirm {selectedCommand?.name}</>
|
||||
)}
|
||||
</p>
|
||||
<form
|
||||
id="review-form"
|
||||
className="absolute opacity-0 inset-0 pointer-events-none"
|
||||
|
@ -31,8 +31,8 @@ function getSemanticSelectionType(selectionType: Array<Selection['type']>) {
|
||||
return Array.from(semanticSelectionType)
|
||||
}
|
||||
|
||||
const selectionSelector = (snapshot: StateFrom<typeof modelingMachine>) =>
|
||||
snapshot.context.selectionRanges
|
||||
const selectionSelector = (snapshot?: StateFrom<typeof modelingMachine>) =>
|
||||
snapshot?.context.selectionRanges
|
||||
|
||||
function CommandBarSelectionInput({
|
||||
arg,
|
||||
@ -49,7 +49,7 @@ function CommandBarSelectionInput({
|
||||
const [hasSubmitted, setHasSubmitted] = useState(false)
|
||||
const selection = useSelector(arg.machineActor, selectionSelector)
|
||||
const selectionsByType = useMemo(() => {
|
||||
const selectionRangeEnd = selection.codeBasedSelections[0]?.range[1]
|
||||
const selectionRangeEnd = selection?.codeBasedSelections[0]?.range[1]
|
||||
return !selectionRangeEnd || selectionRangeEnd === code.length
|
||||
? 'none'
|
||||
: getSelectionType(selection)
|
||||
|
16
src/components/CommandBarOverwriteWarning.tsx
Normal file
16
src/components/CommandBarOverwriteWarning.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
interface CommandBarOverwriteWarningProps {
|
||||
heading?: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
export function CommandBarOverwriteWarning({
|
||||
heading = 'Overwrite current file?',
|
||||
message = 'This will permanently replace the current code in the editor.',
|
||||
}: CommandBarOverwriteWarningProps) {
|
||||
return (
|
||||
<>
|
||||
<p className="font-bold text-destroy-60">{heading}</p>
|
||||
<p>{message}</p>
|
||||
</>
|
||||
)
|
||||
}
|
@ -2,7 +2,7 @@ import { useMachine } from '@xstate/react'
|
||||
import { useNavigate, useRouteLoaderData } from 'react-router-dom'
|
||||
import { type IndexLoaderData } from 'lib/types'
|
||||
import { PATHS } from 'lib/paths'
|
||||
import React, { createContext } from 'react'
|
||||
import React, { createContext, useEffect, useMemo } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import {
|
||||
Actor,
|
||||
@ -22,6 +22,12 @@ import {
|
||||
} from 'lib/constants'
|
||||
import { getProjectInfo } from 'lib/desktop'
|
||||
import { getNextDirName, getNextFileName } from 'lib/desktopFS'
|
||||
import { kclCommands } from 'lib/kclCommands'
|
||||
import { codeManager, kclManager } from 'lib/singletons'
|
||||
import {
|
||||
getKclSamplesManifest,
|
||||
KclSamplesManifestItem,
|
||||
} from 'lib/getKclSamplesManifest'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -41,6 +47,16 @@ export const FileMachineProvider = ({
|
||||
const navigate = useNavigate()
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const { project, file } = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
||||
const [kclSamples, setKclSamples] = React.useState<KclSamplesManifestItem[]>(
|
||||
[]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchKclSamples() {
|
||||
setKclSamples(await getKclSamplesManifest())
|
||||
}
|
||||
fetchKclSamples().catch(reportError)
|
||||
}, [])
|
||||
|
||||
const [state, send] = useMachine(
|
||||
fileMachine.provide({
|
||||
@ -121,6 +137,7 @@ export const FileMachineProvider = ({
|
||||
return {
|
||||
message: `Successfully created "${createdName}"`,
|
||||
path: createdPath,
|
||||
shouldSetToRename: input.shouldSetToRename,
|
||||
}
|
||||
}),
|
||||
createFile: fromPromise(async ({ input }) => {
|
||||
@ -271,6 +288,46 @@ export const FileMachineProvider = ({
|
||||
}
|
||||
)
|
||||
|
||||
const kclCommandMemo = useMemo(
|
||||
() =>
|
||||
kclCommands(
|
||||
async (data) => {
|
||||
if (data.method === 'overwrite') {
|
||||
codeManager.updateCodeStateEditor(data.code)
|
||||
await kclManager.executeCode(true)
|
||||
await codeManager.writeToFile()
|
||||
} else if (data.method === 'newFile' && isDesktop()) {
|
||||
send({
|
||||
type: 'Create file',
|
||||
data: {
|
||||
name: data.sampleName,
|
||||
content: data.code,
|
||||
makeDir: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
kclSamples.map((sample) => ({
|
||||
value: sample.file,
|
||||
name: sample.title,
|
||||
}))
|
||||
).filter(
|
||||
(command) => kclSamples.length || command.name !== 'open-kcl-example'
|
||||
),
|
||||
[codeManager, kclManager, send, kclSamples]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
commandBarSend({ type: 'Add commands', data: { commands: kclCommandMemo } })
|
||||
|
||||
return () => {
|
||||
commandBarSend({
|
||||
type: 'Remove commands',
|
||||
data: { commands: kclCommandMemo },
|
||||
})
|
||||
}
|
||||
}, [commandBarSend, kclCommandMemo])
|
||||
|
||||
return (
|
||||
<FileContext.Provider
|
||||
value={{
|
||||
|
@ -393,14 +393,14 @@ export const FileTreeMenu = () => {
|
||||
function createFile() {
|
||||
send({
|
||||
type: 'Create file',
|
||||
data: { name: '', makeDir: false },
|
||||
data: { name: '', makeDir: false, shouldSetToRename: true },
|
||||
})
|
||||
}
|
||||
|
||||
function createFolder() {
|
||||
send({
|
||||
type: 'Create file',
|
||||
data: { name: '', makeDir: true },
|
||||
data: { name: '', makeDir: true, shouldSetToRename: true },
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -9,10 +9,12 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
|
||||
export const KclEditorMenu = ({ children }: PropsWithChildren) => {
|
||||
const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } =
|
||||
useConvertToVariable()
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
@ -77,6 +79,22 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => {
|
||||
</small>
|
||||
</a>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<button
|
||||
onClick={() => {
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: {
|
||||
groupId: 'code',
|
||||
name: 'open-kcl-example',
|
||||
},
|
||||
})
|
||||
}}
|
||||
className={styles.button}
|
||||
>
|
||||
<span>Load a sample model</span>
|
||||
</button>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<a
|
||||
className={styles.button}
|
||||
@ -85,7 +103,7 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => {
|
||||
rel="noopener noreferrer"
|
||||
onClick={openExternalBrowserIfDesktop()}
|
||||
>
|
||||
<span>KCL samples</span>
|
||||
<span>View all samples</span>
|
||||
<small>
|
||||
zoo.dev
|
||||
<FontAwesomeIcon
|
||||
|
@ -3,8 +3,6 @@ import { createContext, useContext, useEffect, useState } from 'react'
|
||||
import { type IndexLoaderData } from 'lib/types'
|
||||
import { useLoaderData } from 'react-router-dom'
|
||||
import { codeManager, kclManager } from 'lib/singletons'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { Command } from 'lib/commandTypes'
|
||||
|
||||
const KclContext = createContext({
|
||||
code: codeManager?.code || '',
|
||||
@ -37,7 +35,6 @@ export function KclContextProvider({
|
||||
const [errors, setErrors] = useState<KCLError[]>([])
|
||||
const [logs, setLogs] = useState<string[]>([])
|
||||
const [wasmInitFailed, setWasmInitFailed] = useState(false)
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
|
||||
useEffect(() => {
|
||||
codeManager.registerCallBacks({
|
||||
@ -53,28 +50,6 @@ export function KclContextProvider({
|
||||
})
|
||||
}, [])
|
||||
|
||||
// Add format code to command palette.
|
||||
useEffect(() => {
|
||||
const commands: Command[] = [
|
||||
{
|
||||
name: 'format-code',
|
||||
displayName: 'Format Code',
|
||||
description: 'Nicely formats the KCL code in the editor.',
|
||||
needsReview: false,
|
||||
groupId: 'code',
|
||||
icon: 'code',
|
||||
onSubmit: (data) => {
|
||||
kclManager.format()
|
||||
},
|
||||
},
|
||||
]
|
||||
commandBarSend({ type: 'Add commands', data: { commands } })
|
||||
|
||||
return () => {
|
||||
commandBarSend({ type: 'Remove commands', data: { commands } })
|
||||
}
|
||||
}, [kclManager, commandBarSend])
|
||||
|
||||
return (
|
||||
<KclContext.Provider
|
||||
value={{
|
||||
|
@ -4,6 +4,7 @@ import { Actor, AnyStateMachine, ContextFrom, EventFrom } from 'xstate'
|
||||
import { Selection } from './selections'
|
||||
import { Identifier, Expr, VariableDeclaration } from 'lang/wasm'
|
||||
import { commandBarMachine } from 'machines/commandBarMachine'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
type Icon = CustomIconName
|
||||
const PLATFORMS = ['both', 'web', 'desktop'] as const
|
||||
@ -67,6 +68,12 @@ export type Command<
|
||||
name: CommandName
|
||||
groupId: T['id']
|
||||
needsReview: boolean
|
||||
reviewMessage?:
|
||||
| string
|
||||
| ReactNode
|
||||
| ((
|
||||
commandBarContext: { argumentsToSubmit: Record<string, unknown> } // Should be the commandbarMachine's context, but it creates a circular dependency
|
||||
) => string | ReactNode)
|
||||
onSubmit: (data?: CommandSchema) => void
|
||||
onCancel?: () => void
|
||||
args?: {
|
||||
@ -181,7 +188,7 @@ export type CommandArgument<
|
||||
machineContext?: ContextFrom<T>
|
||||
) => boolean)
|
||||
skip?: boolean
|
||||
machineActor: Actor<T>
|
||||
machineActor?: Actor<T>
|
||||
/** For showing a summary display of the current value, such as in
|
||||
* the command bar's header
|
||||
*/
|
||||
|
@ -95,3 +95,10 @@ export const MAKE_TOAST_MESSAGES = {
|
||||
ERROR_STARTING_PRINT: 'Error while starting print',
|
||||
SUCCESS: 'Started print successfully',
|
||||
}
|
||||
|
||||
/** The URL for the KCL samples manifest files */
|
||||
export const KCL_SAMPLES_MANIFEST_URLS = {
|
||||
remote:
|
||||
'https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/manifst.json',
|
||||
localFallback: '/kcl-samples-manifest-fallback.json',
|
||||
} as const
|
||||
|
31
src/lib/getKclSamplesManifest.ts
Normal file
31
src/lib/getKclSamplesManifest.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { KCL_SAMPLES_MANIFEST_URLS } from './constants'
|
||||
import { isDesktop } from './isDesktop'
|
||||
|
||||
export type KclSamplesManifestItem = {
|
||||
file: string
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export async function getKclSamplesManifest() {
|
||||
let response = await fetch(KCL_SAMPLES_MANIFEST_URLS.remote)
|
||||
if (!response.ok) {
|
||||
console.warn(
|
||||
'Failed to fetch latest remote KCL samples manifest, falling back to local:',
|
||||
response.statusText
|
||||
)
|
||||
response = await fetch(
|
||||
(isDesktop() ? '.' : '') + KCL_SAMPLES_MANIFEST_URLS.localFallback
|
||||
)
|
||||
if (!response.ok) {
|
||||
console.error(
|
||||
'Failed to fetch fallback KCL samples manifest:',
|
||||
response.statusText
|
||||
)
|
||||
return []
|
||||
}
|
||||
}
|
||||
return response.json().then((manifest) => {
|
||||
return manifest as KclSamplesManifestItem[]
|
||||
})
|
||||
}
|
110
src/lib/kclCommands.ts
Normal file
110
src/lib/kclCommands.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import { CommandBarOverwriteWarning } from 'components/CommandBarOverwriteWarning'
|
||||
import { Command, CommandArgumentOption } from './commandTypes'
|
||||
import { kclManager } from './singletons'
|
||||
import { isDesktop } from './isDesktop'
|
||||
import { FILE_EXT } from './constants'
|
||||
|
||||
interface OnSubmitProps {
|
||||
sampleName: string
|
||||
code: string
|
||||
method: 'overwrite' | 'newFile'
|
||||
}
|
||||
|
||||
export function kclCommands(
|
||||
onSubmit: (p: OnSubmitProps) => Promise<void>,
|
||||
providedOptions: CommandArgumentOption<string>[]
|
||||
): Command[] {
|
||||
return [
|
||||
{
|
||||
name: 'format-code',
|
||||
displayName: 'Format Code',
|
||||
description: 'Nicely formats the KCL code in the editor.',
|
||||
needsReview: false,
|
||||
groupId: 'code',
|
||||
icon: 'code',
|
||||
onSubmit: () => {
|
||||
kclManager.format()
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'open-kcl-example',
|
||||
displayName: 'Open sample',
|
||||
description: 'Imports an example KCL program into the editor.',
|
||||
needsReview: true,
|
||||
icon: 'code',
|
||||
reviewMessage: ({ argumentsToSubmit }) =>
|
||||
argumentsToSubmit.method === 'newFile'
|
||||
? 'Create a new file with the example code?'
|
||||
: CommandBarOverwriteWarning({}),
|
||||
groupId: 'code',
|
||||
onSubmit(data) {
|
||||
if (!data?.sample) {
|
||||
return
|
||||
}
|
||||
const sampleCodeUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent(
|
||||
data.sample.replace(FILE_EXT, '')
|
||||
)}/${encodeURIComponent(data.sample)}`
|
||||
fetch(sampleCodeUrl)
|
||||
.then(async (response) => {
|
||||
if (!response.ok) {
|
||||
console.error('Failed to fetch sample code:', response.statusText)
|
||||
return
|
||||
}
|
||||
const code = await response.text()
|
||||
|
||||
return {
|
||||
sampleName: data.sample,
|
||||
code,
|
||||
method: data.method,
|
||||
}
|
||||
})
|
||||
.then((props) => {
|
||||
if (props?.code) {
|
||||
onSubmit(props).catch(reportError)
|
||||
}
|
||||
})
|
||||
.catch(reportError)
|
||||
},
|
||||
args: {
|
||||
method: {
|
||||
inputType: 'options',
|
||||
required: true,
|
||||
skip: true,
|
||||
defaultValue: isDesktop() ? 'newFile' : 'overwrite',
|
||||
options() {
|
||||
return [
|
||||
{
|
||||
value: 'overwrite',
|
||||
name: 'Overwrite current code',
|
||||
isCurrent: !isDesktop(),
|
||||
},
|
||||
...(isDesktop()
|
||||
? [
|
||||
{
|
||||
value: 'newFile',
|
||||
name: 'Create a new file',
|
||||
isCurrent: true,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]
|
||||
},
|
||||
},
|
||||
sample: {
|
||||
inputType: 'options',
|
||||
required: true,
|
||||
valueSummary(value) {
|
||||
const MAX_LENGTH = 12
|
||||
if (typeof value === 'string') {
|
||||
return value.length > MAX_LENGTH
|
||||
? value.substring(0, MAX_LENGTH) + '...'
|
||||
: value
|
||||
}
|
||||
return value
|
||||
},
|
||||
options: providedOptions,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
34
src/lib/kclSamplesArray.json
Normal file
34
src/lib/kclSamplesArray.json
Normal file
@ -0,0 +1,34 @@
|
||||
[
|
||||
"80-20-rail",
|
||||
"a-parametric-bearing-pillow-block",
|
||||
"ball-bearing",
|
||||
"bracket",
|
||||
"brake-caliper",
|
||||
"car-wheel-assembly",
|
||||
"car-wheel",
|
||||
"enclosure",
|
||||
"flange-with-patterns",
|
||||
"flange-xy",
|
||||
"focusrite-scarlett-mounting-bracket",
|
||||
"french-press",
|
||||
"gear-rack",
|
||||
"gear",
|
||||
"hex-nut",
|
||||
"kitt",
|
||||
"lego",
|
||||
"lug-nut",
|
||||
"mounting-plate",
|
||||
"multi-axis-robot",
|
||||
"pipe-flange-assembly",
|
||||
"pipe",
|
||||
"poopy-shoe",
|
||||
"router-template-cross-bar",
|
||||
"router-template-slate",
|
||||
"screenshots",
|
||||
"sheet-metal-bracket",
|
||||
"socket-head-cap-screw",
|
||||
"step",
|
||||
"tire",
|
||||
"washer",
|
||||
"wheel-rotor"
|
||||
]
|
@ -469,8 +469,9 @@ export type ResolvedSelectionType = [Selection['type'] | 'other', number]
|
||||
* @returns
|
||||
*/
|
||||
export function getSelectionType(
|
||||
selection: Selections
|
||||
selection?: Selections
|
||||
): ResolvedSelectionType[] {
|
||||
if (!selection) return []
|
||||
const extrudableCount = selection.codeBasedSelections.filter((_, i) => {
|
||||
const singleSelection = {
|
||||
...selection,
|
||||
@ -485,7 +486,7 @@ export function getSelectionType(
|
||||
}
|
||||
|
||||
export function getSelectionTypeDisplayText(
|
||||
selection: Selections
|
||||
selection?: Selections
|
||||
): string | null {
|
||||
const selectionsByType = getSelectionType(selection)
|
||||
|
||||
|
@ -283,7 +283,7 @@ export const commandBarMachine = setup({
|
||||
typeof argConfig.options === 'function'
|
||||
? argConfig.options(
|
||||
input,
|
||||
argConfig.machineActor.getSnapshot().context
|
||||
argConfig.machineActor?.getSnapshot().context
|
||||
)
|
||||
: argConfig.options
|
||||
).some((o) => o.value === argValue)
|
||||
|
@ -20,6 +20,7 @@ type FileMachineEvents =
|
||||
makeDir: boolean
|
||||
content?: string
|
||||
silent?: boolean
|
||||
shouldSetToRename?: boolean
|
||||
}
|
||||
}
|
||||
| { type: 'Delete file'; data: FileEntry }
|
||||
@ -42,6 +43,7 @@ type FileMachineEvents =
|
||||
output: {
|
||||
message: string
|
||||
path: string
|
||||
shouldSetToRename: boolean
|
||||
}
|
||||
}
|
||||
| {
|
||||
@ -107,6 +109,10 @@ export const fileMachine = setup({
|
||||
},
|
||||
'Is not silent': ({ event }) =>
|
||||
event.type === 'Create file' ? !event.data.silent : false,
|
||||
'Should set to rename': ({ event }) =>
|
||||
(event.type === 'xstate.done.actor.create-and-open-file' &&
|
||||
event.output.shouldSetToRename) ||
|
||||
false,
|
||||
},
|
||||
actors: {
|
||||
readFiles: fromPromise(({ input }: { input: Project }) =>
|
||||
@ -119,6 +125,7 @@ export const fileMachine = setup({
|
||||
makeDir: boolean
|
||||
selectedDirectory: FileEntry
|
||||
content: string
|
||||
shouldSetToRename: boolean
|
||||
}
|
||||
}) => Promise.resolve({ message: '', path: '' })
|
||||
),
|
||||
@ -149,7 +156,7 @@ export const fileMachine = setup({
|
||||
),
|
||||
},
|
||||
}).createMachine({
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QDECWAbMACAtgQwGMALVAOzAGI9ZZUpSBtABgF1FQAHAe1oBdUupdiAAeiACwAmADQgAnogAcANgCsAOnHiAjOICcAZh3K9TRQHYAvpdlpMuQiXIUASmABmAJzhFmbJCDcfAJCAWIIUrIKCHpq6nraipJJKorahqrWthjY+MRkYOoAEtRYpFxY7jmwFADC3ni82FWYfsJBqPyCwuG6hurKTAYG5mlSyeJRiHqS2prKg6Mj2pKz4lkgdrmOBcWlLXCuYKR4OM05bQEdXaGgvdpD6qPiioqqieJM2gaqUxELT3MQz0eleBlMhmUGy2Dny5D2sEq1TqDSaSNarHaPE6IR6iD6BgGQxGY1Wikm8gkQM0n2UknERm0qmUw2hOVhTkKJURBxq9TAjXOrW0-k42JueIQBKJw1GujJFOiqkkTE0X3M5mUuiYgxmbPseU5CPRhwAImBMGiDpcxcFumF8dptMp1GZRlqNYYdXo-ml1IlzMkHuZVAZJAY1PrtnCuftkQB5DjHE02wLi3EOqX6QmDWWkiZ-HSKf3gph6ZWqcwJIZRjm7bkmmoAZTAvCwsAtYAITQgWAgqG83a4njkqeuGbu+MkLPU7x+iiGszJfwj4ldTvB6UURh0klrht2-MaZCgWDwpF7XCTpBPJooEEEhTIADcuABrQoEVFgAC054gP5XscP7WpiVzpvak5SnO6hJD8RYfJ8ir4kw06zqoTDiMyTA4WGPz7js8JHvwpCnv+WBATepF3mAnieMO6gcOgjTuMOODqF+ApNH+F6AdeIEXGBto4pBoj4u8GjOiMqjiKMqhJFhfyVqqEbJIoCTkmk3wETG6huCcOC3gc96PuoL7voU3gGb+oGimmdq3GJCARuY8RMroEk6MMihKeWrpYepEZMKMSw6Ua+mnEZOQULR9GeIxzG8KxnjsVZpw2YJdnjqJ4QjK59KaoGLKhh6fyBpIsFgtqKjKuCYW7OalpRZgJnwuZH7qBAnbcbZWIOZKeXqAVyhFT8EbaOYK44f6kjlhYG6FVYNibOyB7wo1rbNZQsUMUxLFsZ13UZRiWUQY5uUakNskjdOY2lZSCAqhV24LlhHpMl89Xwm4eD9tRvKtU+pCvh1DQAbyY5nZKMwqZWwxqMorzltoZUrK6YbOlJoazIoX2FD9f2ngDD5tcDFnqGDAmYLADAin1InndMKrqD85jw8ySPvH8pgulqoYWEjc16HjekCoTjYxXRu2JclqVi1TcCQ-1mYwyzcMRhz6lcw9C56Cz4Yatd05ISLxFbYDZlkx1nGCgrSsM5KTJVgMMmjKkEYmAYfwrOkQ30i8WFSF8mTLTCa2FGb-3RTt8V7UlB02z1mX0xKmZMgu8R6C8YahqYwUow9TqBkNxXiLmUgGEyIsRYZUctSTQMg5ZxzpXbdPgcrUEuW57xYUyXkGD5D2Bhog9aKsLyzQywsbOUXXwAEYeEWAKcTk5P7KH8G+ujhuHDDJTJZ0t2QGsvxrlI2q85fiBhlgMZcQq8+iqDJ3OzAML2qCCqxDEkIsNryK+jMpSV1clIck3xB6ViLIWEwmhXiJF0EYIqptUS3nIpRLaQDHajAqvKCwqxEZTxkIXVChJNTqUDCkB4L9q4t1rkTHI2DMyRAeosIawxFxDESMoLCIsNokUYZgZhUF1IDGdK7LyWgX6TULqCIagYcKSHMAya6VdQ6rTPgTLaC9hKpygnSOY8FA7kj0J6WR0QISzn0J8IYN0tIi0TMcLBHcHZp1wf6cB5UiFZxIdEcEhJKyvQ9BqGSqCuIuL0WvXoHj8HeKSL472E0KrBRfrVRGL9cbWEsEAA */
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QDECWAbMACAtgQwGMALVAOzAGI9ZZUpSBtABgF1FQAHAe1oBdUupdiAAeiACwAmADQgAnogAcANgCsAOnHiAjOICcAZh3K9TRQHYAvpdlpMuQiXIUASmABmAJzhFmbJCDcfAJCAWIIUrIKCHpq6nraipJJKorahqrWthjY+MRkYOoAEtRYpFxY7jmwFADC3ni82FWYfsJBqPyCwuG6hurKTAYG5mlSyeJRiHqS2prKg6Mj2pKz4lkgdrmOBcWlLXCuYKR4OM05bQEdXaGgvdpD6qPiioqqieJM2gaqUxELT3MQz0eleBlMhmUGy2Dny5D2sEq1TqDSaSNarHaPE6IR6iF0ij08SGklUpikym0qkm8gkJie1KYklB70UBkkTChNk2OVhTkKJURBxq9TAjXOrW0-k42JueIQfQMAyGIzGq0UNOiqg5mi+5nMlM+gxm0N5eX5CPRhwAImBMGiDpcZcFumF8dptMp1GZRpT9YZOXo-ml1IlzMkHuZVOyDGpTfZzbtBVaagB5DjHK1OwKy3FuhX6JWDYajXTqzUSRKh8FMPTa1TmBJDePbOEC-bIgDKYF4WFgdrABCaECwEFQ3iHXE8cmz1zzd3xkmUSveP0UJJWyT+sfE3o94PSbK0KxbfN2osaZCgWDwpBHXAzpCvVooEEEhTIADcuABrQoEVEwAAWlvCAgIfY4gMdTErlzV0FwVH5dx3ZdtUSA9lD+B4qW9EkmE5ZkEnMAxT0TeEL34Uhr1ArAIKfKiXzfeEv1-f9AJAu9wMfKCLilLEXVuURpmUcw91JakDAsLQRiwplFHUIxlDeUxZlGRRSJ2cjUWfGi6OfA4KDATxPCndQOHQRp3CnHB1AAsUmg4sC6J4jFpRzAT5SpHDPRGalRlUJJxF+WkEAbJgFOUZJCQ+NJvg0tt1DcE4cH0nJX3fdQWL-dRvGS4DoLcud4KEhBY1EhJ3iCqkdGGRQ-jJDQmCCwlYyYUYlnii0ktOVLMHS5jSG-bLctOfLeMKuDBPCMr4i8qrqW+SS-nDDRJK0VYXmZcRwU63ZupShiDKMkzPDMizeCszwbJGs4XLAWdJvlEZRMkcQDXDVDY20cxltWdRXjZXQzDUSQdu5GEyMKW17V6ygmI-QbWPUCABwcgr+JxYrpv1dRXvepcfi+n6QoMfDQ2ZALzH3d6rHBs1NKh1HYcM4zTPMyzrOR1GxtcjG5XzZ7cbekSCejP0-g5SR-skprVD9Kkvl2+E3DwMdDuReHMsR4axTA4UHo8-MZnCn5iNjOXXjrbRlpWb12U9Hzo1mdS6YTBnEt12Gak1rLCgaPXqgYPjYMNhDjYUhthjUJTCXeP5TC9SlowsS260JJXChVtXr2FFmTrOjmrpy3W7tgA3Mam6YdVNqOLdj62QvXIkY31YWl0+dZXdbC0KOZn3tbY+yefumDnQrzyGyJNQ3teJTYxMAwsNmIkNpeIKpC+TIu7PLT7OZ462fOy6bLs8U7vL-mEKpdd4j0F52WjUw2ob6IPXDXHUPEYspAMKlrG5coKN4ABAhgzPm84SpAUwiFKBuF8L7iZGoEEPwM6WnKCmcBWN8Skynl-CErx9CqGpPHWYAw2TKRmBySSkhUHJmFJgyuiFvqaAmItN45gdB1RCngzQrxEi6CMB9VBvcGK6UfLDBhnlRhSzLBYVYSktoyBCg8UGTwlJ6HDCkB4RDUH7QkSHce+ZIghUWLjYYeFf4qCCqg6GPZ9Fj0viVQkAxPR+RqloIhxNX6glxuGfCkgOGCKTroz26tMDAIcRA8IkU5hIXXhqDRjYvHTFrOoakd98JlQjNoVB6Zjj2PcoYq+0jQxSDkUuJId8lHRHBCuUYTU-T6mpMI7SYSwCSPzN9JIpTkjhgqYorC30pZtSIdqWMbwAr-0sEAA */
|
||||
id: 'File machine',
|
||||
|
||||
initial: 'Reading files',
|
||||
@ -222,15 +229,41 @@ export const fileMachine = setup({
|
||||
makeDir: false,
|
||||
selectedDirectory: context.selectedDirectory,
|
||||
content: '',
|
||||
shouldSetToRename: false,
|
||||
}
|
||||
return {
|
||||
name: event.data.name,
|
||||
makeDir: event.data.makeDir,
|
||||
selectedDirectory: context.selectedDirectory,
|
||||
content: event.data.content ?? '',
|
||||
shouldSetToRename: event.data.shouldSetToRename ?? false,
|
||||
}
|
||||
},
|
||||
onDone: [
|
||||
{
|
||||
target: 'Reading files',
|
||||
|
||||
actions: [
|
||||
{
|
||||
type: 'createToastSuccess',
|
||||
params: ({
|
||||
event,
|
||||
}: {
|
||||
// TODO: rely on type inference
|
||||
event: Extract<
|
||||
FileMachineEvents,
|
||||
{ type: 'xstate.done.actor.create-and-open-file' }
|
||||
>
|
||||
}) => {
|
||||
return { message: event.output.message }
|
||||
},
|
||||
},
|
||||
'addFileToRenamingQueue',
|
||||
'navigateToFile',
|
||||
],
|
||||
|
||||
guard: 'Should set to rename',
|
||||
},
|
||||
{
|
||||
target: 'Reading files',
|
||||
actions: [
|
||||
@ -248,7 +281,6 @@ export const fileMachine = setup({
|
||||
return { message: event.output.message }
|
||||
},
|
||||
},
|
||||
'addFileToRenamingQueue',
|
||||
'navigateToFile',
|
||||
],
|
||||
},
|
||||
|
41
src/wasm-lib/Cargo.lock
generated
41
src/wasm-lib/Cargo.lock
generated
@ -388,9 +388,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.17"
|
||||
version = "4.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac"
|
||||
checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@ -398,9 +398,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.17"
|
||||
version = "4.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73"
|
||||
checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
@ -408,9 +408,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.13"
|
||||
version = "4.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
|
||||
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
@ -1400,7 +1400,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-lib"
|
||||
version = "0.2.17"
|
||||
version = "0.2.18"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"approx 0.5.1",
|
||||
@ -1526,9 +1526,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad-modeling-cmds"
|
||||
version = "0.2.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5128ba683e849388cac4214b65911c343842d559bec10827575c840a47733786"
|
||||
version = "0.2.62"
|
||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=achalmers/no-more-jsonschema#a253330a9cd656afbc5ee04490acca5ea5337af0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@ -1552,8 +1551,18 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "kittycad-modeling-cmds-macros"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0cdc505a33bfffb87c317435ec41ced8f73474217cf30db685e479bf289757e"
|
||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=achalmers/no-more-jsonschema#a253330a9cd656afbc5ee04490acca5ea5337af0"
|
||||
dependencies = [
|
||||
"kittycad-modeling-cmds-macros-impl",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kittycad-modeling-cmds-macros-impl"
|
||||
version = "0.1.9"
|
||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=achalmers/no-more-jsonschema#a253330a9cd656afbc5ee04490acca5ea5337af0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -3079,18 +3088,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.63"
|
||||
version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
|
||||
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.63"
|
||||
version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -73,7 +73,7 @@ members = [
|
||||
http = "0.2.12"
|
||||
kittycad = { version = "0.3.20", default-features = false, features = ["js", "requests"] }
|
||||
kittycad-modeling-session = "0.1.4"
|
||||
kittycad-modeling-cmds = { version = "0.2.61", features = ["websocket"] }
|
||||
kittycad-modeling-cmds = { version = "0.2.62", features = ["websocket"] }
|
||||
|
||||
[[test]]
|
||||
name = "executor"
|
||||
@ -84,6 +84,6 @@ name = "modify"
|
||||
path = "tests/modify/main.rs"
|
||||
|
||||
# Example: how to point modeling-api at a different repo (e.g. a branch or a local clone)
|
||||
#[patch."https://github.com/KittyCAD/modeling-api"]
|
||||
#kittycad-modeling-cmds = { path = "../../../modeling-api/modeling-cmds" }
|
||||
#kittycad-modeling-session = { path = "../../../modeling-api/modeling-session" }
|
||||
[patch.crates-io]
|
||||
kittycad-modeling-cmds = { git = "https://github.com/KittyCAD/modeling-api", branch = "achalmers/no-more-jsonschema" }
|
||||
|
||||
|
@ -16,7 +16,7 @@ async-recursion = "1.1.1"
|
||||
async-trait = "0.1.82"
|
||||
base64 = "0.22.1"
|
||||
chrono = "0.4.38"
|
||||
clap = { version = "4.5.17", default-features = false, optional = true, features = ["std", "derive"] }
|
||||
clap = { version = "4.5.18", default-features = false, optional = true, features = ["std", "derive"] }
|
||||
convert_case = "0.6.0"
|
||||
dashmap = "6.1.0"
|
||||
databake = { version = "0.1.8", features = ["derive"] }
|
||||
@ -42,7 +42,7 @@ serde = { version = "1.0.210", features = ["derive"] }
|
||||
serde_json = "1.0.128"
|
||||
sha2 = "0.10.8"
|
||||
tabled = { version = "0.15.0", optional = true }
|
||||
thiserror = "1.0.63"
|
||||
thiserror = "1.0.64"
|
||||
toml = "0.8.19"
|
||||
ts-rs = { version = "10.0.0", features = ["uuid-impl", "url-impl", "chrono-impl", "no-serde-warnings", "serde-json-impl"] }
|
||||
url = { version = "2.5.2", features = ["serde"] }
|
||||
|
Reference in New Issue
Block a user