Add ability to create and open URLs to create files (#4166)
* Rename `homeMachine` and accessories to `projectsMachine` * Separate out `/home` route from `projectsMachine` * Add logic to navigate out from deleted or renamed project * Show a warning in the command palette for deleting a project * Make it navigate when you create a project * Update "New project" button to use command bar flow Closes #2585 * More explicit warning message text * Make projects watching code not run in web * Tests first version: nested loops * Tests second version: flattened * Remove console logs * Fix tsc * @jtran feedback, use the type guard util * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest) * A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest) * A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest) * Fix tests that relied on one-click, no-navigation project creation * Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)" This reverts commit7545b61b49
. * Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)" This reverts commit3d2e48732c
. * Add a mask to the state indicator to client-side scale test * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest) * A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest) * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest) * Fix lint * Fix tsc * Add menu item to share link to file * Forward query params while redirecting to /home or /file * Add (broken) event logic and command triggering logic * Fix a couple stray tests that still relied on the old way of creating projects * De-flake another text that could be thrown off by toast-based selectors * FMT * Dumb test error because I was rushing * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest) * Ahhh more flaky toasts, they're everywhere! * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest) * Side quest: Only register commands once, power their disabled status while selecting commands via optional actor * Get query-triggered command working in browser too * A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest) * Tests always run on localhost, don't expect the prod origin * rerun CI * wip * wip * Everything's pretty much done but url.zoo.dev has been broken and we need to think about how to test reliably * Merge branch 'main' into franknoirot/4088/create-file-url * Add useCreateFileLinkQuery on Home page * Get primary user flow working on desktop * Rework to open browser app first, then send along to the desktop app if asked * Styling updates to OpenInDesktopAppHandler * Clean up unecessary file * Merge branch 'main' into franknoirot/4088/create-file-url * Separate creating `createFileUrl` and shortlink so it is unit testable * Add E2E test for importing file from URL * Add a couple component tests for OpenInDesktopAppHandler * Fix the "existing project" user flow * Add E2E test for "add to existing project" user flow * Undo mistaken or unecessary changes * Lints, fmt, tsc * Fix unit test * Fix broken rename and delete project commands Something about the `optionsFromContext` config no longer works with file I/O-related commands. I suspect this has to do with our read/write loop patching * Fix unit test, use kebab-case for url query param * Use dev urls everywhere when configured that way I think we were just using some constants that ended up returning bad values for dev, it seemed to return a working shortlink when I went through the flow. * Clean up unneeded PROD_TOKEN * Fix browser command flow, because we had made the projectMachine desktop-only on main * Make the test executor a bit more patient (#5004) * Fix so that all artifact commands are returned regardless of caching (#5005) * Fix so that all artifact commands are returned regardless of caching * Add some more docs and fix up old ones * Add new lint to disallow use of confusing isNaN (#4999) * Point-and-click Sweep (first PR) (#4989) * Refactor 'Delete selection' as actor Will fix #4662 * WIP logging * WIP: working Solid3dGetExtrusionFaceInfo for loft * Working wall deletion of loft * Add offset plane deletion * Add feature tree deletion of shell * Clean up * Revert "Clean up" This reverts commit214763cc2b
. * Clean up rust changes, taking the sketch with the most paths * Working cap selection and deletion * Clean up * Add test for loft and offset plane deletion via selection * A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-16-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-macos-8-cores) * Set reenter: false as it was originally * Passing test * Add shell deletion via feature tree test * Revert the migration to promise actor * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * Trigger CI * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * Trigger CI * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * Trigger CI * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * Trigger CI * Use cmd.id as solid_id after latest engine merge * Add feature tree deletion of offset plane and fix lint * Add feature tree deletion of loft * Clean up * Better comment * Lint fix * Remove sketch sorting * WIP: sweep point-and-click * Working sweep * Add test * Make sweep a development command * Fix tsc error * Clean up for review --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * Upgrade typescript-eslint from 5.62.0 to 8.19.1 and remove eslint-config-react-app (#5006) * Fix lost lints and add new ones (#5011) * Add eslint-plugin-jsx-a11y dependency * Add jsx-a11y lint * Add eslint-plugin-react-hooks dependency * Add react hooks lints * Ignore new react hooks lint in tests * Add eslint-plugin-testing-library dependency * Add testing-library lint * Fix yarn lint to use all files recursively * Developer workflow: added auto generated workspace file from vitest extension in vscode (#4997) * chore: added auto generated workspace file from vitest extension in vscode * fix: auto fmt fixes * Change Dependabot PRs to always be made on Mondays (#5025) * Add packages to Dependabot updates (#5024) * Bump @lezer/generator from 1.7.1 to 1.7.2 (#5018) Bumps [@lezer/generator](https://github.com/lezer-parser/generator) from 1.7.1 to 1.7.2. - [Changelog](https://github.com/lezer-parser/generator/blob/main/CHANGELOG.md) - [Commits](https://github.com/lezer-parser/generator/compare/1.7.1...1.7.2) --- updated-dependencies: - dependency-name: "@lezer/generator" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump handlebars from 6.2.0 to 6.3.0 in /src/wasm-lib (#5012) Bumps [handlebars](https://github.com/sunng87/handlebars-rust) from 6.2.0 to 6.3.0. - [Release notes](https://github.com/sunng87/handlebars-rust/releases) - [Changelog](https://github.com/sunng87/handlebars-rust/blob/master/CHANGELOG.md) - [Commits](https://github.com/sunng87/handlebars-rust/compare/v6.2.0...v6.3.0) --- updated-dependencies: - dependency-name: handlebars dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump syn from 2.0.95 to 2.0.96 in /src/wasm-lib (#5015) Bumps [syn](https://github.com/dtolnay/syn) from 2.0.95 to 2.0.96. - [Release notes](https://github.com/dtolnay/syn/releases) - [Commits](https://github.com/dtolnay/syn/compare/2.0.95...2.0.96) --- updated-dependencies: - dependency-name: syn dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix artifact types to be more accurate (#5022) * Fix Cargo.lock to not have changes (#5034) * Upgrade all wasm-bindgen dependencies together (#5037) * Disable auto-updater on non-versioned builds (#5042) * turns on helix from edge (#5036) * updates for new lib Signed-off-by: Jess Frazelle <github@jessfraz.com> * autocomplete Signed-off-by: Jess Frazelle <github@jessfraz.com> * bump version Signed-off-by: Jess Frazelle <github@jessfraz.com> * bump all the things Signed-off-by: Jess Frazelle <github@jessfraz.com> * new samples Signed-off-by: Jess Frazelle <github@jessfraz.com> * docs Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com> * ci: Add yarn test of packages/codemirror-lang-kcl (#5035) * ci: Add yarn test of packages/codemirror-lang-kcl * Fix CI error running tests * Fix postcss config error * Bump xstate from 5.17.4 to 5.19.2 (#5027) * Hook up chamfer UI with AST-mod (#4694) * button * config * hook up with ast * cmd bar test * button states fix and test * little naming fix * xState action to actor * remove button state test updates * fixture-based approach * nightly Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com> * Update src/lib/toolbar.ts Co-authored-by: Frank Noirot <frank@zoo.dev> --------- Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com> Co-authored-by: Frank Noirot <frank@zoo.dev> * Remove Redundant Fillet Button State Test (#5009) delete obsolete test * Bump @types/node from 20.14.9 to 22.10.6 in /packages/codemirror-lsp-client (#5041) * custom axis and origin example for helix (#5057) * custom axis and origin for helix Signed-off-by: Jess Frazelle <github@jessfraz.com> * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * empty --------- Signed-off-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * Bump typescript from 5.7.2 to 5.7.3 (#5021) Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.7.2 to 5.7.3. - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml) - [Commits](https://github.com/microsoft/TypeScript/compare/v5.7.2...v5.7.3) --- updated-dependencies: - dependency-name: typescript dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Refactor: break out `copyFileShareLink` into standalone function * Add "Share file" to command palette * Update dumb use of site URL instead of prod app URL * fmt * @lf94 nit * @pierremtb spinner feedback * Hide share link command and disable menu item for now * Just comment out the command config for now --------- Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: 49lf <ircsurfer33@gmail.com> Co-authored-by: Adam Sunderland <iterion@gmail.com> Co-authored-by: Adam Sunderland <adam@kittycad.io> Co-authored-by: Jonathan Tran <jonnytran@gmail.com> Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com> Co-authored-by: Kevin Nadro <nadr0@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com> Co-authored-by: max <margorskyi@gmail.com>
This commit is contained in:
@ -2,11 +2,11 @@ import { useMachine } from '@xstate/react'
|
||||
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
|
||||
import { useProjectsLoader } from 'hooks/useProjectsLoader'
|
||||
import { projectsMachine } from 'machines/projectsMachine'
|
||||
import { createContext, useEffect, useState } from 'react'
|
||||
import { createContext, useCallback, useEffect, useState } from 'react'
|
||||
import { Actor, AnyStateMachine, fromPromise, Prop, StateFrom } from 'xstate'
|
||||
import { useLspContext } from './LspProvider'
|
||||
import toast from 'react-hot-toast'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
|
||||
import { PATHS } from 'lib/paths'
|
||||
import {
|
||||
createNewProjectDirectory,
|
||||
@ -18,12 +18,28 @@ import {
|
||||
interpolateProjectNameWithIndex,
|
||||
doesProjectNameNeedInterpolated,
|
||||
getUniqueProjectName,
|
||||
getNextFileName,
|
||||
} from 'lib/desktopFS'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import useStateMachineCommands from 'hooks/useStateMachineCommands'
|
||||
import { projectsCommandBarConfig } from 'lib/commandBarConfigs/projectsCommandConfig'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
import {
|
||||
CREATE_FILE_URL_PARAM,
|
||||
FILE_EXT,
|
||||
PROJECT_ENTRYPOINT,
|
||||
} from 'lib/constants'
|
||||
import { DeepPartial } from 'lib/types'
|
||||
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
||||
import { codeManager } from 'lib/singletons'
|
||||
import {
|
||||
loadAndValidateSettings,
|
||||
projectConfigurationToSettingsPayload,
|
||||
saveSettings,
|
||||
setSettingsAtLevel,
|
||||
} from 'lib/settings/settingsUtils'
|
||||
import { Project } from 'lib/project'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state?: StateFrom<T>
|
||||
@ -53,12 +69,110 @@ export const ProjectsContextProvider = ({
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* We need some of the functionality of the ProjectsContextProvider in the web version
|
||||
* but we can't perform file system operations in the browser,
|
||||
* so most of the behavior of this machine is stubbed out.
|
||||
*/
|
||||
const ProjectsContextWeb = ({ children }: { children: React.ReactNode }) => {
|
||||
const [searchParams, setSearchParams] = useSearchParams()
|
||||
const clearImportSearchParams = useCallback(() => {
|
||||
// Clear the search parameters related to the "Import file from URL" command
|
||||
// or we'll never be able cancel or submit it.
|
||||
searchParams.delete(CREATE_FILE_URL_PARAM)
|
||||
searchParams.delete('code')
|
||||
searchParams.delete('name')
|
||||
searchParams.delete('units')
|
||||
setSearchParams(searchParams)
|
||||
}, [searchParams, setSearchParams])
|
||||
const {
|
||||
settings: { context: settings, send: settingsSend },
|
||||
} = useSettingsAuthContext()
|
||||
|
||||
const [state, send, actor] = useMachine(
|
||||
projectsMachine.provide({
|
||||
actions: {
|
||||
navigateToProject: () => {},
|
||||
navigateToProjectIfNeeded: () => {},
|
||||
navigateToFile: () => {},
|
||||
toastSuccess: ({ event }) =>
|
||||
toast.success(
|
||||
('data' in event && typeof event.data === 'string' && event.data) ||
|
||||
('output' in event &&
|
||||
'message' in event.output &&
|
||||
typeof event.output.message === 'string' &&
|
||||
event.output.message) ||
|
||||
''
|
||||
),
|
||||
toastError: ({ event }) =>
|
||||
toast.error(
|
||||
('data' in event && typeof event.data === 'string' && event.data) ||
|
||||
('output' in event &&
|
||||
typeof event.output === 'string' &&
|
||||
event.output) ||
|
||||
''
|
||||
),
|
||||
},
|
||||
actors: {
|
||||
readProjects: fromPromise(async () => [] as Project[]),
|
||||
createProject: fromPromise(async () => ({
|
||||
message: 'not implemented on web',
|
||||
})),
|
||||
renameProject: fromPromise(async () => ({
|
||||
message: 'not implemented on web',
|
||||
oldName: '',
|
||||
newName: '',
|
||||
})),
|
||||
deleteProject: fromPromise(async () => ({
|
||||
message: 'not implemented on web',
|
||||
name: '',
|
||||
})),
|
||||
createFile: fromPromise(async ({ input }) => {
|
||||
// Browser version doesn't navigate, just overwrites the current file
|
||||
clearImportSearchParams()
|
||||
codeManager.updateCodeStateEditor(input.code || '')
|
||||
await codeManager.writeToFile()
|
||||
|
||||
settingsSend({
|
||||
type: 'set.modeling.defaultUnit',
|
||||
data: {
|
||||
level: 'project',
|
||||
value: input.units,
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
message: 'File and units overwritten successfully',
|
||||
fileName: input.name,
|
||||
projectName: '',
|
||||
}
|
||||
}),
|
||||
},
|
||||
}),
|
||||
{
|
||||
input: {
|
||||
projects: [],
|
||||
defaultProjectName: settings.projects.defaultProjectName.current,
|
||||
defaultDirectory: settings.app.projectDirectory.current,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// register all project-related command palette commands
|
||||
useStateMachineCommands({
|
||||
machineId: 'projects',
|
||||
send,
|
||||
state,
|
||||
commandBarConfig: projectsCommandBarConfig,
|
||||
actor,
|
||||
onCancel: clearImportSearchParams,
|
||||
})
|
||||
|
||||
return (
|
||||
<ProjectsMachineContext.Provider
|
||||
value={{
|
||||
state: undefined,
|
||||
send: () => {},
|
||||
state,
|
||||
send,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
@ -73,18 +187,21 @@ const ProjectsContextDesktop = ({
|
||||
}) => {
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const [searchParams, setSearchParams] = useSearchParams()
|
||||
const clearImportSearchParams = useCallback(() => {
|
||||
// Clear the search parameters related to the "Import file from URL" command
|
||||
// or we'll never be able cancel or submit it.
|
||||
searchParams.delete(CREATE_FILE_URL_PARAM)
|
||||
searchParams.delete('code')
|
||||
searchParams.delete('name')
|
||||
searchParams.delete('units')
|
||||
setSearchParams(searchParams)
|
||||
}, [searchParams, setSearchParams])
|
||||
const { onProjectOpen } = useLspContext()
|
||||
const {
|
||||
settings: { context: settings },
|
||||
} = useSettingsAuthContext()
|
||||
|
||||
useEffect(() => {
|
||||
console.log(
|
||||
'project directory changed',
|
||||
settings.app.projectDirectory.current
|
||||
)
|
||||
}, [settings.app.projectDirectory.current])
|
||||
|
||||
const [projectsLoaderTrigger, setProjectsLoaderTrigger] = useState(0)
|
||||
const { projectPaths, projectsDir } = useProjectsLoader([
|
||||
projectsLoaderTrigger,
|
||||
@ -168,6 +285,31 @@ const ProjectsContextDesktop = ({
|
||||
}
|
||||
}
|
||||
},
|
||||
navigateToFile: ({ context, event }) => {
|
||||
if (event.type !== 'xstate.done.actor.create-file') return
|
||||
// For now, the browser version of create-file doesn't need to navigate
|
||||
// since it just overwrites the current file.
|
||||
if (!isDesktop()) return
|
||||
let projectPath = window.electron.join(
|
||||
context.defaultDirectory,
|
||||
event.output.projectName
|
||||
)
|
||||
let filePath = window.electron.join(
|
||||
projectPath,
|
||||
event.output.fileName
|
||||
)
|
||||
onProjectOpen(
|
||||
{
|
||||
name: event.output.projectName,
|
||||
path: projectPath,
|
||||
},
|
||||
null
|
||||
)
|
||||
const pathToNavigateTo = `${PATHS.FILE}/${encodeURIComponent(
|
||||
filePath
|
||||
)}`
|
||||
navigate(pathToNavigateTo)
|
||||
},
|
||||
toastSuccess: ({ event }) =>
|
||||
toast.success(
|
||||
('data' in event && typeof event.data === 'string' && event.data) ||
|
||||
@ -217,8 +359,6 @@ const ProjectsContextDesktop = ({
|
||||
name = interpolateProjectNameWithIndex(name, nextIndex)
|
||||
}
|
||||
|
||||
console.log('from Project')
|
||||
|
||||
await renameProjectDirectory(
|
||||
window.electron.path.join(defaultDirectory, oldName),
|
||||
name
|
||||
@ -241,13 +381,82 @@ const ProjectsContextDesktop = ({
|
||||
name: input.name,
|
||||
}
|
||||
}),
|
||||
},
|
||||
guards: {
|
||||
'Has at least 1 project': ({ event }) => {
|
||||
if (event.type !== 'xstate.done.actor.read-projects') return false
|
||||
console.log(`from has at least 1 project: ${event.output.length}`)
|
||||
return event.output.length ? event.output.length >= 1 : false
|
||||
},
|
||||
createFile: fromPromise(async ({ input }) => {
|
||||
let projectName =
|
||||
(input.method === 'newProject' ? input.name : input.projectName) ||
|
||||
settings.projects.defaultProjectName.current
|
||||
let fileName =
|
||||
input.method === 'newProject'
|
||||
? PROJECT_ENTRYPOINT
|
||||
: input.name.endsWith(FILE_EXT)
|
||||
? input.name
|
||||
: input.name + FILE_EXT
|
||||
let message = 'File created successfully'
|
||||
const unitsConfiguration: DeepPartial<Configuration> = {
|
||||
settings: {
|
||||
project: {
|
||||
directory: settings.app.projectDirectory.current,
|
||||
},
|
||||
modeling: {
|
||||
base_unit: input.units,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const needsInterpolated = doesProjectNameNeedInterpolated(projectName)
|
||||
if (needsInterpolated) {
|
||||
const nextIndex = getNextProjectIndex(projectName, input.projects)
|
||||
projectName = interpolateProjectNameWithIndex(
|
||||
projectName,
|
||||
nextIndex
|
||||
)
|
||||
}
|
||||
|
||||
// Create the project around the file if newProject
|
||||
if (input.method === 'newProject') {
|
||||
await createNewProjectDirectory(
|
||||
projectName,
|
||||
input.code,
|
||||
unitsConfiguration
|
||||
)
|
||||
message = `Project "${projectName}" created successfully with link contents`
|
||||
} else {
|
||||
let projectPath = window.electron.join(
|
||||
settings.app.projectDirectory.current,
|
||||
projectName
|
||||
)
|
||||
|
||||
message = `File "${fileName}" created successfully`
|
||||
const existingConfiguration = await loadAndValidateSettings(
|
||||
projectPath
|
||||
)
|
||||
const settingsToSave = setSettingsAtLevel(
|
||||
existingConfiguration.settings,
|
||||
'project',
|
||||
projectConfigurationToSettingsPayload(unitsConfiguration)
|
||||
)
|
||||
await saveSettings(settingsToSave, projectPath)
|
||||
}
|
||||
|
||||
// Create the file
|
||||
let baseDir = window.electron.join(
|
||||
settings.app.projectDirectory.current,
|
||||
projectName
|
||||
)
|
||||
const { name, path } = getNextFileName({
|
||||
entryName: fileName,
|
||||
baseDir,
|
||||
})
|
||||
|
||||
fileName = name
|
||||
await window.electron.writeFile(path, input.code || '')
|
||||
|
||||
return {
|
||||
message,
|
||||
fileName,
|
||||
projectName,
|
||||
}
|
||||
}),
|
||||
},
|
||||
}),
|
||||
{
|
||||
@ -270,6 +479,7 @@ const ProjectsContextDesktop = ({
|
||||
state,
|
||||
commandBarConfig: projectsCommandBarConfig,
|
||||
actor,
|
||||
onCancel: clearImportSearchParams,
|
||||
})
|
||||
|
||||
return (
|
||||
|
Reference in New Issue
Block a user