[Feature]: Load external model becomes Add file to project, global application add file to project with home page update. (#6506)
* chore: saving off skeleton * fix: saving skeleton * chore: skeleton for loading projects from project directory path * chore: cleaning up useless state transition to be an on event direct to action state * fix: new structure for web vs desktop vs react machine provider code * chore: saving off skeleton * fix: skeleton logic for react? going to move it from a string to obj.string * fix: trying to prevent error element unmount on global react components. This is bricking JS state * fix: we are so back * chore: implemented navigating to specfic KCL file * chore: implementing renaming project * chore: deleting project * fix: auto fixes * fix: old debug/testing file oops * chore: generic create new file * chore: skeleton for web create file provide * chore: basic machine vitest... need to figure out how to get window.electron implemented in vitest? * chore: save off progress before deleting other project implementation, a few missing features still * chore: trying a different init skeleton? most likely will migrate * chore: first attempt of purging projects context provider * chore: enabling toast for some machine state * chore: enabling more toast success and error * chore: writing read write state to the system io based on the project path * fix: tsc fixes * fix: use file system watcher, navigate to project after creation via the requestProjectName * chore: open project command, hooks vs snapshot context helpers * chore: implemented open and create project for e2e testing. They are hard coded in poor spot for now. * fix: codespell fixes * chore: implementing more project commands * chore: PR improvements for root.tsx * chore: leaving comment about new Router.tsx layout * fix: removing debugging code * fix: rewriting component for readability * fix: improving web initialization * chore: implementing import file from url which is not actually that? * fix: clearing search params on import file from url * fix: fixed two e2e tests, forgot needsReview when making new command * fix: fixing some import from url business logic to pass e2e tests * chore: script for diffing circular deps +/- * fix: formatting * fix: massive fix for circular depsga! * fix: trying to fix some errors and auto fmt * fix: updating deps * fix: removing debugging code * fix: big clean up * fix: more deletion * fix: tsc cleanup * fix: TSC TSC TSC TSC! * fix: typo fix * fix: clear query params on web only, desktop not required * fix: removing unused code * fmt * Bring back `trap` removed in merge * Use explicit types instead of `any`s on arg configs * Add project commands directly to command palette * fix: deleting debugging code, from PR review * fix: this got added back(?) * fix: using referred type * fix: more PR clean up * fix: big block comment for xstate architecture decision * fix: more pr comment fixes * fix: saving off logic, need a big cleanup because I hacked it together to get a POC * fix: extra business? * fix: merge conflict just added them back why dude * fix: more PR comments * fix: big ciruclar deps fix, commandBarActor in appActor * chore: writing e2e test, still need to fix 3 bugs * chore: adding more scenarios * fix: formatting * fix: fixing tsc errors * chore: deleting the old text to cad and using the new application level one, almost there * fix: prompt to edit works * fix: large push to get 1 text to cad command... the usage is a little buggy with delete and navigate within /file * fix: settings for highlight edges now works * chore: adding another e2e test * fix: cleaning up e2e tests and writing more of them * fix: tsc type * chore: more e2e improvements, unique project name in text to cad * chore: e2e tests should be good to go * fix: gotcha comment * fix: enabled web t2c, codespell fixes * fix: fixing merge conflcits?? * feat: implemented load external for kcl samples * feat: load external model from disk * fix: trying to delete old stuff * fix: all command trigger locations now have defaults for current project * fix: gotcha comment for the future * chore: hiding import file from url command, two separate commands for 3d and kcl file adding * chore: commands are now add file to project, 3rd iteration * fix: t2c in file menu fixed * chore: updating file menu for new global actions * fix: auto fixes * fix: the command bar arg flow for web add kcl file seems backwards? * chore: updated home layout, added create from kcl sample button * chore: remapping some menu actions * fix: fixing open dialog copy * fix: an e2e test * fix: fixed e2e tests * fix: fixed e2e tests * fix: auto fixes * fix: pr clean up * fix: removing console log * fix: PR updates * fix: the reviewed stage boolean required the expected state to change. Also I progressed the command bar too soon * fix: no idea how this passed locally yesterday? I removed the {dir} unused but I need the function's logic but not the return value... * fix: should be good to go? --------- Co-authored-by: Frank Noirot <frankjohnson1993@gmail.com>
This commit is contained in:
@ -73,7 +73,7 @@ export class ToolbarFixture {
|
|||||||
this.fileTreeBtn = page.locator('[id="files-button-holder"]')
|
this.fileTreeBtn = page.locator('[id="files-button-holder"]')
|
||||||
this.createFileBtn = page.getByTestId('create-file-button')
|
this.createFileBtn = page.getByTestId('create-file-button')
|
||||||
this.treeInputField = page.getByTestId('tree-input-field')
|
this.treeInputField = page.getByTestId('tree-input-field')
|
||||||
this.loadButton = page.getByTestId('load-external-model-pane-button')
|
this.loadButton = page.getByTestId('add-file-to-project-pane-button')
|
||||||
|
|
||||||
this.filePane = page.locator('#files-pane')
|
this.filePane = page.locator('#files-pane')
|
||||||
this.featureTreePane = page.locator('#feature-tree-pane')
|
this.featureTreePane = page.locator('#feature-tree-pane')
|
||||||
|
@ -550,7 +550,7 @@ test.describe(
|
|||||||
const expected = 'Open project'
|
const expected = 'Open project'
|
||||||
expect(actual).toBe(expected)
|
expect(actual).toBe(expected)
|
||||||
})
|
})
|
||||||
test('Modeling.File.Load external model', async ({
|
test('Modeling.File.Add file to project', async ({
|
||||||
tronApp,
|
tronApp,
|
||||||
cmdBar,
|
cmdBar,
|
||||||
page,
|
page,
|
||||||
@ -571,10 +571,10 @@ test.describe(
|
|||||||
throw new Error('app or app.applicationMenu is missing')
|
throw new Error('app or app.applicationMenu is missing')
|
||||||
}
|
}
|
||||||
const openProject = app.applicationMenu.getMenuItemById(
|
const openProject = app.applicationMenu.getMenuItemById(
|
||||||
'File.Load external model'
|
'File.Add file to project'
|
||||||
)
|
)
|
||||||
if (!openProject) {
|
if (!openProject) {
|
||||||
throw new Error('File.Load external model')
|
throw new Error('File.Add file to project')
|
||||||
}
|
}
|
||||||
openProject.click()
|
openProject.click()
|
||||||
})
|
})
|
||||||
@ -584,7 +584,7 @@ test.describe(
|
|||||||
const actual = await cmdBar.cmdBarElement
|
const actual = await cmdBar.cmdBarElement
|
||||||
.getByTestId('command-name')
|
.getByTestId('command-name')
|
||||||
.textContent()
|
.textContent()
|
||||||
const expected = 'Load external model'
|
const expected = 'Add file to project'
|
||||||
expect(actual).toBe(expected)
|
expect(actual).toBe(expected)
|
||||||
})
|
})
|
||||||
test('Modeling.File.Export current part', async ({
|
test('Modeling.File.Export current part', async ({
|
||||||
|
@ -23,7 +23,6 @@ test.describe('Testing loading external models', () => {
|
|||||||
'Web: should overwrite current code, cannot create new file',
|
'Web: should overwrite current code, cannot create new file',
|
||||||
async ({ editor, context, page, homePage }) => {
|
async ({ editor, context, page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
await test.step(`Test setup`, async () => {
|
await test.step(`Test setup`, async () => {
|
||||||
await context.addInitScript((code) => {
|
await context.addInitScript((code) => {
|
||||||
window.localStorage.setItem('persistCode', code)
|
window.localStorage.setItem('persistCode', code)
|
||||||
@ -82,12 +81,13 @@ test.describe('Testing loading external models', () => {
|
|||||||
* "gear-rack": https://github.com/KittyCAD/kcl-samples/blob/main/gear-rack/main.kcl
|
* "gear-rack": https://github.com/KittyCAD/kcl-samples/blob/main/gear-rack/main.kcl
|
||||||
*/
|
*/
|
||||||
test(
|
test(
|
||||||
'Desktop: should create new file by default, optionally overwrite',
|
'Desktop: should create new file by default, creates a second file with automatic unique name',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ editor, context, page, scene, cmdBar, toolbar }) => {
|
async ({ editor, context, page, scene, cmdBar, toolbar }) => {
|
||||||
if (runningOnWindows()) {
|
if (runningOnWindows()) {
|
||||||
}
|
}
|
||||||
const { dir } = await context.folderSetupFn(async (dir) => {
|
|
||||||
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = join(dir, 'bracket')
|
const bracketDir = join(dir, 'bracket')
|
||||||
await fsp.mkdir(bracketDir, { recursive: true })
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
await fsp.writeFile(join(bracketDir, 'main.kcl'), bracket, {
|
await fsp.writeFile(join(bracketDir, 'main.kcl'), bracket, {
|
||||||
@ -100,37 +100,28 @@ test.describe('Testing loading external models', () => {
|
|||||||
const sampleOne = {
|
const sampleOne = {
|
||||||
file: 'parametric-bearing-pillow-block' + FILE_EXT,
|
file: 'parametric-bearing-pillow-block' + FILE_EXT,
|
||||||
title: 'Parametric Bearing Pillow Block',
|
title: 'Parametric Bearing Pillow Block',
|
||||||
}
|
file1: 'parametric-bearing-pillow-block-1' + FILE_EXT,
|
||||||
const sampleTwo = {
|
|
||||||
file: 'gear-rack' + FILE_EXT,
|
|
||||||
title: '100mm Gear Rack',
|
|
||||||
}
|
}
|
||||||
const projectCard = page.getByRole('link', { name: 'bracket' })
|
const projectCard = page.getByRole('link', { name: 'bracket' })
|
||||||
const commandMethodArgButton = page.getByRole('button', {
|
|
||||||
name: 'Method',
|
|
||||||
})
|
|
||||||
const commandMethodOption = page.getByRole('option', {
|
|
||||||
name: 'Overwrite',
|
|
||||||
})
|
|
||||||
const overwriteWarning = page.getByText(
|
const overwriteWarning = page.getByText(
|
||||||
'Overwrite current file with sample?'
|
'Overwrite current file with sample?'
|
||||||
)
|
)
|
||||||
const confirmButton = page.getByRole('button', { name: 'Submit command' })
|
|
||||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||||
const newlyCreatedFile = (name: string) =>
|
const newlyCreatedFile = (name: string) =>
|
||||||
page.getByRole('listitem').filter({
|
page.getByRole('listitem').filter({
|
||||||
has: page.getByRole('button', { name }),
|
has: page.getByRole('button', { name }),
|
||||||
})
|
})
|
||||||
const defaultLoadCmdBarState: CmdBarSerialised = {
|
const defaultLoadCmdBarState: CmdBarSerialised = {
|
||||||
commandName: 'Load external model',
|
commandName: 'Add file to project',
|
||||||
currentArgKey: 'source',
|
currentArgKey: 'sample',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Method: 'newFile',
|
Method: 'Existing project',
|
||||||
Sample: '',
|
Sample: '',
|
||||||
Source: '',
|
Source: 'kcl-samples',
|
||||||
|
ProjectName: 'bracket',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'source',
|
highlightedHeaderArg: 'sample',
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,11 +143,10 @@ test.describe('Testing loading external models', () => {
|
|||||||
|
|
||||||
await test.step(`Load a KCL sample with the command palette`, async () => {
|
await test.step(`Load a KCL sample with the command palette`, async () => {
|
||||||
await toolbar.loadButton.click()
|
await toolbar.loadButton.click()
|
||||||
|
await cmdBar.selectOption({ name: 'KCL Samples' }).click()
|
||||||
await cmdBar.expectState(defaultLoadCmdBarState)
|
await cmdBar.expectState(defaultLoadCmdBarState)
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
await cmdBar.selectOption({ name: sampleOne.title }).click()
|
await cmdBar.selectOption({ name: sampleOne.title }).click()
|
||||||
await expect(overwriteWarning).not.toBeVisible()
|
await expect(overwriteWarning).not.toBeVisible()
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -166,33 +156,19 @@ test.describe('Testing loading external models', () => {
|
|||||||
await expect(projectMenuButton).toContainText(sampleOne.file)
|
await expect(projectMenuButton).toContainText(sampleOne.file)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Now overwrite the current file`, async () => {
|
await test.step(`Load a KCL sample with the command palette`, async () => {
|
||||||
await toolbar.loadButton.click()
|
await toolbar.loadButton.click()
|
||||||
|
await cmdBar.selectOption({ name: 'KCL Samples' }).click()
|
||||||
await cmdBar.expectState(defaultLoadCmdBarState)
|
await cmdBar.expectState(defaultLoadCmdBarState)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.selectOption({ name: sampleOne.title }).click()
|
||||||
await cmdBar.selectOption({ name: sampleTwo.title }).click()
|
await expect(overwriteWarning).not.toBeVisible()
|
||||||
await commandMethodArgButton.click()
|
await page.waitForTimeout(1000)
|
||||||
await commandMethodOption.click()
|
|
||||||
await expect(commandMethodArgButton).toContainText('overwrite')
|
|
||||||
await expect(overwriteWarning).toBeVisible()
|
|
||||||
await confirmButton.click()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Ensure we overwrote the current file without navigating`, async () => {
|
await test.step(`Ensure we made and opened a new file with a unique name`, async () => {
|
||||||
await editor.expectEditor.toContain('// ' + sampleTwo.title)
|
await editor.expectEditor.toContain('// ' + sampleOne.title)
|
||||||
await test.step(`Check actual file contents`, async () => {
|
await expect(newlyCreatedFile(sampleOne.file1)).toBeVisible()
|
||||||
await expect
|
await expect(projectMenuButton).toContainText(sampleOne.file1)
|
||||||
.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)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -226,19 +202,20 @@ test.describe('Testing loading external models', () => {
|
|||||||
|
|
||||||
async function loadExternalFileThroughCommandBar(tronApp: ElectronZoo) {
|
async function loadExternalFileThroughCommandBar(tronApp: ElectronZoo) {
|
||||||
await toolbar.loadButton.click()
|
await toolbar.loadButton.click()
|
||||||
|
await cmdBar.selectOption({ name: 'Local Drive' }).click()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
commandName: 'Load external model',
|
commandName: 'Add file to project',
|
||||||
currentArgKey: 'source',
|
currentArgKey: 'pathOpen file',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Method: 'newFile',
|
Method: 'Existing project',
|
||||||
Sample: '',
|
Path: '',
|
||||||
Source: '',
|
Source: 'local',
|
||||||
|
ProjectName: 'testDefault',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'source',
|
highlightedHeaderArg: 'path',
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
})
|
})
|
||||||
await cmdBar.selectOption({ name: 'Local Drive' }).click()
|
|
||||||
|
|
||||||
// Mock the file picker selection
|
// Mock the file picker selection
|
||||||
const handleFile = tronApp.electron.evaluate(
|
const handleFile = tronApp.electron.evaluate(
|
||||||
@ -251,14 +228,18 @@ test.describe('Testing loading external models', () => {
|
|||||||
await page.getByTestId('cmd-bar-arg-file-button').click()
|
await page.getByTestId('cmd-bar-arg-file-button').click()
|
||||||
await handleFile
|
await handleFile
|
||||||
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
commandName: 'Load external model',
|
commandName: 'Add file to project',
|
||||||
|
currentArgKey: 'pathOpen file',
|
||||||
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
|
Method: 'Existing project',
|
||||||
|
Path: '',
|
||||||
Source: 'local',
|
Source: 'local',
|
||||||
Path: modelName,
|
ProjectName: 'testDefault',
|
||||||
},
|
},
|
||||||
stage: 'review',
|
highlightedHeaderArg: 'path',
|
||||||
|
stage: 'arguments',
|
||||||
})
|
})
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
}
|
}
|
||||||
|
@ -144,7 +144,16 @@ export const CommandBar = () => {
|
|||||||
data-testid="command-bar"
|
data-testid="command-bar"
|
||||||
>
|
>
|
||||||
{commandBarState.matches('Selecting command') ? (
|
{commandBarState.matches('Selecting command') ? (
|
||||||
<CommandComboBox options={commands} />
|
<CommandComboBox
|
||||||
|
options={commands.filter((command) => {
|
||||||
|
return (
|
||||||
|
// By default everything is undefined
|
||||||
|
// If marked explicitly as false hide
|
||||||
|
command.hideFromSearch === undefined ||
|
||||||
|
command.hideFromSearch === false
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
/>
|
||||||
) : commandBarState.matches('Gathering arguments') ? (
|
) : commandBarState.matches('Gathering arguments') ? (
|
||||||
<CommandBarArgument stepBack={stepBack} />
|
<CommandBarArgument stepBack={stepBack} />
|
||||||
) : (
|
) : (
|
||||||
|
@ -8,6 +8,7 @@ import { isArray, toSync } from '@src/lib/utils'
|
|||||||
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
|
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
|
||||||
import { useSelector } from '@xstate/react'
|
import { useSelector } from '@xstate/react'
|
||||||
import type { AnyStateMachine, SnapshotFrom } from 'xstate'
|
import type { AnyStateMachine, SnapshotFrom } from 'xstate'
|
||||||
|
import type { OpenDialogOptions } from 'electron'
|
||||||
|
|
||||||
// TODO: remove the need for this selector once we decouple all actors from React
|
// TODO: remove the need for this selector once we decouple all actors from React
|
||||||
const machineContextSelector = (snapshot?: SnapshotFrom<AnyStateMachine>) =>
|
const machineContextSelector = (snapshot?: SnapshotFrom<AnyStateMachine>) =>
|
||||||
@ -54,10 +55,16 @@ function CommandBarPathInput({
|
|||||||
if (inputRef.current && inputRefVal && !isArray(inputRefVal)) {
|
if (inputRef.current && inputRefVal && !isArray(inputRefVal)) {
|
||||||
inputRef.current.value = inputRefVal
|
inputRef.current.value = inputRefVal
|
||||||
} else if (inputRef.current) {
|
} else if (inputRef.current) {
|
||||||
const newPath = await window.electron.open({
|
const configuration: OpenDialogOptions = {
|
||||||
properties: ['openFile'],
|
properties: ['openFile'],
|
||||||
title: 'Pick a file to load into the current project',
|
title: 'Pick a file to load into the current project',
|
||||||
})
|
}
|
||||||
|
|
||||||
|
if (arg.filters) {
|
||||||
|
configuration.filters = arg.filters
|
||||||
|
}
|
||||||
|
|
||||||
|
const newPath = await window.electron.open(configuration)
|
||||||
if (newPath.canceled) return
|
if (newPath.canceled) return
|
||||||
inputRef.current.value = newPath.filePaths[0]
|
inputRef.current.value = newPath.filePaths[0]
|
||||||
} else {
|
} else {
|
||||||
|
@ -24,8 +24,6 @@ import {
|
|||||||
} from '@src/lib/constants'
|
} from '@src/lib/constants'
|
||||||
import { getProjectInfo } from '@src/lib/desktop'
|
import { getProjectInfo } from '@src/lib/desktop'
|
||||||
import { getNextDirName, getNextFileName } from '@src/lib/desktopFS'
|
import { getNextDirName, getNextFileName } from '@src/lib/desktopFS'
|
||||||
import type { KclSamplesManifestItem } from '@src/lib/getKclSamplesManifest'
|
|
||||||
import { getKclSamplesManifest } from '@src/lib/getKclSamplesManifest'
|
|
||||||
import { isDesktop } from '@src/lib/isDesktop'
|
import { isDesktop } from '@src/lib/isDesktop'
|
||||||
import { kclCommands } from '@src/lib/kclCommands'
|
import { kclCommands } from '@src/lib/kclCommands'
|
||||||
import { BROWSER_PATH, PATHS } from '@src/lib/paths'
|
import { BROWSER_PATH, PATHS } from '@src/lib/paths'
|
||||||
@ -59,9 +57,6 @@ export const FileMachineProvider = ({
|
|||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
const projectData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
const projectData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
||||||
const { project, file } = projectData
|
const { project, file } = projectData
|
||||||
const [kclSamples, setKclSamples] = React.useState<KclSamplesManifestItem[]>(
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
|
|
||||||
const filePath = useAbsoluteFilePath()
|
const filePath = useAbsoluteFilePath()
|
||||||
// Only create the native file menus on desktop
|
// Only create the native file menus on desktop
|
||||||
@ -102,12 +97,6 @@ export const FileMachineProvider = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
markOnce('code/didLoadFile')
|
markOnce('code/didLoadFile')
|
||||||
async function fetchKclSamples() {
|
|
||||||
const manifest = await getKclSamplesManifest()
|
|
||||||
const filteredFiles = manifest.filter((file) => !file.multipleFiles)
|
|
||||||
setKclSamples(filteredFiles)
|
|
||||||
}
|
|
||||||
fetchKclSamples().catch(reportError)
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const [state, send] = useMachine(
|
const [state, send] = useMachine(
|
||||||
@ -468,28 +457,6 @@ export const FileMachineProvider = ({
|
|||||||
settings.modeling.defaultUnit.current ??
|
settings.modeling.defaultUnit.current ??
|
||||||
DEFAULT_DEFAULT_LENGTH_UNIT,
|
DEFAULT_DEFAULT_LENGTH_UNIT,
|
||||||
},
|
},
|
||||||
specialPropsForLoadCommand: {
|
|
||||||
onSubmit: async (data) => {
|
|
||||||
if (data.method === 'overwrite' && data.content) {
|
|
||||||
codeManager.updateCodeStateEditor(data.content)
|
|
||||||
await kclManager.executeCode()
|
|
||||||
await codeManager.writeToFile()
|
|
||||||
} else if (data.method === 'newFile' && isDesktop()) {
|
|
||||||
send({
|
|
||||||
type: 'Create file',
|
|
||||||
data: {
|
|
||||||
...data,
|
|
||||||
makeDir: false,
|
|
||||||
shouldSetToRename: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
providedOptions: kclSamples.map((sample) => ({
|
|
||||||
value: sample.pathFromProjectDirectoryToFirstFile,
|
|
||||||
name: sample.title,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
specialPropsForInsertCommand: {
|
specialPropsForInsertCommand: {
|
||||||
providedOptions: (isDesktop() && project?.children
|
providedOptions: (isDesktop() && project?.children
|
||||||
? project.children
|
? project.children
|
||||||
@ -510,10 +477,8 @@ export const FileMachineProvider = ({
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}).filter(
|
}),
|
||||||
(command) => kclSamples.length || command.name !== 'load-external-model'
|
[codeManager, kclManager, send, project, file]
|
||||||
),
|
|
||||||
[codeManager, kclManager, send, kclSamples, project, file]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -1872,11 +1872,6 @@ export const ModelingMachineProvider = ({
|
|||||||
commandName: 'Shell',
|
commandName: 'Shell',
|
||||||
groupId: 'modeling',
|
groupId: 'modeling',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
menuLabel: 'Design.Create with Zoo Text-To-CAD',
|
|
||||||
commandName: 'Text-to-CAD',
|
|
||||||
groupId: 'modeling',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
menuLabel: 'Design.Modify with Zoo Text-To-CAD',
|
menuLabel: 'Design.Modify with Zoo Text-To-CAD',
|
||||||
commandName: 'Prompt-to-edit',
|
commandName: 'Prompt-to-edit',
|
||||||
|
@ -9,7 +9,7 @@ import { useConvertToVariable } from '@src/hooks/useToolbarGuards'
|
|||||||
import { openExternalBrowserIfDesktop } from '@src/lib/openWindow'
|
import { openExternalBrowserIfDesktop } from '@src/lib/openWindow'
|
||||||
import { kclManager } from '@src/lib/singletons'
|
import { kclManager } from '@src/lib/singletons'
|
||||||
import { reportRejection } from '@src/lib/trap'
|
import { reportRejection } from '@src/lib/trap'
|
||||||
import { commandBarActor } from '@src/lib/singletons'
|
import { commandBarActor, settingsActor } from '@src/lib/singletons'
|
||||||
|
|
||||||
import styles from './KclEditorMenu.module.css'
|
import styles from './KclEditorMenu.module.css'
|
||||||
|
|
||||||
@ -86,17 +86,23 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => {
|
|||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
const currentProject =
|
||||||
|
settingsActor.getSnapshot().context.currentProject
|
||||||
commandBarActor.send({
|
commandBarActor.send({
|
||||||
type: 'Find and select command',
|
type: 'Find and select command',
|
||||||
data: {
|
data: {
|
||||||
groupId: 'code',
|
name: 'add-kcl-file-to-project',
|
||||||
name: 'load-external-model',
|
groupId: 'application',
|
||||||
|
argDefaultValues: {
|
||||||
|
method: 'existingProject',
|
||||||
|
projectName: currentProject?.name,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
className={styles.button}
|
className={styles.button}
|
||||||
>
|
>
|
||||||
<span>Load external model</span>
|
<span>Add file to project</span>
|
||||||
</button>
|
</button>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
|
@ -29,6 +29,7 @@ import { reportRejection } from '@src/lib/trap'
|
|||||||
import { refreshPage } from '@src/lib/utils'
|
import { refreshPage } from '@src/lib/utils'
|
||||||
import { hotkeyDisplay } from '@src/lib/hotkeyWrapper'
|
import { hotkeyDisplay } from '@src/lib/hotkeyWrapper'
|
||||||
import usePlatform from '@src/hooks/usePlatform'
|
import usePlatform from '@src/hooks/usePlatform'
|
||||||
|
import { settingsActor } from '@src/lib/singletons'
|
||||||
|
|
||||||
interface ModelingSidebarProps {
|
interface ModelingSidebarProps {
|
||||||
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
||||||
@ -79,16 +80,27 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
|||||||
|
|
||||||
const sidebarActions: SidebarAction[] = [
|
const sidebarActions: SidebarAction[] = [
|
||||||
{
|
{
|
||||||
id: 'load-external-model',
|
id: 'add-file-to-project',
|
||||||
title: 'Load external model',
|
title: 'Add file to project',
|
||||||
sidebarName: 'Load external model',
|
sidebarName: 'Add file to project',
|
||||||
icon: 'importFile',
|
icon: 'importFile',
|
||||||
keybinding: 'Mod + Alt + L',
|
keybinding: 'Mod + Alt + L',
|
||||||
action: () =>
|
action: () => {
|
||||||
|
const currentProject =
|
||||||
|
settingsActor.getSnapshot().context.currentProject
|
||||||
commandBarActor.send({
|
commandBarActor.send({
|
||||||
type: 'Find and select command',
|
type: 'Find and select command',
|
||||||
data: { name: 'load-external-model', groupId: 'code' },
|
data: {
|
||||||
}),
|
name: 'add-kcl-file-to-project',
|
||||||
|
groupId: 'application',
|
||||||
|
argDefaultValues: {
|
||||||
|
method: 'existingProject',
|
||||||
|
projectName: currentProject?.name,
|
||||||
|
...(!isDesktop() ? { source: 'kcl-samples' } : {}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'export',
|
id: 'export',
|
||||||
|
@ -13,8 +13,9 @@ import { initializeWindowExceptionHandler } from '@src/lib/exceptions'
|
|||||||
import { isDesktop } from '@src/lib/isDesktop'
|
import { isDesktop } from '@src/lib/isDesktop'
|
||||||
import { markOnce } from '@src/lib/performance'
|
import { markOnce } from '@src/lib/performance'
|
||||||
import { reportRejection } from '@src/lib/trap'
|
import { reportRejection } from '@src/lib/trap'
|
||||||
import { appActor } from '@src/lib/singletons'
|
import { appActor, systemIOActor, commandBarActor } from '@src/lib/singletons'
|
||||||
import reportWebVitals from '@src/reportWebVitals'
|
import reportWebVitals from '@src/reportWebVitals'
|
||||||
|
import { createApplicationCommands } from '@src/lib/commandBarConfigs/applicationCommandConfig'
|
||||||
|
|
||||||
markOnce('code/willAuth')
|
markOnce('code/willAuth')
|
||||||
initializeWindowExceptionHandler()
|
initializeWindowExceptionHandler()
|
||||||
@ -32,6 +33,14 @@ initializeWindowExceptionHandler()
|
|||||||
initPromise
|
initPromise
|
||||||
.then(() => {
|
.then(() => {
|
||||||
appActor.start()
|
appActor.start()
|
||||||
|
// Application commands must be created after the initPromise because
|
||||||
|
// it calls WASM functions to file extensions, this dependency is not available during initialization, it is an async dependency
|
||||||
|
commandBarActor.send({
|
||||||
|
type: 'Add commands',
|
||||||
|
data: {
|
||||||
|
commands: [...createApplicationCommands({ systemIOActor })],
|
||||||
|
},
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.catch(reportRejection)
|
.catch(reportRejection)
|
||||||
|
|
||||||
|
@ -3,6 +3,12 @@ import type { ActorRefFrom } from 'xstate'
|
|||||||
import type { Command, CommandArgumentOption } from '@src/lib/commandTypes'
|
import type { Command, CommandArgumentOption } from '@src/lib/commandTypes'
|
||||||
import { SystemIOMachineEvents } from '@src/machines/systemIO/utils'
|
import { SystemIOMachineEvents } from '@src/machines/systemIO/utils'
|
||||||
import { isDesktop } from '@src/lib/isDesktop'
|
import { isDesktop } from '@src/lib/isDesktop'
|
||||||
|
import { kclSamplesManifestWithNoMultipleFiles } from '@src/lib/kclSamples'
|
||||||
|
import { getUniqueProjectName } from '@src/lib/desktopFS'
|
||||||
|
import { FILE_EXT } from '@src/lib/constants'
|
||||||
|
import toast from 'react-hot-toast'
|
||||||
|
import { reportRejection } from '@src/lib/trap'
|
||||||
|
import { relevantFileExtensions } from '@src/lang/wasmUtils'
|
||||||
|
|
||||||
export function createApplicationCommands({
|
export function createApplicationCommands({
|
||||||
systemIOActor,
|
systemIOActor,
|
||||||
@ -79,5 +85,196 @@ export function createApplicationCommands({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return isDesktop() ? [textToCADCommand] : [textToCADCommand]
|
const addKCLFileToProject: Command = {
|
||||||
|
name: 'add-kcl-file-to-project',
|
||||||
|
displayName: 'Add file to project',
|
||||||
|
description:
|
||||||
|
'Add KCL file, Zoo sample, or 3D model to new or existing project.',
|
||||||
|
needsReview: false,
|
||||||
|
icon: 'importFile',
|
||||||
|
groupId: 'application',
|
||||||
|
onSubmit(data) {
|
||||||
|
if (data) {
|
||||||
|
/** TODO: Make a new machine for models. This is only a temporary location
|
||||||
|
* to move it to the global application level. To reduce its footprint
|
||||||
|
* and complexity the implementation lives here with systemIOMachine. Not
|
||||||
|
* inside the systemIOMachine. We can have a fancy model machine that loads
|
||||||
|
* KCL samples
|
||||||
|
*/
|
||||||
|
const folders = systemIOActor.getSnapshot().context.folders
|
||||||
|
const isProjectNew = !!data.newProjectName
|
||||||
|
const requestedProjectName = data.newProjectName || data.projectName
|
||||||
|
const uniqueNameIfNeeded = isProjectNew
|
||||||
|
? getUniqueProjectName(requestedProjectName, folders)
|
||||||
|
: requestedProjectName
|
||||||
|
|
||||||
|
if (data.source === 'kcl-samples' && data.sample) {
|
||||||
|
const pathParts = data.sample.split('/')
|
||||||
|
const projectPathPart = pathParts[0]
|
||||||
|
const primaryKclFile = pathParts[1]
|
||||||
|
const folderNameBecomesKCLFileName = projectPathPart + FILE_EXT
|
||||||
|
|
||||||
|
const sampleCodeUrl =
|
||||||
|
(isDesktop() ? '.' : '') +
|
||||||
|
`/kcl-samples/${encodeURIComponent(
|
||||||
|
projectPathPart
|
||||||
|
)}/${encodeURIComponent(primaryKclFile)}`
|
||||||
|
|
||||||
|
fetch(sampleCodeUrl)
|
||||||
|
.then(async (codeResponse) => {
|
||||||
|
if (!codeResponse.ok) {
|
||||||
|
console.error(
|
||||||
|
'Failed to fetch sample code:',
|
||||||
|
codeResponse.statusText
|
||||||
|
)
|
||||||
|
return Promise.reject(new Error('Failed to fetch sample code'))
|
||||||
|
}
|
||||||
|
const code = await codeResponse.text()
|
||||||
|
systemIOActor.send({
|
||||||
|
type: SystemIOMachineEvents.importFileFromURL,
|
||||||
|
data: {
|
||||||
|
requestedProjectName: uniqueNameIfNeeded,
|
||||||
|
requestedFileName: folderNameBecomesKCLFileName,
|
||||||
|
requestedCode: code,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(reportError)
|
||||||
|
} else if (data.source === 'local' && data.path) {
|
||||||
|
const clonePath = data.path
|
||||||
|
const fileWithExtension = clonePath.split('/').pop()
|
||||||
|
const readFileContentsAndCreateNewFile = async () => {
|
||||||
|
const text = await window.electron.readFile(clonePath, 'utf8')
|
||||||
|
systemIOActor.send({
|
||||||
|
type: SystemIOMachineEvents.importFileFromURL,
|
||||||
|
data: {
|
||||||
|
requestedProjectName: uniqueNameIfNeeded,
|
||||||
|
requestedFileName: fileWithExtension,
|
||||||
|
requestedCode: text,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
readFileContentsAndCreateNewFile().catch(reportRejection)
|
||||||
|
} else {
|
||||||
|
toast.error("The command couldn't be submitted, check the arguments.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
source: {
|
||||||
|
inputType: 'options',
|
||||||
|
required: true,
|
||||||
|
skip: false,
|
||||||
|
defaultValue: isDesktop() ? 'local' : 'kcl-samples',
|
||||||
|
options() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
value: 'kcl-samples',
|
||||||
|
name: 'KCL Samples',
|
||||||
|
isCurrent: true,
|
||||||
|
},
|
||||||
|
...(isDesktop()
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
value: 'local',
|
||||||
|
name: 'Local Drive',
|
||||||
|
isCurrent: false,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
method: {
|
||||||
|
inputType: 'options',
|
||||||
|
required: true,
|
||||||
|
skip: true,
|
||||||
|
options: isDesktop()
|
||||||
|
? [
|
||||||
|
{ name: 'New project', value: 'newProject', isCurrent: true },
|
||||||
|
{ name: 'Existing project', value: 'existingProject' },
|
||||||
|
]
|
||||||
|
: [{ name: 'Overwrite', value: 'existingProject' }],
|
||||||
|
valueSummary(value) {
|
||||||
|
return isDesktop()
|
||||||
|
? value === 'newProject'
|
||||||
|
? 'New project'
|
||||||
|
: 'Existing project'
|
||||||
|
: 'Overwrite'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
projectName: {
|
||||||
|
inputType: 'options',
|
||||||
|
required: (commandsContext) =>
|
||||||
|
isDesktop() &&
|
||||||
|
commandsContext.argumentsToSubmit.method === 'existingProject',
|
||||||
|
skip: true,
|
||||||
|
options: (_, context) => {
|
||||||
|
const { folders } = systemIOActor.getSnapshot().context
|
||||||
|
const options: CommandArgumentOption<string>[] = []
|
||||||
|
folders.forEach((folder) => {
|
||||||
|
options.push({
|
||||||
|
name: folder.name,
|
||||||
|
value: folder.name,
|
||||||
|
isCurrent: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return options
|
||||||
|
},
|
||||||
|
},
|
||||||
|
newProjectName: {
|
||||||
|
inputType: 'text',
|
||||||
|
required: (commandsContext) =>
|
||||||
|
isDesktop() &&
|
||||||
|
commandsContext.argumentsToSubmit.method === 'newProject',
|
||||||
|
skip: true,
|
||||||
|
},
|
||||||
|
sample: {
|
||||||
|
inputType: 'options',
|
||||||
|
required: (commandContext) =>
|
||||||
|
!['local'].includes(
|
||||||
|
commandContext.argumentsToSubmit.source as string
|
||||||
|
),
|
||||||
|
hidden: (commandContext) =>
|
||||||
|
['local'].includes(commandContext.argumentsToSubmit.source as string),
|
||||||
|
valueSummary(value) {
|
||||||
|
const MAX_LENGTH = 12
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return value.length > MAX_LENGTH
|
||||||
|
? value.substring(0, MAX_LENGTH) + '...'
|
||||||
|
: value
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
},
|
||||||
|
options: kclSamplesManifestWithNoMultipleFiles.map((sample) => {
|
||||||
|
return {
|
||||||
|
value: sample.pathFromProjectDirectoryToFirstFile,
|
||||||
|
name: sample.title,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
path: {
|
||||||
|
inputType: 'path',
|
||||||
|
skip: true,
|
||||||
|
hidden: !isDesktop(),
|
||||||
|
defaultValue: '',
|
||||||
|
valueSummary: (value) => {
|
||||||
|
return isDesktop() ? window.electron.path.basename(value) : ''
|
||||||
|
},
|
||||||
|
required: (commandContext) =>
|
||||||
|
isDesktop() &&
|
||||||
|
['local'].includes(commandContext.argumentsToSubmit.source as string),
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: `Import ${relevantFileExtensions().map((f) => ` .${f}`)}`,
|
||||||
|
extensions: relevantFileExtensions(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return isDesktop()
|
||||||
|
? [textToCADCommand, addKCLFileToProject]
|
||||||
|
: [textToCADCommand, addKCLFileToProject]
|
||||||
}
|
}
|
||||||
|
@ -182,6 +182,7 @@ export function createProjectCommands({
|
|||||||
icon: 'file',
|
icon: 'file',
|
||||||
description: 'Create a file',
|
description: 'Create a file',
|
||||||
needsReview: true,
|
needsReview: true,
|
||||||
|
hideFromSearch: true,
|
||||||
onSubmit: (record) => {
|
onSubmit: (record) => {
|
||||||
if (record) {
|
if (record) {
|
||||||
systemIOActor.send({
|
systemIOActor.send({
|
||||||
|
@ -41,6 +41,12 @@ export interface KclExpressionWithVariable extends KclExpression {
|
|||||||
export type KclCommandValue = KclExpression | KclExpressionWithVariable
|
export type KclCommandValue = KclExpression | KclExpressionWithVariable
|
||||||
export type CommandInputType = INPUT_TYPE[number]
|
export type CommandInputType = INPUT_TYPE[number]
|
||||||
|
|
||||||
|
export type FileFilter = {
|
||||||
|
name: string
|
||||||
|
extensions: string[]
|
||||||
|
}
|
||||||
|
export type FiltersConfig = FileFilter[]
|
||||||
|
|
||||||
export type StateMachineCommandSetSchema<T extends AnyStateMachine> = Partial<{
|
export type StateMachineCommandSetSchema<T extends AnyStateMachine> = Partial<{
|
||||||
[EventType in EventFrom<T>['type']]: Record<string, any>
|
[EventType in EventFrom<T>['type']]: Record<string, any>
|
||||||
}>
|
}>
|
||||||
@ -96,6 +102,7 @@ export type Command<
|
|||||||
description?: string
|
description?: string
|
||||||
icon?: Icon
|
icon?: Icon
|
||||||
hide?: PLATFORM[number]
|
hide?: PLATFORM[number]
|
||||||
|
hideFromSearch?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CommandConfig<
|
export type CommandConfig<
|
||||||
@ -373,6 +380,7 @@ export type CommandArgument<
|
|||||||
commandBarContext: ContextFrom<typeof commandBarMachine>,
|
commandBarContext: ContextFrom<typeof commandBarMachine>,
|
||||||
machineContext?: ContextFrom<T>
|
machineContext?: ContextFrom<T>
|
||||||
) => OutputType)
|
) => OutputType)
|
||||||
|
filters: FiltersConfig
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
inputType: 'text'
|
inputType: 'text'
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import type { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
|
import type { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
|
|
||||||
import { CommandBarOverwriteWarning } from '@src/components/CommandBarOverwriteWarning'
|
|
||||||
import { updateModelingState } from '@src/lang/modelingWorkflows'
|
import { updateModelingState } from '@src/lang/modelingWorkflows'
|
||||||
import { addImportAndInsert } from '@src/lang/modifyAst'
|
import { addImportAndInsert } from '@src/lang/modifyAst'
|
||||||
import {
|
import {
|
||||||
@ -14,10 +13,8 @@ import {
|
|||||||
DEFAULT_DEFAULT_ANGLE_UNIT,
|
DEFAULT_DEFAULT_ANGLE_UNIT,
|
||||||
DEFAULT_DEFAULT_LENGTH_UNIT,
|
DEFAULT_DEFAULT_LENGTH_UNIT,
|
||||||
EXECUTION_TYPE_REAL,
|
EXECUTION_TYPE_REAL,
|
||||||
FILE_EXT,
|
|
||||||
} from '@src/lib/constants'
|
} from '@src/lib/constants'
|
||||||
import { getPathFilenameInVariableCase } from '@src/lib/desktop'
|
import { getPathFilenameInVariableCase } from '@src/lib/desktop'
|
||||||
import { isDesktop } from '@src/lib/isDesktop'
|
|
||||||
import { copyFileShareLink } from '@src/lib/links'
|
import { copyFileShareLink } from '@src/lib/links'
|
||||||
import { baseUnitsUnion } from '@src/lib/settings/settingsTypes'
|
import { baseUnitsUnion } from '@src/lib/settings/settingsTypes'
|
||||||
import { codeManager, editorManager, kclManager } from '@src/lib/singletons'
|
import { codeManager, editorManager, kclManager } from '@src/lib/singletons'
|
||||||
@ -25,21 +22,9 @@ import { err, reportRejection } from '@src/lib/trap'
|
|||||||
import type { IndexLoaderData } from '@src/lib/types'
|
import type { IndexLoaderData } from '@src/lib/types'
|
||||||
import type { CommandBarContext } from '@src/machines/commandBarMachine'
|
import type { CommandBarContext } from '@src/machines/commandBarMachine'
|
||||||
|
|
||||||
interface OnSubmitProps {
|
|
||||||
name: string
|
|
||||||
content?: string
|
|
||||||
targetPathToClone?: string
|
|
||||||
method: 'overwrite' | 'newFile'
|
|
||||||
source: 'kcl-samples' | 'local'
|
|
||||||
}
|
|
||||||
|
|
||||||
interface KclCommandConfig {
|
interface KclCommandConfig {
|
||||||
// TODO: find a different approach that doesn't require
|
// TODO: find a different approach that doesn't require
|
||||||
// special props for a single command
|
// special props for a single command
|
||||||
specialPropsForLoadCommand: {
|
|
||||||
onSubmit: (p: OnSubmitProps) => Promise<void>
|
|
||||||
providedOptions: CommandArgumentOption<string>[]
|
|
||||||
}
|
|
||||||
specialPropsForInsertCommand: {
|
specialPropsForInsertCommand: {
|
||||||
providedOptions: CommandArgumentOption<string>[]
|
providedOptions: CommandArgumentOption<string>[]
|
||||||
}
|
}
|
||||||
@ -189,160 +174,6 @@ export function kclCommands(commandProps: KclCommandConfig): Command[] {
|
|||||||
kclManager.format().catch(reportRejection)
|
kclManager.format().catch(reportRejection)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'load-external-model',
|
|
||||||
displayName: 'Load external model',
|
|
||||||
description:
|
|
||||||
'Loads a model from an external source into the current project.',
|
|
||||||
needsReview: true,
|
|
||||||
icon: 'importFile',
|
|
||||||
reviewMessage: ({ argumentsToSubmit }) =>
|
|
||||||
argumentsToSubmit['method'] === 'overwrite'
|
|
||||||
? CommandBarOverwriteWarning({
|
|
||||||
heading: 'Overwrite current file with sample?',
|
|
||||||
message:
|
|
||||||
'This will erase your current file and load the sample part.',
|
|
||||||
})
|
|
||||||
: 'This will create a new file in the current project and open it.',
|
|
||||||
groupId: 'code',
|
|
||||||
onSubmit(data) {
|
|
||||||
if (!data) {
|
|
||||||
return new Error('No input data')
|
|
||||||
}
|
|
||||||
|
|
||||||
const { method, source, sample, path } = data
|
|
||||||
if (source === 'local' && path) {
|
|
||||||
commandProps.specialPropsForLoadCommand
|
|
||||||
.onSubmit({
|
|
||||||
name: '',
|
|
||||||
targetPathToClone: path,
|
|
||||||
method,
|
|
||||||
source,
|
|
||||||
})
|
|
||||||
.catch(reportError)
|
|
||||||
} else if (source === 'kcl-samples' && sample) {
|
|
||||||
const pathParts = sample.split('/')
|
|
||||||
const projectPathPart = pathParts[0]
|
|
||||||
const primaryKclFile = pathParts[1]
|
|
||||||
// local only
|
|
||||||
const sampleCodeUrl =
|
|
||||||
(isDesktop() ? '.' : '') +
|
|
||||||
`/kcl-samples/${encodeURIComponent(
|
|
||||||
projectPathPart
|
|
||||||
)}/${encodeURIComponent(primaryKclFile)}`
|
|
||||||
|
|
||||||
fetch(sampleCodeUrl)
|
|
||||||
.then(async (codeResponse) => {
|
|
||||||
if (!codeResponse.ok) {
|
|
||||||
console.error(
|
|
||||||
'Failed to fetch sample code:',
|
|
||||||
codeResponse.statusText
|
|
||||||
)
|
|
||||||
return Promise.reject(new Error('Failed to fetch sample code'))
|
|
||||||
}
|
|
||||||
const code = await codeResponse.text()
|
|
||||||
commandProps.specialPropsForLoadCommand
|
|
||||||
.onSubmit({
|
|
||||||
name: data.sample.split('/')[0] + FILE_EXT,
|
|
||||||
content: code,
|
|
||||||
source,
|
|
||||||
method,
|
|
||||||
})
|
|
||||||
.catch(reportError)
|
|
||||||
})
|
|
||||||
.catch(reportError)
|
|
||||||
} else {
|
|
||||||
toast.error("The command couldn't be submitted, check the arguments.")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
args: {
|
|
||||||
source: {
|
|
||||||
inputType: 'options',
|
|
||||||
required: true,
|
|
||||||
skip: false,
|
|
||||||
defaultValue: 'local',
|
|
||||||
hidden: !isDesktop(),
|
|
||||||
options() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
value: 'kcl-samples',
|
|
||||||
name: 'KCL Samples',
|
|
||||||
isCurrent: true,
|
|
||||||
},
|
|
||||||
...(isDesktop()
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
value: 'local',
|
|
||||||
name: 'Local Drive',
|
|
||||||
isCurrent: false,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
method: {
|
|
||||||
inputType: 'options',
|
|
||||||
skip: true,
|
|
||||||
required: (commandContext) =>
|
|
||||||
!['local'].includes(
|
|
||||||
commandContext.argumentsToSubmit.source as string
|
|
||||||
),
|
|
||||||
hidden: (commandContext) =>
|
|
||||||
['local'].includes(
|
|
||||||
commandContext.argumentsToSubmit.source as string
|
|
||||||
),
|
|
||||||
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: (commandContext) =>
|
|
||||||
!['local'].includes(
|
|
||||||
commandContext.argumentsToSubmit.source as string
|
|
||||||
),
|
|
||||||
hidden: (commandContext) =>
|
|
||||||
['local'].includes(
|
|
||||||
commandContext.argumentsToSubmit.source as string
|
|
||||||
),
|
|
||||||
valueSummary(value) {
|
|
||||||
const MAX_LENGTH = 12
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
return value.length > MAX_LENGTH
|
|
||||||
? value.substring(0, MAX_LENGTH) + '...'
|
|
||||||
: value
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
},
|
|
||||||
options: commandProps.specialPropsForLoadCommand.providedOptions,
|
|
||||||
},
|
|
||||||
path: {
|
|
||||||
inputType: 'path',
|
|
||||||
valueSummary: (value) => window.electron.path.basename(value),
|
|
||||||
required: (commandContext) =>
|
|
||||||
['local'].includes(
|
|
||||||
commandContext.argumentsToSubmit.source as string
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'share-file-link',
|
name: 'share-file-link',
|
||||||
displayName: 'Share part via Zoo link',
|
displayName: 'Share part via Zoo link',
|
||||||
|
7
src/lib/kclSamples.ts
Normal file
7
src/lib/kclSamples.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import kclSamplesManifest from '@public/kcl-samples/manifest.json'
|
||||||
|
|
||||||
|
const kclSamplesManifestWithNoMultipleFiles = kclSamplesManifest.filter(
|
||||||
|
(file) => !file.multipleFiles
|
||||||
|
)
|
||||||
|
|
||||||
|
export { kclSamplesManifest, kclSamplesManifestWithNoMultipleFiles }
|
@ -28,7 +28,6 @@ import type { AppMachineContext } from '@src/lib/types'
|
|||||||
import { createAuthCommands } from '@src/lib/commandBarConfigs/authCommandConfig'
|
import { createAuthCommands } from '@src/lib/commandBarConfigs/authCommandConfig'
|
||||||
import { commandBarMachine } from '@src/machines/commandBarMachine'
|
import { commandBarMachine } from '@src/machines/commandBarMachine'
|
||||||
import { createProjectCommands } from '@src/lib/commandBarConfigs/projectsCommandConfig'
|
import { createProjectCommands } from '@src/lib/commandBarConfigs/projectsCommandConfig'
|
||||||
import { createApplicationCommands } from '@src/lib/commandBarConfigs/applicationCommandConfig'
|
|
||||||
|
|
||||||
export const codeManager = new CodeManager()
|
export const codeManager = new CodeManager()
|
||||||
export const engineCommandManager = new EngineCommandManager()
|
export const engineCommandManager = new EngineCommandManager()
|
||||||
@ -227,7 +226,6 @@ commandBarActor.send({
|
|||||||
commands: [
|
commands: [
|
||||||
...createAuthCommands({ authActor }),
|
...createAuthCommands({ authActor }),
|
||||||
...createProjectCommands({ systemIOActor }),
|
...createProjectCommands({ systemIOActor }),
|
||||||
...createApplicationCommands({ systemIOActor }),
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -469,10 +469,13 @@ export const systemIOMachine = setup({
|
|||||||
assign({
|
assign({
|
||||||
requestedFileName: ({ context, event }) => {
|
requestedFileName: ({ context, event }) => {
|
||||||
assertEvent(event, SystemIOMachineEvents.done_importFileFromURL)
|
assertEvent(event, SystemIOMachineEvents.done_importFileFromURL)
|
||||||
// Not the entire path
|
// Gotcha: file could have an ending of .kcl...
|
||||||
|
const file = event.output.fileName.endsWith('.kcl')
|
||||||
|
? event.output.fileName
|
||||||
|
: event.output.fileName + '.kcl'
|
||||||
return {
|
return {
|
||||||
project: event.output.projectName,
|
project: event.output.projectName,
|
||||||
file: event.output.fileName + '.kcl',
|
file,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
@ -204,7 +204,7 @@ export const systemIOMachineDesktop = systemIOMachine.provide({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
message: 'File created successfully',
|
message: 'File created successfully',
|
||||||
fileName: input.requestedFileName,
|
fileName: newFileName,
|
||||||
projectName: newProjectName,
|
projectName: newProjectName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ export type MenuLabels =
|
|||||||
| 'File.Sign out'
|
| 'File.Sign out'
|
||||||
| 'File.Create new file'
|
| 'File.Create new file'
|
||||||
| 'File.Create new folder'
|
| 'File.Create new folder'
|
||||||
| 'File.Load external model'
|
| 'File.Add file to project'
|
||||||
| 'File.Export current part'
|
| 'File.Export current part'
|
||||||
| 'File.Share part via Zoo link'
|
| 'File.Share part via Zoo link'
|
||||||
| 'File.Preferences.Project settings'
|
| 'File.Preferences.Project settings'
|
||||||
|
@ -35,6 +35,25 @@ export const projectFileRole = (
|
|||||||
// TODO https://www.electronjs.org/docs/latest/tutorial/recent-documents
|
// TODO https://www.electronjs.org/docs/latest/tutorial/recent-documents
|
||||||
// Appears to be only Windows and Mac OS specific. Linux does not have support
|
// Appears to be only Windows and Mac OS specific. Linux does not have support
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
label: 'Add file to project',
|
||||||
|
id: 'File.Add file to project',
|
||||||
|
click: () => {
|
||||||
|
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
|
||||||
|
menuLabel: 'File.Add file to project',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Create with Zoo Text-To-CAD',
|
||||||
|
id: 'Design.Create with Zoo Text-To-CAD',
|
||||||
|
click: () => {
|
||||||
|
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
|
||||||
|
menuLabel: 'Design.Create with Zoo Text-To-CAD',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
{
|
{
|
||||||
label: 'Preferences',
|
label: 'Preferences',
|
||||||
submenu: [
|
submenu: [
|
||||||
@ -148,11 +167,11 @@ export const modelingFileRole = (
|
|||||||
// Appears to be only Windows and Mac OS specific. Linux does not have support
|
// Appears to be only Windows and Mac OS specific. Linux does not have support
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{
|
{
|
||||||
label: 'Load external model',
|
label: 'Add file to project',
|
||||||
id: 'File.Load external model',
|
id: 'File.Add file to project',
|
||||||
click: () => {
|
click: () => {
|
||||||
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
|
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
|
||||||
menuLabel: 'File.Load external model',
|
menuLabel: 'File.Add file to project',
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -92,12 +92,17 @@ export function modelingMenuCallbackMostActions(
|
|||||||
}).catch(reportRejection)
|
}).catch(reportRejection)
|
||||||
} else if (data.menuLabel === 'File.Preferences.User default units') {
|
} else if (data.menuLabel === 'File.Preferences.User default units') {
|
||||||
navigate(filePath + PATHS.SETTINGS_USER + '#defaultUnit')
|
navigate(filePath + PATHS.SETTINGS_USER + '#defaultUnit')
|
||||||
} else if (data.menuLabel === 'File.Load external model') {
|
} else if (data.menuLabel === 'File.Add file to project') {
|
||||||
|
const currentProject = settingsActor.getSnapshot().context.currentProject
|
||||||
commandBarActor.send({
|
commandBarActor.send({
|
||||||
type: 'Find and select command',
|
type: 'Find and select command',
|
||||||
data: {
|
data: {
|
||||||
groupId: 'code',
|
name: 'add-kcl-file-to-project',
|
||||||
name: 'load-external-model',
|
groupId: 'application',
|
||||||
|
argDefaultValues: {
|
||||||
|
method: 'existingProject',
|
||||||
|
projectName: currentProject?.name,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else if (data.menuLabel === 'File.Export current part') {
|
} else if (data.menuLabel === 'File.Export current part') {
|
||||||
@ -257,9 +262,17 @@ export function modelingMenuCallbackMostActions(
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else if (data.menuLabel === 'Design.Create with Zoo Text-To-CAD') {
|
} else if (data.menuLabel === 'Design.Create with Zoo Text-To-CAD') {
|
||||||
|
const currentProject = settingsActor.getSnapshot().context.currentProject
|
||||||
commandBarActor.send({
|
commandBarActor.send({
|
||||||
type: 'Find and select command',
|
type: 'Find and select command',
|
||||||
data: { name: 'Text-to-CAD', groupId: 'modeling' },
|
data: {
|
||||||
|
name: 'Text-to-CAD',
|
||||||
|
groupId: 'application',
|
||||||
|
argDefaultValues: {
|
||||||
|
method: 'existingProject',
|
||||||
|
projectName: currentProject?.name,
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
} else if (data.menuLabel === 'Design.Modify with Zoo Text-To-CAD') {
|
} else if (data.menuLabel === 'Design.Modify with Zoo Text-To-CAD') {
|
||||||
commandBarActor.send({
|
commandBarActor.send({
|
||||||
|
@ -26,7 +26,7 @@ type FileRoleLabel =
|
|||||||
| 'Create new folder'
|
| 'Create new folder'
|
||||||
| 'Share part via Zoo link'
|
| 'Share part via Zoo link'
|
||||||
| 'Project settings'
|
| 'Project settings'
|
||||||
| 'Load external model'
|
| 'Add file to project'
|
||||||
| 'User default units'
|
| 'User default units'
|
||||||
|
|
||||||
type EditRoleLabel =
|
type EditRoleLabel =
|
||||||
|
@ -142,6 +142,26 @@ const Home = () => {
|
|||||||
})
|
})
|
||||||
} else if (data.menuLabel === 'File.Preferences.Theme color') {
|
} else if (data.menuLabel === 'File.Preferences.Theme color') {
|
||||||
navigate(`${PATHS.HOME}${PATHS.SETTINGS_USER}#themeColor`)
|
navigate(`${PATHS.HOME}${PATHS.SETTINGS_USER}#themeColor`)
|
||||||
|
} else if (data.menuLabel === 'File.Add file to project') {
|
||||||
|
commandBarActor.send({
|
||||||
|
type: 'Find and select command',
|
||||||
|
data: {
|
||||||
|
name: 'add-kcl-file-to-project',
|
||||||
|
groupId: 'application',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else if (data.menuLabel === 'Design.Create with Zoo Text-To-CAD') {
|
||||||
|
commandBarActor.send({
|
||||||
|
type: 'Find and select command',
|
||||||
|
data: {
|
||||||
|
name: 'Text-to-CAD',
|
||||||
|
groupId: 'application',
|
||||||
|
argDefaultValues: {
|
||||||
|
method: 'newProject',
|
||||||
|
newProjectName: settings.projects.defaultProjectName.current,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
useMenuListener(cb)
|
useMenuListener(cb)
|
||||||
@ -182,7 +202,7 @@ const Home = () => {
|
|||||||
readWriteProjectDir={readWriteProjectDir}
|
readWriteProjectDir={readWriteProjectDir}
|
||||||
className="col-start-2 -col-end-1"
|
className="col-start-2 -col-end-1"
|
||||||
/>
|
/>
|
||||||
<aside className="row-start-2 -row-end-1 flex flex-col justify-between">
|
<aside className="row-start-1 -row-end-1 flex flex-col justify-between">
|
||||||
<ul className="flex flex-col">
|
<ul className="flex flex-col">
|
||||||
<li className="contents">
|
<li className="contents">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
@ -233,6 +253,34 @@ const Home = () => {
|
|||||||
Generate with Text-to-CAD
|
Generate with Text-to-CAD
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</li>
|
</li>
|
||||||
|
<li className="contents">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={() =>
|
||||||
|
commandBarActor.send({
|
||||||
|
type: 'Find and select command',
|
||||||
|
data: {
|
||||||
|
groupId: 'application',
|
||||||
|
name: 'add-kcl-file-to-project',
|
||||||
|
argDefaultValues: {
|
||||||
|
source: 'kcl-samples',
|
||||||
|
method: 'newProject',
|
||||||
|
newProjectName:
|
||||||
|
settings.projects.defaultProjectName.current,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className={sidebarButtonClasses}
|
||||||
|
iconStart={{
|
||||||
|
icon: 'importFile',
|
||||||
|
bgClassName: '!bg-transparent rounded-sm',
|
||||||
|
}}
|
||||||
|
data-testid="home-create-from-sample"
|
||||||
|
>
|
||||||
|
Create from a sample
|
||||||
|
</ActionButton>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul className="flex flex-col">
|
<ul className="flex flex-col">
|
||||||
<li className="contents">
|
<li className="contents">
|
||||||
|
Reference in New Issue
Block a user