2025-04-01 23:54:26 -07:00
|
|
|
import type { FormEvent } from 'react'
|
|
|
|
import { useEffect, useRef, useState } from 'react'
|
2025-04-01 15:31:19 -07:00
|
|
|
import { useHotkeys } from 'react-hotkeys-hook'
|
2025-04-01 23:54:26 -07:00
|
|
|
import { Link } from 'react-router-dom'
|
|
|
|
|
|
|
|
import { ActionButton } from '@src/components/ActionButton'
|
|
|
|
import { DeleteConfirmationDialog } from '@src/components/ProjectCard/DeleteProjectDialog'
|
|
|
|
import { ProjectCardRenameForm } from '@src/components/ProjectCard/ProjectCardRenameForm'
|
|
|
|
import Tooltip from '@src/components/Tooltip'
|
|
|
|
import { FILE_EXT, PROJECT_IMAGE_NAME } from '@src/lib/constants'
|
|
|
|
import { PATHS } from '@src/lib/paths'
|
|
|
|
import type { Project } from '@src/lib/project'
|
|
|
|
import { reportRejection } from '@src/lib/trap'
|
|
|
|
import { toSync } from '@src/lib/utils'
|
2024-05-23 11:47:02 -04:00
|
|
|
|
|
|
|
function ProjectCard({
|
|
|
|
project,
|
|
|
|
handleRenameProject,
|
|
|
|
handleDeleteProject,
|
|
|
|
...props
|
|
|
|
}: {
|
|
|
|
project: Project
|
|
|
|
handleRenameProject: (
|
|
|
|
e: FormEvent<HTMLFormElement>,
|
|
|
|
f: Project
|
|
|
|
) => Promise<void>
|
|
|
|
handleDeleteProject: (f: Project) => Promise<void>
|
|
|
|
}) {
|
|
|
|
useHotkeys('esc', () => setIsEditing(false))
|
|
|
|
const [isEditing, setIsEditing] = useState(false)
|
|
|
|
const [isConfirmingDelete, setIsConfirmingDelete] = useState(false)
|
|
|
|
const [numberOfFiles, setNumberOfFiles] = useState(1)
|
|
|
|
const [numberOfFolders, setNumberOfFolders] = useState(0)
|
2025-02-01 06:40:02 -05:00
|
|
|
const [imageUrl, setImageUrl] = useState('')
|
2024-05-23 11:47:02 -04:00
|
|
|
|
|
|
|
let inputRef = useRef<HTMLInputElement>(null)
|
|
|
|
|
|
|
|
function handleSave(e: FormEvent<HTMLFormElement>) {
|
|
|
|
e.preventDefault()
|
2025-03-05 18:00:36 -05:00
|
|
|
handleRenameProject(e, project)
|
|
|
|
.then(() => setIsEditing(false))
|
|
|
|
.catch(reportRejection)
|
2024-05-23 11:47:02 -04:00
|
|
|
}
|
|
|
|
|
2024-08-16 07:15:42 -04:00
|
|
|
function getDisplayedTime(dateTimeMs: number) {
|
|
|
|
const date = new Date(dateTimeMs)
|
2024-05-23 11:47:02 -04:00
|
|
|
const startOfToday = new Date()
|
|
|
|
startOfToday.setHours(0, 0, 0, 0)
|
|
|
|
return date.getTime() < startOfToday.getTime()
|
|
|
|
? date.toLocaleDateString()
|
|
|
|
: date.toLocaleTimeString()
|
|
|
|
}
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
async function getNumberOfFiles() {
|
|
|
|
setNumberOfFiles(project.kcl_file_count)
|
|
|
|
setNumberOfFolders(project.directory_count)
|
|
|
|
}
|
|
|
|
|
2025-02-01 06:40:02 -05:00
|
|
|
async function setupImageUrl() {
|
|
|
|
const projectImagePath = window.electron.path.join(
|
|
|
|
project.path,
|
|
|
|
PROJECT_IMAGE_NAME
|
|
|
|
)
|
|
|
|
if (await window.electron.exists(projectImagePath)) {
|
|
|
|
const imageData = await window.electron.readFile(projectImagePath)
|
|
|
|
const blob = new Blob([imageData], { type: 'image/png' })
|
|
|
|
const imageUrl = URL.createObjectURL(blob)
|
2025-05-08 14:41:29 -05:00
|
|
|
|
|
|
|
if (blob.size > 0) {
|
|
|
|
/**
|
|
|
|
* Off chance that a thumbnail.png is cancelled writing and ends up writing 0 bytes
|
|
|
|
* We do not want to load a 0 byte image
|
|
|
|
*/
|
|
|
|
setImageUrl(imageUrl)
|
|
|
|
}
|
2025-02-01 06:40:02 -05:00
|
|
|
}
|
|
|
|
}
|
2024-05-23 11:47:02 -04:00
|
|
|
|
|
|
|
void getNumberOfFiles()
|
2025-02-01 06:40:02 -05:00
|
|
|
void setupImageUrl()
|
2024-05-23 11:47:02 -04:00
|
|
|
}, [project.kcl_file_count, project.directory_count])
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (inputRef.current && isEditing) {
|
|
|
|
inputRef.current.focus()
|
|
|
|
inputRef.current.select()
|
|
|
|
}
|
|
|
|
}, [isEditing, inputRef.current])
|
|
|
|
|
|
|
|
return (
|
|
|
|
<li
|
|
|
|
{...props}
|
Rework home layout to have a sidebar (#6423)
* chore: saving off skeleton
* fix: saving skeleton
* chore: skeleton for loading projects from project directory path
* chore: cleaning up useless state transition to be an on event direct to action state
* fix: new structure for web vs desktop vs react machine provider code
* chore: saving off skeleton
* fix: skeleton logic for react? going to move it from a string to obj.string
* fix: trying to prevent error element unmount on global react components. This is bricking JS state
* fix: we are so back
* chore: implemented navigating to specfic KCL file
* chore: implementing renaming project
* chore: deleting project
* fix: auto fixes
* fix: old debug/testing file oops
* chore: generic create new file
* chore: skeleton for web create file provide
* chore: basic machine vitest... need to figure out how to get window.electron implemented in vitest?
* chore: save off progress before deleting other project implementation, a few missing features still
* chore: trying a different init skeleton? most likely will migrate
* chore: first attempt of purging projects context provider
* chore: enabling toast for some machine state
* chore: enabling more toast success and error
* chore: writing read write state to the system io based on the project path
* fix: tsc fixes
* fix: use file system watcher, navigate to project after creation via the requestProjectName
* chore: open project command, hooks vs snapshot context helpers
* chore: implemented open and create project for e2e testing. They are hard coded in poor spot for now.
* fix: codespell fixes
* chore: implementing more project commands
* chore: PR improvements for root.tsx
* chore: leaving comment about new Router.tsx layout
* fix: removing debugging code
* fix: rewriting component for readability
* fix: improving web initialization
* chore: implementing import file from url which is not actually that?
* fix: clearing search params on import file from url
* fix: fixed two e2e tests, forgot needsReview when making new command
* fix: fixing some import from url business logic to pass e2e tests
* chore: script for diffing circular deps +/-
* fix: formatting
* fix: massive fix for circular depsga!
* fix: trying to fix some errors and auto fmt
* fix: updating deps
* fix: removing debugging code
* fix: big clean up
* fix: more deletion
* fix: tsc cleanup
* fix: TSC TSC TSC TSC!
* fix: typo fix
* fix: clear query params on web only, desktop not required
* fix: removing unused code
* fmt
* Bring back `trap` removed in merge
* Use explicit types instead of `any`s on arg configs
* Add project commands directly to command palette
* fix: deleting debugging code, from PR review
* fix: this got added back(?)
* fix: using referred type
* fix: more PR clean up
* fix: big block comment for xstate architecture decision
* fix: more pr comment fixes
* fix: saving off logic, need a big cleanup because I hacked it together to get a POC
* fix: extra business?
* fix: merge conflict just added them back why dude
* fix: more PR comments
* fix: big ciruclar deps fix, commandBarActor in appActor
* chore: writing e2e test, still need to fix 3 bugs
* chore: adding more scenarios
* fix: formatting
* fix: fixing tsc errors
* chore: deleting the old text to cad and using the new application level one, almost there
* fix: prompt to edit works
* fix: large push to get 1 text to cad command... the usage is a little buggy with delete and navigate within /file
* fix: settings for highlight edges now works
* chore: adding another e2e test
* fix: cleaning up e2e tests and writing more of them
* fix: tsc type
* chore: more e2e improvements, unique project name in text to cad
* chore: e2e tests should be good to go
* fix: gotcha comment
* fix: enabled web t2c, codespell fixes
* fix: fixing merge conflcits??
* fix: t2c is back
* Rework home layout to have a sidebar
fmt I think
* Add two links to the bottom of the sidebar
Mostly to visually anchor it
* Tweak some style things
* update test util whose locator needs to change
* tsc and fmt
* Stupid heading change broke the dang E2E tests
* Make that heading locator a part of the home page fixture
* pierremtb/new-snaps-for-frank (#6516)
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
---------
Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Kevin Nadro <nadr0@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
2025-04-26 11:55:01 -04:00
|
|
|
className="group relative flex flex-col rounded-sm border border-chalkboard-50 dark:border-chalkboard-80 hover:!border-primary"
|
2024-05-23 11:47:02 -04:00
|
|
|
>
|
|
|
|
<Link
|
|
|
|
data-testid="project-link"
|
2025-03-24 14:57:01 -05:00
|
|
|
to={
|
|
|
|
project.readWriteAccess
|
|
|
|
? `${PATHS.FILE}/${encodeURIComponent(project.default_file)}`
|
|
|
|
: ''
|
|
|
|
}
|
Rework home layout to have a sidebar (#6423)
* chore: saving off skeleton
* fix: saving skeleton
* chore: skeleton for loading projects from project directory path
* chore: cleaning up useless state transition to be an on event direct to action state
* fix: new structure for web vs desktop vs react machine provider code
* chore: saving off skeleton
* fix: skeleton logic for react? going to move it from a string to obj.string
* fix: trying to prevent error element unmount on global react components. This is bricking JS state
* fix: we are so back
* chore: implemented navigating to specfic KCL file
* chore: implementing renaming project
* chore: deleting project
* fix: auto fixes
* fix: old debug/testing file oops
* chore: generic create new file
* chore: skeleton for web create file provide
* chore: basic machine vitest... need to figure out how to get window.electron implemented in vitest?
* chore: save off progress before deleting other project implementation, a few missing features still
* chore: trying a different init skeleton? most likely will migrate
* chore: first attempt of purging projects context provider
* chore: enabling toast for some machine state
* chore: enabling more toast success and error
* chore: writing read write state to the system io based on the project path
* fix: tsc fixes
* fix: use file system watcher, navigate to project after creation via the requestProjectName
* chore: open project command, hooks vs snapshot context helpers
* chore: implemented open and create project for e2e testing. They are hard coded in poor spot for now.
* fix: codespell fixes
* chore: implementing more project commands
* chore: PR improvements for root.tsx
* chore: leaving comment about new Router.tsx layout
* fix: removing debugging code
* fix: rewriting component for readability
* fix: improving web initialization
* chore: implementing import file from url which is not actually that?
* fix: clearing search params on import file from url
* fix: fixed two e2e tests, forgot needsReview when making new command
* fix: fixing some import from url business logic to pass e2e tests
* chore: script for diffing circular deps +/-
* fix: formatting
* fix: massive fix for circular depsga!
* fix: trying to fix some errors and auto fmt
* fix: updating deps
* fix: removing debugging code
* fix: big clean up
* fix: more deletion
* fix: tsc cleanup
* fix: TSC TSC TSC TSC!
* fix: typo fix
* fix: clear query params on web only, desktop not required
* fix: removing unused code
* fmt
* Bring back `trap` removed in merge
* Use explicit types instead of `any`s on arg configs
* Add project commands directly to command palette
* fix: deleting debugging code, from PR review
* fix: this got added back(?)
* fix: using referred type
* fix: more PR clean up
* fix: big block comment for xstate architecture decision
* fix: more pr comment fixes
* fix: saving off logic, need a big cleanup because I hacked it together to get a POC
* fix: extra business?
* fix: merge conflict just added them back why dude
* fix: more PR comments
* fix: big ciruclar deps fix, commandBarActor in appActor
* chore: writing e2e test, still need to fix 3 bugs
* chore: adding more scenarios
* fix: formatting
* fix: fixing tsc errors
* chore: deleting the old text to cad and using the new application level one, almost there
* fix: prompt to edit works
* fix: large push to get 1 text to cad command... the usage is a little buggy with delete and navigate within /file
* fix: settings for highlight edges now works
* chore: adding another e2e test
* fix: cleaning up e2e tests and writing more of them
* fix: tsc type
* chore: more e2e improvements, unique project name in text to cad
* chore: e2e tests should be good to go
* fix: gotcha comment
* fix: enabled web t2c, codespell fixes
* fix: fixing merge conflcits??
* fix: t2c is back
* Rework home layout to have a sidebar
fmt I think
* Add two links to the bottom of the sidebar
Mostly to visually anchor it
* Tweak some style things
* update test util whose locator needs to change
* tsc and fmt
* Stupid heading change broke the dang E2E tests
* Make that heading locator a part of the home page fixture
* pierremtb/new-snaps-for-frank (#6516)
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
---------
Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Kevin Nadro <nadr0@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
2025-04-26 11:55:01 -04:00
|
|
|
className={`flex flex-col flex-1 !no-underline !text-chalkboard-110 dark:!text-chalkboard-10 min-h-[5em] divide-y divide-chalkboard-50 dark:divide-chalkboard-80 ${
|
2025-03-24 14:57:01 -05:00
|
|
|
project.readWriteAccess
|
|
|
|
? 'group-hover:!divide-primary group-hover:!hue-rotate-0'
|
|
|
|
: 'cursor-not-allowed'
|
|
|
|
}`}
|
2024-05-23 11:47:02 -04:00
|
|
|
>
|
2025-02-01 06:40:02 -05:00
|
|
|
<div className="h-36 relative overflow-hidden bg-gradient-to-b from-transparent to-primary/10 rounded-t-sm">
|
2024-05-23 11:47:02 -04:00
|
|
|
{imageUrl && (
|
|
|
|
<img
|
|
|
|
src={imageUrl}
|
|
|
|
alt=""
|
|
|
|
className="h-full w-full transition-transform group-hover:scale-105 object-cover"
|
|
|
|
/>
|
|
|
|
)}
|
2025-02-01 06:40:02 -05:00
|
|
|
</div>
|
2024-05-23 11:47:02 -04:00
|
|
|
<div className="pb-2 flex flex-col flex-grow flex-auto gap-2 rounded-b-sm">
|
|
|
|
{isEditing ? (
|
|
|
|
<ProjectCardRenameForm
|
|
|
|
onSubmit={handleSave}
|
|
|
|
className="flex items-center gap-2 p-2"
|
|
|
|
onClick={(e) => e.stopPropagation()}
|
|
|
|
project={project}
|
|
|
|
onDismiss={() => setIsEditing(false)}
|
|
|
|
ref={inputRef}
|
|
|
|
/>
|
|
|
|
) : (
|
2024-10-01 07:56:04 +10:00
|
|
|
<h3
|
|
|
|
className="font-sans relative z-0 p-2"
|
|
|
|
data-testid="project-title"
|
|
|
|
>
|
2024-05-23 11:47:02 -04:00
|
|
|
{project.name?.replace(FILE_EXT, '')}
|
|
|
|
</h3>
|
|
|
|
)}
|
2025-03-24 14:57:01 -05:00
|
|
|
{project.readWriteAccess && (
|
|
|
|
<span className="px-2 text-chalkboard-60 text-xs">
|
|
|
|
<span data-testid="project-file-count">{numberOfFiles}</span> file
|
|
|
|
{numberOfFiles === 1 ? '' : 's'}{' '}
|
|
|
|
{numberOfFolders > 0 && (
|
|
|
|
<>
|
|
|
|
{'/ '}
|
|
|
|
<span data-testid="project-folder-count">
|
|
|
|
{numberOfFolders}
|
|
|
|
</span>{' '}
|
|
|
|
folder{numberOfFolders === 1 ? '' : 's'}
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</span>
|
|
|
|
)}
|
2024-05-23 11:47:02 -04:00
|
|
|
<span className="px-2 text-chalkboard-60 text-xs">
|
|
|
|
Edited{' '}
|
2024-10-01 07:56:04 +10:00
|
|
|
<span data-testid="project-edit-date">
|
|
|
|
{project.metadata && project.metadata.modified
|
|
|
|
? getDisplayedTime(parseInt(project.metadata.modified))
|
|
|
|
: 'never'}
|
|
|
|
</span>
|
2024-05-23 11:47:02 -04:00
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
</Link>
|
|
|
|
{!isEditing && (
|
2024-08-19 11:49:03 +10:00
|
|
|
<div
|
|
|
|
className="absolute z-10 flex items-center gap-1 opacity-0 bottom-2 right-2 group-hover:opacity-100 group-focus-within:opacity-100"
|
|
|
|
data-edit-buttons-for={project.name?.replace(FILE_EXT, '')}
|
|
|
|
>
|
2024-05-23 11:47:02 -04:00
|
|
|
<ActionButton
|
2025-03-24 14:57:01 -05:00
|
|
|
disabled={!project.readWriteAccess}
|
2024-05-23 11:47:02 -04:00
|
|
|
Element="button"
|
|
|
|
iconStart={{
|
|
|
|
icon: 'sketch',
|
|
|
|
iconClassName: 'dark:!text-chalkboard-20',
|
|
|
|
bgClassName: '!bg-transparent',
|
|
|
|
}}
|
|
|
|
onClick={(e) => {
|
|
|
|
e.stopPropagation()
|
|
|
|
e.nativeEvent.stopPropagation()
|
|
|
|
setIsEditing(true)
|
|
|
|
}}
|
|
|
|
className="!p-0"
|
|
|
|
>
|
2025-03-24 22:58:54 -04:00
|
|
|
<Tooltip position="top-right">Rename project</Tooltip>
|
2024-05-23 11:47:02 -04:00
|
|
|
</ActionButton>
|
|
|
|
<ActionButton
|
2025-03-24 14:57:01 -05:00
|
|
|
disabled={!project.readWriteAccess}
|
2024-05-23 11:47:02 -04:00
|
|
|
Element="button"
|
|
|
|
iconStart={{
|
|
|
|
icon: 'trash',
|
|
|
|
iconClassName: 'dark:!text-chalkboard-30',
|
|
|
|
bgClassName: '!bg-transparent',
|
|
|
|
}}
|
|
|
|
className="!p-0"
|
|
|
|
onClick={(e) => {
|
|
|
|
e.stopPropagation()
|
|
|
|
e.nativeEvent.stopPropagation()
|
|
|
|
setIsConfirmingDelete(true)
|
|
|
|
}}
|
|
|
|
>
|
2025-03-24 22:58:54 -04:00
|
|
|
<Tooltip position="top-right">Delete project</Tooltip>
|
2024-05-23 11:47:02 -04:00
|
|
|
</ActionButton>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
{isConfirmingDelete && (
|
2024-05-24 18:12:39 -04:00
|
|
|
<DeleteConfirmationDialog
|
|
|
|
title="Delete Project"
|
2024-09-09 18:17:45 -04:00
|
|
|
onConfirm={toSync(async () => {
|
2024-05-23 11:47:02 -04:00
|
|
|
await handleDeleteProject(project)
|
|
|
|
setIsConfirmingDelete(false)
|
2024-09-09 18:17:45 -04:00
|
|
|
}, reportRejection)}
|
2024-05-23 11:47:02 -04:00
|
|
|
onDismiss={() => setIsConfirmingDelete(false)}
|
2024-05-24 18:12:39 -04:00
|
|
|
>
|
|
|
|
<p className="my-4">
|
|
|
|
This will permanently delete "{project.name || 'this file'}
|
|
|
|
".
|
|
|
|
</p>
|
|
|
|
<p className="my-4">
|
|
|
|
Are you sure you want to delete "{project.name || 'this file'}
|
|
|
|
"? This action cannot be undone.
|
|
|
|
</p>
|
|
|
|
</DeleteConfirmationDialog>
|
2024-05-23 11:47:02 -04:00
|
|
|
)}
|
|
|
|
</li>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
export default ProjectCard
|