* WIP: Add point-and-click Import for geometry Will eventually fix #6120 Right now the whole loop is there but the codemod doesn't work yet * Better pathToNOde, log on non-working cm dispatch call * Add workaround to updateModelingState not working * Back to updateModelingState with a skip flag * Better todo * Change working from Import to Insert, cleanups * Sister command in kclCommands to populate file options * Improve path selector * Unsure: move importAstMod to kclCommands onSubmit 😶 * Add e2e test * Clean up for review * Add native file menu entry and test * No await yo lint said so * WIP: UX improvements around foreign file imports Fixes #6152 * @lrev-Dev's suggestion to remove a comment Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch> * Update to scene.settled(cmdBar) * Add partNNN default name for alias * Lint * Lint * Fix unit tests * Add sad path insert test Thanks @Irev-Dev for the suggestion * Add step insert test * Lint * Add test for second foreign import thru file tree click * Add default value for local name alias * Aligning tests * Fix tests * Add padding for filenames starting with a digit --------- Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
289 lines
9.1 KiB
TypeScript
289 lines
9.1 KiB
TypeScript
import type { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
|
|
import toast from 'react-hot-toast'
|
|
|
|
import { CommandBarOverwriteWarning } from '@src/components/CommandBarOverwriteWarning'
|
|
import { DEV } from '@src/env'
|
|
import { updateModelingState } from '@src/lang/modelingWorkflows'
|
|
import { addImportAndInsert } from '@src/lang/modifyAst'
|
|
import {
|
|
changeKclSettings,
|
|
unitAngleToUnitAng,
|
|
unitLengthToUnitLen,
|
|
} from '@src/lang/wasm'
|
|
import type { Command, CommandArgumentOption } from '@src/lib/commandTypes'
|
|
import {
|
|
DEFAULT_DEFAULT_ANGLE_UNIT,
|
|
DEFAULT_DEFAULT_LENGTH_UNIT,
|
|
EXECUTION_TYPE_REAL,
|
|
FILE_EXT,
|
|
} from '@src/lib/constants'
|
|
import { getPathFilenameInVariableCase } from '@src/lib/desktop'
|
|
import { isDesktop } from '@src/lib/isDesktop'
|
|
import { copyFileShareLink } from '@src/lib/links'
|
|
import { baseUnitsUnion } from '@src/lib/settings/settingsTypes'
|
|
import { codeManager, editorManager, kclManager } from '@src/lib/singletons'
|
|
import { err, reportRejection } from '@src/lib/trap'
|
|
import type { IndexLoaderData } from '@src/lib/types'
|
|
import type { CommandBarContext } from '@src/machines/commandBarMachine'
|
|
import { IS_NIGHTLY_OR_DEBUG } from '@src/routes/utils'
|
|
|
|
interface OnSubmitProps {
|
|
sampleName: string
|
|
code: string
|
|
sampleUnits?: UnitLength_type
|
|
method: 'overwrite' | 'newFile'
|
|
}
|
|
|
|
interface KclCommandConfig {
|
|
// TODO: find a different approach that doesn't require
|
|
// special props for a single command
|
|
specialPropsForSampleCommand: {
|
|
onSubmit: (p: OnSubmitProps) => Promise<void>
|
|
providedOptions: CommandArgumentOption<string>[]
|
|
}
|
|
specialPropsForInsertCommand: {
|
|
providedOptions: CommandArgumentOption<string>[]
|
|
}
|
|
projectData: IndexLoaderData
|
|
authToken: string
|
|
settings: {
|
|
defaultUnit: UnitLength_type
|
|
}
|
|
}
|
|
|
|
export function kclCommands(commandProps: KclCommandConfig): Command[] {
|
|
return [
|
|
{
|
|
name: 'set-file-units',
|
|
displayName: 'Set file units',
|
|
description:
|
|
'Set the length unit for all dimensions not given explicit units in the current file.',
|
|
needsReview: false,
|
|
groupId: 'code',
|
|
icon: 'code',
|
|
args: {
|
|
unit: {
|
|
required: true,
|
|
inputType: 'options',
|
|
defaultValue:
|
|
kclManager.fileSettings.defaultLengthUnit ||
|
|
DEFAULT_DEFAULT_LENGTH_UNIT,
|
|
options: () =>
|
|
Object.values(baseUnitsUnion).map((v) => {
|
|
return {
|
|
name: v,
|
|
value: v,
|
|
isCurrent: kclManager.fileSettings.defaultLengthUnit
|
|
? v === kclManager.fileSettings.defaultLengthUnit
|
|
: v === DEFAULT_DEFAULT_LENGTH_UNIT,
|
|
}
|
|
}),
|
|
},
|
|
},
|
|
onSubmit: (data) => {
|
|
if (typeof data === 'object' && 'unit' in data) {
|
|
const newCode = changeKclSettings(codeManager.code, {
|
|
defaultLengthUnits: unitLengthToUnitLen(data.unit),
|
|
defaultAngleUnits: unitAngleToUnitAng(
|
|
kclManager.fileSettings.defaultAngleUnit ??
|
|
DEFAULT_DEFAULT_ANGLE_UNIT
|
|
),
|
|
})
|
|
if (err(newCode)) {
|
|
toast.error(`Failed to set per-file units: ${newCode.message}`)
|
|
} else {
|
|
codeManager.updateCodeStateEditor(newCode)
|
|
Promise.all([codeManager.writeToFile(), kclManager.executeCode()])
|
|
.then(() => {
|
|
toast.success(`Updated per-file units to ${data.unit}`)
|
|
})
|
|
.catch(reportRejection)
|
|
}
|
|
} else {
|
|
toast.error(
|
|
'Failed to set per-file units: no value provided to submit function. This is a bug.'
|
|
)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: 'Insert',
|
|
description: 'Insert from a file in the current project directory',
|
|
icon: 'import',
|
|
groupId: 'code',
|
|
hide: DEV || IS_NIGHTLY_OR_DEBUG ? 'web' : 'both',
|
|
needsReview: true,
|
|
reviewMessage:
|
|
'Reminder: point-and-click insert is in development and only supports one part instance per assembly.',
|
|
args: {
|
|
path: {
|
|
inputType: 'options',
|
|
required: true,
|
|
options: commandProps.specialPropsForInsertCommand.providedOptions,
|
|
},
|
|
localName: {
|
|
inputType: 'string',
|
|
required: true,
|
|
defaultValue: (context: CommandBarContext) => {
|
|
if (!context.argumentsToSubmit['path']) {
|
|
return
|
|
}
|
|
|
|
const path = context.argumentsToSubmit['path'] as string
|
|
return getPathFilenameInVariableCase(path)
|
|
},
|
|
},
|
|
},
|
|
onSubmit: (data) => {
|
|
if (!data) {
|
|
return new Error('No input provided')
|
|
}
|
|
|
|
const ast = kclManager.ast
|
|
const { path, localName } = data
|
|
const { modifiedAst, pathToImportNode, pathToInsertNode } =
|
|
addImportAndInsert({
|
|
node: ast,
|
|
path,
|
|
localName,
|
|
})
|
|
updateModelingState(
|
|
modifiedAst,
|
|
EXECUTION_TYPE_REAL,
|
|
{ kclManager, editorManager, codeManager },
|
|
{
|
|
skipUpdateAst: true,
|
|
focusPath: [pathToImportNode, pathToInsertNode],
|
|
}
|
|
).catch(reportRejection)
|
|
},
|
|
},
|
|
{
|
|
name: 'format-code',
|
|
displayName: 'Format Code',
|
|
description: 'Nicely formats the KCL code in the editor.',
|
|
needsReview: false,
|
|
groupId: 'code',
|
|
icon: 'code',
|
|
onSubmit: () => {
|
|
kclManager.format().catch(reportRejection)
|
|
},
|
|
},
|
|
{
|
|
name: 'open-kcl-example',
|
|
displayName: 'Open sample',
|
|
description: 'Imports an example KCL program into the editor.',
|
|
needsReview: true,
|
|
icon: 'code',
|
|
reviewMessage: ({ argumentsToSubmit }) =>
|
|
CommandBarOverwriteWarning({
|
|
heading:
|
|
'method' in argumentsToSubmit &&
|
|
argumentsToSubmit.method === 'newFile'
|
|
? 'Create a new file from sample?'
|
|
: 'Overwrite current file with sample?',
|
|
message:
|
|
'method' in argumentsToSubmit &&
|
|
argumentsToSubmit.method === 'newFile'
|
|
? 'This will create a new file in the current project and open it.'
|
|
: 'This will erase your current file and load the sample part.',
|
|
}),
|
|
groupId: 'code',
|
|
onSubmit(data) {
|
|
if (!data?.sample) {
|
|
return
|
|
}
|
|
const pathParts = data.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): Promise<OnSubmitProps> => {
|
|
if (!codeResponse.ok) {
|
|
console.error(
|
|
'Failed to fetch sample code:',
|
|
codeResponse.statusText
|
|
)
|
|
return Promise.reject(new Error('Failed to fetch sample code'))
|
|
}
|
|
const code = await codeResponse.text()
|
|
return {
|
|
sampleName: data.sample.split('/')[0] + FILE_EXT,
|
|
code,
|
|
method: data.method,
|
|
}
|
|
})
|
|
.then((props) => {
|
|
if (props?.code) {
|
|
commandProps.specialPropsForSampleCommand
|
|
.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: commandProps.specialPropsForSampleCommand.providedOptions,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'share-file-link',
|
|
displayName: 'Share current part (via Zoo link)',
|
|
description: 'Create a link that contains a copy of the current file.',
|
|
groupId: 'code',
|
|
needsReview: false,
|
|
icon: 'link',
|
|
onSubmit: () => {
|
|
copyFileShareLink({
|
|
token: commandProps.authToken,
|
|
code: codeManager.code,
|
|
name: commandProps.projectData.project?.name || '',
|
|
}).catch(reportRejection)
|
|
},
|
|
},
|
|
]
|
|
}
|