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:
Frank Noirot
2024-08-15 14:24:27 -04:00
committed by GitHub
parent 55a3e2a4ed
commit 545e610bbc
10 changed files with 240 additions and 99 deletions

View File

@ -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')
})
}

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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',

View File

@ -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,
},
}}

View File

@ -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'

View File

@ -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,
}
}

View File

@ -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,
}
})

View File

@ -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
},
},
}
)