refactor selections (#876)
* migrate selection types * extract selection event into selections.ts * move code-mirror selection functions into selections.ts * move more selection logit out of code mirror and engine connection * add selection functions pure * tidy up naming * write a novel about how selections work * final comments
This commit is contained in:
@ -36,11 +36,8 @@ import { applyConstraintAngleBetween } from './Toolbar/SetAngleBetween'
|
||||
import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { pathMapToSelections } from 'lang/util'
|
||||
import {
|
||||
dispatchCodeMirrorCursor,
|
||||
setCodeMirrorCursor,
|
||||
useStore,
|
||||
} from 'useStore'
|
||||
import { useStore } from 'useStore'
|
||||
import { handleSelectionBatch, handleSelectionWithShift } from 'lib/selections'
|
||||
import { applyConstraintIntersect } from './Toolbar/Intersect'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
@ -270,25 +267,37 @@ export const ModelingMachineProvider = ({
|
||||
// I've found this the best way to deal with the editor without causing an infinite loop
|
||||
// and really we want the editor to be in charge of cursor positions and for `selectionRanges` mirror it
|
||||
// because we want to respect the user manually placing the cursor too.
|
||||
const selectionRangeTypeMap = setCodeMirrorCursor({
|
||||
|
||||
// for more details on how selections see `src/lib/selections.ts`.
|
||||
const { codeMirrorSelection, selectionRangeTypeMap } =
|
||||
handleSelectionWithShift({
|
||||
codeSelection: setSelections.selection,
|
||||
currestSelections: selectionRanges,
|
||||
editorView,
|
||||
isShiftDown,
|
||||
})
|
||||
return {
|
||||
selectionRangeTypeMap,
|
||||
if (codeMirrorSelection) {
|
||||
setTimeout(() => {
|
||||
editorView.dispatch({
|
||||
selection: codeMirrorSelection,
|
||||
})
|
||||
})
|
||||
}
|
||||
return { selectionRangeTypeMap }
|
||||
}
|
||||
// This DOES NOT set the `selectionRanges` in xstate context
|
||||
// same as comment above
|
||||
const { selectionRangeTypeMap } = dispatchCodeMirrorCursor({
|
||||
const { codeMirrorSelection, selectionRangeTypeMap } =
|
||||
handleSelectionBatch({
|
||||
selections: setSelections.selection,
|
||||
editorView,
|
||||
})
|
||||
return {
|
||||
selectionRangeTypeMap,
|
||||
if (codeMirrorSelection) {
|
||||
setTimeout(() => {
|
||||
editorView.dispatch({
|
||||
selection: codeMirrorSelection,
|
||||
})
|
||||
})
|
||||
}
|
||||
return { selectionRangeTypeMap }
|
||||
}),
|
||||
},
|
||||
guards: {
|
||||
|
@ -13,7 +13,8 @@ import { useConvertToVariable } from 'hooks/useToolbarGuards'
|
||||
import { Themes } from 'lib/theme'
|
||||
import { useMemo } from 'react'
|
||||
import { linter, lintGutter } from '@codemirror/lint'
|
||||
import { Selections, useStore } from 'useStore'
|
||||
import { useStore } from 'useStore'
|
||||
import { processCodeMirrorRanges } from 'lib/selections'
|
||||
import { LanguageServerClient } from 'editor/lsp'
|
||||
import kclLanguage from 'editor/lsp/language'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
@ -132,74 +133,17 @@ export const TextEditor = ({
|
||||
if (!editorView) {
|
||||
setEditorView(viewUpdate.view)
|
||||
}
|
||||
const ranges = viewUpdate.state.selection.ranges
|
||||
const eventInfo = processCodeMirrorRanges({
|
||||
codeMirrorRanges: viewUpdate.state.selection.ranges,
|
||||
selectionRanges,
|
||||
selectionRangeTypeMap,
|
||||
})
|
||||
if (!eventInfo) return
|
||||
|
||||
const isChange =
|
||||
ranges.length !== selectionRanges.codeBasedSelections.length ||
|
||||
ranges.some(({ from, to }, i) => {
|
||||
return (
|
||||
from !== selectionRanges.codeBasedSelections[i].range[0] ||
|
||||
to !== selectionRanges.codeBasedSelections[i].range[1]
|
||||
send(eventInfo.modelingEvent)
|
||||
eventInfo.engineEvents.forEach((event) =>
|
||||
engineCommandManager.sendSceneCommand(event)
|
||||
)
|
||||
})
|
||||
|
||||
if (!isChange) return
|
||||
const codeBasedSelections: Selections['codeBasedSelections'] = ranges.map(
|
||||
({ from, to }) => {
|
||||
if (selectionRangeTypeMap[to]) {
|
||||
return {
|
||||
type: selectionRangeTypeMap[to],
|
||||
range: [from, to],
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: 'default',
|
||||
range: [from, to],
|
||||
}
|
||||
}
|
||||
)
|
||||
const idBasedSelections = codeBasedSelections
|
||||
.map(({ type, range }) => {
|
||||
// TODO #868: loops over all artifacts will become inefficient at a large scale
|
||||
const entriesWithOverlap = Object.entries(
|
||||
engineCommandManager.artifactMap || {}
|
||||
).filter(([_, artifact]) => {
|
||||
return artifact.range && isOverlap(artifact.range, range)
|
||||
? artifact
|
||||
: false
|
||||
})
|
||||
if (entriesWithOverlap.length) {
|
||||
const [id, artifact] = entriesWithOverlap?.[0]
|
||||
return {
|
||||
type,
|
||||
id:
|
||||
type === 'line-end' &&
|
||||
artifact.type === 'result' &&
|
||||
artifact.headVertexId
|
||||
? artifact.headVertexId
|
||||
: id,
|
||||
}
|
||||
}
|
||||
return null
|
||||
})
|
||||
.filter(Boolean) as any
|
||||
|
||||
engineCommandManager.cusorsSelected({
|
||||
otherSelections: [],
|
||||
idBasedSelections,
|
||||
})
|
||||
|
||||
selectionRanges &&
|
||||
send({
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'mirrorCodeMirrorSelections',
|
||||
selection: {
|
||||
...selectionRanges,
|
||||
codeBasedSelections,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const editorExtensions = useMemo(() => {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Selections, toolTips } from '../../useStore'
|
||||
import { toolTips } from '../../useStore'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Program, Value, VariableDeclarator } from '../../lang/wasm'
|
||||
import {
|
||||
getNodePathFromSourceRange,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Selections, toolTips } from '../../useStore'
|
||||
import { toolTips } from '../../useStore'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Program, Value, VariableDeclarator } from '../../lang/wasm'
|
||||
import {
|
||||
getNodePathFromSourceRange,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { toolTips } from '../../useStore'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Program, ProgramMemory, Value } from '../../lang/wasm'
|
||||
import {
|
||||
getNodePathFromSourceRange,
|
||||
@ -10,7 +11,6 @@ import {
|
||||
transformAstSketchLines,
|
||||
} from '../../lang/std/sketchcombos'
|
||||
import { kclManager } from 'lang/KclSinglton'
|
||||
import { Selections } from 'useStore'
|
||||
|
||||
export function horzVertInfo(
|
||||
selectionRanges: Selections,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Selections, toolTips } from '../../useStore'
|
||||
import { toolTips } from '../../useStore'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { BinaryPart, Program, Value, VariableDeclarator } from '../../lang/wasm'
|
||||
import {
|
||||
getNodePathFromSourceRange,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Selections, toolTips } from '../../useStore'
|
||||
import { toolTips } from '../../useStore'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Program, Value } from '../../lang/wasm'
|
||||
import {
|
||||
getNodePathFromSourceRange,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Selections, toolTips } from '../../useStore'
|
||||
import { toolTips } from '../../useStore'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { BinaryPart, Program, Value } from '../../lang/wasm'
|
||||
import {
|
||||
getNodePathFromSourceRange,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Selections, toolTips } from '../../useStore'
|
||||
import { toolTips } from '../../useStore'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { BinaryPart, Program, Value, VariableDeclarator } from '../../lang/wasm'
|
||||
import {
|
||||
getNodePathFromSourceRange,
|
||||
|
@ -14,7 +14,7 @@ import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
|
||||
import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst'
|
||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||
import { kclManager } from 'lang/KclSinglton'
|
||||
import { Selections } from 'useStore'
|
||||
import { Selections } from 'lib/selections'
|
||||
|
||||
const getModalInfo = createInfoModal(GetInfoModal)
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Selections, toolTips } from '../../useStore'
|
||||
import { toolTips } from '../../useStore'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { BinaryPart, Program, Value } from '../../lang/wasm'
|
||||
import {
|
||||
getNodePathFromSourceRange,
|
||||
|
@ -4,6 +4,7 @@ import { engineCommandManager } from '../lang/std/engineConnection'
|
||||
import { useModelingContext } from './useModelingContext'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { SourceRange } from 'lang/wasm'
|
||||
import { getEventForSelectWithPoint } from 'lib/selections'
|
||||
|
||||
export function useEngineConnectionSubscriptions() {
|
||||
const { setHighlightRange, highlightRange } = useStore((s) => ({
|
||||
@ -37,63 +38,11 @@ export function useEngineConnectionSubscriptions() {
|
||||
})
|
||||
const unSubClick = engineCommandManager.subscribeTo({
|
||||
event: 'select_with_point',
|
||||
callback: ({ data }) => {
|
||||
if (!data?.entity_id) {
|
||||
send({
|
||||
type: 'Set selection',
|
||||
data: { selectionType: 'singleCodeCursor' },
|
||||
callback: async (engineEvent) => {
|
||||
const event = await getEventForSelectWithPoint(engineEvent, {
|
||||
sketchEnginePathId: context.sketchEnginePathId,
|
||||
})
|
||||
return
|
||||
}
|
||||
const sourceRange =
|
||||
engineCommandManager.artifactMap[data.entity_id]?.range
|
||||
if (engineCommandManager.artifactMap[data.entity_id]) {
|
||||
send({
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: { range: sourceRange, type: 'default' },
|
||||
},
|
||||
})
|
||||
} else {
|
||||
// selected a vertex
|
||||
engineCommandManager
|
||||
.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'path_get_curve_uuids_for_vertices',
|
||||
vertex_ids: [data.entity_id],
|
||||
path_id: context.sketchEnginePathId,
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
const curveIds = res?.data?.data?.curve_ids
|
||||
const ranges: RangeAndId[] = curveIds
|
||||
.map(
|
||||
(id: string): RangeAndId => ({
|
||||
id,
|
||||
range: engineCommandManager.artifactMap[id].range,
|
||||
})
|
||||
)
|
||||
.sort((a: RangeAndId, b: RangeAndId) => a.range[0] - b.range[0])
|
||||
// default to the head of the curve selected
|
||||
const _sourceRange = ranges?.[0].range
|
||||
const artifact = engineCommandManager.artifactMap[ranges?.[0]?.id]
|
||||
if (artifact.type === 'result') {
|
||||
artifact.headVertexId = data.entity_id
|
||||
}
|
||||
send({
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
// line-end is used to indicate that headVertexId should be sent to the engine as "selected"
|
||||
// not the whole curve
|
||||
selection: { range: _sourceRange, type: 'line-end' },
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
send(event)
|
||||
},
|
||||
})
|
||||
return () => {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Selections, executeAst, executeCode } from 'useStore'
|
||||
import { executeAst, executeCode } from 'useStore'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { KCLError } from './errors'
|
||||
import {
|
||||
EngineCommandManager,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Selection, ToolTip } from '../useStore'
|
||||
import { ToolTip } from '../useStore'
|
||||
import { Selection } from 'lib/selections'
|
||||
import {
|
||||
Program,
|
||||
CallExpression,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Selection, ToolTip } from '../useStore'
|
||||
import { ToolTip } from '../useStore'
|
||||
import { Selection } from 'lib/selections'
|
||||
import {
|
||||
BinaryExpression,
|
||||
Program,
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { SourceRange } from 'lang/wasm'
|
||||
import { Selections } from 'useStore'
|
||||
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_CONNECTION_TIMEOUT_MS } from 'env'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { exportSave } from 'lib/exportSave'
|
||||
@ -911,30 +910,6 @@ export class EngineCommandManager {
|
||||
this.engineConnection?.send(deletCmd)
|
||||
})
|
||||
}
|
||||
cusorsSelected(selections: {
|
||||
otherSelections: Selections['otherSelections']
|
||||
idBasedSelections: { type: string; id: string }[]
|
||||
}) {
|
||||
if (!this.engineConnection?.isReady()) {
|
||||
console.log('engine connection isnt ready')
|
||||
return
|
||||
}
|
||||
this.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'select_clear',
|
||||
},
|
||||
cmd_id: uuidv4(),
|
||||
})
|
||||
this.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'select_add',
|
||||
entities: selections.idBasedSelections.map((s) => s.id),
|
||||
},
|
||||
cmd_id: uuidv4(),
|
||||
})
|
||||
}
|
||||
sendSceneCommand(command: EngineCommand): Promise<any> {
|
||||
if (this.engineConnection === undefined) {
|
||||
return Promise.resolve()
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
transformAstSketchLines,
|
||||
} from './sketchcombos'
|
||||
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
|
||||
import { Selection } from '../../useStore'
|
||||
import { Selection } from 'lib/selections'
|
||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||
|
||||
beforeAll(() => initPromise)
|
||||
|
@ -7,7 +7,8 @@ import {
|
||||
ConstraintType,
|
||||
getConstraintLevelFromSourceRange,
|
||||
} from './sketchcombos'
|
||||
import { Selections, ToolTip } from '../../useStore'
|
||||
import { ToolTip } from '../../useStore'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||
|
||||
beforeAll(() => initPromise)
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { TransformCallback } from './stdTypes'
|
||||
import { Selections, toolTips, ToolTip, Selection } from '../../useStore'
|
||||
import { toolTips, ToolTip } from '../../useStore'
|
||||
import { Selections, Selection } from 'lib/selections'
|
||||
import {
|
||||
CallExpression,
|
||||
Program,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Selections, StoreState } from '../useStore'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Program, PathToNode } from './wasm'
|
||||
import { getNodeFromPath } from './queryAst'
|
||||
import { ArtifactMap } from './std/engineConnection'
|
||||
|
326
src/lib/selections.ts
Normal file
326
src/lib/selections.ts
Normal file
@ -0,0 +1,326 @@
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { engineCommandManager } from 'lang/std/engineConnection'
|
||||
import { SourceRange } from 'lang/wasm'
|
||||
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { EditorSelection } from '@codemirror/state'
|
||||
import { kclManager } from 'lang/KclSinglton'
|
||||
import { SelectionRange } from '@uiw/react-codemirror'
|
||||
import { isOverlap } from 'lib/utils'
|
||||
|
||||
/*
|
||||
How selections work is complex due to the nature that we rely on the engine
|
||||
to tell what has been selected after we send a click command. But than the
|
||||
app needs these selections to be based on cursors, therefore the app must
|
||||
be in control of selections. On top of that because we need to set cursor
|
||||
positions in code-mirror for selections, both from app logic, and still
|
||||
allow the user to add multiple cursors like a normal editor, it's best to
|
||||
let code mirror control cursor positions and assosiate those source ranges
|
||||
with entity ids from code-mirror events later.
|
||||
|
||||
So it's a lot of back and forth. conceptually the back and forth is:
|
||||
|
||||
1) we send a click command to the engine
|
||||
2) the engine sends back ids of entities that were clicked
|
||||
3) we associate that source ranges with those ids
|
||||
4) we set the codemirror selection based on those source ranges (taking
|
||||
into account if the user is holding shift to add to current selections
|
||||
or not). we also create and remember a SelectionRangeTypeMap
|
||||
5) Code mirror fires a an event that cursors have changed, we loop through
|
||||
these ranges and associate them with entity ids again with the ArtifactMap,
|
||||
but also we can pick up selection types using the SelectionRangeTypeMap
|
||||
6) we clear all previous selections in the engine and set the new ones
|
||||
|
||||
The above is less likely to get stale but below is some more details,
|
||||
because this wonders all over the code-base, I've tried to centeralise it
|
||||
by putting relevant utils in this file. All of the functions below are
|
||||
pure with the exception of getEventForSelectWithPoint which makes a call
|
||||
to the engine, but it's a query call (not mutation) so I'm okay with this.
|
||||
Actual side effects that change cursors or tell the engine what's selected
|
||||
are still done throughout the in their relevant parts in the codebase.
|
||||
|
||||
In detail:
|
||||
|
||||
1) Click commands are mostly sent in stream.tsx search for
|
||||
"select_with_point"
|
||||
2) The handler for when the engine sends back entitiy ids calls
|
||||
getEventForSelectWithPoint, it fires an XState event to update our
|
||||
selections is xstate context
|
||||
3 and 4) The XState handler for the above uses handleSelectionBatch and
|
||||
handleSelectionWithShift to update the selections in xstate context as
|
||||
well as returning our SelectionRangeTypeMap and a codeMirror specific
|
||||
event to be dispatched.
|
||||
5) The codeMirror handler for changes to the cursor uses
|
||||
processCodeMirrorRanges to associate the ranges back with their original
|
||||
types and the entity ids (the id can vary depending on the type, as
|
||||
there's only one source range for a given segment, but depending on if
|
||||
the user selected the segment directly or the vertex, the id will be
|
||||
different)
|
||||
6) We take all of the ids and create events for the engine with
|
||||
resetAndSetEngineEntitySelectionCmds
|
||||
|
||||
An important note is that if a user changes the cursor directly themselves
|
||||
then they skip directly to step 5, And these selections get a type of
|
||||
"default".
|
||||
|
||||
There are a few more nuances than this, but best to find them in the code.
|
||||
*/
|
||||
|
||||
export type Axis = 'y-axis' | 'x-axis' | 'z-axis'
|
||||
|
||||
export type Selection = {
|
||||
type:
|
||||
| 'default'
|
||||
| 'line-end'
|
||||
| 'line-mid'
|
||||
| 'face'
|
||||
| 'point'
|
||||
| 'edge'
|
||||
| 'line'
|
||||
| 'arc'
|
||||
| 'all'
|
||||
range: SourceRange
|
||||
}
|
||||
export type Selections = {
|
||||
otherSelections: Axis[]
|
||||
codeBasedSelections: Selection[]
|
||||
}
|
||||
|
||||
export interface SelectionRangeTypeMap {
|
||||
[key: number]: Selection['type']
|
||||
}
|
||||
|
||||
interface RangeAndId {
|
||||
id: string
|
||||
range: SourceRange
|
||||
}
|
||||
|
||||
export async function getEventForSelectWithPoint(
|
||||
{
|
||||
data,
|
||||
}: Extract<
|
||||
Models['OkModelingCmdResponse_type'],
|
||||
{ type: 'select_with_point' }
|
||||
>,
|
||||
{ sketchEnginePathId }: { sketchEnginePathId: string }
|
||||
): Promise<ModelingMachineEvent> {
|
||||
if (!data?.entity_id) {
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: { selectionType: 'singleCodeCursor' },
|
||||
}
|
||||
}
|
||||
const sourceRange = engineCommandManager.artifactMap[data.entity_id]?.range
|
||||
if (engineCommandManager.artifactMap[data.entity_id]) {
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: { range: sourceRange, type: 'default' },
|
||||
},
|
||||
}
|
||||
}
|
||||
// selected a vertex
|
||||
const res = await engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'path_get_curve_uuids_for_vertices',
|
||||
vertex_ids: [data.entity_id],
|
||||
path_id: sketchEnginePathId,
|
||||
},
|
||||
})
|
||||
const curveIds = res?.data?.data?.curve_ids
|
||||
const ranges: RangeAndId[] = curveIds
|
||||
.map(
|
||||
(id: string): RangeAndId => ({
|
||||
id,
|
||||
range: engineCommandManager.artifactMap[id].range,
|
||||
})
|
||||
)
|
||||
.sort((a: RangeAndId, b: RangeAndId) => a.range[0] - b.range[0])
|
||||
// default to the head of the curve selected
|
||||
const _sourceRange = ranges?.[0].range
|
||||
const artifact = engineCommandManager.artifactMap[ranges?.[0]?.id]
|
||||
if (artifact.type === 'result') {
|
||||
artifact.headVertexId = data.entity_id
|
||||
}
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
// line-end is used to indicate that headVertexId should be sent to the engine as "selected"
|
||||
// not the whole curve
|
||||
selection: { range: _sourceRange, type: 'line-end' },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function handleSelectionBatch({
|
||||
selections,
|
||||
}: {
|
||||
selections: Selections
|
||||
}): {
|
||||
selectionRangeTypeMap: SelectionRangeTypeMap
|
||||
codeMirrorSelection?: EditorSelection
|
||||
} {
|
||||
const ranges: ReturnType<typeof EditorSelection.cursor>[] = []
|
||||
const selectionRangeTypeMap: SelectionRangeTypeMap = {}
|
||||
selections.codeBasedSelections.forEach(({ range, type }) => {
|
||||
if (range?.[1]) {
|
||||
ranges.push(EditorSelection.cursor(range[1]))
|
||||
selectionRangeTypeMap[range[1]] = type
|
||||
}
|
||||
})
|
||||
if (ranges.length)
|
||||
return {
|
||||
selectionRangeTypeMap,
|
||||
codeMirrorSelection: EditorSelection.create(
|
||||
ranges,
|
||||
selections.codeBasedSelections.length - 1
|
||||
),
|
||||
}
|
||||
|
||||
return {
|
||||
selectionRangeTypeMap,
|
||||
}
|
||||
}
|
||||
|
||||
export function handleSelectionWithShift({
|
||||
codeSelection,
|
||||
currestSelections,
|
||||
isShiftDown,
|
||||
}: {
|
||||
codeSelection?: Selection
|
||||
currestSelections: Selections
|
||||
isShiftDown: boolean
|
||||
}): {
|
||||
selectionRangeTypeMap: SelectionRangeTypeMap
|
||||
codeMirrorSelection?: EditorSelection
|
||||
} {
|
||||
const code = kclManager.code
|
||||
if (!codeSelection)
|
||||
return handleSelectionBatch({
|
||||
selections: {
|
||||
otherSelections: currestSelections.otherSelections,
|
||||
codeBasedSelections: [
|
||||
{
|
||||
range: [0, code.length ? code.length - 1 : 0],
|
||||
type: 'default',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
const selections: Selections = {
|
||||
...currestSelections,
|
||||
codeBasedSelections: isShiftDown
|
||||
? [...currestSelections.codeBasedSelections, codeSelection]
|
||||
: [codeSelection],
|
||||
}
|
||||
return handleSelectionBatch({ selections })
|
||||
}
|
||||
|
||||
type SelectionToEngine = { type: Selection['type']; id: string }
|
||||
|
||||
export function processCodeMirrorRanges({
|
||||
codeMirrorRanges,
|
||||
selectionRanges,
|
||||
selectionRangeTypeMap,
|
||||
}: {
|
||||
codeMirrorRanges: readonly SelectionRange[]
|
||||
selectionRanges: Selections
|
||||
selectionRangeTypeMap: SelectionRangeTypeMap
|
||||
}): null | {
|
||||
modelingEvent: ModelingMachineEvent
|
||||
engineEvents: Models['WebSocketRequest_type'][]
|
||||
} {
|
||||
const isChange =
|
||||
codeMirrorRanges.length !== selectionRanges.codeBasedSelections.length ||
|
||||
codeMirrorRanges.some(({ from, to }, i) => {
|
||||
return (
|
||||
from !== selectionRanges.codeBasedSelections[i].range[0] ||
|
||||
to !== selectionRanges.codeBasedSelections[i].range[1]
|
||||
)
|
||||
})
|
||||
|
||||
if (!isChange) return null
|
||||
const codeBasedSelections: Selections['codeBasedSelections'] =
|
||||
codeMirrorRanges.map(({ from, to }) => {
|
||||
if (selectionRangeTypeMap[to]) {
|
||||
return {
|
||||
type: selectionRangeTypeMap[to],
|
||||
range: [from, to],
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: 'default',
|
||||
range: [from, to],
|
||||
}
|
||||
})
|
||||
const idBasedSelections: SelectionToEngine[] = codeBasedSelections
|
||||
.map(({ type, range }): null | SelectionToEngine => {
|
||||
// TODO #868: loops over all artifacts will become inefficient at a large scale
|
||||
const entriesWithOverlap = Object.entries(
|
||||
engineCommandManager.artifactMap || {}
|
||||
).filter(([_, artifact]) => {
|
||||
return artifact.range && isOverlap(artifact.range, range)
|
||||
? artifact
|
||||
: false
|
||||
})
|
||||
if (entriesWithOverlap.length) {
|
||||
const [id, artifact] = entriesWithOverlap?.[0]
|
||||
return {
|
||||
type,
|
||||
id:
|
||||
type === 'line-end' &&
|
||||
artifact.type === 'result' &&
|
||||
artifact.headVertexId
|
||||
? artifact.headVertexId
|
||||
: id,
|
||||
}
|
||||
}
|
||||
return null
|
||||
})
|
||||
.filter(Boolean) as any
|
||||
|
||||
if (!selectionRanges) return null
|
||||
return {
|
||||
modelingEvent: {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'mirrorCodeMirrorSelections',
|
||||
selection: {
|
||||
...selectionRanges,
|
||||
codeBasedSelections,
|
||||
},
|
||||
},
|
||||
},
|
||||
engineEvents: resetAndSetEngineEntitySelectionCmds(idBasedSelections),
|
||||
}
|
||||
}
|
||||
|
||||
export function resetAndSetEngineEntitySelectionCmds(
|
||||
selections: SelectionToEngine[]
|
||||
): Models['WebSocketRequest_type'][] {
|
||||
if (!engineCommandManager.engineConnection?.isReady()) {
|
||||
console.log('engine connection isnt ready')
|
||||
return []
|
||||
}
|
||||
return [
|
||||
{
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'select_clear',
|
||||
},
|
||||
cmd_id: uuidv4(),
|
||||
},
|
||||
{
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'select_add',
|
||||
entities: selections.map(({ id }) => id),
|
||||
},
|
||||
cmd_id: uuidv4(),
|
||||
},
|
||||
]
|
||||
}
|
@ -1,7 +1,12 @@
|
||||
import { PathToNode } from 'lang/wasm'
|
||||
import { engineCommandManager } from 'lang/std/engineConnection'
|
||||
import { isReducedMotion } from 'lang/util'
|
||||
import { Axis, Selection, SelectionRangeTypeMap, Selections } from 'useStore'
|
||||
import {
|
||||
Axis,
|
||||
Selection,
|
||||
SelectionRangeTypeMap,
|
||||
Selections,
|
||||
} from 'lib/selections'
|
||||
import { assign, createMachine } from 'xstate'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||
@ -59,30 +64,7 @@ export type SetSelections =
|
||||
selection: Selections
|
||||
}
|
||||
|
||||
export const modelingMachine = createMachine(
|
||||
{
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogDMAVgDsAOmEiAjLMGCAHIIAsAJlHKANCACeiaQDZBEsctGrVATmmiaNQQF8H2tBhz5x2CJjAEAymDsAASwWGDknNy0DEggLGyRPLECCMrCCuL20qrCRpYagvnaegjZBtLiBqLploIGCtZVyk4u6Fh4UJ7evgAicGERQSx47NG88RxcSaApdcKSNKIKi8ppwsrLwsX60oriqgaWBgbKImpWqi0gru0eXj4EfaE+g5AwY7ETibyzx5lLu1sygM61ERV0+ho0mU4gURlOwihlis2SuN3cnXuvX6L2CJD42FgH2YrEm3B+QnM4mqdhoyPUILqBm2CAaFTS5houQUCMczmubQxXQeAVxQ1QI2JcVJ32SiGUXPEhjBtWklgaokMzIhqVUNHEG0WgjVqkEUN2aMFHWFvlF4WCbzAUq+UwpqRoGQ2pwacPW4JKJxhgYU0kWu3Wymklrc1qx-gGeIJRPo4xlrrlqUEqkqdMKOSRNEOLPWGTVhkswlU0O5fNaMbu3XjYoAZiRKM60+SMwqMgY7Go6mbalCWaIjLCjtJ0n36tUFNHbpjGwBRXDsMAAJxCAGtAuQABYdhLpmY7SPiSzAyOXwGFUQs01BtXVEEV9VSZr89Gxldrzc7vdD2kGISWPLtTwQepBGpEN8lDGhVBDLYdTUfVZzvYFQ3MS4vytBsHieBMglbdsU0+Ttpn4Sk0KzBR1EKdING1EozQqYxajyNQ6MOBchTjO1BhITBMCPMlKJSSNoIsEEllQrlhGQko9RhZEDFUL1vWEUMcLrRcbUeHF7SCISRLI0CxLdRR2ROJQwQQ2RmMQENJD1OxjR5aE+1rAV6yXB4wD4dgNwAVwwIIRjANdRNlCDlGRJU6kfapK0vdYWSnBQJEsfJMtODYrM-XS+MbAKgtCsBwr-KLgNTMDxPlawJ0DKtXNkLl0o2NjNULPMPQ2HSfL0vxd3YA9iDIShMGGwDopPKjSg0fUaDURFFoVbJ7x1S9oOSmQcl2CtRF461ptG-dxFOg8AElGwE4JhiiszpTqt1pB9C8NA8plynVFlyn1EMzVfdTrAU46PEu87IZukUiNCKAAFtItGJ6XXA+a3vVD6vV2Y41IQlkzAMakLCnD0K0jK9wc6SGLpG67G0IsUHpRkDnosjM3oUi91SsRYjmUywWRUDJRFEY0PXzdQrGpunALls6YexZ4jPhpHHrZtH6tKHkJHkLJ1AUGpsofQxxBEWpxayCXFFl2noZXABHYLsCYIJ2FQVBTM1ijLK5Njp20wtPMEUc+yVRC4vgpZlqjXDfIVg9E-3JWCGXZ3XaCBHUAANwqj2vdm9HZg9NjpPUZbIykFkKYNTD1nsGycjt+modb1OAmCFWIimIvtbVMwczF4wtXqRyEDHfVsiNwtahyTU46Kk7W+T1PkBIXcQjARHkaCPON04cghL716kKVdYjErEWFEy9LoXZKsQT7I3jWEFv5Ydh5183tXd-3VANzYAAF7cHYMfVGvtOZggyOkewqkjBqQrOlOBlQqjyCrEoacR145DRXp-XwRBuCwCCiQPAQR-6AJAWuISQQICEjARQJ0ECXpQOqJIawHl0hMizOlfI8xsrqTBHZD084cFCntu3RshDcDEI3KQ3Ae9NyHxoXQ4hE0mE+xYRBGwlh9SIlNEoRQvMFDpRfgacohhIyS0jO-M6q8pFEJIWQsgUAfAn05nCFS8g6j2AOPSIWOo9SZSVBWBSthMpiykLYpO+DiCOLkWQnw+B2CHmYRzbRRsMig2hCI0MwhLymyxrYPI5g5zZEKoNcReDJEPGkbI+RQxNxMEinQ8gwVMAkC3KohhpFNHpIxjSTIGweRV0LMtRSiBTSXkqNyMW8kBzRLboBVOdSnEKIocA0BJkdDGRwFAXA7jtFLGJkoBC5gGhqXqBM0o6kYSRg0EsRCdhLyWEWfY2p8SGn72UcJHZQlsD7MORjZY0EFTAiqIWcwYh0oiB2rINQyIRBaThG82JqyEkKLAM7GhSSoApKBSkN6NgDS+iqDkCxaUdTVhgVpYcfZrBuVRTUghnyyFME6SZagaSYrAsRDzVYZZVjqACSULU8wpxmGROsLMhQDBMuWQ4mRayggbjANnPOQRyCsrXMmPpPLCVi2zOkNQ3oRBIhMVShSlgEpG0RO+coYs3kABk8AVQACqe0wGnDObt1X509QSoQigYGPIUmqdInlRxKCVFyHqU58liwqd+CGK8XW4HdZ68QAAFCUa4ggAEEIAYAgAQQtEBxSSm5XNEuwJzYNHSFINSJqtA6kRBUSxUJMp+ltd5ZNNNU2uqCB6r2F1t7q2CGWyApai0Vo1rVfpsxEIVEOJlLyZpCz1EJqaA0Bx1JPyJdJZ1g7h2YFHTvPNk6S2EIRkwHw64gjuA0fO-VQhgQSBnsPNIDQciExvkqXK9zlpWDike9NQ7M0BHPROotU704uzdumgA7u7ANVbi5CArvsN6Y8EJHCOMLN6ISbxaV2PWpNeF+3yzTRmkdV1cAcAIIGhA6k1T-CRZqXIVc-r2EyFcsWRLESFlAzR09dGGNUBquRLR80pnWrpKaOBSgQQtpKJWYmhQuRGz5rYMcwnwMjoAHKoCCDmkYsBp3lpZkxhk1rrDqDBCleTxZ1JKgVPkukWRGJvLQBqk98Yu4Jl7mh7WTIsq1DNAcFQ7nNolA0PMU4fZZ7uXFuRhOtMfMifEBlyq4nguWXc9SLMBxp66OhMgv4hwZwKmNKcS83nc6Zey3gcTknzIvoQCLcVxpLzmHqNYZEd8rCSDyIUaEFMZZiOXvLbLJ6ssNZy+wRjqg9XVqEOkDIFzTTmGq15O+ujKhwiQqXXYFpJspvlvmxDpDggbKoWAzAtD6HqMqs2VABAIDcDAJ4XAOdUC7nEDAdgABaW7WzMBA7wK96zdF+FvkpjfTkMX9DZWtUsdUCFBOmkMG8y713yEAM2dQh73TnuQ7e5uDcADxC3pIOwV7G4EYA8CCDgnd2hIQ9wFDvLGZEIHFQYUU4dJqt1F4eUD61QwRQXFscHHV2OCKIPtgI+xOnuMJe29j76bvu-f+4DoH3ylfs7J9D+QB2UtVinBxUx44QwWBqycaE4tZd44N8rx7ai1dk4IBTqnNO6cAMZ3r13RvOeoBNzCJFfNkRfpF1Sui2ZtIRs1KpW2Z3KN2Nx-LlxPh1fvc+9rv7X29fZ7ABzrnK30PMaUBtxExxbAekONcqsdRYRNGWBseopxe0UeTuITPeJ8A569z7jc1OOn+4Z0z4HJey9h+5xBSO+ochzHVJVkQ489T5EyIiP0Eb9Hd7SyvfvD7Ip4v3LnzXX28A66L8z3FKTZ8m+JuqE0+SLCXg3wceYtIrDHFUhE53eXe-c-YfDcSnUfP3enQPO-U-B-Y3efGTWySoOKG+bKGwKsaFQJCwI1bKYEG+SFLSAaPtXvY-ZpDcZpXAVpdpTpd3HpCqL3S-AvXXZnMgigqgjpDcIHEnRhR-BAlIQ2GEVaI2IwTUNQDfNzc2PqNIYcNtQA+6JpFpJXagrpVXSgXPEfMfWnKAqfIHVgxQtpDgrg1Q0veAivbWAQhYJYXIEeMQh8EQPWKQ-JM0WQtPZOIIXAYzEiW0IiDAVsdpe6DpdNJjcpa1fJZFa8EZIwQmKcC8UMXGI2aecwA-PSEgOXTgfALeXEKYfzTI+0ILMw16YOakXRMcHkY4SMKcdKTjakPsDYdUVdS8URJeDwA8cIbcDoXInubgRjPg-Qe5fYTjDkA4M5KotSElVYc0HxGxVw1o8gdojI7uRIRjVrdmdre+IMY0CuDQdUYEEVPoqQSoXYaQxFD0BCJwfkDwjAeAWIPtZ9VbDrOkSQGQOQRQFQdQFTRAIHfhaBHkfJI2RHbBZovyMAO4yvYEI1DTKoQ4TUeEUOKlQcCOXROkN6EMLvRZUE-uQ4bMIDVaMFKEdQFkZyI2WoQGYk8seVRWboDE16RFfYJkKEbxWVJHBAZEJfBSRkkMJI1LXBKjY9T1aknnA4EwRFBNKQbAj45jM0SoXA+QJEiwJIvTWbHoT7AUiCYwKU3IcWJdTtBkZzaCSEvJZEdUZIqpXksDWbUzC9GDCAVUmTXRfU-IRQAoeQE4ZkysIMNzeokEdYCwRUiDMdXeS9W0xdcOBzCmcFIcZzD0rkL0hSc4P02jejdgYMyZV+dhU0UUordSYsQjO3WoPKQXI4BM09IzEzXNdga4tre4g4WtBLE0TYm+ced05AmMjzV+TKerXzfkqTBdINMWC8clMwN9OoaQGuIbD-TTasHKJoypKbOxGbTNJrJMlMjrFaAczjJiaXUcqlQsO5YEZE2QEIuVVw9LebWbbLRDDgf+YKYIVVIHAKcIG8kEns9rYwdYAchUcoDBaoU0crPcmcQgzKQsGc4g08rskdC8q8oIB8tpdcFct84mIQ79FQY0Zk2QdQc2A6BUPdEMewTszLUsv1eCiXTIN8BoIGaEVQO+P9BLVdA6XAuQ-HShMHWg0nUPFcgxWzXRLSZacoLSWPUVfIbMXnJ5H-TCRi4PFXD3NQsnDi6rakOkdYSsGeEQUxccixPsIRNTbk00jPNI4yQfeg9il86s-onRMQLSMwaWC1JSERGoj0FQEk-JV5E8o-fS4A9XOS2QC8KQOkF8EMbcpSR+EmC5WwUMU4IgnvWmUghQygpQjg1iz3YyqsyvLbDIFQWoqEKQHTOwsXJ+avDvfjHSucg8dwzwtsZ8lK7WI4UWM1TGcNQsKilCRYc2HYukCxeyWWVI67DoxYuaLWN0XKC8CFYkpdTUqo4JbKJYAofKVE2WWY+YqATonlAazmbA4bFAjaHkWoPY0odIBPKcGwWvHsHCJwIAA */
|
||||
id: 'Modeling',
|
||||
|
||||
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
|
||||
predictableActionArguments: true,
|
||||
preserveActionOrder: true,
|
||||
|
||||
context: {
|
||||
guiMode: 'default',
|
||||
selection: [] as string[],
|
||||
selectionRanges: {
|
||||
otherSelections: [],
|
||||
codeBasedSelections: [],
|
||||
} as Selections,
|
||||
selectionRangeTypeMap: {} as SelectionRangeTypeMap,
|
||||
sketchPathToNode: null as PathToNode | null, // maybe too specific, and we should have a generic pathToNode, but being specific seems less risky when I'm not sure
|
||||
sketchEnginePathId: '' as string,
|
||||
sketchPlaneId: '' as string,
|
||||
},
|
||||
|
||||
schema: {
|
||||
events: {} as
|
||||
export type ModelingMachineEvent =
|
||||
| { type: 'Deselect all' }
|
||||
| { type: 'Deselect edge'; data: Selection & { type: 'edge' } }
|
||||
| { type: 'Deselect axis'; data: Axis }
|
||||
@ -138,8 +120,32 @@ export const modelingMachine = createMachine(
|
||||
| { type: 'Constrain equal length' }
|
||||
| { type: 'Constrain parallel' }
|
||||
| { type: 'Constrain remove constraints' }
|
||||
| { type: 'extrude intent' },
|
||||
// ,
|
||||
| { type: 'extrude intent' }
|
||||
|
||||
export const modelingMachine = createMachine(
|
||||
{
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogDMAVgDsAOmEiAjLMGCAHIIAsAJlHKANCACeiaQDZBEsctGrVATmmiaNQQF8H2tBhz5x2CJjAEAymDsAASwWGDknNy0DEggLGyRPLECCMrCCuL20qrCRpYagvnaegjZBtLiBqLploIGCtZVyk4u6Fh4UJ7evgAicGERQSx47NG88RxcSaApdcKSNKIKi8ppwsrLwsX60oriqgaWBgbKImpWqi0gru0eXj4EfaE+g5AwY7ETibyzx5lLu1sygM61ERV0+ho0mU4gURlOwihlis2SuN3cnXuvX6L2CJD42FgH2YrEm3B+QnM4mqdhoyPUILqBm2CAaFTS5houQUCMczmubQxXQeAVxQ1QI2JcVJ32SiGUXPEhjBtWklgaokMzIhqVUNHEG0WgjVqkEUN2aMFHWFvlF4WCbzAUq+UwpqRoGQ2pwacPW4JKJxhgYU0kWu3Wymklrc1qx-gGeIJRPo4xlrrlqUEqkqdMKOSRNEOLPWGTVhkswlU0O5fNaMbu3XjYoAZiRKM60+SMwqMgY7Go6mbalCWaIjLCjtJ0n36tUFNHbpjGwBRXDsMAAJxCAGtAuQABYdhLpmY7SPiSzAyOXwGFUQs01BtXVEEV9VSZr89Gxldrzc7vdD2kGISWPLtTwQepBGpEN8lDGhVBDLYdTUfVZzvYFQ3MS4vytBsHieBMglbdsU0+Ttpn4Sk0KzBR1EKdING1EozQqYxajyNQ6MOBchTjO1BhITBMCPMlKJSSNoIsEEllQrlhGQko9RhZEDFUL1vWEUMcLrRcbUeHF7SCISRLI0CxLdRR2ROJQwQQ2RmMQENJD1OxjR5aE+1rAV6yXB4wD4dgNwAVwwIIRjANdRNlCDlGRJU6kfapK0vdYWSnBQJEsfJMtODYrM-XS+MbAKgtCsBwr-KLgNTMDxPlawJ0DKtXNkLl0o2NjNULPMPQ2HSfL0vxd3YA9iDIShMGGwDopPKjSg0fUaDURFFoVbJ7x1S9oOSmQcl2CtRF461ptG-dxFOg8AElGwE4JhiiszpTqt1pB9C8NA8plynVFlyn1EMzVfdTrAU46PEu87IZukUiNCKAAFtItGJ6XXA+a3vVD6vV2Y41IQlkzAMakLCnD0K0jK9wc6SGLpG67G0IsUHpRkDnosjM3oUi91SsRYjmUywWRUDJRFEY0PXzdQrGpunALls6YexZ4jPhpHHrZtH6tKHkJHkLJ1AUGpsofQxxBEWpxayCXFFl2noZXABHYLsCYIJ2FQVBTM1ijLK5Njp20wtPMEUc+yVRC4vgpZlqjXDfIVg9E-3JWCGXZ3XaCBHUAANwqj2vdm9HZg9NjpPUZbIykFkKYNTD1nsGycjt+modb1OAmCFWIimIvtbVMwczF4wtXqRyEDHfVsiNwtahyTU46Kk7W+T1PkBIXcQjARHkaCPON04cghL716kKVdYjErEWFEy9LoXZKsQT7I3jWEFv5Ydh5183tXd-3VANzYAAF7cHYMfVGvtOZggyOkewqkjBqQrOlOBlQqjyCrEoacR145DRXp-XwRBuCwCCiQPAQR-6AJAWuISQQICEjARQJ0ECXpQOqJIawHl0hMizOlfI8xsrqTBHZD084cFCntu3RshDcDEI3KQ3Ae9NyHxoXQ4hE0mE+xYRBGwlh9SIlNEoRQvMFDpRfgacohhIyS0jO-M6q8pFEJIWQsgUAfAn05nCFS8g6j2AOPSIWOo9SZSVBWBSthMpiykLYpO+DiCOLkWQnw+B2CHmYRzbRRsMig2hCI0MwhLymyxrYPI5g5zZEKoNcReDJEPGkbI+RQxNxMEinQ8gwVMAkC3KohhpFNHpIxjSTIGweRV0LMtRSiBTSXkqNyMW8kBzRLboBVOdSnEKIocA0BJkdDGRwFAXA7jtFLGJkoBC5gGhqXqBM0o6kYSRg0EsRCdhLyWEWfY2p8SGn72UcJHZQlsD7MORjZY0EFTAiqIWcwYh0oiB2rINQyIRBaThG82JqyEkKLAM7GhSSoApKBSkN6NgDS+iqDkCxaUdTVhgVpYcfZrBuVRTUghnyyFME6SZagaSYrAsRDzVYZZVjqACSULU8wpxmGROsLMhQDBMuWQ4mRayggbjANnPOQRyCsrXMmPpPLCVi2zOkNQ3oRBIhMVShSlgEpG0RO+coYs3kABk8AVQACqe0wGnDObt1X509QSoQigYGPIUmqdInlRxKCVFyHqU58liwqd+CGK8XW4HdZ68QAAFCUa4ggAEEIAYAgAQQtEBxSSm5XNEuwJzYNHSFINSJqtA6kRBUSxUJMp+ltd5ZNNNU2uqCB6r2F1t7q2CGWyApai0Vo1rVfpsxEIVEOJlLyZpCz1EJqaA0Bx1JPyJdJZ1g7h2YFHTvPNk6S2EIRkwHw64gjuA0fO-VQhgQSBnsPNIDQciExvkqXK9zlpWDike9NQ7M0BHPROotU704uzdumgA7u7ANVbi5CArvsN6Y8EJHCOMLN6ISbxaV2PWpNeF+3yzTRmkdV1cAcAIIGhA6k1T-CRZqXIVc-r2EyFcsWRLESFlAzR09dGGNUBquRLR80pnWrpKaOBSgQQtpKJWYmhQuRGz5rYMcwnwMjoAHKoCCDmkYsBp3lpZkxhk1rrDqDBCleTxZ1JKgVPkukWRGJvLQBqk98Yu4Jl7mh7WTIsq1DNAcFQ7nNolA0PMU4fZZ7uXFuRhOtMfMifEBlyq4nguWXc9SLMBxp66OhMgv4hwZwKmNKcS83nc6Zey3gcTknzIvoQCLcVxpLzmHqNYZEd8rCSDyIUaEFMZZiOXvLbLJ6ssNZy+wRjqg9XVqEOkDIFzTTmGq15O+ujKhwiQqXXYFpJspvlvmxDpDggbKoWAzAtD6HqMqs2VABAIDcDAJ4XAOdUC7nEDAdgABaW7WzMBA7wK96zdF+FvkpjfTkMX9DZWtUsdUCFBOmkMG8y713yEAM2dQh73TnuQ7e5uDcADxC3pIOwV7G4EYA8CCDgnd2hIQ9wFDvLGZEIHFQYUU4dJqt1F4eUD61QwRQXFscHHV2OCKIPtgI+xOnuMJe29j76bvu-f+4DoH3ylfs7J9D+QB2UtVinBxUx44QwWBqycaE4tZd44N8rx7ai1dk4IBTqnNO6cAMZ3r13RvOeoBNzCJFfNkRfpF1Sui2ZtIRs1KpW2Z3KN2Nx-LlxPh1fvc+9rv7X29fZ7ABzrnK30PMaUBtxExxbAekONcqsdRYRNGWBseopxe0UeTuITPeJ8A569z7jc1OOn+4Z0z4HJey9h+5xBSO+ochzHVJVkQ489T5EyIiP0Eb9Hd7SyvfvD7Ip4v3LnzXX28A66L8z3FKTZ8m+JuqE0+SLCXg3wceYtIrDHFUhE53eXe-c-YfDcSnUfP3enQPO-U-B-Y3efGTWySoOKG+bKGwKsaFQJCwI1bKYEG+SFLSAaPtXvY-ZpDcZpXAVpdpTpd3HpCqL3S-AvXXZnMgigqgjpDcIHEnRhR-BAlIQ2GEVaI2IwTUNQDfNzc2PqNIYcNtQA+6JpFpJXagrpVXSgXPEfMfWnKAqfIHVgxQtpDgrg1Q0veAivbWAQhYJYXIEeMQh8EQPWKQ-JM0WQtPZOIIXAYzEiW0IiDAVsdpe6DpdNJjcpa1fJZFa8EZIwQmKcC8UMXGI2aecwA-PSEgOXTgfALeXEKYfzTI+0ILMw16YOakXRMcHkY4SMKcdKTjakPsDYdUVdS8URJeDwA8cIbcDoXInubgRjPg-Qe5fYTjDkA4M5KotSElVYc0HxGxVw1o8gdojI7uRIRjVrdmdre+IMY0CuDQdUYEEVPoqQSoXYaQxFD0BCJwfkDwjAeAWIPtZ9VbDrOkSQGQOQRQFQdQFTRAIHfhaBHkfJI2RHbBZovyMAO4yvYEI1DTKoQ4TUeEUOKlQcCOXROkN6EMLvRZUE-uQ4bMIDVaMFKEdQFkZyI2WoQGYk8seVRWboDE16RFfYJkKEbxWVJHBAZEJfBSRkkMJI1LXBKjY9T1aknnA4EwRFBNKQbAj45jM0SoXA+QJEiwJIvTWbHoT7AUiCYwKU3IcWJdTtBkZzaCSEvJZEdUZIqpXksDWbUzC9GDCAVUmTXRfU-IRQAoeQE4ZkysIMNzeokEdYCwRUiDMdXeS9W0xdcOBzCmcFIcZzD0rkL0hSc4P02jejdgYMyZV+dhU0UUordSYsQjO3WoPKQXI4BM09IzEzXNdga4tre4g4WtBLE0TYm+ced05AmMjzV+TKerXzfkqTBdINMWC8clMwN9OoaQGuIbD-TTasHKJoypKbOxGbTNJrJMlMjrFaAczjJiaXUcqlQsO5YEZE2QEIuVVw9LebWbbLRDDgf+YKYIVVIHAKcIG8kEns9rYwdYAchUcoDBaoU0crPcmcQgzKQsGc4g08rskdC8q8oIB8tpdcFct84mIQ79FQY0Zk2QdQc2A6BUPdEMewTszLUsv1eCiXTIN8BoIGaEVQO+P9BLVdA6XAuQ-HShMHWg0nUPFcgxWzXRLSZacoLSWPUVfIbMXnJ5H-TCRi4PFXD3NQsnDi6rakOkdYSsGeEQUxccixPsIRNTbk00jPNI4yQfeg9il86s-onRMQLSMwaWC1JSERGoj0FQEk-JV5E8o-fS4A9XOS2QC8KQOkF8EMbcpSR+EmC5WwUMU4IgnvWmUghQygpQjg1iz3YyqsyvLbDIFQWoqEKQHTOwsXJ+avDvfjHSucg8dwzwtsZ8lK7WI4UWM1TGcNQsKilCRYc2HYukCxeyWWVI67DoxYuaLWN0XKC8CFYkpdTUqo4JbKJYAofKVE2WWY+YqATonlAazmbA4bFAjaHkWoPY0odIBPKcGwWvHsHCJwIAA */
|
||||
id: 'Modeling',
|
||||
|
||||
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
|
||||
predictableActionArguments: true,
|
||||
preserveActionOrder: true,
|
||||
|
||||
context: {
|
||||
guiMode: 'default',
|
||||
selection: [] as string[],
|
||||
selectionRanges: {
|
||||
otherSelections: [],
|
||||
codeBasedSelections: [],
|
||||
} as Selections,
|
||||
selectionRangeTypeMap: {} as SelectionRangeTypeMap,
|
||||
sketchPathToNode: null as PathToNode | null, // maybe too specific, and we should have a generic pathToNode, but being specific seems less risky when I'm not sure
|
||||
sketchEnginePathId: '' as string,
|
||||
sketchPlaneId: '' as string,
|
||||
},
|
||||
|
||||
schema: {
|
||||
events: {} as ModelingMachineEvent,
|
||||
},
|
||||
|
||||
states: {
|
||||
|
108
src/useStore.ts
108
src/useStore.ts
@ -1,13 +1,8 @@
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
import { addLineHighlight, EditorView } from './editor/highlightextension'
|
||||
import {
|
||||
parse,
|
||||
Program,
|
||||
_executor,
|
||||
ProgramMemory,
|
||||
SourceRange,
|
||||
} from './lang/wasm'
|
||||
import { parse, Program, _executor, ProgramMemory } from './lang/wasm'
|
||||
import { Selection, Selections, SelectionRangeTypeMap } from 'lib/selections'
|
||||
import { enginelessExecutor } from './lib/testHelpers'
|
||||
import { EditorSelection } from '@codemirror/state'
|
||||
import { EngineCommandManager } from './lang/std/engineConnection'
|
||||
@ -15,25 +10,6 @@ import { KCLError } from './lang/errors'
|
||||
import { kclManager } from 'lang/KclSinglton'
|
||||
import { DefaultPlanes } from './wasm-lib/kcl/bindings/DefaultPlanes'
|
||||
|
||||
export type Axis = 'y-axis' | 'x-axis' | 'z-axis'
|
||||
|
||||
export type Selection = {
|
||||
type:
|
||||
| 'default'
|
||||
| 'line-end'
|
||||
| 'line-mid'
|
||||
| 'face'
|
||||
| 'point'
|
||||
| 'edge'
|
||||
| 'line'
|
||||
| 'arc'
|
||||
| 'all'
|
||||
range: SourceRange
|
||||
}
|
||||
export type Selections = {
|
||||
otherSelections: Axis[]
|
||||
codeBasedSelections: Selection[]
|
||||
}
|
||||
export type ToolTip =
|
||||
| 'lineTo'
|
||||
| 'line'
|
||||
@ -74,10 +50,6 @@ export type PaneType =
|
||||
| 'logs'
|
||||
| 'lspMessages'
|
||||
|
||||
export interface SelectionRangeTypeMap {
|
||||
[key: number]: Selection['type']
|
||||
}
|
||||
|
||||
export interface StoreState {
|
||||
editorView: EditorView | null
|
||||
setEditorView: (editorView: EditorView) => void
|
||||
@ -342,79 +314,3 @@ export async function executeAst({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function dispatchCodeMirrorCursor({
|
||||
selections,
|
||||
editorView,
|
||||
}: {
|
||||
selections: Selections
|
||||
editorView: EditorView
|
||||
}): {
|
||||
selectionRangeTypeMap: SelectionRangeTypeMap
|
||||
} {
|
||||
const ranges: ReturnType<typeof EditorSelection.cursor>[] = []
|
||||
const selectionRangeTypeMap: SelectionRangeTypeMap = {}
|
||||
selections.codeBasedSelections.forEach(({ range, type }) => {
|
||||
if (range?.[1]) {
|
||||
ranges.push(EditorSelection.cursor(range[1]))
|
||||
selectionRangeTypeMap[range[1]] = type
|
||||
}
|
||||
})
|
||||
setTimeout(() => {
|
||||
ranges.length &&
|
||||
editorView.dispatch({
|
||||
selection: EditorSelection.create(
|
||||
ranges,
|
||||
selections.codeBasedSelections.length - 1
|
||||
),
|
||||
})
|
||||
})
|
||||
return {
|
||||
selectionRangeTypeMap,
|
||||
}
|
||||
}
|
||||
|
||||
export function setCodeMirrorCursor({
|
||||
codeSelection,
|
||||
currestSelections,
|
||||
editorView,
|
||||
isShiftDown,
|
||||
}: {
|
||||
codeSelection?: Selection
|
||||
currestSelections: Selections
|
||||
editorView: EditorView
|
||||
isShiftDown: boolean
|
||||
}): SelectionRangeTypeMap {
|
||||
// This DOES NOT set the `selectionRanges` in xstate context
|
||||
// instead it updates/dispatches to the editor, which in turn updates the xstate context
|
||||
// I've found this the best way to deal with the editor without causing an infinite loop
|
||||
// and really we want the editor to be in charge of cursor positions and for `selectionRanges` mirror it
|
||||
// because we want to respect the user manually placing the cursor too.
|
||||
const code = kclManager.code
|
||||
if (!codeSelection) {
|
||||
const { selectionRangeTypeMap } = dispatchCodeMirrorCursor({
|
||||
editorView,
|
||||
selections: {
|
||||
otherSelections: currestSelections.otherSelections,
|
||||
codeBasedSelections: [
|
||||
{
|
||||
range: [0, code.length ? code.length - 1 : 0],
|
||||
type: 'default',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
return selectionRangeTypeMap
|
||||
}
|
||||
const selections: Selections = {
|
||||
...currestSelections,
|
||||
codeBasedSelections: isShiftDown
|
||||
? [...currestSelections.codeBasedSelections, codeSelection]
|
||||
: [codeSelection],
|
||||
}
|
||||
const { selectionRangeTypeMap } = dispatchCodeMirrorCursor({
|
||||
editorView,
|
||||
selections,
|
||||
})
|
||||
return selectionRangeTypeMap
|
||||
}
|
||||
|
Reference in New Issue
Block a user