Always give new files and dirs a new index if their names are taken (#3460)
* Add actual support for makeUnique parameter * Add uniqueness logic to dirs, make text-to-cad receive unique filename * No longer need makeUnique flag, it's always on * fmt * Don't show toast when name hasn't changed during a rename * fmt * Get "interact with many" text-to-cad test passing again * Get "engine fails export" back to reliably green * Maybe more stable click target for text-to-cad test * Make "export is already going" test moderately more reliable * Mark "export is already going" as fixme * Undo that fixme thing I take it back
This commit is contained in:
@ -3,6 +3,10 @@ import { test, expect, Page } from '@playwright/test'
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
import {
|
||||
PLAYWRIGHT_MOCK_EXPORT_DURATION,
|
||||
PLAYWRIGHT_TOAST_DURATION,
|
||||
} from 'lib/constants'
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await setup(context, page)
|
||||
@ -250,7 +254,7 @@ const sketch001 = startSketchAt([-0, -0])
|
||||
await expect(exportButton).toBeVisible()
|
||||
|
||||
// Click the export button
|
||||
exportButton.click()
|
||||
await exportButton.click()
|
||||
|
||||
// Click the stl.
|
||||
const stlOption = page.getByText('glTF')
|
||||
@ -279,7 +283,7 @@ const sketch001 = startSketchAt([-0, -0])
|
||||
await expect(exportingToastMessage).not.toBeVisible()
|
||||
|
||||
// Click the code editor
|
||||
page.locator('.cm-content').click()
|
||||
await page.locator('.cm-content').click()
|
||||
|
||||
await page.waitForTimeout(2000)
|
||||
|
||||
@ -288,8 +292,7 @@ const sketch001 = startSketchAt([-0, -0])
|
||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
||||
|
||||
// Now add in code that works.
|
||||
page.locator('.cm-content').fill(bracket)
|
||||
page.locator('.cm-content').click()
|
||||
await page.locator('.cm-content').fill(bracket)
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
@ -302,7 +305,7 @@ const sketch001 = startSketchAt([-0, -0])
|
||||
// Now try exporting
|
||||
|
||||
// Click the export button
|
||||
exportButton.click()
|
||||
await exportButton.click()
|
||||
|
||||
// Click the stl.
|
||||
await expect(stlOption).toBeVisible()
|
||||
@ -330,9 +333,23 @@ const sketch001 = startSketchAt([-0, -0])
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async (code) => {
|
||||
await test.step('Set up the code and durations', async () => {
|
||||
await page.addInitScript(
|
||||
async ({ code, toastDurationKey, exportDurationKey }) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
}, bracket)
|
||||
// Normally we make these durations short to speed up PW tests
|
||||
// to superhuman speeds. But in this case we want to make sure
|
||||
// the export toast is visible for a while, and the export
|
||||
// duration is long enough to make sure the export toast is visible
|
||||
localStorage.setItem(toastDurationKey, '1500')
|
||||
localStorage.setItem(exportDurationKey, '750')
|
||||
},
|
||||
{
|
||||
code: bracket,
|
||||
toastDurationKey: PLAYWRIGHT_TOAST_DURATION,
|
||||
exportDurationKey: PLAYWRIGHT_MOCK_EXPORT_DURATION,
|
||||
}
|
||||
)
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
@ -345,35 +362,42 @@ const sketch001 = startSketchAt([-0, -0])
|
||||
|
||||
// expect zero errors in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
})
|
||||
|
||||
const errorToastMessage = page.getByText(`Error while exporting`)
|
||||
const exportingToastMessage = page.getByText(`Exporting...`)
|
||||
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
||||
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
|
||||
const successToastMessage = page.getByText(`Exported successfully`)
|
||||
|
||||
await test.step('Blocked second export', async () => {
|
||||
await clickExportButton(page)
|
||||
|
||||
await expect(exportingToastMessage).toBeVisible()
|
||||
|
||||
await clickExportButton(page)
|
||||
|
||||
await test.step('The second export is blocked', async () => {
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
await expect(exportingToastMessage).toBeVisible()
|
||||
await expect(alreadyExportingToastMessage).toBeVisible()
|
||||
|
||||
await page.waitForTimeout(1000)
|
||||
})
|
||||
|
||||
// Expect it to succeed.
|
||||
await test.step('The first export still succeeds', async () => {
|
||||
await expect(exportingToastMessage).not.toBeVisible()
|
||||
await expect(errorToastMessage).not.toBeVisible()
|
||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
||||
|
||||
const successToastMessage = page.getByText(`Exported successfully`)
|
||||
await expect(successToastMessage).toBeVisible()
|
||||
|
||||
await expect(alreadyExportingToastMessage).not.toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
await test.step('Successful, unblocked export', async () => {
|
||||
// Try exporting again.
|
||||
await clickExportButton(page)
|
||||
|
||||
@ -389,18 +413,20 @@ const sketch001 = startSketchAt([-0, -0])
|
||||
|
||||
await expect(successToastMessage).toBeVisible()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
async function clickExportButton(page: Page) {
|
||||
await test.step('Running export flow', async () => {
|
||||
// export the model
|
||||
const exportButton = page.getByTestId('export-pane-button')
|
||||
await expect(exportButton).toBeVisible()
|
||||
await expect(exportButton).toBeEnabled()
|
||||
|
||||
// Click the export button
|
||||
exportButton.click()
|
||||
await exportButton.click()
|
||||
|
||||
// Click the stl.
|
||||
const gltfOption = page.getByText('glTF')
|
||||
const gltfOption = page.getByRole('option', { name: 'glTF' })
|
||||
await expect(gltfOption).toBeVisible()
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
@ -410,4 +436,5 @@ async function clickExportButton(page: Page) {
|
||||
await expect(submitButton).toBeVisible()
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
})
|
||||
}
|
||||
|
@ -502,7 +502,7 @@ test.describe('Text-to-CAD tests', () => {
|
||||
const rejectButton = page.getByRole('button', { name: 'Reject' })
|
||||
await expect(rejectButton.first()).toBeVisible()
|
||||
// Click the reject button on the first toast.
|
||||
rejectButton.first().click()
|
||||
await rejectButton.first().click()
|
||||
|
||||
// The first toast should disappear, but not the others.
|
||||
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
|
||||
@ -521,7 +521,7 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await expect(page.getByText('Copied')).toBeVisible()
|
||||
|
||||
// Click in the code editor.
|
||||
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
|
||||
await page.locator('.cm-content').click()
|
||||
|
||||
// Paste the code.
|
||||
await page.keyboard.down(CtrlKey)
|
||||
@ -550,16 +550,12 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await expect(page.getByText('Copied')).toBeVisible()
|
||||
|
||||
// Click in the code editor.
|
||||
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
|
||||
await page.locator('.cm-content').click()
|
||||
|
||||
// Paste the code.
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.press('KeyA')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.press('ControlOrMeta+a')
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.down(CtrlKey)
|
||||
await page.keyboard.press('KeyV')
|
||||
await page.keyboard.up(CtrlKey)
|
||||
await page.keyboard.press('ControlOrMeta+v')
|
||||
|
||||
// Expect the code to be pasted.
|
||||
await expect(page.locator('.cm-content')).toContainText(`2x4`)
|
||||
@ -568,7 +564,6 @@ test.describe('Text-to-CAD tests', () => {
|
||||
// Find the toast close button.
|
||||
await expect(closeButton).toBeVisible()
|
||||
await closeButton.click()
|
||||
await expect(page.getByText('Copied')).not.toBeVisible()
|
||||
await expect(successToastMessage).not.toBeVisible()
|
||||
})
|
||||
|
||||
@ -620,7 +615,7 @@ test.describe('Text-to-CAD tests', () => {
|
||||
const dismissButton = page.getByRole('button', { name: 'Dismiss' })
|
||||
await expect(dismissButton).toBeVisible()
|
||||
// Click the dismiss button on the first toast.
|
||||
dismissButton.first().click()
|
||||
await dismissButton.first().click()
|
||||
|
||||
// Make sure the failure toast disappears.
|
||||
await expect(failureToastMessage).not.toBeVisible()
|
||||
|
@ -21,11 +21,13 @@ import {
|
||||
rename,
|
||||
create,
|
||||
writeTextFile,
|
||||
exists,
|
||||
} from '@tauri-apps/plugin-fs'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { join, sep } from '@tauri-apps/api/path'
|
||||
import { DEFAULT_FILE_NAME, FILE_EXT } from 'lib/constants'
|
||||
import { getProjectInfo } from 'lib/tauri'
|
||||
import { getNextDirName, getNextFileName } from 'lib/tauriFS'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -105,14 +107,20 @@ export const FileMachineProvider = ({
|
||||
let createdPath: string
|
||||
|
||||
if (event.data.makeDir) {
|
||||
createdPath = await join(context.selectedDirectory.path, createdName)
|
||||
let { name, path } = await getNextDirName({
|
||||
entryName: createdName,
|
||||
baseDir: context.selectedDirectory.path,
|
||||
})
|
||||
createdName = name
|
||||
createdPath = path
|
||||
await mkdir(createdPath)
|
||||
} else {
|
||||
createdPath =
|
||||
context.selectedDirectory.path +
|
||||
sep() +
|
||||
createdName +
|
||||
(createdName.endsWith(FILE_EXT) ? '' : FILE_EXT)
|
||||
const { name, path } = await getNextFileName({
|
||||
entryName: createdName,
|
||||
baseDir: context.selectedDirectory.path,
|
||||
})
|
||||
createdName = name
|
||||
createdPath = path
|
||||
await create(createdPath)
|
||||
if (event.data.content) {
|
||||
await writeTextFile(createdPath, event.data.content)
|
||||
@ -129,14 +137,20 @@ export const FileMachineProvider = ({
|
||||
let createdPath: string
|
||||
|
||||
if (event.data.makeDir) {
|
||||
createdPath = await join(context.selectedDirectory.path, createdName)
|
||||
let { name, path } = await getNextDirName({
|
||||
entryName: createdName,
|
||||
baseDir: context.selectedDirectory.path,
|
||||
})
|
||||
createdName = name
|
||||
createdPath = path
|
||||
await mkdir(createdPath)
|
||||
} else {
|
||||
createdPath =
|
||||
context.selectedDirectory.path +
|
||||
sep() +
|
||||
createdName +
|
||||
(createdName.endsWith(FILE_EXT) ? '' : FILE_EXT)
|
||||
const { name, path } = await getNextFileName({
|
||||
entryName: createdName,
|
||||
baseDir: context.selectedDirectory.path,
|
||||
})
|
||||
createdName = name
|
||||
createdPath = path
|
||||
await create(createdPath)
|
||||
if (event.data.content) {
|
||||
await writeTextFile(createdPath, event.data.content)
|
||||
|
@ -382,11 +382,17 @@ export const FileTreeMenu = () => {
|
||||
const { send } = useFileContext()
|
||||
|
||||
async function createFile() {
|
||||
send({ type: 'Create file', data: { name: '', makeDir: false } })
|
||||
send({
|
||||
type: 'Create file',
|
||||
data: { name: '', makeDir: false },
|
||||
})
|
||||
}
|
||||
|
||||
async function createFolder() {
|
||||
send({ type: 'Create file', data: { name: '', makeDir: true } })
|
||||
send({
|
||||
type: 'Create file',
|
||||
data: { name: '', makeDir: true },
|
||||
})
|
||||
}
|
||||
|
||||
useHotkeyWrapper(['meta + n'], createFile)
|
||||
|
@ -85,6 +85,7 @@ import {
|
||||
} from 'lang/std/engineConnection'
|
||||
import { submitAndAwaitTextToKcl } from 'lib/textToCad'
|
||||
import { useFileContext } from 'hooks/useFileContext'
|
||||
import { PLAYWRIGHT_MOCK_EXPORT_DURATION } from 'lib/constants'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -393,10 +394,24 @@ export const ModelingMachineProvider = ({
|
||||
selection: { type: 'default_scene' },
|
||||
}
|
||||
|
||||
const mockExportDuration = window.localStorage.getItem(
|
||||
PLAYWRIGHT_MOCK_EXPORT_DURATION
|
||||
)
|
||||
|
||||
console.log('mockExportDuration', mockExportDuration)
|
||||
|
||||
// Artificially delay the export in playwright tests
|
||||
toast.promise(
|
||||
Promise.all([
|
||||
exportFromEngine({
|
||||
format: format,
|
||||
}),
|
||||
mockExportDuration
|
||||
? new Promise((resolve) =>
|
||||
setTimeout(resolve, Number(mockExportDuration))
|
||||
)
|
||||
: Promise.resolve(),
|
||||
]),
|
||||
{
|
||||
loading: 'Starting print...',
|
||||
success: 'Started print successfully',
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
createUpdaterRestartModal,
|
||||
} from 'components/UpdaterRestartModal'
|
||||
import { AppStreamProvider } from 'AppState'
|
||||
import { PLAYWRIGHT_KEY, PLAYWRIGHT_TOAST_DURATION } from 'lib/constants'
|
||||
|
||||
// uncomment for xstate inspector
|
||||
// import { DEV } from 'env'
|
||||
@ -24,6 +25,9 @@ import { AppStreamProvider } from 'AppState'
|
||||
// })
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
|
||||
const maybePlaywrightToastDuration = Number(
|
||||
window?.localStorage.getItem(PLAYWRIGHT_TOAST_DURATION)
|
||||
)
|
||||
|
||||
root.render(
|
||||
<HotkeysProvider>
|
||||
@ -44,8 +48,10 @@ root.render(
|
||||
secondary: 'oklch(48.62% 0.1654 142.5deg)',
|
||||
},
|
||||
duration:
|
||||
window?.localStorage.getItem('playwright') === 'true'
|
||||
? 10 // speed up e2e tests
|
||||
window?.localStorage.getItem(PLAYWRIGHT_KEY) === 'true'
|
||||
? maybePlaywrightToastDuration > 0
|
||||
? maybePlaywrightToastDuration
|
||||
: 10 // optionally speed up e2e tests
|
||||
: 1500,
|
||||
},
|
||||
}}
|
||||
|
@ -56,3 +56,10 @@ export const KCL_DEFAULT_CONSTANT_PREFIXES = {
|
||||
/** The default KCL length expression */
|
||||
export const KCL_DEFAULT_LENGTH = `5`
|
||||
export const COOKIE_NAME = '__Secure-next-auth.session-token'
|
||||
|
||||
/** localStorage key to determine if we're in Playwright tests */
|
||||
export const PLAYWRIGHT_KEY = 'playwright'
|
||||
/** localStorage key to set toast duration in Playwright tests */
|
||||
export const PLAYWRIGHT_TOAST_DURATION = 'playwright-toast-duration'
|
||||
/** localStorage key to set mock export pause duration in Playwright tests */
|
||||
export const PLAYWRIGHT_MOCK_EXPORT_DURATION = 'playwright-mock-export-duration'
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { appConfigDir } from '@tauri-apps/api/path'
|
||||
import { appConfigDir, join } from '@tauri-apps/api/path'
|
||||
import { isTauri } from './isTauri'
|
||||
import type { FileEntry } from 'lib/types'
|
||||
import {
|
||||
FILE_EXT,
|
||||
INDEX_IDENTIFIER,
|
||||
MAX_PADDING,
|
||||
ONBOARDING_PROJECT_NAME,
|
||||
@ -15,6 +16,7 @@ import {
|
||||
readAppSettingsFile,
|
||||
} from './tauri'
|
||||
import { engineCommandManager } from './singletons'
|
||||
import { exists } from '@tauri-apps/plugin-fs'
|
||||
|
||||
export const isHidden = (fileOrDir: FileEntry) =>
|
||||
!!fileOrDir.name?.startsWith('.')
|
||||
@ -162,3 +164,56 @@ export async function createAndOpenNewProject({
|
||||
)
|
||||
return newProject
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next available file name by appending a hyphen and number to the end of the name
|
||||
* @todo move this to the equivalent of tauriFS.ts for Electron migration
|
||||
*/
|
||||
export async function getNextFileName({
|
||||
entryName,
|
||||
baseDir,
|
||||
}: {
|
||||
entryName: string
|
||||
baseDir: string
|
||||
}) {
|
||||
// Remove any existing index from the name before adding a new one
|
||||
let createdName = entryName.replace(FILE_EXT, '') + FILE_EXT
|
||||
let createdPath = await join(baseDir, createdName)
|
||||
let i = 1
|
||||
while (await exists(createdPath)) {
|
||||
const matchOnIndexAndExtension = new RegExp(`(-\\d+)?(${FILE_EXT})?$`)
|
||||
createdName =
|
||||
entryName.replace(matchOnIndexAndExtension, '') + `-${i}` + FILE_EXT
|
||||
createdPath = await join(baseDir, createdName)
|
||||
i++
|
||||
}
|
||||
return {
|
||||
name: createdName,
|
||||
path: createdPath,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next available directory name by appending a hyphen and number to the end of the name
|
||||
* @todo move this to the equivalent of tauriFS.ts for Electron migration
|
||||
*/
|
||||
export async function getNextDirName({
|
||||
entryName,
|
||||
baseDir,
|
||||
}: {
|
||||
entryName: string
|
||||
baseDir: string
|
||||
}) {
|
||||
let createdName = entryName
|
||||
let createdPath = await join(baseDir, createdName)
|
||||
let i = 1
|
||||
while (await exists(createdPath)) {
|
||||
createdName = entryName.replace(/-\d+$/, '') + `-${i}`
|
||||
createdPath = await join(baseDir, createdName)
|
||||
i++
|
||||
}
|
||||
return {
|
||||
name: createdName,
|
||||
path: createdPath,
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import crossPlatformFetch from './crossPlatformFetch'
|
||||
import { isTauri } from './isTauri'
|
||||
import { Themes } from './theme'
|
||||
import { commandBarMachine } from 'machines/commandBarMachine'
|
||||
import { getNextFileName } from './tauriFS'
|
||||
|
||||
export async function submitTextToCadPrompt(
|
||||
prompt: string,
|
||||
@ -166,7 +167,7 @@ export async function submitAndAwaitTextToKcl({
|
||||
showFailureToast('Failed to generate parametric model')
|
||||
return e
|
||||
})
|
||||
.then((value) => {
|
||||
.then(async (value) => {
|
||||
if (value.code === undefined || !value.code || value.code.length === 0) {
|
||||
// We want to show the real error message to the user.
|
||||
if (value.error && value.error.length > 0) {
|
||||
@ -180,13 +181,23 @@ export async function submitAndAwaitTextToKcl({
|
||||
}
|
||||
|
||||
const TRUNCATED_PROMPT_LENGTH = 24
|
||||
const newFileName = `${value.prompt
|
||||
let newFileName = `${value.prompt
|
||||
.slice(0, TRUNCATED_PROMPT_LENGTH)
|
||||
.replace(/\s/gi, '-')
|
||||
.replace(/\W/gi, '-')
|
||||
.toLowerCase()}`
|
||||
.toLowerCase()}${FILE_EXT}`
|
||||
|
||||
if (isTauri()) {
|
||||
// We have to pre-emptively run our unique file name logic,
|
||||
// so that we can pass the unique file name to the toast,
|
||||
// and by extension the file-deletion-on-reject logic.
|
||||
newFileName = (
|
||||
await getNextFileName({
|
||||
entryName: newFileName,
|
||||
baseDir: context.selectedDirectory.path,
|
||||
})
|
||||
).name
|
||||
|
||||
fileMachineSend({
|
||||
type: 'Create file',
|
||||
data: {
|
||||
@ -194,14 +205,13 @@ export async function submitAndAwaitTextToKcl({
|
||||
makeDir: false,
|
||||
content: value.code,
|
||||
silent: true,
|
||||
makeUnique: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
...value,
|
||||
fileName: newFileName + FILE_EXT,
|
||||
fileName: newFileName,
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { Project } from 'wasm-lib/kcl/bindings/Project'
|
||||
|
||||
export const fileMachine = createMachine(
|
||||
{
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QDECWAbMACAtgQwGMALVAOzAGI9ZZUpSBtABgF1FQAHAe1oBdUupdiAAeiACwAmADQgAnogAcANgCsAOnHiAjOICcAZh3K9TRQHYAvpdlpMuQiXIUASmABmAJzhFmbJCDcfAJCAWIIUrIKCHpq6nraipJJKorahqrWthjY+MRkYOoAEtRYpFxY7jmwFADC3ni82FWYfsJBqPyCwuG6hurKTAYG5mlSyeJRiHqS2prKg6Mj2pKz4lkgdrmOBcWlLXCuYKR4OM05bQEdXaGgvdpD6qPiioqqieJM2gaqUxELT3MQz0eleBlMhmUGy2Dny5D2sEq1TqDSaSNarHaPE6IR6iD6BgGQxGY1Wikm8gkQM0n2UknERm0qmUw2hOVhTkKJURBxqABEwJg0QdLpxsTc8QhtNLlOozKNlNpzOZDEwTH80upEuZkg9zKoDJIDGo2fY8pyEejDgB5DjHK2iwLi3FhfH6QmDYajXRkinRHSKLXgph6VSSVQqh4GU3bOFc-bIgDKYF4WFggrABCaECwEFQ3izXE8ckd1xdd3xkhZ6neP0UQ1mZL+xvEcul4PSiiMOkkMY5u3qYEaZCgWDwpBzXDtpBHVooEEEhTIADcuABrQoEVFgAC044gO6nxx3IsxV2d3VdUtr6iSPwDH0+fvxTCrNdUTHEzKYP8NPz75oDqis77lgR4zqQo4HBQYCeJ4RbqBw6CNO4RY4OoW5Dk0e4Toe04nhcZ5isEl4VteTJaiy+riKMqhJF+fwRkw6jGskigJOSaTfABOzwm4Jw4LO0ELvCK7roU3gCbup7+MROKkaIiAjOY6j0uYyg6iyBqKuYfx0hoqixECIaGZITCqJkNibOygF8ccpxCTkMFwQhSEoWh6iSac0mEbJTokbcikIMpqk0RpVY-MaSp-Dqki3mCuhmGoRp6DxcbqAKQqOZg86LuoYkbuoEAZthMlYgFkohWp4VaVFumUsFP5apIoYWO26nKmlFqZSm2WULB8GeIhyG8KhnjocVQo+Rifllgp4RVWFmmRTpfxmXFXb1l+OlMl8XW7G4eB5pBVo1CJS6kKuhUNAevKlhegXhDMzE-OYwxqMoryhtoMUrHKhraIqtWzIo+12UdfVnXlBUSUOt3VAw2izQ9krPSxEbvcyX3vH8piyoqBoWF9rWpVZMK2YUh3HVByIDa5I1jehN0EZgsD3RVV5o69mOfexOMNfWegsUayphVWT5g-GPLIoOjTnK0SPlfJj1uv0nokj6EyMWZmhfMqiq0iGvZkzZvGFLL-AncJ0OXeJGHbizYDs8rkpMiqAyqDRryfcaJgGH8KzpKp9IvF+UhfJZ2Rmmb6gW31zmDcN7njfbWHTU7RH+S7V5MvW8R6C8hoGqYTDmD9DXSjqqlaeInpSAYTLWFZ5TFfAATk2bSsSleO7KH8O4aCCQ-D8PP6R9Z0fpdyZQVLyXflkF3whgMtcQq8+gWc+MSzAMm2GTMZkGEkkuWnP54c2R3xKpoEzfEfEYBn8q+aK8iS6EYmkn3HJ2geBfXz-NfEow4o+gsKsT6LVIgV1fISdS7EdQpAeBZE+-EHJWxyAAlWEQZANUWKpYYDYhiJGUF+E+PVLY00wJgyU7EBiA09uYHQ3YLL1WiJ2VSOofySEYR-AmKC4aQ2oVeOkcx7xh3JHoZUkjcbL09gXX84UuIn1tMcf+59s6X2AVqKQYCqxJALjg6I4JCQRi2jpZUnsv7AXQVQ9R3dNFJG0ckWKECDEByVHFUuFkwzGjeHRJulggA */
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QDECWAbMACAtgQwGMALVAOzAGI9ZZUpSBtABgF1FQAHAe1oBdUupdiAAeiACwAmADQgAnogAcANgCsAOnHiAjOICcAZh3K9TRQHYAvpdlpMuQiXIUASmABmAJzhFmbJCDcfAJCAWIIUrIKCHpq6nraipJJKorahqrWthjY+MRkYOoAEtRYpFxY7jmwFADC3ni82FWYfsJBqPyCwuG6hurKTAYG5mlSyeJRiHqS2prKg6Mj2pKz4lkgdrmOBcWlLXCuYKR4OM05bQEdXaGgvdpD6qPiioqqieJM2gaqUxELT3MQz0eleBlMhmUGy2Dny5D2sEq1TqDSaSNarHaPE6IR6iD6BgGQxGY1Wikm8gkQM0n2UknERm0qmUw2hOVhTkKJURBxq9TAjXOrW0-k42JueIQBKJw1GujJFOiqkkTE0X3M5mUuiYgxmbPseU5CPRhwAImBMGiDpcxcFumF8dptMp1GZRlqNYYdXo-ml1IlzMkHuZVAZJAY1PrtnCuftkQB5DjHE02wLi3EOqX6QmDWWkiZ-HSKf3gph6ZWqcwJIZRjm7bkmmoAZTAvCwsAtYAITQgWAgqG83a4njkqeuGbu+MkLPU7x+iiGszJfwj4ldTvB6UURh0klrht2-MaZCgWDwpF7XCTpBPJooEEEhTIADcuABrQoEVFgAC054gP5XscP7WpiVzpvak5SnO6hJD8RYfJ8ir4kw06zqoTDiMyTA4WGPz7js8JHvwpCnv+WBATepF3mAnieMO6gcOgjTuMOODqF+ApNH+F6AdeIEXGBto4pBoj4u8GjOiMqjiKMqhJFhfyVqqEbJIoCTkmk3wETG6huCcOC3gc96PuoL7voU3gGb+oGimmdq3GJCARuY8RMroEk6MMihKeWrpYepEZMKMSw6Ua+mnEZOQULR9GeIxzG8KxnjsVZpw2YJdnjqJ4QjK59KaoGLKhh6fyBpIsFgtqKjKuCYW7OalpRZgJnwuZH7qBAnbcbZWIOZKeXqAVyhFT8EbaOYK44f6kjlhYG6FVYNibOyB7wo1rbNZQsUMUxLFsZ13UZRiWUQY5uUakNskjdOY2lZSCAqhV24LlhHpMl89Xwm4eD9tRvKtU+pCvh1DQAbyY5nZKMwqZWwxqMorzltoZUrK6YbOlJoazIoX2FD9f2ngDD5tcDFnqGDAmYLADAin1InndMKrqD85jw8ySPvH8pgulqoYWEjc16HjekCoTjYxXRu2JclqVi1TcCQ-1mYwyzcMRhz6lcw9C56Cz4Yatd05ISLxFbYDZlkx1nGCgrSsM5KTJVgMMmjKkEYmAYfwrOkQ30i8WFSF8mTLTCa2FGb-3RTt8V7UlB02z1mX0xKmZMgu8R6C8YahqYwUow9TqBkNxXiLmUgGEyIsRYZUctSTQMg5ZxzpXbdPgcrUEuW57xYUyXkGD5D2Bhog9aKsLyzQywsbOUXXwAEYeEWAKcTk5P7KH8G+ujhuHDDJTJZ0t2QGsvxrlI2q85fiBhlgMZcQq8+iqDJ3OzAML2qCCqxDEkIsNryK+jMpSV1clIck3xB6ViLIWEwmhXiJF0EYIqptUS3nIpRLaQDHajAqvKCwqxEZTxkIXVChJNTqUDCkB4L9q4t1rkTHI2DMyRAeosIawxFxDESMoLCIsNokUYZgZhUF1IDGdK7LyWgX6TULqCIagYcKSHMAya6VdQ6rTPgTLaC9hKpygnSOY8FA7kj0J6WR0QISzn0J8IYN0tIi0TMcLBHcHZp1wf6cB5UiFZxIdEcEhJKyvQ9BqGSqCuIuL0WvXoHj8HeKSL472E0KrBRfrVRGL9cbWEsEAA */
|
||||
id: 'File machine',
|
||||
|
||||
initial: 'Reading files',
|
||||
@ -94,7 +94,9 @@ export const fileMachine = createMachine(
|
||||
{
|
||||
target: '#File machine.Reading files',
|
||||
actions: ['renameToastSuccess'],
|
||||
cond: 'Name has been changed',
|
||||
},
|
||||
'Reading files',
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
@ -176,7 +178,6 @@ export const fileMachine = createMachine(
|
||||
makeDir: boolean
|
||||
content?: string
|
||||
silent?: boolean
|
||||
makeUnique?: boolean
|
||||
}
|
||||
}
|
||||
| { type: 'Delete file'; data: FileEntry }
|
||||
@ -224,5 +225,10 @@ export const fileMachine = createMachine(
|
||||
return { selectedDirectory: event.data }
|
||||
}),
|
||||
},
|
||||
guards: {
|
||||
'Name has been changed': (_, event) => {
|
||||
return event.data.newPath !== event.data.oldPath
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
Reference in New Issue
Block a user