-
Notifications
You must be signed in to change notification settings - Fork 895
docs(desktop): v2 project create/import flow design #3521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
30c90ce
019bc29
ab69534
3abed7a
2023aec
b3cdb48
673eab0
adf9aa1
d047895
8244298
1e2ea6a
4c3f302
5267d73
ad9f0bd
f976aa0
4a69097
3c7b980
19612d1
610706b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| import { toast } from "@superset/ui/sonner"; | ||
| import { useEffect } from "react"; | ||
| import { | ||
| useAddRepositoryModalActive, | ||
| useCloseAddRepositoryModal, | ||
| useFolderImportTrigger, | ||
| } from "renderer/stores/add-repository-modal"; | ||
| import { FolderFirstImportModal } from "../../v2-workspaces/components/FolderFirstImportModal"; | ||
| import { NewProjectModal } from "../../v2-workspaces/components/NewProjectModal"; | ||
| import { PinAndSetupModal } from "../../v2-workspaces/components/PinAndSetupModal"; | ||
| import { useFolderFirstImport } from "../../v2-workspaces/hooks/useFolderFirstImport"; | ||
|
|
||
| /** | ||
| * Layout-level host for the three add-repository flows (New project, Import | ||
| * existing folder, Pin & set up). Any component in the dashboard can open | ||
| * one via the `useAddRepositoryModalStore` actions — sidebar dropdown, | ||
| * workspaces-tab Available rows, future empty-state CTAs, etc. | ||
| * | ||
| * Why centralize: modal state lives once per app, not once per trigger. | ||
| * Also keeps the folder-first picker's internal state machine in one place | ||
| * so nothing races if two triggers happen quickly. | ||
| */ | ||
| export function AddRepositoryModals() { | ||
| const active = useAddRepositoryModalActive(); | ||
| const close = useCloseAddRepositoryModal(); | ||
| const folderImportTrigger = useFolderImportTrigger(); | ||
|
|
||
| const folderImport = useFolderFirstImport({ | ||
| onSuccess: () => { | ||
| toast.success("Project ready — open it from the sidebar."); | ||
| }, | ||
| onError: (message) => { | ||
| toast.error(`Import failed: ${message}`); | ||
| }, | ||
| }); | ||
|
|
||
| // Run the folder-first picker when the store's trigger counter bumps. | ||
| // Using a counter (vs a boolean) lets successive clicks re-invoke the | ||
| // flow after the previous one resolves. | ||
| useEffect(() => { | ||
| if (folderImportTrigger === 0) return; | ||
| void folderImport.start(); | ||
| // We intentionally depend only on the counter — folderImport.start's | ||
| // identity changes every render (new hook instance per render) and | ||
| // we don't want to restart the flow on those changes. | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [folderImportTrigger, folderImport.start]); | ||
|
|
||
| return ( | ||
| <> | ||
| <NewProjectModal | ||
| open={active.kind === "new-project"} | ||
| onOpenChange={(open) => { | ||
| if (!open) close(); | ||
| }} | ||
| onSuccess={() => toast.success("Project created.")} | ||
| onError={(message) => toast.error(`Create failed: ${message}`)} | ||
| /> | ||
| <PinAndSetupModal | ||
| project={active.kind === "pin-and-setup" ? active.target : null} | ||
| forceRepoint={ | ||
| active.kind === "pin-and-setup" ? active.forceRepoint : false | ||
| } | ||
| onOpenChange={(open) => { | ||
| if (!open) close(); | ||
| }} | ||
| onSuccess={() => { | ||
| toast.success("Project pinned and set up."); | ||
| // Per-open one-shot callback (e.g. retry a pending workspace | ||
| // create that surfaced PROJECT_NOT_SETUP). | ||
| if (active.kind === "pin-and-setup") active.onSuccess?.(); | ||
| }} | ||
| onError={(message) => toast.error(`Setup failed: ${message}`)} | ||
| /> | ||
| <FolderFirstImportModal | ||
| state={folderImport.state} | ||
| onCancel={folderImport.cancel} | ||
| onConfirmCreateAsNew={folderImport.confirmCreateAsNew} | ||
| onConfirmPickCandidate={folderImport.confirmPickCandidate} | ||
| onConfirmRepoint={folderImport.confirmRepoint} | ||
| /> | ||
| </> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { AddRepositoryModals } from "./AddRepositoryModals"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,8 @@ import type { | |
| } from "@dnd-kit/core"; | ||
| import { cn } from "@superset/ui/utils"; | ||
| import { AnimatePresence, motion } from "framer-motion"; | ||
| import { useMemo } from "react"; | ||
| import { useCallback, useMemo } from "react"; | ||
| import { useOpenPinAndSetupModal } from "renderer/stores/add-repository-modal"; | ||
| import type { DashboardSidebarProject } from "../../types"; | ||
| import { getProjectChildrenWorkspaces } from "../../utils/projectChildren"; | ||
| import { DashboardSidebarCollapsedProjectContent } from "./components/DashboardSidebarCollapsedProjectContent"; | ||
|
|
@@ -60,6 +61,28 @@ export function DashboardSidebarProjectSection({ | |
|
|
||
| const totalWorkspaceCount = flattenedCollapsedWorkspaces.length; | ||
|
|
||
| // Phase 2: "Set up here" inline CTA for unbacked-on-this-host rows. | ||
| // Opens the same Pin & set up modal that the Available section uses, | ||
| // with the project pre-filled so the user only has to pick a folder. | ||
| // Phase 4: "Repair" reuses the same modal with forceRepoint so the | ||
| // user gets the destructive-confirmation copy immediately. | ||
| const openPinAndSetup = useOpenPinAndSetupModal(); | ||
| const pinTarget = useMemo( | ||
| () => ({ | ||
| id: project.id, | ||
| name: project.name, | ||
| githubOwner: project.githubOwner, | ||
| githubRepoName: project.githubRepoName, | ||
| }), | ||
| [project.githubOwner, project.githubRepoName, project.id, project.name], | ||
| ); | ||
| const handleSetUpHere = useCallback(() => { | ||
| openPinAndSetup(pinTarget); | ||
| }, [openPinAndSetup, pinTarget]); | ||
| const handleRepairPath = useCallback(() => { | ||
| openPinAndSetup(pinTarget, { forceRepoint: true }); | ||
| }, [openPinAndSetup, pinTarget]); | ||
|
|
||
| if (isSidebarCollapsed) { | ||
| return ( | ||
| <DashboardSidebarProjectContextMenu | ||
|
|
@@ -73,12 +96,15 @@ export function DashboardSidebarProjectSection({ | |
| <DashboardSidebarCollapsedProjectContent | ||
| projectName={project.name} | ||
| githubOwner={project.githubOwner} | ||
| backingState={project.backingState} | ||
| isCollapsed={project.isCollapsed} | ||
| totalWorkspaceCount={totalWorkspaceCount} | ||
| workspaces={flattenedCollapsedWorkspaces} | ||
| workspaceShortcutLabels={workspaceShortcutLabels} | ||
| onWorkspaceHover={onWorkspaceHover} | ||
| onToggleCollapse={() => onToggleCollapse(project.id)} | ||
| onSetUpHere={handleSetUpHere} | ||
| onRepairPath={handleRepairPath} | ||
| /> | ||
| </div> | ||
| </DashboardSidebarProjectContextMenu> | ||
|
|
@@ -97,6 +123,7 @@ export function DashboardSidebarProjectSection({ | |
| <DashboardSidebarProjectRow | ||
| projectName={project.name} | ||
| githubOwner={project.githubOwner} | ||
| backingState={project.backingState} | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WE should be able to remove this backing state as a concept from most places - instead, we can query if a host is online to see if the project is already set up in the new workspace creation modal and the eventual cron logic (which'll probably also share the project selector, so maybe we can consolidate some of this in a project selector component)? |
||
| totalWorkspaceCount={totalWorkspaceCount} | ||
| isCollapsed={project.isCollapsed} | ||
| isRenaming={isRenaming} | ||
|
|
@@ -107,6 +134,8 @@ export function DashboardSidebarProjectSection({ | |
| onStartRename={startRename} | ||
| onToggleCollapse={() => onToggleCollapse(project.id)} | ||
| onNewWorkspace={handleNewWorkspace} | ||
| onSetUpHere={handleSetUpHere} | ||
| onRepairPath={handleRepairPath} | ||
| {...(dragHandleAttributes ?? {})} | ||
| {...(dragHandleListeners ?? {})} | ||
| /> | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: The effect dependency list includes
folderImport.start, which is not stable here and can repeatedly re-triggerstart()after a single trigger bump. Depend only onfolderImportTriggerfor this counter-based pulse effect.(Based on your team's feedback about narrowing React effect dependencies to required fields.)
View Feedback
Prompt for AI agents