2023-08-01 13:23:17 -04:00
|
|
|
import { create } from 'zustand'
|
2023-02-21 14:50:22 +11:00
|
|
|
import { persist } from 'zustand/middleware'
|
2022-11-26 08:34:23 +11:00
|
|
|
import { addLineHighlight, EditorView } from './editor/highlightextension'
|
2023-08-18 19:37:52 +10:00
|
|
|
import { parser_wasm } from './lang/abstractSyntaxTree'
|
2023-07-13 16:57:22 +10:00
|
|
|
import { Program } from './lang/abstractSyntaxTreeTypes'
|
2023-03-03 20:35:48 +11:00
|
|
|
import { getNodeFromPath } from './lang/queryAst'
|
2023-04-03 16:05:25 +10:00
|
|
|
import {
|
|
|
|
ProgramMemory,
|
|
|
|
Position,
|
|
|
|
PathToNode,
|
|
|
|
Rotation,
|
|
|
|
SourceRange,
|
|
|
|
} from './lang/executor'
|
2022-11-28 09:37:46 +11:00
|
|
|
import { recast } from './lang/recast'
|
2023-02-21 10:28:34 +11:00
|
|
|
import { EditorSelection } from '@codemirror/state'
|
2023-07-26 11:47:18 -05:00
|
|
|
import {
|
|
|
|
ArtifactMap,
|
|
|
|
SourceRangeMap,
|
|
|
|
EngineCommandManager,
|
|
|
|
} from './lang/std/engineConnection'
|
2023-08-18 17:14:35 -05:00
|
|
|
import { KCLError } from './lang/errors'
|
2023-09-08 17:50:37 +10:00
|
|
|
import { defferExecution } from 'lib/utils'
|
2022-11-26 05:13:07 +11:00
|
|
|
|
2023-04-03 16:05:25 +10:00
|
|
|
export type Selection = {
|
|
|
|
type: 'default' | 'line-end' | 'line-mid'
|
|
|
|
range: SourceRange
|
|
|
|
}
|
|
|
|
export type Selections = {
|
|
|
|
otherSelections: ('y-axis' | 'x-axis' | 'z-axis')[]
|
|
|
|
codeBasedSelections: Selection[]
|
|
|
|
}
|
2023-02-12 10:56:45 +11:00
|
|
|
export type TooTip =
|
|
|
|
| 'lineTo'
|
|
|
|
| 'line'
|
|
|
|
| 'angledLine'
|
|
|
|
| 'angledLineOfXLength'
|
|
|
|
| 'angledLineOfYLength'
|
|
|
|
| 'angledLineToX'
|
|
|
|
| 'angledLineToY'
|
|
|
|
| 'xLine'
|
|
|
|
| 'yLine'
|
|
|
|
| 'xLineTo'
|
|
|
|
| 'yLineTo'
|
2023-03-19 18:46:39 +11:00
|
|
|
| 'angledLineThatIntersects'
|
2022-11-26 05:13:07 +11:00
|
|
|
|
2023-02-12 10:56:45 +11:00
|
|
|
export const toolTips: TooTip[] = [
|
|
|
|
'lineTo',
|
|
|
|
'line',
|
|
|
|
'angledLine',
|
|
|
|
'angledLineOfXLength',
|
|
|
|
'angledLineOfYLength',
|
|
|
|
'angledLineToX',
|
|
|
|
'angledLineToY',
|
|
|
|
'xLine',
|
|
|
|
'yLine',
|
|
|
|
'xLineTo',
|
|
|
|
'yLineTo',
|
2023-03-19 18:46:39 +11:00
|
|
|
'angledLineThatIntersects',
|
2023-02-12 10:56:45 +11:00
|
|
|
]
|
|
|
|
|
|
|
|
export type GuiModes =
|
2022-11-27 14:06:33 +11:00
|
|
|
| {
|
|
|
|
mode: 'default'
|
|
|
|
}
|
|
|
|
| {
|
|
|
|
mode: 'sketch'
|
2023-02-12 10:56:45 +11:00
|
|
|
sketchMode: TooTip
|
|
|
|
isTooltip: true
|
2023-01-08 16:37:31 +11:00
|
|
|
rotation: Rotation
|
2023-01-04 01:28:26 +11:00
|
|
|
position: Position
|
2022-12-04 15:50:52 +11:00
|
|
|
id?: string
|
2022-12-06 05:40:05 +11:00
|
|
|
pathToNode: PathToNode
|
2022-11-27 14:06:33 +11:00
|
|
|
}
|
2022-12-06 05:40:05 +11:00
|
|
|
| {
|
|
|
|
mode: 'sketch'
|
|
|
|
sketchMode: 'sketchEdit'
|
2023-01-08 16:37:31 +11:00
|
|
|
rotation: Rotation
|
2023-01-04 01:28:26 +11:00
|
|
|
position: Position
|
2022-12-06 05:40:05 +11:00
|
|
|
pathToNode: PathToNode
|
2022-12-23 07:37:42 +11:00
|
|
|
}
|
2022-11-27 14:06:33 +11:00
|
|
|
| {
|
|
|
|
mode: 'sketch'
|
|
|
|
sketchMode: 'selectFace'
|
|
|
|
}
|
|
|
|
| {
|
2022-12-06 05:40:05 +11:00
|
|
|
mode: 'canEditSketch'
|
|
|
|
pathToNode: PathToNode
|
2023-01-08 16:37:31 +11:00
|
|
|
rotation: Rotation
|
2023-01-04 01:28:26 +11:00
|
|
|
position: Position
|
2022-12-23 07:37:42 +11:00
|
|
|
}
|
2023-01-09 13:19:14 +11:00
|
|
|
| {
|
|
|
|
mode: 'canEditExtrude'
|
|
|
|
pathToNode: PathToNode
|
|
|
|
rotation: Rotation
|
|
|
|
position: Position
|
|
|
|
}
|
2022-11-27 14:06:33 +11:00
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
export type PaneType =
|
|
|
|
| 'code'
|
|
|
|
| 'variables'
|
|
|
|
| 'debug'
|
|
|
|
| 'kclErrors'
|
|
|
|
| 'logs'
|
|
|
|
| 'lspMessages'
|
2023-08-06 21:29:26 -04:00
|
|
|
|
2023-04-14 07:49:36 +10:00
|
|
|
export interface StoreState {
|
2022-11-26 08:34:23 +11:00
|
|
|
editorView: EditorView | null
|
|
|
|
setEditorView: (editorView: EditorView) => void
|
|
|
|
highlightRange: [number, number]
|
2023-04-03 16:05:25 +10:00
|
|
|
setHighlightRange: (range: Selection['range']) => void
|
|
|
|
setCursor: (selections: Selections) => void
|
2023-08-09 20:49:10 +10:00
|
|
|
setCursor2: (a?: Selection) => void
|
2023-04-03 16:05:25 +10:00
|
|
|
selectionRanges: Selections
|
|
|
|
selectionRangeTypeMap: { [key: number]: Selection['type'] }
|
|
|
|
setSelectionRanges: (range: Selections) => void
|
2022-11-27 14:06:33 +11:00
|
|
|
guiMode: GuiModes
|
|
|
|
lastGuiMode: GuiModes
|
|
|
|
setGuiMode: (guiMode: GuiModes) => void
|
|
|
|
logs: string[]
|
|
|
|
addLog: (log: string) => void
|
2023-07-26 18:16:20 -05:00
|
|
|
resetLogs: () => void
|
2023-07-26 14:10:30 -05:00
|
|
|
kclErrors: KCLError[]
|
|
|
|
addKCLError: (err: KCLError) => void
|
2023-07-26 18:16:20 -05:00
|
|
|
resetKCLErrors: () => void
|
2022-11-28 09:37:46 +11:00
|
|
|
ast: Program | null
|
2023-02-01 07:30:55 +11:00
|
|
|
setAst: (ast: Program | null) => void
|
2023-04-14 07:49:36 +10:00
|
|
|
updateAst: (
|
|
|
|
ast: Program,
|
|
|
|
optionalParams?: {
|
|
|
|
focusPath?: PathToNode
|
|
|
|
callBack?: (ast: Program) => void
|
|
|
|
}
|
|
|
|
) => void
|
2023-04-01 20:38:31 +11:00
|
|
|
updateAstAsync: (ast: Program, focusPath?: PathToNode) => void
|
2022-11-28 09:37:46 +11:00
|
|
|
code: string
|
2023-09-08 17:50:37 +10:00
|
|
|
defferedCode: string
|
2022-11-28 09:37:46 +11:00
|
|
|
setCode: (code: string) => void
|
2023-09-08 17:50:37 +10:00
|
|
|
defferedSetCode: (code: string) => void
|
2022-11-28 19:43:20 +11:00
|
|
|
formatCode: () => void
|
2022-12-06 05:40:05 +11:00
|
|
|
errorState: {
|
|
|
|
isError: boolean
|
|
|
|
error: string
|
|
|
|
}
|
|
|
|
setError: (error?: string) => void
|
2023-01-04 01:28:26 +11:00
|
|
|
programMemory: ProgramMemory
|
|
|
|
setProgramMemory: (programMemory: ProgramMemory) => void
|
2023-02-21 10:28:34 +11:00
|
|
|
isShiftDown: boolean
|
|
|
|
setIsShiftDown: (isShiftDown: boolean) => void
|
2023-06-22 16:43:33 +10:00
|
|
|
artifactMap: ArtifactMap
|
|
|
|
sourceRangeMap: SourceRangeMap
|
|
|
|
setArtifactNSourceRangeMaps: (a: {
|
|
|
|
artifactMap: ArtifactMap
|
|
|
|
sourceRangeMap: SourceRangeMap
|
|
|
|
}) => void
|
|
|
|
engineCommandManager?: EngineCommandManager
|
|
|
|
setEngineCommandManager: (engineCommandManager: EngineCommandManager) => void
|
|
|
|
mediaStream?: MediaStream
|
|
|
|
setMediaStream: (mediaStream: MediaStream) => void
|
2023-06-23 14:19:15 +10:00
|
|
|
isStreamReady: boolean
|
|
|
|
setIsStreamReady: (isStreamReady: boolean) => void
|
2023-09-05 16:02:27 -07:00
|
|
|
isLSPServerReady: boolean
|
|
|
|
setIsLSPServerReady: (isLSPServerReady: boolean) => void
|
2023-08-06 21:29:26 -04:00
|
|
|
isMouseDownInStream: boolean
|
|
|
|
setIsMouseDownInStream: (isMouseDownInStream: boolean) => void
|
2023-08-09 20:49:10 +10:00
|
|
|
didDragInStream: boolean
|
|
|
|
setDidDragInStream: (didDragInStream: boolean) => void
|
2023-08-06 21:29:26 -04:00
|
|
|
fileId: string
|
|
|
|
setFileId: (fileId: string) => void
|
2023-08-09 20:49:10 +10:00
|
|
|
streamDimensions: { streamWidth: number; streamHeight: number }
|
|
|
|
setStreamDimensions: (dimensions: {
|
|
|
|
streamWidth: number
|
|
|
|
streamHeight: number
|
|
|
|
}) => void
|
2023-09-08 17:50:37 +10:00
|
|
|
isExecuting: boolean
|
|
|
|
setIsExecuting: (isExecuting: boolean) => void
|
2023-06-19 10:16:45 +10:00
|
|
|
|
|
|
|
showHomeMenu: boolean
|
|
|
|
setHomeShowMenu: (showMenu: boolean) => void
|
2023-08-18 13:58:29 -04:00
|
|
|
isBannerDismissed: boolean
|
|
|
|
setBannerDismissed: (isBannerDismissed: boolean) => void
|
2023-08-06 21:29:26 -04:00
|
|
|
openPanes: PaneType[]
|
|
|
|
setOpenPanes: (panes: PaneType[]) => void
|
2023-06-19 10:16:45 +10:00
|
|
|
homeMenuItems: {
|
|
|
|
name: string
|
|
|
|
path: string
|
|
|
|
}[]
|
|
|
|
setHomeMenuItems: (items: { name: string; path: string }[]) => void
|
2022-11-26 05:13:07 +11:00
|
|
|
}
|
|
|
|
|
2023-04-01 20:38:31 +11:00
|
|
|
let pendingAstUpdates: number[] = []
|
|
|
|
|
2023-02-21 14:50:22 +11:00
|
|
|
export const useStore = create<StoreState>()(
|
|
|
|
persist(
|
2023-09-08 17:50:37 +10:00
|
|
|
(set, get) => {
|
|
|
|
const setDefferedCode = defferExecution(
|
|
|
|
(code: string) => set({ defferedCode: code }),
|
|
|
|
600
|
|
|
|
)
|
|
|
|
return {
|
|
|
|
editorView: null,
|
|
|
|
setEditorView: (editorView) => {
|
|
|
|
set({ editorView })
|
|
|
|
},
|
|
|
|
highlightRange: [0, 0],
|
|
|
|
setHighlightRange: (selection) => {
|
|
|
|
set({ highlightRange: selection })
|
|
|
|
const editorView = get().editorView
|
|
|
|
if (editorView) {
|
|
|
|
editorView.dispatch({ effects: addLineHighlight.of(selection) })
|
2023-06-22 16:43:33 +10:00
|
|
|
}
|
2023-09-08 17:50:37 +10:00
|
|
|
},
|
|
|
|
setCursor: (selections) => {
|
|
|
|
const { editorView } = get()
|
|
|
|
if (!editorView) return
|
|
|
|
const ranges: ReturnType<typeof EditorSelection.cursor>[] = []
|
|
|
|
const selectionRangeTypeMap: { [key: number]: Selection['type'] } = {}
|
|
|
|
set({ selectionRangeTypeMap })
|
|
|
|
selections.codeBasedSelections.forEach(({ range, type }) => {
|
|
|
|
if (range?.[1]) {
|
|
|
|
ranges.push(EditorSelection.cursor(range[1]))
|
|
|
|
selectionRangeTypeMap[range[1]] = type
|
|
|
|
}
|
2023-04-03 16:05:25 +10:00
|
|
|
})
|
2023-02-21 14:50:22 +11:00
|
|
|
setTimeout(() => {
|
2023-09-08 17:50:37 +10:00
|
|
|
editorView.dispatch({
|
|
|
|
selection: EditorSelection.create(
|
|
|
|
ranges,
|
|
|
|
selections.codeBasedSelections.length - 1
|
|
|
|
),
|
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
setCursor2: (codeSelections) => {
|
|
|
|
const currestSelections = get().selectionRanges
|
|
|
|
const code = get().code
|
|
|
|
if (!codeSelections) {
|
2023-04-03 16:05:25 +10:00
|
|
|
get().setCursor({
|
2023-09-08 17:50:37 +10:00
|
|
|
otherSelections: currestSelections.otherSelections,
|
2023-04-03 16:05:25 +10:00
|
|
|
codeBasedSelections: [
|
2023-09-08 17:50:37 +10:00
|
|
|
{ range: [0, code.length - 1], type: 'default' },
|
2023-04-03 16:05:25 +10:00
|
|
|
],
|
|
|
|
})
|
2023-09-08 17:50:37 +10:00
|
|
|
return
|
|
|
|
}
|
|
|
|
const selections: Selections = {
|
|
|
|
...currestSelections,
|
|
|
|
codeBasedSelections: get().isShiftDown
|
|
|
|
? [...currestSelections.codeBasedSelections, codeSelections]
|
|
|
|
: [codeSelections],
|
|
|
|
}
|
|
|
|
get().setCursor(selections)
|
|
|
|
},
|
|
|
|
selectionRangeTypeMap: {},
|
|
|
|
selectionRanges: {
|
|
|
|
otherSelections: [],
|
|
|
|
codeBasedSelections: [],
|
|
|
|
},
|
|
|
|
setSelectionRanges: (selectionRanges) =>
|
|
|
|
set({ selectionRanges, selectionRangeTypeMap: {} }),
|
|
|
|
guiMode: { mode: 'default' },
|
|
|
|
lastGuiMode: { mode: 'default' },
|
|
|
|
setGuiMode: (guiMode) => {
|
|
|
|
set({ guiMode })
|
|
|
|
},
|
|
|
|
logs: [],
|
|
|
|
addLog: (log) => {
|
|
|
|
if (Array.isArray(log)) {
|
|
|
|
const cleanLog: any = log.map(({ __geoMeta, ...rest }) => rest)
|
|
|
|
set((state) => ({ logs: [...state.logs, cleanLog] }))
|
|
|
|
} else {
|
|
|
|
set((state) => ({ logs: [...state.logs, log] }))
|
|
|
|
}
|
|
|
|
},
|
|
|
|
resetLogs: () => {
|
|
|
|
set({ logs: [] })
|
|
|
|
},
|
|
|
|
kclErrors: [],
|
|
|
|
addKCLError: (e) => {
|
|
|
|
set((state) => ({ kclErrors: [...state.kclErrors, e] }))
|
|
|
|
},
|
|
|
|
resetKCLErrors: () => {
|
|
|
|
set({ kclErrors: [] })
|
|
|
|
},
|
|
|
|
ast: null,
|
|
|
|
setAst: (ast) => {
|
|
|
|
set({ ast })
|
|
|
|
},
|
|
|
|
updateAst: async (ast, { focusPath, callBack = () => {} } = {}) => {
|
|
|
|
const newCode = recast(ast)
|
|
|
|
const astWithUpdatedSource = parser_wasm(newCode)
|
|
|
|
callBack(astWithUpdatedSource)
|
|
|
|
|
|
|
|
set({ ast: astWithUpdatedSource, code: newCode })
|
|
|
|
if (focusPath) {
|
|
|
|
const { node } = getNodeFromPath<any>(
|
|
|
|
astWithUpdatedSource,
|
|
|
|
focusPath
|
|
|
|
)
|
|
|
|
const { start, end } = node
|
|
|
|
if (!start || !end) return
|
|
|
|
setTimeout(() => {
|
|
|
|
get().setCursor({
|
|
|
|
codeBasedSelections: [
|
|
|
|
{
|
|
|
|
type: 'default',
|
|
|
|
range: [start, end],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
otherSelections: [],
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
|
|
|
updateAstAsync: async (ast, focusPath) => {
|
|
|
|
// clear any pending updates
|
|
|
|
pendingAstUpdates.forEach((id) => clearTimeout(id))
|
|
|
|
pendingAstUpdates = []
|
|
|
|
// setup a new update
|
|
|
|
pendingAstUpdates.push(
|
|
|
|
setTimeout(() => {
|
|
|
|
get().updateAst(ast, { focusPath })
|
|
|
|
}, 100) as unknown as number
|
|
|
|
)
|
|
|
|
},
|
|
|
|
code: '',
|
|
|
|
defferedCode: '',
|
|
|
|
setCode: (code) => set({ code, defferedCode: code }),
|
|
|
|
defferedSetCode: (code) => {
|
|
|
|
set({ code })
|
|
|
|
setDefferedCode(code)
|
|
|
|
},
|
|
|
|
formatCode: async () => {
|
|
|
|
const code = get().code
|
|
|
|
const ast = parser_wasm(code)
|
|
|
|
const newCode = recast(ast)
|
|
|
|
set({ code: newCode, ast })
|
|
|
|
},
|
|
|
|
errorState: {
|
|
|
|
isError: false,
|
|
|
|
error: '',
|
|
|
|
},
|
|
|
|
setError: (error = '') => {
|
|
|
|
set({ errorState: { isError: !!error, error } })
|
|
|
|
},
|
|
|
|
programMemory: { root: {}, pendingMemory: {} },
|
|
|
|
setProgramMemory: (programMemory) => set({ programMemory }),
|
|
|
|
isShiftDown: false,
|
|
|
|
setIsShiftDown: (isShiftDown) => set({ isShiftDown }),
|
|
|
|
artifactMap: {},
|
|
|
|
sourceRangeMap: {},
|
|
|
|
setArtifactNSourceRangeMaps: (maps) => set({ ...maps }),
|
|
|
|
setEngineCommandManager: (engineCommandManager) =>
|
|
|
|
set({ engineCommandManager }),
|
|
|
|
setMediaStream: (mediaStream) => set({ mediaStream }),
|
|
|
|
isStreamReady: false,
|
|
|
|
setIsStreamReady: (isStreamReady) => set({ isStreamReady }),
|
|
|
|
isLSPServerReady: false,
|
|
|
|
setIsLSPServerReady: (isLSPServerReady) => set({ isLSPServerReady }),
|
|
|
|
isMouseDownInStream: false,
|
|
|
|
setIsMouseDownInStream: (isMouseDownInStream) => {
|
|
|
|
set({ isMouseDownInStream })
|
|
|
|
},
|
|
|
|
didDragInStream: false,
|
|
|
|
setDidDragInStream: (didDragInStream) => {
|
|
|
|
set({ didDragInStream })
|
|
|
|
},
|
|
|
|
// For stream event handling
|
|
|
|
fileId: '',
|
|
|
|
setFileId: (fileId) => set({ fileId }),
|
|
|
|
streamDimensions: { streamWidth: 1280, streamHeight: 720 },
|
|
|
|
setStreamDimensions: (streamDimensions) => set({ streamDimensions }),
|
|
|
|
isExecuting: false,
|
|
|
|
setIsExecuting: (isExecuting) => set({ isExecuting }),
|
2023-07-26 11:47:18 -05:00
|
|
|
|
2023-09-08 17:50:37 +10:00
|
|
|
// tauri specific app settings
|
|
|
|
defaultDir: {
|
|
|
|
dir: '',
|
|
|
|
},
|
|
|
|
isBannerDismissed: false,
|
|
|
|
setBannerDismissed: (isBannerDismissed) => set({ isBannerDismissed }),
|
|
|
|
openPanes: ['code'],
|
|
|
|
setOpenPanes: (openPanes) => set({ openPanes }),
|
|
|
|
showHomeMenu: true,
|
|
|
|
setHomeShowMenu: (showHomeMenu) => set({ showHomeMenu }),
|
|
|
|
homeMenuItems: [],
|
|
|
|
setHomeMenuItems: (homeMenuItems) => set({ homeMenuItems }),
|
|
|
|
}
|
|
|
|
},
|
2023-02-21 14:50:22 +11:00
|
|
|
{
|
|
|
|
name: 'store',
|
|
|
|
partialize: (state) =>
|
|
|
|
Object.fromEntries(
|
2023-07-26 11:47:18 -05:00
|
|
|
Object.entries(state).filter(([key]) =>
|
2023-09-08 17:52:50 +10:00
|
|
|
['code', 'defferedCode', 'openPanes'].includes(key)
|
2023-07-26 11:47:18 -05:00
|
|
|
)
|
2023-02-21 14:50:22 +11:00
|
|
|
),
|
2023-01-13 17:58:37 +11:00
|
|
|
}
|
2023-02-21 14:50:22 +11:00
|
|
|
)
|
|
|
|
)
|