Files
modeling-app/src/routes/Home.tsx
Frank Noirot 04ed6f52ee 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 commit 7545b61b49.

* Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)"

This reverts commit 3d2e48732c.

* 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 commit 214763cc2b.

* 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>
2025-01-24 00:18:27 +00:00

264 lines
9.1 KiB
TypeScript

import { FormEvent, useEffect, useRef, useState } from 'react'
import { ActionButton } from 'components/ActionButton'
import { AppHeader } from 'components/AppHeader'
import ProjectCard from 'components/ProjectCard/ProjectCard'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { Link } from 'react-router-dom'
import { toast } from 'react-hot-toast'
import Loading from 'components/Loading'
import { PATHS } from 'lib/paths'
import {
getNextSearchParams,
getSortFunction,
getSortIcon,
} from '../lib/sorting'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { useHotkeys } from 'react-hotkeys-hook'
import { isDesktop } from 'lib/isDesktop'
import { kclManager } from 'lib/singletons'
import { useRefreshSettings } from 'hooks/useRefreshSettings'
import { LowerRightControls } from 'components/LowerRightControls'
import { ProjectSearchBar, useProjectSearch } from 'components/ProjectSearchBar'
import { Project } from 'lib/project'
import { markOnce } from 'lib/performance'
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
import { useProjectsLoader } from 'hooks/useProjectsLoader'
import { useProjectsContext } from 'hooks/useProjectsContext'
import { commandBarActor } from 'machines/commandBarMachine'
import { useCreateFileLinkQuery } from 'hooks/useCreateFileLinkQueryWatcher'
// This route only opens in the desktop context for now,
// as defined in Router.tsx, so we can use the desktop APIs and types.
const Home = () => {
const { state, send } = useProjectsContext()
const [projectsLoaderTrigger, setProjectsLoaderTrigger] = useState(0)
const { projectsDir } = useProjectsLoader([projectsLoaderTrigger])
// Keep a lookout for a URL query string that invokes the 'import file from URL' command
useCreateFileLinkQuery((argDefaultValues) => {
commandBarActor.send({
type: 'Find and select command',
data: {
groupId: 'projects',
name: 'Import file from URL',
argDefaultValues,
},
})
})
useRefreshSettings(PATHS.HOME + 'SETTINGS')
const navigate = useNavigate()
const {
settings: { context: settings },
} = useSettingsAuthContext()
// Cancel all KCL executions while on the home page
useEffect(() => {
markOnce('code/didLoadHome')
kclManager.cancelAllExecutions()
}, [])
useHotkeys('backspace', (e) => {
e.preventDefault()
})
useHotkeys(
isDesktop() ? 'mod+,' : 'shift+mod+,',
() => navigate(PATHS.HOME + PATHS.SETTINGS),
{
splitKey: '|',
}
)
const ref = useRef<HTMLDivElement>(null)
// Re-read projects listing if the projectDir has any updates.
useFileSystemWatcher(
async () => {
setProjectsLoaderTrigger(projectsLoaderTrigger + 1)
},
projectsDir ? [projectsDir] : []
)
const projects = state?.context.projects ?? []
const [searchParams, setSearchParams] = useSearchParams()
const { searchResults, query, setQuery } = useProjectSearch(projects)
const sort = searchParams.get('sort_by') ?? 'modified:desc'
const isSortByModified = sort?.includes('modified') || !sort || sort === null
// Update the default project name and directory in the home machine
// when the settings change
useEffect(() => {
send({
type: 'assign',
data: {
defaultProjectName: settings.projects.defaultProjectName.current,
defaultDirectory: settings.app.projectDirectory.current,
},
})
}, [
settings.app.projectDirectory.current,
settings.projects.defaultProjectName.current,
send,
])
async function handleRenameProject(
e: FormEvent<HTMLFormElement>,
project: Project
) {
const { newProjectName } = Object.fromEntries(
new FormData(e.target as HTMLFormElement)
)
if (typeof newProjectName === 'string' && newProjectName.startsWith('.')) {
toast.error('Project names cannot start with a dot (.)')
return
}
if (newProjectName !== project.name) {
send({
type: 'Rename project',
data: { oldName: project.name, newName: newProjectName as string },
})
}
}
async function handleDeleteProject(project: Project) {
send({
type: 'Delete project',
data: { name: project.name || '' },
})
}
return (
<div className="relative flex flex-col h-screen overflow-hidden" ref={ref}>
<AppHeader showToolbar={false} />
<div className="w-full flex flex-col overflow-hidden max-w-5xl px-4 mx-auto mt-24 lg:px-2">
<section>
<div className="flex justify-between items-center select-none">
<div className="flex gap-8 items-center">
<h1 className="text-3xl font-bold">Your Projects</h1>
<ActionButton
Element="button"
onClick={() =>
commandBarActor.send({
type: 'Find and select command',
data: {
groupId: 'projects',
name: 'Create project',
argDefaultValues: {
name: settings.projects.defaultProjectName.current,
},
},
})
}
className="group !bg-primary !text-chalkboard-10 !border-primary hover:shadow-inner hover:hue-rotate-15"
iconStart={{
icon: 'plus',
bgClassName: '!bg-transparent rounded-sm',
iconClassName:
'!text-chalkboard-10 transition-transform group-active:rotate-90',
}}
data-testid="home-new-file"
>
Create project
</ActionButton>
</div>
<div className="flex gap-2 items-center">
<ProjectSearchBar setQuery={setQuery} />
<small>Sort by</small>
<ActionButton
Element="button"
data-testid="home-sort-by-name"
className={
'text-xs border-primary/10 ' +
(!sort.includes('name')
? 'text-chalkboard-80 dark:text-chalkboard-40'
: '')
}
onClick={() =>
setSearchParams(getNextSearchParams(sort, 'name'))
}
iconStart={{
icon: getSortIcon(sort, 'name'),
bgClassName: 'bg-transparent',
iconClassName: !sort.includes('name')
? '!text-chalkboard-90 dark:!text-chalkboard-30'
: '',
}}
>
Name
</ActionButton>
<ActionButton
Element="button"
data-testid="home-sort-by-modified"
className={
'text-xs border-primary/10 ' +
(!isSortByModified
? 'text-chalkboard-80 dark:text-chalkboard-40'
: '')
}
onClick={() =>
setSearchParams(getNextSearchParams(sort, 'modified'))
}
iconStart={{
icon: sort ? getSortIcon(sort, 'modified') : 'arrowDown',
bgClassName: 'bg-transparent',
iconClassName: !isSortByModified
? '!text-chalkboard-90 dark:!text-chalkboard-30'
: '',
}}
>
Last Modified
</ActionButton>
</div>
</div>
<p className="my-4 text-sm text-chalkboard-80 dark:text-chalkboard-30">
Loaded from{' '}
<Link
data-testid="project-directory-settings-link"
to={`${PATHS.HOME + PATHS.SETTINGS_USER}#projectDirectory`}
className="text-chalkboard-90 dark:text-chalkboard-20 underline underline-offset-2"
>
{settings.app.projectDirectory.current}
</Link>
.
</p>
</section>
<section
data-testid="home-section"
className="flex-1 overflow-y-auto pr-2 pb-24"
>
{state?.matches('Reading projects') ? (
<Loading>Loading your Projects...</Loading>
) : (
<>
{searchResults.length > 0 ? (
<ul className="grid w-full grid-cols-4 gap-4">
{searchResults.sort(getSortFunction(sort)).map((project) => (
<ProjectCard
key={project.name}
project={project}
handleRenameProject={handleRenameProject}
handleDeleteProject={handleDeleteProject}
/>
))}
</ul>
) : (
<p className="p-4 my-8 border border-dashed rounded border-chalkboard-30 dark:border-chalkboard-70">
No Projects found
{projects.length === 0
? ', ready to make your first one?'
: ` with the search term "${query}"`}
</p>
)}
</>
)}
</section>
<LowerRightControls />
</div>
</div>
)
}
export default Home