[Fix]: Allow importing assemblies into exsiting projects and handling the collision (#7108)
* fix: saving off code * fix: saving off progress * chore: implemented kcl sample assembly unique sub dir creation * fix: removing testing console logs * fix: cleaning up old comment * fix: add to file always does subdir/main.kcl now for single files * fix: auto fmt * fix: delete project and folder from ttc * fix: fixed deleting projects and subdirs * fix: if statement logic fixed for deleting project or subdir * fix: TTC isProjectNew makes main.kcl not a subdir. * fix: fixing e2e test * fix: this should pass now
This commit is contained in:
@ -103,6 +103,8 @@ test.describe('Testing loading external models', () => {
|
|||||||
file: 'ball-bearing' + FILE_EXT,
|
file: 'ball-bearing' + FILE_EXT,
|
||||||
title: 'Ball Bearing',
|
title: 'Ball Bearing',
|
||||||
file1: 'ball-bearing-1' + FILE_EXT,
|
file1: 'ball-bearing-1' + FILE_EXT,
|
||||||
|
folderName: 'ball-bearing',
|
||||||
|
folderName1: 'ball-bearing-1',
|
||||||
}
|
}
|
||||||
const projectCard = page.getByRole('link', { name: 'bracket' })
|
const projectCard = page.getByRole('link', { name: 'bracket' })
|
||||||
const overwriteWarning = page.getByText(
|
const overwriteWarning = page.getByText(
|
||||||
@ -154,8 +156,10 @@ test.describe('Testing loading external models', () => {
|
|||||||
|
|
||||||
await test.step(`Ensure we made and opened a new file`, async () => {
|
await test.step(`Ensure we made and opened a new file`, async () => {
|
||||||
await editor.expectEditor.toContain('// ' + sampleOne.title)
|
await editor.expectEditor.toContain('// ' + sampleOne.title)
|
||||||
await expect(newlyCreatedFile(sampleOne.file)).toBeVisible()
|
await expect(
|
||||||
await expect(projectMenuButton).toContainText(sampleOne.file)
|
page.getByTestId('file-tree-item').getByText(sampleOne.folderName)
|
||||||
|
).toBeVisible()
|
||||||
|
await expect(projectMenuButton).toContainText('main.kcl')
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Load a KCL sample with the command palette`, async () => {
|
await test.step(`Load a KCL sample with the command palette`, async () => {
|
||||||
@ -169,8 +173,10 @@ test.describe('Testing loading external models', () => {
|
|||||||
|
|
||||||
await test.step(`Ensure we made and opened a new file with a unique name`, async () => {
|
await test.step(`Ensure we made and opened a new file with a unique name`, async () => {
|
||||||
await editor.expectEditor.toContain('// ' + sampleOne.title)
|
await editor.expectEditor.toContain('// ' + sampleOne.title)
|
||||||
await expect(newlyCreatedFile(sampleOne.file1)).toBeVisible()
|
await expect(
|
||||||
await expect(projectMenuButton).toContainText(sampleOne.file1)
|
page.getByTestId('file-tree-item').getByText(sampleOne.folderName1)
|
||||||
|
).toBeVisible()
|
||||||
|
await expect(projectMenuButton).toContainText('main.kcl')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -984,12 +984,12 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
|||||||
)
|
)
|
||||||
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
'2x2x2-cube.kcl'
|
'main.kcl'
|
||||||
)
|
)
|
||||||
|
|
||||||
await u.openFilePanel()
|
await u.openFilePanel()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
|
page.getByTestId('file-tree-item').getByText('2x2x2-cube')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -1184,13 +1184,13 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
|||||||
)
|
)
|
||||||
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
'2x2x2-cube.kcl'
|
'main.kcl'
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check file is created
|
// Check file is created
|
||||||
await u.openFilePanel()
|
await u.openFilePanel()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
|
page.getByTestId('file-tree-item').getByText('2x2x2-cube')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -1476,13 +1476,13 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
|||||||
)
|
)
|
||||||
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
'2x2x2-cube.kcl'
|
'main.kcl'
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check file is created
|
// Check file is created
|
||||||
await u.openFilePanel()
|
await u.openFilePanel()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
|
page.getByTestId('file-tree-item').getByText('2x2x2-cube')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByTestId('file-tree-item').getByText('main.kcl')
|
page.getByTestId('file-tree-item').getByText('main.kcl')
|
||||||
|
@ -159,6 +159,7 @@ export function ToastTextToCadSuccess({
|
|||||||
projectName,
|
projectName,
|
||||||
fileName,
|
fileName,
|
||||||
isProjectNew,
|
isProjectNew,
|
||||||
|
rootProjectName,
|
||||||
}: {
|
}: {
|
||||||
toastId: string
|
toastId: string
|
||||||
data: TextToCad_type & { fileName: string }
|
data: TextToCad_type & { fileName: string }
|
||||||
@ -170,6 +171,7 @@ export function ToastTextToCadSuccess({
|
|||||||
projectName: string
|
projectName: string
|
||||||
fileName: string
|
fileName: string
|
||||||
isProjectNew: boolean
|
isProjectNew: boolean
|
||||||
|
rootProjectName: string
|
||||||
}) {
|
}) {
|
||||||
const wrapperRef = useRef<HTMLDivElement | null>(null)
|
const wrapperRef = useRef<HTMLDivElement | null>(null)
|
||||||
const canvasRef = useRef<HTMLCanvasElement | null>(null)
|
const canvasRef = useRef<HTMLCanvasElement | null>(null)
|
||||||
@ -361,26 +363,23 @@ export function ToastTextToCadSuccess({
|
|||||||
if (isDesktop()) {
|
if (isDesktop()) {
|
||||||
// Delete the file from the project
|
// Delete the file from the project
|
||||||
|
|
||||||
if (projectName && fileName) {
|
if (isProjectNew) {
|
||||||
// You are in the new workflow for text to cad at the global application level
|
// Delete the entire project if it was newly created from text to CAD
|
||||||
if (isProjectNew) {
|
systemIOActor.send({
|
||||||
// Delete the entire project if it was newly created from text to CAD
|
type: SystemIOMachineEvents.deleteProject,
|
||||||
systemIOActor.send({
|
data: {
|
||||||
type: SystemIOMachineEvents.deleteProject,
|
requestedProjectName: rootProjectName,
|
||||||
data: {
|
},
|
||||||
requestedProjectName: projectName,
|
})
|
||||||
},
|
} else if (projectName && fileName) {
|
||||||
})
|
// deletes the folder when inside the modeling page
|
||||||
} else {
|
// The TTC Create will make a subdir, delete that dir with the main.kcl as well
|
||||||
// Only delete the file if the project was preexisting
|
systemIOActor.send({
|
||||||
systemIOActor.send({
|
type: SystemIOMachineEvents.deleteProject,
|
||||||
type: SystemIOMachineEvents.deleteKCLFile,
|
data: {
|
||||||
data: {
|
requestedProjectName: projectName,
|
||||||
requestedProjectName: projectName,
|
},
|
||||||
requestedFileName: fileName,
|
})
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,12 +10,16 @@ import {
|
|||||||
kclSamplesManifestWithNoMultipleFiles,
|
kclSamplesManifestWithNoMultipleFiles,
|
||||||
} from '@src/lib/kclSamples'
|
} from '@src/lib/kclSamples'
|
||||||
import { getUniqueProjectName } from '@src/lib/desktopFS'
|
import { getUniqueProjectName } from '@src/lib/desktopFS'
|
||||||
import { IS_ML_EXPERIMENTAL, PROJECT_ENTRYPOINT } from '@src/lib/constants'
|
import { IS_ML_EXPERIMENTAL } from '@src/lib/constants'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { reportRejection } from '@src/lib/trap'
|
import { reportRejection } from '@src/lib/trap'
|
||||||
import { relevantFileExtensions } from '@src/lang/wasmUtils'
|
import { relevantFileExtensions } from '@src/lang/wasmUtils'
|
||||||
import { getStringAfterLastSeparator, webSafePathSplit } from '@src/lib/paths'
|
import {
|
||||||
import { FILE_EXT } from '@src/lib/constants'
|
getStringAfterLastSeparator,
|
||||||
|
joinOSPaths,
|
||||||
|
webSafePathSplit,
|
||||||
|
} from '@src/lib/paths'
|
||||||
|
import { getAllSubDirectoriesAtProjectRoot } from '@src/machines/systemIO/snapshotContext'
|
||||||
|
|
||||||
function onSubmitKCLSampleCreation({
|
function onSubmitKCLSampleCreation({
|
||||||
sample,
|
sample,
|
||||||
@ -69,20 +73,32 @@ function onSubmitKCLSampleCreation({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When adding assemblies to an existing project create the assembly into a unique sub directory
|
||||||
|
*/
|
||||||
|
if (!isProjectNew) {
|
||||||
|
requestedFiles.forEach((requestedFile) => {
|
||||||
|
const subDirectoryName = projectPathPart
|
||||||
|
const firstLevelDirectories = getAllSubDirectoriesAtProjectRoot({
|
||||||
|
projectFolderName: requestedFile.requestedProjectName,
|
||||||
|
})
|
||||||
|
const uniqueSubDirectoryName = getUniqueProjectName(
|
||||||
|
subDirectoryName,
|
||||||
|
firstLevelDirectories
|
||||||
|
)
|
||||||
|
requestedFile.requestedProjectName = joinOSPaths(
|
||||||
|
requestedFile.requestedProjectName,
|
||||||
|
uniqueSubDirectoryName
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (requestedFiles.length === 1) {
|
if (requestedFiles.length === 1) {
|
||||||
/**
|
|
||||||
* Navigates to the single file that could be renamed on disk for duplicates
|
|
||||||
*/
|
|
||||||
const folderNameBecomesKCLFileName = projectPathPart + FILE_EXT
|
|
||||||
// If the project is new create the single file as main.kcl
|
|
||||||
const requestedFileNameWithExtension = isProjectNew
|
|
||||||
? PROJECT_ENTRYPOINT
|
|
||||||
: folderNameBecomesKCLFileName
|
|
||||||
systemIOActor.send({
|
systemIOActor.send({
|
||||||
type: SystemIOMachineEvents.importFileFromURL,
|
type: SystemIOMachineEvents.importFileFromURL,
|
||||||
data: {
|
data: {
|
||||||
requestedProjectName: requestedFiles[0].requestedProjectName,
|
requestedProjectName: requestedFiles[0].requestedProjectName,
|
||||||
requestedFileNameWithExtension: requestedFileNameWithExtension,
|
requestedFileNameWithExtension: requestedFiles[0].requestedFileName,
|
||||||
requestedCode: requestedFiles[0].requestedCode,
|
requestedCode: requestedFiles[0].requestedCode,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -278,10 +294,9 @@ export function createApplicationCommands({
|
|||||||
return value
|
return value
|
||||||
},
|
},
|
||||||
options: ({ argumentsToSubmit }) => {
|
options: ({ argumentsToSubmit }) => {
|
||||||
const samples =
|
const samples = isDesktop()
|
||||||
isDesktop() && argumentsToSubmit.method !== 'existingProject'
|
? everyKclSample
|
||||||
? everyKclSample
|
: kclSamplesManifestWithNoMultipleFiles
|
||||||
: kclSamplesManifestWithNoMultipleFiles
|
|
||||||
return samples.map((sample) => {
|
return samples.map((sample) => {
|
||||||
return {
|
return {
|
||||||
value: sample.pathFromProjectDirectoryToFirstFile,
|
value: sample.pathFromProjectDirectoryToFirstFile,
|
||||||
@ -296,17 +311,10 @@ export function createApplicationCommands({
|
|||||||
skip: true,
|
skip: true,
|
||||||
options: ({ argumentsToSubmit }, _) => {
|
options: ({ argumentsToSubmit }, _) => {
|
||||||
if (isDesktop() && typeof argumentsToSubmit.sample === 'string') {
|
if (isDesktop() && typeof argumentsToSubmit.sample === 'string') {
|
||||||
const kclSample = findKclSample(argumentsToSubmit.sample)
|
return [
|
||||||
if (kclSample && kclSample.files.length > 1) {
|
{ name: 'New project', value: 'newProject', isCurrent: true },
|
||||||
return [
|
{ name: 'Existing project', value: 'existingProject' },
|
||||||
{ name: 'New project', value: 'newProject', isCurrent: true },
|
]
|
||||||
]
|
|
||||||
} else {
|
|
||||||
return [
|
|
||||||
{ name: 'New project', value: 'newProject', isCurrent: true },
|
|
||||||
{ name: 'Existing project', value: 'existingProject' },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return [{ name: 'Overwrite', value: 'existingProject' }]
|
return [{ name: 'Overwrite', value: 'existingProject' }]
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,9 @@ import {
|
|||||||
ToastTextToCadError,
|
ToastTextToCadError,
|
||||||
ToastTextToCadSuccess,
|
ToastTextToCadSuccess,
|
||||||
} from '@src/components/ToastTextToCad'
|
} from '@src/components/ToastTextToCad'
|
||||||
import { FILE_EXT, PROJECT_ENTRYPOINT } from '@src/lib/constants'
|
import { PROJECT_ENTRYPOINT } from '@src/lib/constants'
|
||||||
import crossPlatformFetch from '@src/lib/crossPlatformFetch'
|
import crossPlatformFetch from '@src/lib/crossPlatformFetch'
|
||||||
import { getNextFileName } from '@src/lib/desktopFS'
|
import { getUniqueProjectName } from '@src/lib/desktopFS'
|
||||||
import { isDesktop } from '@src/lib/isDesktop'
|
import { isDesktop } from '@src/lib/isDesktop'
|
||||||
import { kclManager, systemIOActor } from '@src/lib/singletons'
|
import { kclManager, systemIOActor } from '@src/lib/singletons'
|
||||||
import {
|
import {
|
||||||
@ -17,6 +17,8 @@ import {
|
|||||||
} from '@src/machines/systemIO/utils'
|
} from '@src/machines/systemIO/utils'
|
||||||
import { reportRejection } from '@src/lib/trap'
|
import { reportRejection } from '@src/lib/trap'
|
||||||
import { toSync } from '@src/lib/utils'
|
import { toSync } from '@src/lib/utils'
|
||||||
|
import { getAllSubDirectoriesAtProjectRoot } from '@src/machines/systemIO/snapshotContext'
|
||||||
|
import { joinOSPaths } from '@src/lib/paths'
|
||||||
|
|
||||||
export async function submitTextToCadPrompt(
|
export async function submitTextToCadPrompt(
|
||||||
prompt: string,
|
prompt: string,
|
||||||
@ -173,7 +175,8 @@ export async function submitAndAwaitTextToKclSystemIO({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
let newFileName = ''
|
let newFileName = PROJECT_ENTRYPOINT
|
||||||
|
let uniqueProjectName = projectName
|
||||||
|
|
||||||
const textToCadOutputCreated = await textToCadComplete
|
const textToCadOutputCreated = await textToCadComplete
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
@ -197,30 +200,28 @@ export async function submitAndAwaitTextToKclSystemIO({
|
|||||||
|
|
||||||
const TRUNCATED_PROMPT_LENGTH = 24
|
const TRUNCATED_PROMPT_LENGTH = 24
|
||||||
// Only add the prompt name if it is a preexisting project
|
// Only add the prompt name if it is a preexisting project
|
||||||
newFileName = `${value.prompt
|
const subDirectoryAsPromptName = `${value.prompt
|
||||||
.slice(0, TRUNCATED_PROMPT_LENGTH)
|
.slice(0, TRUNCATED_PROMPT_LENGTH)
|
||||||
.replace(/\s/gi, '-')
|
.replace(/\s/gi, '-')
|
||||||
.replace(/\W/gi, '-')
|
.replace(/\W/gi, '-')
|
||||||
.toLowerCase()}${FILE_EXT}`
|
.toLowerCase()}`
|
||||||
|
|
||||||
// If the project is new generate a main.kcl
|
|
||||||
if (isProjectNew) {
|
|
||||||
newFileName = PROJECT_ENTRYPOINT
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDesktop()) {
|
if (isDesktop()) {
|
||||||
// We have to preemptively run our unique file name logic,
|
if (!isProjectNew) {
|
||||||
// so that we can pass the unique file name to the toast,
|
// If the project is new, use a sub dir
|
||||||
// and by extension the file-deletion-on-reject logic.
|
const firstLevelDirectories = getAllSubDirectoriesAtProjectRoot({
|
||||||
newFileName = getNextFileName({
|
projectFolderName: projectName,
|
||||||
entryName: newFileName,
|
})
|
||||||
baseDir: projectName,
|
const uniqueSubDirectoryName = getUniqueProjectName(
|
||||||
}).name
|
subDirectoryAsPromptName,
|
||||||
|
firstLevelDirectories
|
||||||
|
)
|
||||||
|
uniqueProjectName = joinOSPaths(projectName, uniqueSubDirectoryName)
|
||||||
|
}
|
||||||
systemIOActor.send({
|
systemIOActor.send({
|
||||||
type: SystemIOMachineEvents.createKCLFile,
|
type: SystemIOMachineEvents.createKCLFile,
|
||||||
data: {
|
data: {
|
||||||
requestedProjectName: projectName,
|
requestedProjectName: uniqueProjectName,
|
||||||
requestedCode: value.code,
|
requestedCode: value.code,
|
||||||
requestedFileNameWithExtension: newFileName,
|
requestedFileNameWithExtension: newFileName,
|
||||||
},
|
},
|
||||||
@ -251,11 +252,14 @@ export async function submitAndAwaitTextToKclSystemIO({
|
|||||||
toastId,
|
toastId,
|
||||||
data: textToCadOutputCreated,
|
data: textToCadOutputCreated,
|
||||||
token,
|
token,
|
||||||
projectName: projectName,
|
// This can be a subdir within the rootProjectName
|
||||||
|
projectName: uniqueProjectName,
|
||||||
fileName: newFileName,
|
fileName: newFileName,
|
||||||
navigate,
|
navigate,
|
||||||
isProjectNew,
|
isProjectNew,
|
||||||
settings,
|
settings,
|
||||||
|
// This is always the root project name, no subdir
|
||||||
|
rootProjectName: projectName,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
id: toastId,
|
id: toastId,
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
import type { FileEntry } from '@src/lib/project'
|
||||||
import { systemIOActor } from '@src/lib/singletons'
|
import { systemIOActor } from '@src/lib/singletons'
|
||||||
|
import { isArray } from '@src/lib/utils'
|
||||||
|
|
||||||
export const folderSnapshot = () => {
|
export const folderSnapshot = () => {
|
||||||
const { folders } = systemIOActor.getSnapshot().context
|
const { folders } = systemIOActor.getSnapshot().context
|
||||||
@ -9,3 +11,48 @@ export const defaultProjectFolderNameSnapshot = () => {
|
|||||||
const { defaultProjectFolderName } = systemIOActor.getSnapshot().context
|
const { defaultProjectFolderName } = systemIOActor.getSnapshot().context
|
||||||
return defaultProjectFolderName
|
return defaultProjectFolderName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From the application project directory go down to a project folder and list all the folders at that directory level
|
||||||
|
* application project directory: /home/documents/zoo-modeling-app-projects/
|
||||||
|
*
|
||||||
|
* /home/documents/zoo-modeling-app-projects/car-door/
|
||||||
|
* ├── handle
|
||||||
|
* ├── main.kcl
|
||||||
|
* └── window
|
||||||
|
*
|
||||||
|
* The two folders are handle and window
|
||||||
|
*
|
||||||
|
* @param {Object} params
|
||||||
|
* @param {string} params.projectFolderName - The name with no path information.
|
||||||
|
* @returns {FileEntry[]} An array of subdirectory names found at the root level of the specified project folder.
|
||||||
|
*/
|
||||||
|
export const getAllSubDirectoriesAtProjectRoot = ({
|
||||||
|
projectFolderName,
|
||||||
|
}: { projectFolderName: string }): FileEntry[] => {
|
||||||
|
const subDirectories: FileEntry[] = []
|
||||||
|
const { folders } = systemIOActor.getSnapshot().context
|
||||||
|
|
||||||
|
const projectFolder = folders.find((folder) => {
|
||||||
|
return folder.name === projectFolderName
|
||||||
|
})
|
||||||
|
|
||||||
|
// Find the subdirectories
|
||||||
|
if (projectFolder) {
|
||||||
|
// 1st level
|
||||||
|
const children = projectFolder.children
|
||||||
|
if (children) {
|
||||||
|
children.forEach((childFileOrDirectory) => {
|
||||||
|
// 2nd level
|
||||||
|
const secondLevelChild = childFileOrDirectory.children
|
||||||
|
// if secondLevelChild is null then it is a file
|
||||||
|
if (secondLevelChild && isArray(secondLevelChild)) {
|
||||||
|
// this is a directory!
|
||||||
|
subDirectories.push(childFileOrDirectory)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return subDirectories
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user