Fix existing: file renaming (and more things that spin out of settings file path parsing) (#3584)
* Fix the behavior so that we navigate to the new file path * This change is done in other PRs but is also necessary here * Add an Electron Playwright test for renaming a file * Add tests for renaming dir, one is failing * Don't need that console.warn * Add DeepPartial utility type * Fix settings parsing so that project path parsing is fixed * Move URL check after DOM checks * Revert this fallback behavior from https://github.com/KittyCAD/modeling-app/pull/3564 as we don't need it now that config parsing is fixed * Make new bad prompt each run * Fix onboarding asset path in web * Remove double parsing of settings config * Remove unused imports * More unused imports * Fix broken rename test * Update src/lib/desktop.ts Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch> * Add test for renaming file we do not have open * fmt --------- Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
This commit is contained in:
@ -10,6 +10,7 @@ import {
|
|||||||
import fsp from 'fs/promises'
|
import fsp from 'fs/promises'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
import { FILE_EXT } from 'lib/constants'
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
await tearDown(page, testInfo)
|
await tearDown(page, testInfo)
|
||||||
@ -1337,3 +1338,369 @@ test(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test.describe('Renaming in the file tree', () => {
|
||||||
|
test(
|
||||||
|
'A file you have open',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browser: _ }, testInfo) => {
|
||||||
|
test.skip(
|
||||||
|
process.platform === 'win32',
|
||||||
|
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
||||||
|
)
|
||||||
|
const { electronApp, page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
folderSetupFn: async (dir) => {
|
||||||
|
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||||
|
const exampleDir = join(
|
||||||
|
'src',
|
||||||
|
'wasm-lib',
|
||||||
|
'tests',
|
||||||
|
'executor',
|
||||||
|
'inputs'
|
||||||
|
)
|
||||||
|
await fsp.copyFile(
|
||||||
|
join(exampleDir, 'basic_fillet_cube_end.kcl'),
|
||||||
|
join(dir, 'Test Project', 'main.kcl')
|
||||||
|
)
|
||||||
|
await fsp.copyFile(
|
||||||
|
join(exampleDir, 'cylinder.kcl'),
|
||||||
|
join(dir, 'Test Project', 'fileToRename.kcl')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
// Constants and locators
|
||||||
|
const projectLink = page.getByText('Test Project')
|
||||||
|
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||||
|
const fileToRename = page
|
||||||
|
.getByRole('listitem')
|
||||||
|
.filter({ has: page.getByRole('button', { name: 'fileToRename.kcl' }) })
|
||||||
|
const renamedFile = page
|
||||||
|
.getByRole('listitem')
|
||||||
|
.filter({ has: page.getByRole('button', { name: 'newFileName.kcl' }) })
|
||||||
|
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
||||||
|
const renameInput = page.getByPlaceholder('fileToRename.kcl')
|
||||||
|
const newFileName = 'newFileName'
|
||||||
|
const codeLocator = page.locator('.cm-content')
|
||||||
|
|
||||||
|
await test.step('Open project and file pane', async () => {
|
||||||
|
await expect(projectLink).toBeVisible()
|
||||||
|
await projectLink.click()
|
||||||
|
await expect(projectMenuButton).toBeVisible()
|
||||||
|
await expect(projectMenuButton).toContainText('main.kcl')
|
||||||
|
|
||||||
|
await u.openFilePanel()
|
||||||
|
await expect(fileToRename).toBeVisible()
|
||||||
|
await fileToRename.click()
|
||||||
|
await expect(projectMenuButton).toContainText('fileToRename.kcl')
|
||||||
|
await u.openKclCodePanel()
|
||||||
|
await expect(codeLocator).toContainText('circle(')
|
||||||
|
await u.closeKclCodePanel()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Rename the file', async () => {
|
||||||
|
await fileToRename.click({ button: 'right' })
|
||||||
|
await renameMenuItem.click()
|
||||||
|
await expect(renameInput).toBeVisible()
|
||||||
|
await renameInput.fill(newFileName)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Verify the file is renamed', async () => {
|
||||||
|
await expect(fileToRename).not.toBeAttached()
|
||||||
|
await expect(renamedFile).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Verify we navigated', async () => {
|
||||||
|
await expect(projectMenuButton).toContainText(newFileName + FILE_EXT)
|
||||||
|
const url = page.url()
|
||||||
|
expect(url).toContain(newFileName)
|
||||||
|
await expect(projectMenuButton).not.toContainText('fileToRename.kcl')
|
||||||
|
await expect(projectMenuButton).not.toContainText('main.kcl')
|
||||||
|
expect(url).not.toContain('fileToRename.kcl')
|
||||||
|
expect(url).not.toContain('main.kcl')
|
||||||
|
|
||||||
|
await u.openKclCodePanel()
|
||||||
|
await expect(codeLocator).toContainText('circle(')
|
||||||
|
})
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'A file you do not have open',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browser: _ }, testInfo) => {
|
||||||
|
test.skip(
|
||||||
|
process.platform === 'win32',
|
||||||
|
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
||||||
|
)
|
||||||
|
const { electronApp, page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
folderSetupFn: async (dir) => {
|
||||||
|
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||||
|
const exampleDir = join(
|
||||||
|
'src',
|
||||||
|
'wasm-lib',
|
||||||
|
'tests',
|
||||||
|
'executor',
|
||||||
|
'inputs'
|
||||||
|
)
|
||||||
|
await fsp.copyFile(
|
||||||
|
join(exampleDir, 'basic_fillet_cube_end.kcl'),
|
||||||
|
join(dir, 'Test Project', 'main.kcl')
|
||||||
|
)
|
||||||
|
await fsp.copyFile(
|
||||||
|
join(exampleDir, 'cylinder.kcl'),
|
||||||
|
join(dir, 'Test Project', 'fileToRename.kcl')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
// Constants and locators
|
||||||
|
const newFileName = 'newFileName'
|
||||||
|
const projectLink = page.getByText('Test Project')
|
||||||
|
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||||
|
const fileToRename = page
|
||||||
|
.getByRole('listitem')
|
||||||
|
.filter({ has: page.getByRole('button', { name: 'fileToRename.kcl' }) })
|
||||||
|
const renamedFile = page.getByRole('listitem').filter({
|
||||||
|
has: page.getByRole('button', { name: newFileName + FILE_EXT }),
|
||||||
|
})
|
||||||
|
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
||||||
|
const renameInput = page.getByPlaceholder('fileToRename.kcl')
|
||||||
|
const codeLocator = page.locator('.cm-content')
|
||||||
|
|
||||||
|
await test.step('Open project and file pane', async () => {
|
||||||
|
await expect(projectLink).toBeVisible()
|
||||||
|
await projectLink.click()
|
||||||
|
await expect(projectMenuButton).toBeVisible()
|
||||||
|
await expect(projectMenuButton).toContainText('main.kcl')
|
||||||
|
|
||||||
|
await u.openFilePanel()
|
||||||
|
await expect(fileToRename).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Rename the file', async () => {
|
||||||
|
await fileToRename.click({ button: 'right' })
|
||||||
|
await renameMenuItem.click()
|
||||||
|
await expect(renameInput).toBeVisible()
|
||||||
|
await renameInput.fill(newFileName)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Verify the file is renamed', async () => {
|
||||||
|
await expect(fileToRename).not.toBeAttached()
|
||||||
|
await expect(renamedFile).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Verify we have not navigated', async () => {
|
||||||
|
await expect(projectMenuButton).toContainText('main.kcl')
|
||||||
|
await expect(projectMenuButton).not.toContainText(
|
||||||
|
newFileName + FILE_EXT
|
||||||
|
)
|
||||||
|
await expect(projectMenuButton).not.toContainText('fileToRename.kcl')
|
||||||
|
|
||||||
|
const url = page.url()
|
||||||
|
expect(url).toContain('main.kcl')
|
||||||
|
expect(url).not.toContain(newFileName)
|
||||||
|
expect(url).not.toContain('fileToRename.kcl')
|
||||||
|
|
||||||
|
await u.openKclCodePanel()
|
||||||
|
await expect(codeLocator).toContainText('fillet(')
|
||||||
|
})
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
`A folder you're not inside`,
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browser: _ }, testInfo) => {
|
||||||
|
test.skip(
|
||||||
|
process.platform === 'win32',
|
||||||
|
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
||||||
|
)
|
||||||
|
const { electronApp, page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
folderSetupFn: async (dir) => {
|
||||||
|
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||||
|
await fsp.mkdir(join(dir, 'Test Project', 'folderToRename'), {
|
||||||
|
recursive: true,
|
||||||
|
})
|
||||||
|
const exampleDir = join(
|
||||||
|
'src',
|
||||||
|
'wasm-lib',
|
||||||
|
'tests',
|
||||||
|
'executor',
|
||||||
|
'inputs'
|
||||||
|
)
|
||||||
|
await fsp.copyFile(
|
||||||
|
join(exampleDir, 'basic_fillet_cube_end.kcl'),
|
||||||
|
join(dir, 'Test Project', 'main.kcl')
|
||||||
|
)
|
||||||
|
await fsp.copyFile(
|
||||||
|
join(exampleDir, 'cylinder.kcl'),
|
||||||
|
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
// Constants and locators
|
||||||
|
const projectLink = page.getByText('Test Project')
|
||||||
|
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||||
|
const folderToRename = page.getByRole('button', {
|
||||||
|
name: 'folderToRename',
|
||||||
|
})
|
||||||
|
const renamedFolder = page.getByRole('button', { name: 'newFolderName' })
|
||||||
|
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
||||||
|
const renameInput = page.getByPlaceholder('folderToRename')
|
||||||
|
const newFolderName = 'newFolderName'
|
||||||
|
|
||||||
|
await test.step('Open project and file pane', async () => {
|
||||||
|
await expect(projectLink).toBeVisible()
|
||||||
|
await projectLink.click()
|
||||||
|
await expect(projectMenuButton).toBeVisible()
|
||||||
|
await expect(projectMenuButton).toContainText('main.kcl')
|
||||||
|
|
||||||
|
const url = page.url()
|
||||||
|
expect(url).toContain('main.kcl')
|
||||||
|
expect(url).not.toContain('folderToRename')
|
||||||
|
|
||||||
|
await u.openFilePanel()
|
||||||
|
await expect(folderToRename).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Rename the folder', async () => {
|
||||||
|
await folderToRename.click({ button: 'right' })
|
||||||
|
await expect(renameMenuItem).toBeVisible()
|
||||||
|
await renameMenuItem.click()
|
||||||
|
await expect(renameInput).toBeVisible()
|
||||||
|
await renameInput.fill(newFolderName)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Verify the folder is renamed, and no navigation occurred', async () => {
|
||||||
|
const url = page.url()
|
||||||
|
expect(url).toContain('main.kcl')
|
||||||
|
expect(url).not.toContain('folderToRename')
|
||||||
|
|
||||||
|
await expect(projectMenuButton).toContainText('main.kcl')
|
||||||
|
await expect(renamedFolder).toBeVisible()
|
||||||
|
await expect(folderToRename).not.toBeAttached()
|
||||||
|
})
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
`A folder you are inside`,
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browser: _ }, testInfo) => {
|
||||||
|
test.skip(
|
||||||
|
process.platform === 'win32',
|
||||||
|
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
||||||
|
)
|
||||||
|
const exampleDir = join('src', 'wasm-lib', 'tests', 'executor', 'inputs')
|
||||||
|
const { electronApp, page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
folderSetupFn: async (dir) => {
|
||||||
|
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||||
|
await fsp.mkdir(join(dir, 'Test Project', 'folderToRename'), {
|
||||||
|
recursive: true,
|
||||||
|
})
|
||||||
|
await fsp.copyFile(
|
||||||
|
join(exampleDir, 'basic_fillet_cube_end.kcl'),
|
||||||
|
join(dir, 'Test Project', 'main.kcl')
|
||||||
|
)
|
||||||
|
await fsp.copyFile(
|
||||||
|
join(exampleDir, 'cylinder.kcl'),
|
||||||
|
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
// Constants and locators
|
||||||
|
const projectLink = page.getByText('Test Project')
|
||||||
|
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||||
|
const folderToRename = page.getByRole('button', {
|
||||||
|
name: 'folderToRename',
|
||||||
|
})
|
||||||
|
const renamedFolder = page.getByRole('button', { name: 'newFolderName' })
|
||||||
|
const fileWithinFolder = page.getByRole('listitem').filter({
|
||||||
|
has: page.getByRole('button', { name: 'someFileWithin.kcl' }),
|
||||||
|
})
|
||||||
|
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
||||||
|
const renameInput = page.getByPlaceholder('folderToRename')
|
||||||
|
const newFolderName = 'newFolderName'
|
||||||
|
|
||||||
|
await test.step('Open project and navigate into folder', async () => {
|
||||||
|
await expect(projectLink).toBeVisible()
|
||||||
|
await projectLink.click()
|
||||||
|
await expect(projectMenuButton).toBeVisible()
|
||||||
|
await expect(projectMenuButton).toContainText('main.kcl')
|
||||||
|
|
||||||
|
const url = page.url()
|
||||||
|
expect(url).toContain('main.kcl')
|
||||||
|
expect(url).not.toContain('folderToRename')
|
||||||
|
|
||||||
|
await u.openFilePanel()
|
||||||
|
await expect(folderToRename).toBeVisible()
|
||||||
|
await folderToRename.click()
|
||||||
|
await expect(fileWithinFolder).toBeVisible()
|
||||||
|
await fileWithinFolder.click()
|
||||||
|
|
||||||
|
await expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
||||||
|
const newUrl = page.url()
|
||||||
|
expect(newUrl).toContain('folderToRename')
|
||||||
|
expect(newUrl).toContain('someFileWithin.kcl')
|
||||||
|
expect(newUrl).not.toContain('main.kcl')
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Rename the folder', async () => {
|
||||||
|
await folderToRename.click({ button: 'right' })
|
||||||
|
await expect(renameMenuItem).toBeVisible()
|
||||||
|
await renameMenuItem.click()
|
||||||
|
await expect(renameInput).toBeVisible()
|
||||||
|
await renameInput.fill(newFolderName)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Verify the folder is renamed, and navigated to new path', async () => {
|
||||||
|
const urlSnippet = encodeURIComponent(
|
||||||
|
join(newFolderName, 'someFileWithin.kcl')
|
||||||
|
)
|
||||||
|
await page.waitForURL(new RegExp(urlSnippet))
|
||||||
|
await expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
||||||
|
await expect(renamedFolder).toBeVisible()
|
||||||
|
await expect(folderToRename).not.toBeAttached()
|
||||||
|
|
||||||
|
// URL is synchronous, so we check the other stuff first
|
||||||
|
const url = page.url()
|
||||||
|
expect(url).not.toContain('main.kcl')
|
||||||
|
expect(url).toContain(newFolderName)
|
||||||
|
expect(url).toContain('someFileWithin.kcl')
|
||||||
|
})
|
||||||
|
|
||||||
|
await electronApp.close()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@ -190,7 +190,8 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await expect(prompt.first()).toBeVisible()
|
await expect(prompt.first()).toBeVisible()
|
||||||
|
|
||||||
// Type the prompt.
|
// Type the prompt.
|
||||||
await page.keyboard.type('akjsndladf ghgsssswefiuwq22262664')
|
const randomPrompt = `aslkdfja;` + Date.now() + `FFFFEIWJF`
|
||||||
|
await page.keyboard.type(randomPrompt)
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
@ -153,33 +153,34 @@ export const FileMachineProvider = ({
|
|||||||
event: EventFrom<typeof fileMachine, 'Rename file'>
|
event: EventFrom<typeof fileMachine, 'Rename file'>
|
||||||
) => {
|
) => {
|
||||||
const { oldName, newName, isDir } = event.data
|
const { oldName, newName, isDir } = event.data
|
||||||
const name = newName ? newName : DEFAULT_FILE_NAME
|
const name = newName
|
||||||
|
? newName.endsWith(FILE_EXT) || isDir
|
||||||
|
? newName
|
||||||
|
: newName + FILE_EXT
|
||||||
|
: DEFAULT_FILE_NAME
|
||||||
const oldPath = window.electron.path.join(
|
const oldPath = window.electron.path.join(
|
||||||
context.selectedDirectory.path,
|
context.selectedDirectory.path,
|
||||||
oldName
|
oldName
|
||||||
)
|
)
|
||||||
const newDirPath = window.electron.path.join(
|
const newPath = window.electron.path.join(
|
||||||
context.selectedDirectory.path,
|
context.selectedDirectory.path,
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
const newPath =
|
|
||||||
newDirPath + (name.endsWith(FILE_EXT) || isDir ? '' : FILE_EXT)
|
|
||||||
|
|
||||||
await window.electron.rename(oldPath, newPath)
|
window.electron.rename(oldPath, newPath)
|
||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return Promise.reject(new Error('file is not defined'))
|
return Promise.reject(new Error('file is not defined'))
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentFilePath = window.electron.path.join(file.path, file.name)
|
if (oldPath === file.path && project?.path) {
|
||||||
if (oldPath === currentFilePath && project?.path) {
|
|
||||||
// If we just renamed the current file, navigate to the new path
|
// If we just renamed the current file, navigate to the new path
|
||||||
navigate(`..${PATHS.FILE}/${encodeURIComponent(newPath)}`)
|
navigate(`..${PATHS.FILE}/${encodeURIComponent(newPath)}`)
|
||||||
} else if (file?.path.includes(oldPath)) {
|
} else if (file?.path.includes(oldPath)) {
|
||||||
// If we just renamed a directory that the current file is in, navigate to the new path
|
// If we just renamed a directory that the current file is in, navigate to the new path
|
||||||
navigate(
|
navigate(
|
||||||
`..${PATHS.FILE}/${encodeURIComponent(
|
`..${PATHS.FILE}/${encodeURIComponent(
|
||||||
file.path.replace(oldPath, newDirPath)
|
file.path.replace(oldPath, newPath)
|
||||||
)}`
|
)}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -19,11 +19,6 @@ import init, {
|
|||||||
parse_project_route,
|
parse_project_route,
|
||||||
base64_decode,
|
base64_decode,
|
||||||
} from '../wasm-lib/pkg/wasm_lib'
|
} from '../wasm-lib/pkg/wasm_lib'
|
||||||
import {
|
|
||||||
configurationToSettingsPayload,
|
|
||||||
projectConfigurationToSettingsPayload,
|
|
||||||
} from 'lib/settings/settingsUtils'
|
|
||||||
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
|
||||||
import { KCLError } from './errors'
|
import { KCLError } from './errors'
|
||||||
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
||||||
import { EngineCommandManager } from './std/engineConnection'
|
import { EngineCommandManager } from './std/engineConnection'
|
||||||
@ -40,6 +35,9 @@ import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
|||||||
import { TEST } from 'env'
|
import { TEST } from 'env'
|
||||||
import { ProjectRoute } from 'wasm-lib/kcl/bindings/ProjectRoute'
|
import { ProjectRoute } from 'wasm-lib/kcl/bindings/ProjectRoute'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
|
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
||||||
|
import { DeepPartial } from 'lib/types'
|
||||||
|
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
||||||
|
|
||||||
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
||||||
export type { Expr } from '../wasm-lib/kcl/bindings/Expr'
|
export type { Expr } from '../wasm-lib/kcl/bindings/Expr'
|
||||||
@ -570,31 +568,30 @@ export function tomlStringify(toml: any): string | Error {
|
|||||||
return toml_stringify(JSON.stringify(toml))
|
return toml_stringify(JSON.stringify(toml))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function defaultAppSettings(): Partial<SaveSettingsPayload> {
|
export function defaultAppSettings(): DeepPartial<Configuration> | Error {
|
||||||
// Immediately go from Configuration -> Partial<SaveSettingsPayload>
|
return default_app_settings()
|
||||||
// The returned Rust type is Configuration but it's a lie. Every
|
|
||||||
// property in that returned object is optional. The Partial<T> essentially
|
|
||||||
// brings that type in-line with that definition.
|
|
||||||
return configurationToSettingsPayload(default_app_settings())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseAppSettings(toml: string): Partial<SaveSettingsPayload> {
|
export function parseAppSettings(
|
||||||
const parsed = parse_app_settings(toml)
|
toml: string
|
||||||
return configurationToSettingsPayload(parsed)
|
): DeepPartial<Configuration> | Error {
|
||||||
|
return parse_app_settings(toml)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function defaultProjectSettings(): Partial<SaveSettingsPayload> {
|
export function defaultProjectSettings():
|
||||||
return projectConfigurationToSettingsPayload(default_project_settings())
|
| DeepPartial<ProjectConfiguration>
|
||||||
|
| Error {
|
||||||
|
return default_project_settings()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseProjectSettings(
|
export function parseProjectSettings(
|
||||||
toml: string
|
toml: string
|
||||||
): Partial<SaveSettingsPayload> {
|
): DeepPartial<ProjectConfiguration> | Error {
|
||||||
return projectConfigurationToSettingsPayload(parse_project_settings(toml))
|
return parse_project_settings(toml)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseProjectRoute(
|
export function parseProjectRoute(
|
||||||
configuration: Partial<SaveSettingsPayload>,
|
configuration: DeepPartial<Configuration>,
|
||||||
route_str: string
|
route_str: string
|
||||||
): ProjectRoute | Error {
|
): ProjectRoute | Error {
|
||||||
return parse_project_route(JSON.stringify(configuration), route_str)
|
return parse_project_route(JSON.stringify(configuration), route_str)
|
||||||
|
@ -3,11 +3,9 @@ import { Models } from '@kittycad/lib'
|
|||||||
import { Project } from 'wasm-lib/kcl/bindings/Project'
|
import { Project } from 'wasm-lib/kcl/bindings/Project'
|
||||||
import { ProjectState } from 'wasm-lib/kcl/bindings/ProjectState'
|
import { ProjectState } from 'wasm-lib/kcl/bindings/ProjectState'
|
||||||
import { FileEntry } from 'wasm-lib/kcl/bindings/FileEntry'
|
import { FileEntry } from 'wasm-lib/kcl/bindings/FileEntry'
|
||||||
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
defaultAppSettings,
|
defaultAppSettings,
|
||||||
tomlStringify,
|
|
||||||
parseAppSettings,
|
parseAppSettings,
|
||||||
parseProjectSettings,
|
parseProjectSettings,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
@ -18,6 +16,9 @@ import {
|
|||||||
PROJECT_SETTINGS_FILE_NAME,
|
PROJECT_SETTINGS_FILE_NAME,
|
||||||
SETTINGS_FILE_NAME,
|
SETTINGS_FILE_NAME,
|
||||||
} from './constants'
|
} from './constants'
|
||||||
|
import { DeepPartial } from './types'
|
||||||
|
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
||||||
|
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
||||||
export { parseProjectRoute } from 'lang/wasm'
|
export { parseProjectRoute } from 'lang/wasm'
|
||||||
|
|
||||||
export async function renameProjectDirectory(
|
export async function renameProjectDirectory(
|
||||||
@ -61,10 +62,13 @@ export async function renameProjectDirectory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function ensureProjectDirectoryExists(
|
export async function ensureProjectDirectoryExists(
|
||||||
config: Partial<SaveSettingsPayload>
|
config: DeepPartial<Configuration>
|
||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
const projectDir = config.app?.projectDirectory
|
const projectDir =
|
||||||
|
config.settings?.app?.project_directory ||
|
||||||
|
config.settings?.project?.directory
|
||||||
if (!projectDir) {
|
if (!projectDir) {
|
||||||
|
console.error('projectDir is falsey', config)
|
||||||
return Promise.reject(new Error('projectDir is falsey'))
|
return Promise.reject(new Error('projectDir is falsey'))
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@ -81,12 +85,13 @@ export async function ensureProjectDirectoryExists(
|
|||||||
export async function createNewProjectDirectory(
|
export async function createNewProjectDirectory(
|
||||||
projectName: string,
|
projectName: string,
|
||||||
initialCode?: string,
|
initialCode?: string,
|
||||||
configuration?: Partial<SaveSettingsPayload>
|
configuration?: DeepPartial<Configuration> | Error
|
||||||
): Promise<Project> {
|
): Promise<Project> {
|
||||||
if (!configuration) {
|
if (!configuration) {
|
||||||
configuration = await readAppSettingsFile()
|
configuration = await readAppSettingsFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (err(configuration)) return Promise.reject(configuration)
|
||||||
const mainDir = await ensureProjectDirectoryExists(configuration)
|
const mainDir = await ensureProjectDirectoryExists(configuration)
|
||||||
|
|
||||||
if (!projectName) {
|
if (!projectName) {
|
||||||
@ -124,11 +129,13 @@ export async function createNewProjectDirectory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function listProjects(
|
export async function listProjects(
|
||||||
configuration?: Partial<SaveSettingsPayload>
|
configuration?: DeepPartial<Configuration> | Error
|
||||||
): Promise<Project[]> {
|
): Promise<Project[]> {
|
||||||
if (configuration === undefined) {
|
if (configuration === undefined) {
|
||||||
configuration = await readAppSettingsFile()
|
configuration = await readAppSettingsFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (err(configuration)) return Promise.reject(configuration)
|
||||||
const projectDir = await ensureProjectDirectoryExists(configuration)
|
const projectDir = await ensureProjectDirectoryExists(configuration)
|
||||||
const projects = []
|
const projects = []
|
||||||
if (!projectDir) return Promise.reject(new Error('projectDir was falsey'))
|
if (!projectDir) return Promise.reject(new Error('projectDir was falsey'))
|
||||||
@ -179,7 +186,7 @@ const collectAllFilesRecursiveFrom = async (path: string) => {
|
|||||||
return Promise.reject(new Error(`Path ${path} is not a directory`))
|
return Promise.reject(new Error(`Path ${path} is not a directory`))
|
||||||
}
|
}
|
||||||
|
|
||||||
const pathParts = path.split('/')
|
const pathParts = path.split(window.electron.path.sep)
|
||||||
let entry: FileEntry = {
|
let entry: FileEntry = {
|
||||||
name: pathParts.slice(-1)[0],
|
name: pathParts.slice(-1)[0],
|
||||||
path,
|
path,
|
||||||
@ -358,10 +365,9 @@ export async function getProjectInfo(projectPath: string): Promise<Project> {
|
|||||||
// Write project settings file.
|
// Write project settings file.
|
||||||
export async function writeProjectSettingsFile(
|
export async function writeProjectSettingsFile(
|
||||||
projectPath: string,
|
projectPath: string,
|
||||||
configuration: Partial<SaveSettingsPayload>
|
tomlStr: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const projectSettingsFilePath = await getProjectSettingsFilePath(projectPath)
|
const projectSettingsFilePath = await getProjectSettingsFilePath(projectPath)
|
||||||
const tomlStr = tomlStringify({ settings: configuration })
|
|
||||||
if (err(tomlStr)) return Promise.reject(tomlStr)
|
if (err(tomlStr)) return Promise.reject(tomlStr)
|
||||||
return window.electron.writeFile(projectSettingsFilePath, tomlStr)
|
return window.electron.writeFile(projectSettingsFilePath, tomlStr)
|
||||||
}
|
}
|
||||||
@ -416,7 +422,7 @@ export const getInitialDefaultDir = async () => {
|
|||||||
|
|
||||||
export const readProjectSettingsFile = async (
|
export const readProjectSettingsFile = async (
|
||||||
projectPath: string
|
projectPath: string
|
||||||
): Promise<Partial<SaveSettingsPayload>> => {
|
): Promise<DeepPartial<ProjectConfiguration>> => {
|
||||||
let settingsPath = await getProjectSettingsFilePath(projectPath)
|
let settingsPath = await getProjectSettingsFilePath(projectPath)
|
||||||
|
|
||||||
// Check if this file exists.
|
// Check if this file exists.
|
||||||
@ -431,6 +437,9 @@ export const readProjectSettingsFile = async (
|
|||||||
|
|
||||||
const configToml = await window.electron.readFile(settingsPath)
|
const configToml = await window.electron.readFile(settingsPath)
|
||||||
const configObj = parseProjectSettings(configToml)
|
const configObj = parseProjectSettings(configToml)
|
||||||
|
if (err(configObj)) {
|
||||||
|
return Promise.reject(configObj)
|
||||||
|
}
|
||||||
return configObj
|
return configObj
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -441,29 +450,25 @@ export const readAppSettingsFile = async () => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e === 'ENOENT') {
|
if (e === 'ENOENT') {
|
||||||
const config = defaultAppSettings()
|
const config = defaultAppSettings()
|
||||||
if (!config.app) {
|
if (err(config)) return Promise.reject(config)
|
||||||
|
if (!config.settings?.app)
|
||||||
return Promise.reject(new Error('config.app is falsey'))
|
return Promise.reject(new Error('config.app is falsey'))
|
||||||
}
|
|
||||||
config.app.projectDirectory = await getInitialDefaultDir()
|
config.settings.app.project_directory = await getInitialDefaultDir()
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const configToml = await window.electron.readFile(settingsPath)
|
const configToml = await window.electron.readFile(settingsPath)
|
||||||
const configObj = parseAppSettings(configToml)
|
const configObj = parseAppSettings(configToml)
|
||||||
if (!configObj.app) {
|
if (err(configObj)) {
|
||||||
return Promise.reject(new Error('config.app is falsey'))
|
return Promise.reject(configObj)
|
||||||
}
|
|
||||||
if (!configObj.app.projectDirectory) {
|
|
||||||
configObj.app.projectDirectory = await getInitialDefaultDir()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return configObj
|
return configObj
|
||||||
}
|
}
|
||||||
|
|
||||||
export const writeAppSettingsFile = async (
|
export const writeAppSettingsFile = async (tomlStr: string) => {
|
||||||
config: Partial<SaveSettingsPayload>
|
|
||||||
) => {
|
|
||||||
const appSettingsFilePath = await getAppSettingsFilePath()
|
const appSettingsFilePath = await getAppSettingsFilePath()
|
||||||
const tomlStr = tomlStringify({ settings: config })
|
|
||||||
if (err(tomlStr)) return Promise.reject(tomlStr)
|
if (err(tomlStr)) return Promise.reject(tomlStr)
|
||||||
return window.electron.writeFile(appSettingsFilePath, tomlStr)
|
return window.electron.writeFile(appSettingsFilePath, tomlStr)
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,10 @@ import { isDesktop } from './isDesktop'
|
|||||||
import { ProjectRoute } from 'wasm-lib/kcl/bindings/ProjectRoute'
|
import { ProjectRoute } from 'wasm-lib/kcl/bindings/ProjectRoute'
|
||||||
import { parseProjectRoute, readAppSettingsFile } from './desktop'
|
import { parseProjectRoute, readAppSettingsFile } from './desktop'
|
||||||
import { readLocalStorageAppSettingsFile } from './settings/settingsUtils'
|
import { readLocalStorageAppSettingsFile } from './settings/settingsUtils'
|
||||||
import { SaveSettingsPayload } from './settings/settingsTypes'
|
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { IS_PLAYWRIGHT_KEY } from '../../e2e/playwright/storageStates'
|
import { IS_PLAYWRIGHT_KEY } from '../../e2e/playwright/storageStates'
|
||||||
|
import { DeepPartial } from './types'
|
||||||
|
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
||||||
|
|
||||||
const prependRoutes =
|
const prependRoutes =
|
||||||
(routesObject: Record<string, string>) => (prepend: string) => {
|
(routesObject: Record<string, string>) => (prepend: string) => {
|
||||||
@ -39,7 +40,7 @@ export const BROWSER_PATH = `%2F${BROWSER_PROJECT_NAME}%2F${BROWSER_FILE_NAME}${
|
|||||||
|
|
||||||
export async function getProjectMetaByRouteId(
|
export async function getProjectMetaByRouteId(
|
||||||
id?: string,
|
id?: string,
|
||||||
configuration?: Partial<SaveSettingsPayload> | Error
|
configuration?: DeepPartial<Configuration> | Error
|
||||||
): Promise<ProjectRoute | undefined> {
|
): Promise<ProjectRoute | undefined> {
|
||||||
if (!id) return undefined
|
if (!id) return undefined
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
} from 'lib/constants'
|
} from 'lib/constants'
|
||||||
import { loadAndValidateSettings } from './settings/settingsUtils'
|
import { loadAndValidateSettings } from './settings/settingsUtils'
|
||||||
import makeUrlPathRelative from './makeUrlPathRelative'
|
import makeUrlPathRelative from './makeUrlPathRelative'
|
||||||
import { codeManager, kclManager } from 'lib/singletons'
|
import { codeManager } from 'lib/singletons'
|
||||||
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
||||||
import {
|
import {
|
||||||
getProjectInfo,
|
getProjectInfo,
|
||||||
@ -107,8 +107,6 @@ export const fileLoader: LoaderFunction = async (
|
|||||||
// the file system and not the editor.
|
// the file system and not the editor.
|
||||||
codeManager.updateCurrentFilePath(current_file_path)
|
codeManager.updateCurrentFilePath(current_file_path)
|
||||||
codeManager.updateCodeStateEditor(code)
|
codeManager.updateCodeStateEditor(code)
|
||||||
// We don't want to call await on execute code since we don't want to block the UI
|
|
||||||
kclManager.executeCode(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the file system manager to the project path
|
// Set the file system manager to the project path
|
||||||
@ -125,14 +123,22 @@ export const fileLoader: LoaderFunction = async (
|
|||||||
default_file: project_path,
|
default_file: project_path,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const maybeProjectInfo = isDesktop()
|
||||||
|
? await getProjectInfo(project_path)
|
||||||
|
: null
|
||||||
|
|
||||||
|
console.log('maybeProjectInfo', {
|
||||||
|
maybeProjectInfo,
|
||||||
|
defaultProjectData,
|
||||||
|
projectPathData,
|
||||||
|
})
|
||||||
|
|
||||||
const projectData: IndexLoaderData = {
|
const projectData: IndexLoaderData = {
|
||||||
code,
|
code,
|
||||||
project: isDesktop()
|
project: maybeProjectInfo ?? defaultProjectData,
|
||||||
? (await getProjectInfo(project_path)) ?? defaultProjectData
|
|
||||||
: defaultProjectData,
|
|
||||||
file: {
|
file: {
|
||||||
name: current_file_name || '',
|
name: current_file_name || '',
|
||||||
path: current_file_path?.split('/').slice(0, -1).join('/') ?? '',
|
path: current_file_path || '',
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
} from 'lib/desktop'
|
} from 'lib/desktop'
|
||||||
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
||||||
import { BROWSER_PROJECT_NAME } from 'lib/constants'
|
import { BROWSER_PROJECT_NAME } from 'lib/constants'
|
||||||
|
import { DeepPartial } from 'lib/types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert from a rust settings struct into the JS settings struct.
|
* Convert from a rust settings struct into the JS settings struct.
|
||||||
@ -28,8 +29,8 @@ import { BROWSER_PROJECT_NAME } from 'lib/constants'
|
|||||||
* for hiding and showing settings.
|
* for hiding and showing settings.
|
||||||
**/
|
**/
|
||||||
export function configurationToSettingsPayload(
|
export function configurationToSettingsPayload(
|
||||||
configuration: Configuration
|
configuration: DeepPartial<Configuration>
|
||||||
): Partial<SaveSettingsPayload> {
|
): DeepPartial<SaveSettingsPayload> {
|
||||||
return {
|
return {
|
||||||
app: {
|
app: {
|
||||||
theme: appThemeToTheme(configuration?.settings?.app?.appearance?.theme),
|
theme: appThemeToTheme(configuration?.settings?.app?.appearance?.theme),
|
||||||
@ -66,8 +67,8 @@ export function configurationToSettingsPayload(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function projectConfigurationToSettingsPayload(
|
export function projectConfigurationToSettingsPayload(
|
||||||
configuration: ProjectConfiguration
|
configuration: DeepPartial<ProjectConfiguration>
|
||||||
): Partial<SaveSettingsPayload> {
|
): DeepPartial<SaveSettingsPayload> {
|
||||||
return {
|
return {
|
||||||
app: {
|
app: {
|
||||||
theme: appThemeToTheme(configuration?.settings?.app?.appearance?.theme),
|
theme: appThemeToTheme(configuration?.settings?.app?.appearance?.theme),
|
||||||
@ -106,7 +107,7 @@ function localStorageProjectSettingsPath() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function readLocalStorageAppSettingsFile():
|
export function readLocalStorageAppSettingsFile():
|
||||||
| Partial<SaveSettingsPayload>
|
| DeepPartial<Configuration>
|
||||||
| Error {
|
| Error {
|
||||||
// TODO: Remove backwards compatibility after a few releases.
|
// TODO: Remove backwards compatibility after a few releases.
|
||||||
let stored =
|
let stored =
|
||||||
@ -132,7 +133,7 @@ export function readLocalStorageAppSettingsFile():
|
|||||||
}
|
}
|
||||||
|
|
||||||
function readLocalStorageProjectSettingsFile():
|
function readLocalStorageProjectSettingsFile():
|
||||||
| Partial<SaveSettingsPayload>
|
| DeepPartial<ProjectConfiguration>
|
||||||
| Error {
|
| Error {
|
||||||
// TODO: Remove backwards compatibility after a few releases.
|
// TODO: Remove backwards compatibility after a few releases.
|
||||||
let stored = localStorage.getItem(localStorageProjectSettingsPath()) ?? ''
|
let stored = localStorage.getItem(localStorageProjectSettingsPath()) ?? ''
|
||||||
@ -156,7 +157,7 @@ function readLocalStorageProjectSettingsFile():
|
|||||||
|
|
||||||
export interface AppSettings {
|
export interface AppSettings {
|
||||||
settings: ReturnType<typeof createSettings>
|
settings: ReturnType<typeof createSettings>
|
||||||
configuration: Partial<SaveSettingsPayload>
|
configuration: DeepPartial<Configuration>
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadAndValidateSettings(
|
export async function loadAndValidateSettings(
|
||||||
@ -175,7 +176,11 @@ export async function loadAndValidateSettings(
|
|||||||
if (err(appSettingsPayload)) return Promise.reject(appSettingsPayload)
|
if (err(appSettingsPayload)) return Promise.reject(appSettingsPayload)
|
||||||
|
|
||||||
const settings = createSettings()
|
const settings = createSettings()
|
||||||
setSettingsAtLevel(settings, 'user', appSettingsPayload)
|
setSettingsAtLevel(
|
||||||
|
settings,
|
||||||
|
'user',
|
||||||
|
configurationToSettingsPayload(appSettingsPayload)
|
||||||
|
)
|
||||||
|
|
||||||
// Load the project settings if they exist
|
// Load the project settings if they exist
|
||||||
if (projectPath) {
|
if (projectPath) {
|
||||||
@ -187,11 +192,18 @@ export async function loadAndValidateSettings(
|
|||||||
return Promise.reject(new Error('Invalid project settings'))
|
return Promise.reject(new Error('Invalid project settings'))
|
||||||
|
|
||||||
const projectSettingsPayload = projectSettings
|
const projectSettingsPayload = projectSettings
|
||||||
setSettingsAtLevel(settings, 'project', projectSettingsPayload)
|
setSettingsAtLevel(
|
||||||
|
settings,
|
||||||
|
'project',
|
||||||
|
projectConfigurationToSettingsPayload(projectSettingsPayload)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the settings object
|
// Return the settings object
|
||||||
return { settings, configuration: appSettingsPayload }
|
return {
|
||||||
|
settings,
|
||||||
|
configuration: appSettingsPayload,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveSettings(
|
export async function saveSettings(
|
||||||
@ -204,21 +216,14 @@ export async function saveSettings(
|
|||||||
|
|
||||||
// Get the user settings.
|
// Get the user settings.
|
||||||
const jsAppSettings = getChangedSettingsAtLevel(allSettings, 'user')
|
const jsAppSettings = getChangedSettingsAtLevel(allSettings, 'user')
|
||||||
const tomlString = tomlStringify({ settings: jsAppSettings })
|
const appTomlString = tomlStringify({ settings: jsAppSettings })
|
||||||
if (err(tomlString)) return
|
if (err(appTomlString)) return
|
||||||
|
|
||||||
// Parse this as a Configuration.
|
|
||||||
const appSettings = parseAppSettings(tomlString)
|
|
||||||
if (err(appSettings)) return
|
|
||||||
|
|
||||||
const tomlString2 = tomlStringify({ settings: appSettings })
|
|
||||||
if (err(tomlString2)) return
|
|
||||||
|
|
||||||
// Write the app settings.
|
// Write the app settings.
|
||||||
if (onDesktop) {
|
if (onDesktop) {
|
||||||
await writeAppSettingsFile(appSettings)
|
await writeAppSettingsFile(appTomlString)
|
||||||
} else {
|
} else {
|
||||||
localStorage.setItem(localStorageAppSettingsPath(), tomlString2)
|
localStorage.setItem(localStorageAppSettingsPath(), appTomlString)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!projectPath) {
|
if (!projectPath) {
|
||||||
@ -231,19 +236,11 @@ export async function saveSettings(
|
|||||||
const projectTomlString = tomlStringify({ settings: jsProjectSettings })
|
const projectTomlString = tomlStringify({ settings: jsProjectSettings })
|
||||||
if (err(projectTomlString)) return
|
if (err(projectTomlString)) return
|
||||||
|
|
||||||
// Parse this as a Configuration.
|
|
||||||
const projectSettings = parseProjectSettings(projectTomlString)
|
|
||||||
if (err(projectSettings)) return
|
|
||||||
|
|
||||||
const tomlStr = tomlStringify(projectSettings)
|
|
||||||
|
|
||||||
if (err(tomlStr)) return
|
|
||||||
|
|
||||||
// Write the project settings.
|
// Write the project settings.
|
||||||
if (onDesktop) {
|
if (onDesktop) {
|
||||||
await writeProjectSettingsFile(projectPath, projectSettings)
|
await writeProjectSettingsFile(projectPath, projectTomlString)
|
||||||
} else {
|
} else {
|
||||||
localStorage.setItem(localStorageProjectSettingsPath(), tomlStr)
|
localStorage.setItem(localStorageProjectSettingsPath(), projectTomlString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,3 +95,9 @@ export function isEnumMember<T extends Record<string, unknown>>(
|
|||||||
) {
|
) {
|
||||||
return Object.values(e).includes(v)
|
return Object.values(e).includes(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// utility type to make all *nested* object properties optional
|
||||||
|
// https://www.geodev.me/blog/deeppartial-in-typescript
|
||||||
|
export type DeepPartial<T> = {
|
||||||
|
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
|
||||||
|
}
|
||||||
|
@ -145,7 +145,7 @@ function OnboardingIntroductionInner() {
|
|||||||
<div className="max-w-3xl p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
|
<div className="max-w-3xl p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
|
||||||
<h1 className="flex flex-wrap items-center gap-4 text-3xl font-bold">
|
<h1 className="flex flex-wrap items-center gap-4 text-3xl font-bold">
|
||||||
<img
|
<img
|
||||||
src={`./zma-logomark${getLogoTheme()}.svg`}
|
src={`${isDesktop() ? '.' : ''}/zma-logomark${getLogoTheme()}.svg`}
|
||||||
alt={APP_NAME}
|
alt={APP_NAME}
|
||||||
className="h-20 max-w-full"
|
className="h-20 max-w-full"
|
||||||
/>
|
/>
|
||||||
|
Reference in New Issue
Block a user