Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update lower-right corner units menu to read and edit inline settings annotations if present #5212

Merged
merged 18 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions e2e/playwright/testing-settings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -896,4 +896,53 @@ test.describe('Testing settings', () => {
})
}
)

test(`Change inline units setting`, async ({
page,
homePage,
context,
editor,
}) => {
const initialInlineUnits = 'yd'
const editedInlineUnits = { short: 'mm', long: 'Millimeters' }
const inlineSettingsString = (s: string) =>
`@settings(defaultLengthUnit = ${s})`
const unitsIndicator = page.getByRole('button', {
name: 'Current units are:',
})
const unitsChangeButton = (name: string) =>
page.getByRole('button', { name, exact: true })

await context.folderSetupFn(async (dir) => {
const bracketDir = join(dir, 'project-000')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cube.kcl'),
join(bracketDir, 'main.kcl')
)
})

await test.step(`Initial units from settings`, async () => {
await homePage.openProject('project-000')
await expect(unitsIndicator).toHaveText('Current units are: in')
})

await test.step(`Manually write inline settings`, async () => {
await editor.openPane()
await editor.replaceCode(
`fn cube`,
`${inlineSettingsString(initialInlineUnits)}
fn cube`
)
await expect(unitsIndicator).toContainText(initialInlineUnits)
})

await test.step(`Change units setting via lower-right control`, async () => {
await unitsIndicator.click()
await unitsChangeButton(editedInlineUnits.long).click()
await expect(
page.getByText(`Updated per-file units to ${editedInlineUnits.short}`)
).toBeVisible()
})
})
})
62 changes: 53 additions & 9 deletions src/components/UnitsMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
import { Popover } from '@headlessui/react'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { changeKclSettings, unitLengthToUnitLen } from 'lang/wasm'
import { baseUnitLabels, baseUnitsUnion } from 'lib/settings/settingsTypes'
import { codeManager, kclManager } from 'lib/singletons'
import { err, reportRejection } from 'lib/trap'
import { useEffect, useState } from 'react'
import toast from 'react-hot-toast'

export function UnitsMenu() {
const { settings } = useSettingsAuthContext()
const [hasPerFileLengthUnit, setHasPerFileLengthUnit] = useState(
Boolean(kclManager.fileSettings.defaultLengthUnit)
)
const [lengthSetting, setLengthSetting] = useState(
kclManager.fileSettings.defaultLengthUnit ||
settings.context.modeling.defaultUnit.current
)
useEffect(() => {
setHasPerFileLengthUnit(Boolean(kclManager.fileSettings.defaultLengthUnit))
setLengthSetting(
kclManager.fileSettings.defaultLengthUnit ||
settings.context.modeling.defaultUnit.current
)
}, [
kclManager.fileSettings.defaultLengthUnit,
settings.context.modeling.defaultUnit.current,
])
return (
<Popover className="relative pointer-events-auto">
{({ close }) => (
Expand All @@ -18,7 +40,7 @@ export function UnitsMenu() {
<div className="absolute w-[1px] h-[1em] bg-primary right-0 top-1/2 -translate-y-1/2"></div>
</div>
<span className="sr-only">Current units are:&nbsp;</span>
{settings.context.modeling.defaultUnit.current}
{lengthSetting}
</Popover.Button>
<Popover.Panel
className={`absolute bottom-full right-0 mb-2 w-48 bg-chalkboard-10 dark:bg-chalkboard-90
Expand All @@ -31,18 +53,40 @@ export function UnitsMenu() {
<button
className="flex items-center gap-2 m-0 py-1.5 px-2 cursor-pointer hover:bg-chalkboard-20 dark:hover:bg-chalkboard-80 border-none text-left"
onClick={() => {
settings.send({
type: 'set.modeling.defaultUnit',
data: {
level: 'project',
value: unit,
},
})
if (hasPerFileLengthUnit) {
const newCode = changeKclSettings(codeManager.code, {
defaultLengthUnits: unitLengthToUnitLen(unit),
defaultAngleUnits: { type: 'Degrees' },
})
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 ${unit}`)
})
.catch(reportRejection)
}
} else {
settings.send({
type: 'set.modeling.defaultUnit',
data: {
level: 'project',
value: unit,
},
})
}
close()
}}
>
<span className="flex-1">{baseUnitLabels[unit]}</span>
{unit === settings.context.modeling.defaultUnit.current && (
{unit === lengthSetting && (
<span className="text-chalkboard-60">current</span>
)}
</button>
Expand Down
14 changes: 13 additions & 1 deletion src/lang/KclSingleton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
SourceRange,
topLevelRange,
} from 'lang/wasm'
import { getNodeFromPath } from './queryAst'
import { getNodeFromPath, getSettingsAnnotation } from './queryAst'
import { codeManager, editorManager, sceneInfra } from 'lib/singletons'
import { Diagnostic } from '@codemirror/lint'
import { markOnce } from 'lib/performance'
Expand All @@ -34,6 +34,7 @@ import {
ModelingCmdReq_type,
} from '@kittycad/lib/dist/types/src/models'
import { Operation } from 'wasm-lib/kcl/bindings/Operation'
import { KclSettingsAnnotation } from 'lib/settings/settingsTypes'

interface ExecuteArgs {
ast?: Node<Program>
Expand Down Expand Up @@ -69,6 +70,7 @@ export class KclManager {
private _wasmInitFailed = true
private _hasErrors = false
private _switchedFiles = false
private _fileSettings: KclSettingsAnnotation = {}

engineCommandManager: EngineCommandManager

Expand Down Expand Up @@ -367,6 +369,8 @@ export class KclManager {
await this.disableSketchMode()
}

this.fileSettings = getSettingsAnnotation(ast)

this.logs = logs
this.errors = errors
// Do not add the errors since the program was interrupted and the error is not a real KCL error
Expand Down Expand Up @@ -697,6 +701,14 @@ export class KclManager {
_isAstEmpty(ast: Node<Program>) {
return ast.start === 0 && ast.end === 0 && ast.body.length === 0
}

get fileSettings() {
return this._fileSettings
}

set fileSettings(settings: KclSettingsAnnotation) {
this._fileSettings = settings
}
}

const defaultSelectionFilter: EntityType_type[] = [
Expand Down
45 changes: 44 additions & 1 deletion src/lang/queryAst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ import {
getConstraintType,
} from './std/sketchcombos'
import { err, Reason } from 'lib/trap'
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
jtran marked this conversation as resolved.
Show resolved Hide resolved
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { codeRefFromRange } from './std/artifactGraph'
import { ATTRIBUTE_NAME_ANGLE, ATTRIBUTE_NAME_LENGTH } from 'lib/constants'
import { UnitLength } from 'wasm-lib/kcl/bindings/UnitLength'
import { UnitAngle_type } from '@kittycad/lib/dist/types/src/models'
import { KclSettingsAnnotation } from 'lib/settings/settingsTypes'

/**
* Retrieves a node from a given path within a Program node structure, optionally stopping at a specified node type.
Expand Down Expand Up @@ -792,3 +795,43 @@ export function getObjExprProperty(
if (index === -1) return null
return { expr: node.properties[index].value, index }
}

/**
* Given an AST, returns the settings annotation object if it exists.
*/
export function getSettingsAnnotation(
jtran marked this conversation as resolved.
Show resolved Hide resolved
node?: Node<Program>
): KclSettingsAnnotation {
const settings: KclSettingsAnnotation = {}
jtran marked this conversation as resolved.
Show resolved Hide resolved
const maybeAnnotations = node?.nonCodeMeta?.startNodes?.filter(
(n) => n.value.type === 'annotation' && n.value.name.name === 'settings'
)
if (!maybeAnnotations?.length) return settings

// Use the last settings annotation
// we could merge all settings annotations, but that's not necessary for now
const annotation = maybeAnnotations.pop()

// For tsc
if (
!(
annotation?.value.type == 'annotation' &&
annotation.value.name.name === 'settings' &&
annotation.value.properties !== null
)
) {
return settings
}

annotation.value.properties.forEach((prop) => {
if ('name' in prop.value) {
if (prop.key.name === ATTRIBUTE_NAME_LENGTH) {
settings[ATTRIBUTE_NAME_LENGTH] = prop.value.name as UnitLength
} else if (prop.key.name === ATTRIBUTE_NAME_ANGLE) {
settings[ATTRIBUTE_NAME_ANGLE] = prop.value.name as UnitAngle_type
}
}
})

return settings
}
22 changes: 22 additions & 0 deletions src/lang/wasm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ import { Artifact } from './std/artifactGraph'
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
import { NumericSuffix } from 'wasm-lib/kcl/bindings/NumericSuffix'
import { MetaSettings } from 'wasm-lib/kcl/bindings/MetaSettings'
import { UnitLength } from 'wasm-lib/kcl/bindings/ModelingCmd'
import { UnitLen } from 'wasm-lib/kcl/bindings/UnitLen'

export type { Artifact } from 'wasm-lib/kcl/bindings/Artifact'
export type { ArtifactCommand } from 'wasm-lib/kcl/bindings/Artifact'
Expand Down Expand Up @@ -860,3 +862,23 @@ export function changeKclSettings(
return new Error('Caught error changing kcl settings: ' + e)
}
}

/**
* Convert a `UnitLength_type` to a `UnitLen`
*/
export function unitLengthToUnitLen(input: UnitLength): UnitLen {
switch (input) {
case 'm':
return { type: 'M' }
case 'cm':
return { type: 'Cm' }
case 'yd':
return { type: 'Yards' }
case 'ft':
return { type: 'Feet' }
case 'in':
return { type: 'Inches' }
default:
return { type: 'Mm' }
}
}
7 changes: 7 additions & 0 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,10 @@ export const ZOO_STUDIO_PROTOCOL = 'zoo-studio'
* to "open in desktop app" when present in the URL
*/
export const ASK_TO_OPEN_QUERY_PARAM = 'ask-open-desktop'

/**
* attribute names for default units. Also defined on rust side
* in src/wasm-lib/kcl/src/execution/annotations.rs
*/
export const ATTRIBUTE_NAME_LENGTH = 'defaultLengthUnit'
export const ATTRIBUTE_NAME_ANGLE = 'defaultAngleUnit'
10 changes: 10 additions & 0 deletions src/lib/settings/settingsTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import { AtLeast, PathValue, Paths } from 'lib/types'
import { CommandArgumentConfig } from 'lib/commandTypes'
import { Themes } from 'lib/theme'
import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType'
import { ATTRIBUTE_NAME_ANGLE, ATTRIBUTE_NAME_LENGTH } from 'lib/constants'
import {
UnitAngle_type,
UnitLength_type,
} from '@kittycad/lib/dist/types/src/models'
import { CameraOrbitType } from 'wasm-lib/kcl/bindings/CameraOrbitType'

export interface SettingsViaQueryString {
Expand Down Expand Up @@ -138,3 +143,8 @@ type RecursiveSettingsPayloads<T> = {
}

export type SaveSettingsPayload = RecursiveSettingsPayloads<typeof settings>

export interface KclSettingsAnnotation {
[ATTRIBUTE_NAME_LENGTH]?: UnitLength_type
[ATTRIBUTE_NAME_ANGLE]?: UnitAngle_type
}
Loading