2023-12-06 14:44:13 -05:00
|
|
|
import { FormEvent, useEffect, useRef, useState } from 'react'
|
2024-02-11 12:59:00 +11:00
|
|
|
import { type ProjectWithEntryPointMetadata } from 'lib/types'
|
|
|
|
import { paths } from 'lib/paths'
|
2023-08-15 21:56:24 -04:00
|
|
|
import { Link } from 'react-router-dom'
|
|
|
|
import { ActionButton } from './ActionButton'
|
|
|
|
import {
|
|
|
|
faCheck,
|
|
|
|
faPenAlt,
|
|
|
|
faTrashAlt,
|
|
|
|
faX,
|
|
|
|
} from '@fortawesome/free-solid-svg-icons'
|
2024-04-02 10:29:34 -04:00
|
|
|
import { getPartsCount, readProject } from '../lib/tauriFS'
|
|
|
|
import { FILE_EXT } from 'lib/constants'
|
2023-08-15 21:56:24 -04:00
|
|
|
import { Dialog } from '@headlessui/react'
|
|
|
|
import { useHotkeys } from 'react-hotkeys-hook'
|
2024-04-05 00:59:02 -04:00
|
|
|
import Tooltip from './Tooltip'
|
2023-08-15 21:56:24 -04:00
|
|
|
|
|
|
|
function ProjectCard({
|
|
|
|
project,
|
|
|
|
handleRenameProject,
|
|
|
|
handleDeleteProject,
|
|
|
|
...props
|
|
|
|
}: {
|
|
|
|
project: ProjectWithEntryPointMetadata
|
|
|
|
handleRenameProject: (
|
|
|
|
e: FormEvent<HTMLFormElement>,
|
|
|
|
f: ProjectWithEntryPointMetadata
|
|
|
|
) => Promise<void>
|
|
|
|
handleDeleteProject: (f: ProjectWithEntryPointMetadata) => Promise<void>
|
|
|
|
}) {
|
|
|
|
useHotkeys('esc', () => setIsEditing(false))
|
|
|
|
const [isEditing, setIsEditing] = useState(false)
|
|
|
|
const [isConfirmingDelete, setIsConfirmingDelete] = useState(false)
|
2023-10-16 13:28:41 -04:00
|
|
|
const [numberOfParts, setNumberOfParts] = useState(1)
|
|
|
|
const [numberOfFolders, setNumberOfFolders] = useState(0)
|
2023-08-15 21:56:24 -04:00
|
|
|
|
2023-12-06 14:44:13 -05:00
|
|
|
let inputRef = useRef<HTMLInputElement>(null)
|
|
|
|
|
2023-08-15 21:56:24 -04:00
|
|
|
function handleSave(e: FormEvent<HTMLFormElement>) {
|
|
|
|
e.preventDefault()
|
2023-12-06 14:44:13 -05:00
|
|
|
void handleRenameProject(e, project).then(() => setIsEditing(false))
|
2023-08-15 21:56:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
function getDisplayedTime(date: Date) {
|
|
|
|
const startOfToday = new Date()
|
|
|
|
startOfToday.setHours(0, 0, 0, 0)
|
|
|
|
return date.getTime() < startOfToday.getTime()
|
|
|
|
? date.toLocaleDateString()
|
|
|
|
: date.toLocaleTimeString()
|
|
|
|
}
|
|
|
|
|
2023-10-16 13:28:41 -04:00
|
|
|
useEffect(() => {
|
|
|
|
async function getNumberOfParts() {
|
|
|
|
const { kclFileCount, kclDirCount } = getPartsCount(
|
|
|
|
await readProject(project.path)
|
|
|
|
)
|
|
|
|
setNumberOfParts(kclFileCount)
|
|
|
|
setNumberOfFolders(kclDirCount)
|
|
|
|
}
|
2023-12-06 14:44:13 -05:00
|
|
|
void getNumberOfParts()
|
2023-10-16 13:28:41 -04:00
|
|
|
}, [project.path])
|
|
|
|
|
2023-12-06 14:44:13 -05:00
|
|
|
useEffect(() => {
|
|
|
|
if (inputRef.current) {
|
|
|
|
inputRef.current.focus()
|
|
|
|
inputRef.current.select()
|
|
|
|
}
|
2024-04-05 00:59:02 -04:00
|
|
|
}, [inputRef.current])
|
2023-12-06 14:44:13 -05:00
|
|
|
|
2023-08-15 21:56:24 -04:00
|
|
|
return (
|
|
|
|
<li
|
|
|
|
{...props}
|
2024-04-05 00:59:02 -04:00
|
|
|
className="group relative min-h-[5em] p-1 rounded-sm border border-chalkboard-20 dark:border-chalkboard-80 hover:!border-primary"
|
2023-08-15 21:56:24 -04:00
|
|
|
>
|
|
|
|
{isEditing ? (
|
|
|
|
<form onSubmit={handleSave} className="flex gap-2 items-center">
|
|
|
|
<input
|
2024-04-05 00:59:02 -04:00
|
|
|
className="dark:bg-chalkboard-80 dark:border-chalkboard-40 min-w-0 p-1 focus:outline-none"
|
2023-08-15 21:56:24 -04:00
|
|
|
type="text"
|
|
|
|
id="newProjectName"
|
|
|
|
name="newProjectName"
|
|
|
|
autoCorrect="off"
|
|
|
|
autoCapitalize="off"
|
|
|
|
defaultValue={project.name}
|
2023-12-06 14:44:13 -05:00
|
|
|
ref={inputRef}
|
2023-08-15 21:56:24 -04:00
|
|
|
/>
|
|
|
|
<div className="flex gap-1 items-center">
|
|
|
|
<ActionButton
|
|
|
|
Element="button"
|
|
|
|
type="submit"
|
2024-04-05 00:59:02 -04:00
|
|
|
icon={{
|
|
|
|
icon: faCheck,
|
|
|
|
size: 'sm',
|
|
|
|
className: 'p-1',
|
|
|
|
bgClassName: '!bg-transparent',
|
|
|
|
}}
|
2023-08-15 21:56:24 -04:00
|
|
|
className="!p-0"
|
2024-04-05 00:59:02 -04:00
|
|
|
>
|
|
|
|
<Tooltip position="left" delay={1000}>
|
|
|
|
Rename project
|
|
|
|
</Tooltip>
|
|
|
|
</ActionButton>
|
2023-08-15 21:56:24 -04:00
|
|
|
<ActionButton
|
|
|
|
Element="button"
|
2023-12-06 14:44:13 -05:00
|
|
|
icon={{
|
|
|
|
icon: faX,
|
|
|
|
size: 'sm',
|
|
|
|
iconClassName: 'dark:!text-chalkboard-20',
|
2024-04-05 00:59:02 -04:00
|
|
|
bgClassName: '!bg-transparent',
|
2023-12-06 14:44:13 -05:00
|
|
|
className: 'p-1',
|
|
|
|
}}
|
2023-08-15 21:56:24 -04:00
|
|
|
className="!p-0"
|
|
|
|
onClick={() => setIsEditing(false)}
|
2024-04-05 00:59:02 -04:00
|
|
|
>
|
|
|
|
<Tooltip position="left" delay={1000}>
|
|
|
|
Cancel
|
|
|
|
</Tooltip>
|
|
|
|
</ActionButton>
|
2023-08-15 21:56:24 -04:00
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
) : (
|
|
|
|
<>
|
2023-10-16 13:28:41 -04:00
|
|
|
<div className="p-1 flex flex-col h-full gap-2">
|
2023-08-15 21:56:24 -04:00
|
|
|
<Link
|
2024-04-05 00:59:02 -04:00
|
|
|
className="flex-1 !no-underline !text-chalkboard-110 dark:!text-chalkboard-10 after:content-[''] after:absolute after:inset-0"
|
2023-08-15 21:56:24 -04:00
|
|
|
to={`${paths.FILE}/${encodeURIComponent(project.path)}`}
|
2024-01-04 04:54:07 -05:00
|
|
|
data-testid="project-link"
|
2023-08-15 21:56:24 -04:00
|
|
|
>
|
|
|
|
{project.name?.replace(FILE_EXT, '')}
|
|
|
|
</Link>
|
2023-08-18 10:27:01 -04:00
|
|
|
<span className="text-chalkboard-60 text-xs">
|
2023-10-16 13:28:41 -04:00
|
|
|
{numberOfParts} part{numberOfParts === 1 ? '' : 's'}{' '}
|
|
|
|
{numberOfFolders > 0 &&
|
|
|
|
`/ ${numberOfFolders} folder${
|
|
|
|
numberOfFolders === 1 ? '' : 's'
|
|
|
|
}`}
|
|
|
|
</span>
|
|
|
|
<span className="text-chalkboard-60 text-xs">
|
2024-04-09 08:04:36 -04:00
|
|
|
Edited{' '}
|
|
|
|
{project.entrypointMetadata.mtime
|
|
|
|
? getDisplayedTime(project.entrypointMetadata.mtime)
|
|
|
|
: 'never'}
|
2023-08-15 21:56:24 -04:00
|
|
|
</span>
|
2023-12-06 14:44:13 -05:00
|
|
|
<div className="absolute z-10 bottom-2 right-2 flex gap-1 items-center opacity-0 group-hover:opacity-100 group-focus-within:opacity-100">
|
2023-08-15 21:56:24 -04:00
|
|
|
<ActionButton
|
|
|
|
Element="button"
|
2023-12-06 14:44:13 -05:00
|
|
|
icon={{
|
|
|
|
icon: faPenAlt,
|
|
|
|
className: 'p-1',
|
|
|
|
iconClassName: 'dark:!text-chalkboard-20',
|
2024-04-05 00:59:02 -04:00
|
|
|
bgClassName: '!bg-transparent',
|
2023-12-06 14:44:13 -05:00
|
|
|
size: 'xs',
|
|
|
|
}}
|
|
|
|
onClick={(e) => {
|
|
|
|
e.stopPropagation()
|
|
|
|
e.nativeEvent.stopPropagation()
|
|
|
|
setIsEditing(true)
|
|
|
|
}}
|
2023-08-15 21:56:24 -04:00
|
|
|
className="!p-0"
|
2024-04-05 00:59:02 -04:00
|
|
|
>
|
|
|
|
<Tooltip position="left" delay={1000}>
|
|
|
|
Rename project
|
|
|
|
</Tooltip>
|
|
|
|
</ActionButton>
|
2023-08-15 21:56:24 -04:00
|
|
|
<ActionButton
|
|
|
|
Element="button"
|
|
|
|
icon={{
|
|
|
|
icon: faTrashAlt,
|
2023-12-06 14:44:13 -05:00
|
|
|
className: 'p-1',
|
|
|
|
size: 'xs',
|
2024-04-05 00:59:02 -04:00
|
|
|
bgClassName: '!bg-transparent',
|
|
|
|
iconClassName: '!text-destroy-70',
|
2023-08-15 21:56:24 -04:00
|
|
|
}}
|
|
|
|
className="!p-0 hover:border-destroy-40 dark:hover:border-destroy-40"
|
2023-12-06 14:44:13 -05:00
|
|
|
onClick={(e) => {
|
|
|
|
e.stopPropagation()
|
|
|
|
e.nativeEvent.stopPropagation()
|
|
|
|
setIsConfirmingDelete(true)
|
|
|
|
}}
|
2024-04-05 00:59:02 -04:00
|
|
|
>
|
|
|
|
<Tooltip position="left" delay={1000}>
|
|
|
|
Delete project
|
|
|
|
</Tooltip>
|
|
|
|
</ActionButton>
|
2023-08-15 21:56:24 -04:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<Dialog
|
|
|
|
open={isConfirmingDelete}
|
|
|
|
onClose={() => setIsConfirmingDelete(false)}
|
|
|
|
className="relative z-50"
|
|
|
|
>
|
|
|
|
<div className="fixed inset-0 bg-chalkboard-110/80 grid place-content-center">
|
|
|
|
<Dialog.Panel className="rounded p-4 bg-chalkboard-10 dark:bg-chalkboard-100 border border-destroy-80 max-w-2xl">
|
|
|
|
<Dialog.Title as="h2" className="text-2xl font-bold mb-4">
|
|
|
|
Delete File
|
|
|
|
</Dialog.Title>
|
|
|
|
<Dialog.Description>
|
|
|
|
This will permanently delete "{project.name || 'this file'}".
|
|
|
|
</Dialog.Description>
|
|
|
|
|
|
|
|
<p className="my-4">
|
|
|
|
Are you sure you want to delete "{project.name || 'this file'}
|
|
|
|
"? This action cannot be undone.
|
|
|
|
</p>
|
|
|
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
<ActionButton
|
|
|
|
Element="button"
|
|
|
|
onClick={async () => {
|
|
|
|
await handleDeleteProject(project)
|
|
|
|
setIsConfirmingDelete(false)
|
|
|
|
}}
|
|
|
|
icon={{
|
|
|
|
icon: faTrashAlt,
|
|
|
|
bgClassName: 'bg-destroy-80',
|
2023-12-06 14:44:13 -05:00
|
|
|
className: 'p-1',
|
|
|
|
size: 'sm',
|
Command bar: add extrude command, nonlinear editing, etc (#1204)
* Tweak toaster look and feel
* Add icons, tweak plus icon names
* Rename commandBarMeta to commandBarConfig
* Refactor command bar, add support for icons
* Create a tailwind plugin for aria-pressed button state
* Remove overlay from behind command bar
* Clean up toolbar
* Button and other style tweaks
* Icon tweaks follow-up: make old icons work with new sizing
* Delete unused static icons
* More CSS tweaks
* Small CSS tweak to project sidebar
* Add command bar E2E test
* fumpt
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* fix typo in a comment
* Fix icon padding (built version only)
* Update onboarding and warning banner icons padding
* Misc minor style fixes
* Get Extrude opening and canceling from command bar
* Iconography tweaks
* Get extrude kind of working
* Refactor command bar config types and organization
* Move command bar configs to be co-located with each other
* Start building a state machine for the command bar
* Start converting command bar to state machine
* Add support for multiple args, confirmation step
* Submission behavior, hotkeys, code organization
* Add new test for extruding from command bar
* Polish step back and selection hotkeys, CSS tweaks
* Loading style tweaks
* Validate selection inputs, polish UX of args re-editing
* Prevent submission with multiple selection on singlular arg
* Remove stray console logs
* Tweak test, CSS nit, remove extrude "result" argument
* Fix linting warnings
* Show Ctrl+/ instead of ⌘K on all platforms but Mac
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* Add "Enter sketch" to command bar
* fix command bar test
* Fix flaky cmd bar extrude test by waiting for engine select response
* Cover both button labels '⌘K' and 'Ctrl+/' in test
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-12-13 12:49:01 -05:00
|
|
|
iconClassName: '!text-destroy-70 dark:!text-destroy-40',
|
2023-08-15 21:56:24 -04:00
|
|
|
}}
|
|
|
|
className="hover:border-destroy-40 dark:hover:border-destroy-40"
|
|
|
|
>
|
|
|
|
Delete
|
|
|
|
</ActionButton>
|
|
|
|
<ActionButton
|
|
|
|
Element="button"
|
|
|
|
onClick={() => setIsConfirmingDelete(false)}
|
|
|
|
>
|
|
|
|
Cancel
|
|
|
|
</ActionButton>
|
|
|
|
</div>
|
|
|
|
</Dialog.Panel>
|
|
|
|
</div>
|
|
|
|
</Dialog>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</li>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
export default ProjectCard
|