Add ability to open KCL samples in-app (#3912)
* Add a shell script to get the list of KCL samples into the app * Add support for overwriting current file with sample * Move these KCL commands down into FileMachineProvider * Add support for creating a new file on desktop * Make it so these files aren't set to "renaming mode" right away * Add support for initializing default values that are functions * Add E2E tests * Add a code menu item to load a sample * Fix tsc issues * Remove `yarn fetch:samples` from `yarn postinstall` * Remove change to arg initialization logic, I was holding it wrong * Switch to use new manifest file from kcl-samples repo * Update tests now that we use proper sample titles * Remove double-load from units menu test * @jtran feedback * Don't encode `https://` that's silly * fmt * Update e2e/playwright/testing-samples-loading.spec.ts Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch> * Test feedback * Add a test step to actually check the file contents were written to (@Irev-Dev feedback) --------- Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
This commit is contained in:
@ -6,8 +6,8 @@ import { CommandArgument, CommandArgumentOption } from 'lib/commandTypes'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { AnyStateMachine, StateFrom } from 'xstate'
|
||||
|
||||
const contextSelector = (snapshot: StateFrom<AnyStateMachine>) =>
|
||||
snapshot.context
|
||||
const contextSelector = (snapshot: StateFrom<AnyStateMachine> | undefined) =>
|
||||
snapshot?.context
|
||||
|
||||
function CommandArgOptionInput({
|
||||
arg,
|
||||
|
@ -140,7 +140,11 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
||||
JSON.stringify(argValue)
|
||||
)
|
||||
) : (
|
||||
<em>{argValue}</em>
|
||||
<em>
|
||||
{arg.valueSummary
|
||||
? arg.valueSummary(argValue)
|
||||
: argValue}
|
||||
</em>
|
||||
)
|
||||
) : null}
|
||||
</span>
|
||||
|
@ -58,7 +58,17 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
|
||||
|
||||
return (
|
||||
<CommandBarHeader>
|
||||
<p className="px-4">Confirm {selectedCommand?.name}</p>
|
||||
<p className="px-4">
|
||||
{selectedCommand?.reviewMessage ? (
|
||||
selectedCommand.reviewMessage instanceof Function ? (
|
||||
selectedCommand.reviewMessage(commandBarState.context)
|
||||
) : (
|
||||
selectedCommand.reviewMessage
|
||||
)
|
||||
) : (
|
||||
<>Confirm {selectedCommand?.name}</>
|
||||
)}
|
||||
</p>
|
||||
<form
|
||||
id="review-form"
|
||||
className="absolute opacity-0 inset-0 pointer-events-none"
|
||||
|
@ -31,8 +31,8 @@ function getSemanticSelectionType(selectionType: Array<Selection['type']>) {
|
||||
return Array.from(semanticSelectionType)
|
||||
}
|
||||
|
||||
const selectionSelector = (snapshot: StateFrom<typeof modelingMachine>) =>
|
||||
snapshot.context.selectionRanges
|
||||
const selectionSelector = (snapshot?: StateFrom<typeof modelingMachine>) =>
|
||||
snapshot?.context.selectionRanges
|
||||
|
||||
function CommandBarSelectionInput({
|
||||
arg,
|
||||
@ -49,7 +49,7 @@ function CommandBarSelectionInput({
|
||||
const [hasSubmitted, setHasSubmitted] = useState(false)
|
||||
const selection = useSelector(arg.machineActor, selectionSelector)
|
||||
const selectionsByType = useMemo(() => {
|
||||
const selectionRangeEnd = selection.codeBasedSelections[0]?.range[1]
|
||||
const selectionRangeEnd = selection?.codeBasedSelections[0]?.range[1]
|
||||
return !selectionRangeEnd || selectionRangeEnd === code.length
|
||||
? 'none'
|
||||
: getSelectionType(selection)
|
||||
|
16
src/components/CommandBarOverwriteWarning.tsx
Normal file
16
src/components/CommandBarOverwriteWarning.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
interface CommandBarOverwriteWarningProps {
|
||||
heading?: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
export function CommandBarOverwriteWarning({
|
||||
heading = 'Overwrite current file?',
|
||||
message = 'This will permanently replace the current code in the editor.',
|
||||
}: CommandBarOverwriteWarningProps) {
|
||||
return (
|
||||
<>
|
||||
<p className="font-bold text-destroy-60">{heading}</p>
|
||||
<p>{message}</p>
|
||||
</>
|
||||
)
|
||||
}
|
@ -2,7 +2,7 @@ import { useMachine } from '@xstate/react'
|
||||
import { useNavigate, useRouteLoaderData } from 'react-router-dom'
|
||||
import { type IndexLoaderData } from 'lib/types'
|
||||
import { PATHS } from 'lib/paths'
|
||||
import React, { createContext } from 'react'
|
||||
import React, { createContext, useEffect, useMemo } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import {
|
||||
Actor,
|
||||
@ -22,6 +22,12 @@ import {
|
||||
} from 'lib/constants'
|
||||
import { getProjectInfo } from 'lib/desktop'
|
||||
import { getNextDirName, getNextFileName } from 'lib/desktopFS'
|
||||
import { kclCommands } from 'lib/kclCommands'
|
||||
import { codeManager, kclManager } from 'lib/singletons'
|
||||
import {
|
||||
getKclSamplesManifest,
|
||||
KclSamplesManifestItem,
|
||||
} from 'lib/getKclSamplesManifest'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -41,6 +47,16 @@ export const FileMachineProvider = ({
|
||||
const navigate = useNavigate()
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const { project, file } = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
||||
const [kclSamples, setKclSamples] = React.useState<KclSamplesManifestItem[]>(
|
||||
[]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchKclSamples() {
|
||||
setKclSamples(await getKclSamplesManifest())
|
||||
}
|
||||
fetchKclSamples().catch(reportError)
|
||||
}, [])
|
||||
|
||||
const [state, send] = useMachine(
|
||||
fileMachine.provide({
|
||||
@ -121,6 +137,7 @@ export const FileMachineProvider = ({
|
||||
return {
|
||||
message: `Successfully created "${createdName}"`,
|
||||
path: createdPath,
|
||||
shouldSetToRename: input.shouldSetToRename,
|
||||
}
|
||||
}),
|
||||
createFile: fromPromise(async ({ input }) => {
|
||||
@ -271,6 +288,46 @@ export const FileMachineProvider = ({
|
||||
}
|
||||
)
|
||||
|
||||
const kclCommandMemo = useMemo(
|
||||
() =>
|
||||
kclCommands(
|
||||
async (data) => {
|
||||
if (data.method === 'overwrite') {
|
||||
codeManager.updateCodeStateEditor(data.code)
|
||||
await kclManager.executeCode(true)
|
||||
await codeManager.writeToFile()
|
||||
} else if (data.method === 'newFile' && isDesktop()) {
|
||||
send({
|
||||
type: 'Create file',
|
||||
data: {
|
||||
name: data.sampleName,
|
||||
content: data.code,
|
||||
makeDir: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
kclSamples.map((sample) => ({
|
||||
value: sample.file,
|
||||
name: sample.title,
|
||||
}))
|
||||
).filter(
|
||||
(command) => kclSamples.length || command.name !== 'open-kcl-example'
|
||||
),
|
||||
[codeManager, kclManager, send, kclSamples]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
commandBarSend({ type: 'Add commands', data: { commands: kclCommandMemo } })
|
||||
|
||||
return () => {
|
||||
commandBarSend({
|
||||
type: 'Remove commands',
|
||||
data: { commands: kclCommandMemo },
|
||||
})
|
||||
}
|
||||
}, [commandBarSend, kclCommandMemo])
|
||||
|
||||
return (
|
||||
<FileContext.Provider
|
||||
value={{
|
||||
|
@ -393,14 +393,14 @@ export const FileTreeMenu = () => {
|
||||
function createFile() {
|
||||
send({
|
||||
type: 'Create file',
|
||||
data: { name: '', makeDir: false },
|
||||
data: { name: '', makeDir: false, shouldSetToRename: true },
|
||||
})
|
||||
}
|
||||
|
||||
function createFolder() {
|
||||
send({
|
||||
type: 'Create file',
|
||||
data: { name: '', makeDir: true },
|
||||
data: { name: '', makeDir: true, shouldSetToRename: true },
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -9,10 +9,12 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
|
||||
export const KclEditorMenu = ({ children }: PropsWithChildren) => {
|
||||
const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } =
|
||||
useConvertToVariable()
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
@ -77,6 +79,22 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => {
|
||||
</small>
|
||||
</a>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<button
|
||||
onClick={() => {
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: {
|
||||
groupId: 'code',
|
||||
name: 'open-kcl-example',
|
||||
},
|
||||
})
|
||||
}}
|
||||
className={styles.button}
|
||||
>
|
||||
<span>Load a sample model</span>
|
||||
</button>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<a
|
||||
className={styles.button}
|
||||
@ -85,7 +103,7 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => {
|
||||
rel="noopener noreferrer"
|
||||
onClick={openExternalBrowserIfDesktop()}
|
||||
>
|
||||
<span>KCL samples</span>
|
||||
<span>View all samples</span>
|
||||
<small>
|
||||
zoo.dev
|
||||
<FontAwesomeIcon
|
||||
|
@ -3,8 +3,6 @@ import { createContext, useContext, useEffect, useState } from 'react'
|
||||
import { type IndexLoaderData } from 'lib/types'
|
||||
import { useLoaderData } from 'react-router-dom'
|
||||
import { codeManager, kclManager } from 'lib/singletons'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { Command } from 'lib/commandTypes'
|
||||
|
||||
const KclContext = createContext({
|
||||
code: codeManager?.code || '',
|
||||
@ -37,7 +35,6 @@ export function KclContextProvider({
|
||||
const [errors, setErrors] = useState<KCLError[]>([])
|
||||
const [logs, setLogs] = useState<string[]>([])
|
||||
const [wasmInitFailed, setWasmInitFailed] = useState(false)
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
|
||||
useEffect(() => {
|
||||
codeManager.registerCallBacks({
|
||||
@ -53,28 +50,6 @@ export function KclContextProvider({
|
||||
})
|
||||
}, [])
|
||||
|
||||
// Add format code to command palette.
|
||||
useEffect(() => {
|
||||
const commands: Command[] = [
|
||||
{
|
||||
name: 'format-code',
|
||||
displayName: 'Format Code',
|
||||
description: 'Nicely formats the KCL code in the editor.',
|
||||
needsReview: false,
|
||||
groupId: 'code',
|
||||
icon: 'code',
|
||||
onSubmit: (data) => {
|
||||
kclManager.format()
|
||||
},
|
||||
},
|
||||
]
|
||||
commandBarSend({ type: 'Add commands', data: { commands } })
|
||||
|
||||
return () => {
|
||||
commandBarSend({ type: 'Remove commands', data: { commands } })
|
||||
}
|
||||
}, [kclManager, commandBarSend])
|
||||
|
||||
return (
|
||||
<KclContext.Provider
|
||||
value={{
|
||||
|
@ -4,6 +4,7 @@ import { Actor, AnyStateMachine, ContextFrom, EventFrom } from 'xstate'
|
||||
import { Selection } from './selections'
|
||||
import { Identifier, Expr, VariableDeclaration } from 'lang/wasm'
|
||||
import { commandBarMachine } from 'machines/commandBarMachine'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
type Icon = CustomIconName
|
||||
const PLATFORMS = ['both', 'web', 'desktop'] as const
|
||||
@ -67,6 +68,12 @@ export type Command<
|
||||
name: CommandName
|
||||
groupId: T['id']
|
||||
needsReview: boolean
|
||||
reviewMessage?:
|
||||
| string
|
||||
| ReactNode
|
||||
| ((
|
||||
commandBarContext: { argumentsToSubmit: Record<string, unknown> } // Should be the commandbarMachine's context, but it creates a circular dependency
|
||||
) => string | ReactNode)
|
||||
onSubmit: (data?: CommandSchema) => void
|
||||
onCancel?: () => void
|
||||
args?: {
|
||||
@ -181,7 +188,7 @@ export type CommandArgument<
|
||||
machineContext?: ContextFrom<T>
|
||||
) => boolean)
|
||||
skip?: boolean
|
||||
machineActor: Actor<T>
|
||||
machineActor?: Actor<T>
|
||||
/** For showing a summary display of the current value, such as in
|
||||
* the command bar's header
|
||||
*/
|
||||
|
@ -95,3 +95,10 @@ export const MAKE_TOAST_MESSAGES = {
|
||||
ERROR_STARTING_PRINT: 'Error while starting print',
|
||||
SUCCESS: 'Started print successfully',
|
||||
}
|
||||
|
||||
/** The URL for the KCL samples manifest files */
|
||||
export const KCL_SAMPLES_MANIFEST_URLS = {
|
||||
remote:
|
||||
'https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/manifst.json',
|
||||
localFallback: '/kcl-samples-manifest-fallback.json',
|
||||
} as const
|
||||
|
31
src/lib/getKclSamplesManifest.ts
Normal file
31
src/lib/getKclSamplesManifest.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { KCL_SAMPLES_MANIFEST_URLS } from './constants'
|
||||
import { isDesktop } from './isDesktop'
|
||||
|
||||
export type KclSamplesManifestItem = {
|
||||
file: string
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export async function getKclSamplesManifest() {
|
||||
let response = await fetch(KCL_SAMPLES_MANIFEST_URLS.remote)
|
||||
if (!response.ok) {
|
||||
console.warn(
|
||||
'Failed to fetch latest remote KCL samples manifest, falling back to local:',
|
||||
response.statusText
|
||||
)
|
||||
response = await fetch(
|
||||
(isDesktop() ? '.' : '') + KCL_SAMPLES_MANIFEST_URLS.localFallback
|
||||
)
|
||||
if (!response.ok) {
|
||||
console.error(
|
||||
'Failed to fetch fallback KCL samples manifest:',
|
||||
response.statusText
|
||||
)
|
||||
return []
|
||||
}
|
||||
}
|
||||
return response.json().then((manifest) => {
|
||||
return manifest as KclSamplesManifestItem[]
|
||||
})
|
||||
}
|
110
src/lib/kclCommands.ts
Normal file
110
src/lib/kclCommands.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import { CommandBarOverwriteWarning } from 'components/CommandBarOverwriteWarning'
|
||||
import { Command, CommandArgumentOption } from './commandTypes'
|
||||
import { kclManager } from './singletons'
|
||||
import { isDesktop } from './isDesktop'
|
||||
import { FILE_EXT } from './constants'
|
||||
|
||||
interface OnSubmitProps {
|
||||
sampleName: string
|
||||
code: string
|
||||
method: 'overwrite' | 'newFile'
|
||||
}
|
||||
|
||||
export function kclCommands(
|
||||
onSubmit: (p: OnSubmitProps) => Promise<void>,
|
||||
providedOptions: CommandArgumentOption<string>[]
|
||||
): Command[] {
|
||||
return [
|
||||
{
|
||||
name: 'format-code',
|
||||
displayName: 'Format Code',
|
||||
description: 'Nicely formats the KCL code in the editor.',
|
||||
needsReview: false,
|
||||
groupId: 'code',
|
||||
icon: 'code',
|
||||
onSubmit: () => {
|
||||
kclManager.format()
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'open-kcl-example',
|
||||
displayName: 'Open sample',
|
||||
description: 'Imports an example KCL program into the editor.',
|
||||
needsReview: true,
|
||||
icon: 'code',
|
||||
reviewMessage: ({ argumentsToSubmit }) =>
|
||||
argumentsToSubmit.method === 'newFile'
|
||||
? 'Create a new file with the example code?'
|
||||
: CommandBarOverwriteWarning({}),
|
||||
groupId: 'code',
|
||||
onSubmit(data) {
|
||||
if (!data?.sample) {
|
||||
return
|
||||
}
|
||||
const sampleCodeUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent(
|
||||
data.sample.replace(FILE_EXT, '')
|
||||
)}/${encodeURIComponent(data.sample)}`
|
||||
fetch(sampleCodeUrl)
|
||||
.then(async (response) => {
|
||||
if (!response.ok) {
|
||||
console.error('Failed to fetch sample code:', response.statusText)
|
||||
return
|
||||
}
|
||||
const code = await response.text()
|
||||
|
||||
return {
|
||||
sampleName: data.sample,
|
||||
code,
|
||||
method: data.method,
|
||||
}
|
||||
})
|
||||
.then((props) => {
|
||||
if (props?.code) {
|
||||
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: providedOptions,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
34
src/lib/kclSamplesArray.json
Normal file
34
src/lib/kclSamplesArray.json
Normal file
@ -0,0 +1,34 @@
|
||||
[
|
||||
"80-20-rail",
|
||||
"a-parametric-bearing-pillow-block",
|
||||
"ball-bearing",
|
||||
"bracket",
|
||||
"brake-caliper",
|
||||
"car-wheel-assembly",
|
||||
"car-wheel",
|
||||
"enclosure",
|
||||
"flange-with-patterns",
|
||||
"flange-xy",
|
||||
"focusrite-scarlett-mounting-bracket",
|
||||
"french-press",
|
||||
"gear-rack",
|
||||
"gear",
|
||||
"hex-nut",
|
||||
"kitt",
|
||||
"lego",
|
||||
"lug-nut",
|
||||
"mounting-plate",
|
||||
"multi-axis-robot",
|
||||
"pipe-flange-assembly",
|
||||
"pipe",
|
||||
"poopy-shoe",
|
||||
"router-template-cross-bar",
|
||||
"router-template-slate",
|
||||
"screenshots",
|
||||
"sheet-metal-bracket",
|
||||
"socket-head-cap-screw",
|
||||
"step",
|
||||
"tire",
|
||||
"washer",
|
||||
"wheel-rotor"
|
||||
]
|
@ -469,8 +469,9 @@ export type ResolvedSelectionType = [Selection['type'] | 'other', number]
|
||||
* @returns
|
||||
*/
|
||||
export function getSelectionType(
|
||||
selection: Selections
|
||||
selection?: Selections
|
||||
): ResolvedSelectionType[] {
|
||||
if (!selection) return []
|
||||
const extrudableCount = selection.codeBasedSelections.filter((_, i) => {
|
||||
const singleSelection = {
|
||||
...selection,
|
||||
@ -485,7 +486,7 @@ export function getSelectionType(
|
||||
}
|
||||
|
||||
export function getSelectionTypeDisplayText(
|
||||
selection: Selections
|
||||
selection?: Selections
|
||||
): string | null {
|
||||
const selectionsByType = getSelectionType(selection)
|
||||
|
||||
|
@ -283,7 +283,7 @@ export const commandBarMachine = setup({
|
||||
typeof argConfig.options === 'function'
|
||||
? argConfig.options(
|
||||
input,
|
||||
argConfig.machineActor.getSnapshot().context
|
||||
argConfig.machineActor?.getSnapshot().context
|
||||
)
|
||||
: argConfig.options
|
||||
).some((o) => o.value === argValue)
|
||||
|
@ -20,6 +20,7 @@ type FileMachineEvents =
|
||||
makeDir: boolean
|
||||
content?: string
|
||||
silent?: boolean
|
||||
shouldSetToRename?: boolean
|
||||
}
|
||||
}
|
||||
| { type: 'Delete file'; data: FileEntry }
|
||||
@ -42,6 +43,7 @@ type FileMachineEvents =
|
||||
output: {
|
||||
message: string
|
||||
path: string
|
||||
shouldSetToRename: boolean
|
||||
}
|
||||
}
|
||||
| {
|
||||
@ -107,6 +109,10 @@ export const fileMachine = setup({
|
||||
},
|
||||
'Is not silent': ({ event }) =>
|
||||
event.type === 'Create file' ? !event.data.silent : false,
|
||||
'Should set to rename': ({ event }) =>
|
||||
(event.type === 'xstate.done.actor.create-and-open-file' &&
|
||||
event.output.shouldSetToRename) ||
|
||||
false,
|
||||
},
|
||||
actors: {
|
||||
readFiles: fromPromise(({ input }: { input: Project }) =>
|
||||
@ -119,6 +125,7 @@ export const fileMachine = setup({
|
||||
makeDir: boolean
|
||||
selectedDirectory: FileEntry
|
||||
content: string
|
||||
shouldSetToRename: boolean
|
||||
}
|
||||
}) => Promise.resolve({ message: '', path: '' })
|
||||
),
|
||||
@ -149,7 +156,7 @@ export const fileMachine = setup({
|
||||
),
|
||||
},
|
||||
}).createMachine({
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QDECWAbMACAtgQwGMALVAOzAGI9ZZUpSBtABgF1FQAHAe1oBdUupdiAAeiACwAmADQgAnogAcANgCsAOnHiAjOICcAZh3K9TRQHYAvpdlpMuQiXIUASmABmAJzhFmbJCDcfAJCAWIIUrIKCHpq6nraipJJKorahqrWthjY+MRkYOoAEtRYpFxY7jmwFADC3ni82FWYfsJBqPyCwuG6hurKTAYG5mlSyeJRiHqS2prKg6Mj2pKz4lkgdrmOBcWlLXCuYKR4OM05bQEdXaGgvdpD6qPiioqqieJM2gaqUxELT3MQz0eleBlMhmUGy2Dny5D2sEq1TqDSaSNarHaPE6IR6iD6BgGQxGY1Wikm8gkQM0n2UknERm0qmUw2hOVhTkKJURBxq9TAjXOrW0-k42JueIQBKJw1GujJFOiqkkTE0X3M5mUuiYgxmbPseU5CPRhwAImBMGiDpcxcFumF8dptMp1GZRlqNYYdXo-ml1IlzMkHuZVAZJAY1PrtnCuftkQB5DjHE02wLi3EOqX6QmDWWkiZ-HSKf3gph6ZWqcwJIZRjm7bkmmoAZTAvCwsAtYAITQgWAgqG83a4njkqeuGbu+MkLPU7x+iiGszJfwj4ldTvB6UURh0klrht2-MaZCgWDwpF7XCTpBPJooEEEhTIADcuABrQoEVFgAC054gP5XscP7WpiVzpvak5SnO6hJD8RYfJ8ir4kw06zqoTDiMyTA4WGPz7js8JHvwpCnv+WBATepF3mAnieMO6gcOgjTuMOODqF+ApNH+F6AdeIEXGBto4pBoj4u8GjOiMqjiKMqhJFhfyVqqEbJIoCTkmk3wETG6huCcOC3gc96PuoL7voU3gGb+oGimmdq3GJCARuY8RMroEk6MMihKeWrpYepEZMKMSw6Ua+mnEZOQULR9GeIxzG8KxnjsVZpw2YJdnjqJ4QjK59KaoGLKhh6fyBpIsFgtqKjKuCYW7OalpRZgJnwuZH7qBAnbcbZWIOZKeXqAVyhFT8EbaOYK44f6kjlhYG6FVYNibOyB7wo1rbNZQsUMUxLFsZ13UZRiWUQY5uUakNskjdOY2lZSCAqhV24LlhHpMl89Xwm4eD9tRvKtU+pCvh1DQAbyY5nZKMwqZWwxqMorzltoZUrK6YbOlJoazIoX2FD9f2ngDD5tcDFnqGDAmYLADAin1InndMKrqD85jw8ySPvH8pgulqoYWEjc16HjekCoTjYxXRu2JclqVi1TcCQ-1mYwyzcMRhz6lcw9C56Cz4Yatd05ISLxFbYDZlkx1nGCgrSsM5KTJVgMMmjKkEYmAYfwrOkQ30i8WFSF8mTLTCa2FGb-3RTt8V7UlB02z1mX0xKmZMgu8R6C8YahqYwUow9TqBkNxXiLmUgGEyIsRYZUctSTQMg5ZxzpXbdPgcrUEuW57xYUyXkGD5D2Bhog9aKsLyzQywsbOUXXwAEYeEWAKcTk5P7KH8G+ujhuHDDJTJZ0t2QGsvxrlI2q85fiBhlgMZcQq8+iqDJ3OzAML2qCCqxDEkIsNryK+jMpSV1clIck3xB6ViLIWEwmhXiJF0EYIqptUS3nIpRLaQDHajAqvKCwqxEZTxkIXVChJNTqUDCkB4L9q4t1rkTHI2DMyRAeosIawxFxDESMoLCIsNokUYZgZhUF1IDGdK7LyWgX6TULqCIagYcKSHMAya6VdQ6rTPgTLaC9hKpygnSOY8FA7kj0J6WR0QISzn0J8IYN0tIi0TMcLBHcHZp1wf6cB5UiFZxIdEcEhJKyvQ9BqGSqCuIuL0WvXoHj8HeKSL472E0KrBRfrVRGL9cbWEsEAA */
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QDECWAbMACAtgQwGMALVAOzAGI9ZZUpSBtABgF1FQAHAe1oBdUupdiAAeiACwAmADQgAnogAcANgCsAOnHiAjOICcAZh3K9TRQHYAvpdlpMuQiXIUASmABmAJzhFmbJCDcfAJCAWIIUrIKCHpq6nraipJJKorahqrWthjY+MRkYOoAEtRYpFxY7jmwFADC3ni82FWYfsJBqPyCwuG6hurKTAYG5mlSyeJRiHqS2prKg6Mj2pKz4lkgdrmOBcWlLXCuYKR4OM05bQEdXaGgvdpD6qPiioqqieJM2gaqUxELT3MQz0eleBlMhmUGy2Dny5D2sEq1TqDSaSNarHaPE6IR6iF0ij08SGklUpikym0qkm8gkJie1KYklB70UBkkTChNk2OVhTkKJURBxq9TAjXOrW0-k42JueIQfQMAyGIzGq0UNOiqg5mi+5nMlM+gxm0N5eX5CPRhwAImBMGiDpcZcFumF8dptMp1GZRpT9YZOXo-ml1IlzMkHuZVOyDGpTfZzbtBVaagB5DjHK1OwKy3FuhX6JWDYajXTqzUSRKh8FMPTa1TmBJDePbOEC-bIgDKYF4WFgdrABCaECwEFQ3iHXE8cmz1zzd3xkmUSveP0UJJWyT+sfE3o94PSbK0KxbfN2osaZCgWDwpBHXAzpCvVooEEEhTIADcuABrQoEVEwAAWlvCAgIfY4gMdTErlzV0FwVH5dx3ZdtUSA9lD+B4qW9EkmE5ZkEnMAxT0TeEL34Uhr1ArAIKfKiXzfeEv1-f9AJAu9wMfKCLilLEXVuURpmUcw91JakDAsLQRiwplFHUIxlDeUxZlGRRSJ2cjUWfGi6OfA4KDATxPCndQOHQRp3CnHB1AAsUmg4sC6J4jFpRzAT5SpHDPRGalRlUJJxF+WkEAbJgFOUZJCQ+NJvg0tt1DcE4cH0nJX3fdQWL-dRvGS4DoLcud4KEhBY1EhJ3iCqkdGGRQ-jJDQmCCwlYyYUYlnii0ktOVLMHS5jSG-bLctOfLeMKuDBPCMr4i8qrqW+SS-nDDRJK0VYXmZcRwU63ZupShiDKMkzPDMizeCszwbJGs4XLAWdJvlEZRMkcQDXDVDY20cxltWdRXjZXQzDUSQdu5GEyMKW17V6ygmI-QbWPUCABwcgr+JxYrpv1dRXvepcfi+n6QoMfDQ2ZALzH3d6rHBs1NKh1HYcM4zTPMyzrOR1GxtcjG5XzZ7cbekSCejP0-g5SR-skprVD9Kkvl2+E3DwMdDuReHMsR4axTA4UHo8-MZnCn5iNjOXXjrbRlpWb12U9Hzo1mdS6YTBnEt12Gak1rLCgaPXqgYPjYMNhDjYUhthjUJTCXeP5TC9SlowsS260JJXChVtXr2FFmTrOjmrpy3W7tgA3Mam6YdVNqOLdj62QvXIkY31YWl0+dZXdbC0KOZn3tbY+yefumDnQrzyGyJNQ3teJTYxMAwsNmIkNpeIKpC+TIu7PLT7OZ462fOy6bLs8U7vL-mEKpdd4j0F52WjUw2ob6IPXDXHUPEYspAMKlrG5coKN4ABAhgzPm84SpAUwiFKBuF8L7iZGoEEPwM6WnKCmcBWN8Skynl-CErx9CqGpPHWYAw2TKRmBySSkhUHJmFJgyuiFvqaAmItN45gdB1RCngzQrxEi6CMB9VBvcGK6UfLDBhnlRhSzLBYVYSktoyBCg8UGTwlJ6HDCkB4RDUH7QkSHce+ZIghUWLjYYeFf4qCCqg6GPZ9Fj0viVQkAxPR+RqloIhxNX6glxuGfCkgOGCKTroz26tMDAIcRA8IkU5hIXXhqDRjYvHTFrOoakd98JlQjNoVB6Zjj2PcoYq+0jQxSDkUuJId8lHRHBCuUYTU-T6mpMI7SYSwCSPzN9JIpTkjhgqYorC30pZtSIdqWMbwAr-0sEAA */
|
||||
id: 'File machine',
|
||||
|
||||
initial: 'Reading files',
|
||||
@ -222,15 +229,41 @@ export const fileMachine = setup({
|
||||
makeDir: false,
|
||||
selectedDirectory: context.selectedDirectory,
|
||||
content: '',
|
||||
shouldSetToRename: false,
|
||||
}
|
||||
return {
|
||||
name: event.data.name,
|
||||
makeDir: event.data.makeDir,
|
||||
selectedDirectory: context.selectedDirectory,
|
||||
content: event.data.content ?? '',
|
||||
shouldSetToRename: event.data.shouldSetToRename ?? false,
|
||||
}
|
||||
},
|
||||
onDone: [
|
||||
{
|
||||
target: 'Reading files',
|
||||
|
||||
actions: [
|
||||
{
|
||||
type: 'createToastSuccess',
|
||||
params: ({
|
||||
event,
|
||||
}: {
|
||||
// TODO: rely on type inference
|
||||
event: Extract<
|
||||
FileMachineEvents,
|
||||
{ type: 'xstate.done.actor.create-and-open-file' }
|
||||
>
|
||||
}) => {
|
||||
return { message: event.output.message }
|
||||
},
|
||||
},
|
||||
'addFileToRenamingQueue',
|
||||
'navigateToFile',
|
||||
],
|
||||
|
||||
guard: 'Should set to rename',
|
||||
},
|
||||
{
|
||||
target: 'Reading files',
|
||||
actions: [
|
||||
@ -248,7 +281,6 @@ export const fileMachine = setup({
|
||||
return { message: event.output.message }
|
||||
},
|
||||
},
|
||||
'addFileToRenamingQueue',
|
||||
'navigateToFile',
|
||||
],
|
||||
},
|
||||
|
Reference in New Issue
Block a user