-
Notifications
You must be signed in to change notification settings - Fork 897
feat(desktop): SUPER-362 V2 workspace sidebar + modal + projects #2399
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
Changes from all commits
a1c18fe
b18f3af
dbfc962
e67d80b
e9d4d6a
ffa51c6
66b03bd
f9ac698
58bd3d9
caa3e17
21b8de9
cda5d11
da655d7
8a5093a
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,42 @@ | ||
| import { | ||
| Dialog, | ||
| DialogContent, | ||
| DialogDescription, | ||
| DialogHeader, | ||
| DialogTitle, | ||
| } from "@superset/ui/dialog"; | ||
| import { | ||
| useCloseNewWorkspaceModal, | ||
| useNewWorkspaceModalOpen, | ||
| usePreSelectedProjectId, | ||
| } from "renderer/stores/new-workspace-modal"; | ||
| import { V2NewWorkspaceModalContent } from "./components/V2NewWorkspaceModalContent"; | ||
| import { V2NewWorkspaceModalDraftProvider } from "./V2NewWorkspaceModalDraftContext"; | ||
|
|
||
| export function V2NewWorkspaceModal() { | ||
| const isOpen = useNewWorkspaceModalOpen(); | ||
| const closeModal = useCloseNewWorkspaceModal(); | ||
| const preSelectedProjectId = usePreSelectedProjectId(); | ||
|
|
||
| return ( | ||
| <V2NewWorkspaceModalDraftProvider onClose={closeModal}> | ||
| <Dialog open={isOpen} onOpenChange={(open) => !open && closeModal()}> | ||
| <DialogHeader className="sr-only"> | ||
| <DialogTitle>New Workspace</DialogTitle> | ||
| <DialogDescription> | ||
| Create a new workspace from a PR, branch, issue, or prompt. | ||
| </DialogDescription> | ||
| </DialogHeader> | ||
| <DialogContent | ||
| showCloseButton={false} | ||
| className="bg-popover text-popover-foreground sm:max-w-[560px] max-h-[min(70vh,600px)] !top-[calc(50%-min(35vh,300px))] !-translate-y-0 flex flex-col overflow-hidden p-0" | ||
| > | ||
| <V2NewWorkspaceModalContent | ||
| isOpen={isOpen} | ||
| preSelectedProjectId={preSelectedProjectId} | ||
| /> | ||
| </DialogContent> | ||
| </Dialog> | ||
| </V2NewWorkspaceModalDraftProvider> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,190 @@ | ||
| import { toast } from "@superset/ui/sonner"; | ||
| import { | ||
| createContext, | ||
| type PropsWithChildren, | ||
| useCallback, | ||
| useContext, | ||
| useMemo, | ||
| useState, | ||
| } from "react"; | ||
|
|
||
| export type V2NewWorkspaceModalTab = | ||
| | "prompt" | ||
| | "issues" | ||
| | "pull-requests" | ||
| | "branches"; | ||
|
|
||
| export interface V2NewWorkspaceModalDraft { | ||
| activeTab: V2NewWorkspaceModalTab; | ||
| selectedProjectId: string | null; | ||
| selectedDeviceId: string | null; | ||
| prompt: string; | ||
| branchName: string; | ||
| branchNameEdited: boolean; | ||
| baseBranch: string | null; | ||
| showAdvanced: boolean; | ||
| branchSearch: string; | ||
| issuesQuery: string; | ||
| pullRequestsQuery: string; | ||
| branchesQuery: string; | ||
| } | ||
|
|
||
| interface V2NewWorkspaceModalDraftState extends V2NewWorkspaceModalDraft { | ||
| draftVersion: number; | ||
| } | ||
|
|
||
| const initialDraft: V2NewWorkspaceModalDraft = { | ||
| activeTab: "prompt", | ||
| selectedProjectId: null, | ||
| selectedDeviceId: null, | ||
| prompt: "", | ||
| branchName: "", | ||
| branchNameEdited: false, | ||
| baseBranch: null, | ||
| showAdvanced: false, | ||
| branchSearch: "", | ||
| issuesQuery: "", | ||
| pullRequestsQuery: "", | ||
| branchesQuery: "", | ||
| }; | ||
|
|
||
| function buildInitialDraftState(): V2NewWorkspaceModalDraftState { | ||
| return { | ||
| ...initialDraft, | ||
| draftVersion: 0, | ||
| }; | ||
| } | ||
|
|
||
| interface V2NewWorkspaceModalActionMessages { | ||
| loading: string; | ||
| success: string; | ||
| error: (err: unknown) => string; | ||
| } | ||
|
|
||
| interface V2NewWorkspaceModalDraftContextValue { | ||
| draft: V2NewWorkspaceModalDraft; | ||
| draftVersion: number; | ||
| closeModal: () => void; | ||
| closeAndResetDraft: () => void; | ||
| runAsyncAction: <T>( | ||
| promise: Promise<T>, | ||
| messages: V2NewWorkspaceModalActionMessages, | ||
| ) => Promise<T>; | ||
| updateDraft: (patch: Partial<V2NewWorkspaceModalDraft>) => void; | ||
| resetDraft: () => void; | ||
| resetDraftIfVersion: (draftVersion: number) => void; | ||
| } | ||
|
|
||
| const V2NewWorkspaceModalDraftContext = | ||
| createContext<V2NewWorkspaceModalDraftContextValue | null>(null); | ||
|
|
||
| export function V2NewWorkspaceModalDraftProvider({ | ||
|
Contributor
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. P2: Extract shared draft-context behavior instead of duplicating this provider logic; keeping two near-identical implementations will make fixes/features drift between legacy and V2 modal flows. (Based on your team's feedback about avoiding duplicated business logic across components.) Prompt for AI agents |
||
| children, | ||
| onClose, | ||
| }: PropsWithChildren<{ onClose: () => void }>) { | ||
| const [state, setState] = useState(buildInitialDraftState); | ||
|
|
||
| const updateDraft = useCallback( | ||
| (patch: Partial<V2NewWorkspaceModalDraft>) => { | ||
| setState((state) => ({ | ||
| ...state, | ||
| ...patch, | ||
| draftVersion: state.draftVersion + 1, | ||
| })); | ||
| }, | ||
| [], | ||
| ); | ||
|
|
||
| const resetDraft = useCallback(() => { | ||
| setState((state) => ({ | ||
| ...initialDraft, | ||
| draftVersion: state.draftVersion + 1, | ||
| })); | ||
| }, []); | ||
|
|
||
| const resetDraftIfVersion = useCallback((draftVersion: number) => { | ||
| setState((state) => | ||
| state.draftVersion !== draftVersion | ||
| ? state | ||
| : { | ||
| ...initialDraft, | ||
| draftVersion: state.draftVersion + 1, | ||
| }, | ||
| ); | ||
| }, []); | ||
|
|
||
| const closeAndResetDraft = useCallback(() => { | ||
| resetDraft(); | ||
| onClose(); | ||
| }, [onClose, resetDraft]); | ||
|
|
||
| const runAsyncAction = useCallback( | ||
| <T,>(promise: Promise<T>, messages: V2NewWorkspaceModalActionMessages) => { | ||
| const submitDraftVersion = state.draftVersion; | ||
| onClose(); | ||
| toast.promise(promise, { | ||
| loading: messages.loading, | ||
| success: messages.success, | ||
| error: (err) => messages.error(err), | ||
| }); | ||
| void promise | ||
| .then(() => { | ||
| resetDraftIfVersion(submitDraftVersion); | ||
| }) | ||
| .catch(() => undefined); | ||
| return promise; | ||
| }, | ||
| [onClose, resetDraftIfVersion, state.draftVersion], | ||
| ); | ||
|
|
||
| const value = useMemo<V2NewWorkspaceModalDraftContextValue>( | ||
| () => ({ | ||
| draft: { | ||
| activeTab: state.activeTab, | ||
| selectedProjectId: state.selectedProjectId, | ||
| selectedDeviceId: state.selectedDeviceId, | ||
| prompt: state.prompt, | ||
| branchName: state.branchName, | ||
| branchNameEdited: state.branchNameEdited, | ||
| baseBranch: state.baseBranch, | ||
| showAdvanced: state.showAdvanced, | ||
| branchSearch: state.branchSearch, | ||
| issuesQuery: state.issuesQuery, | ||
| pullRequestsQuery: state.pullRequestsQuery, | ||
| branchesQuery: state.branchesQuery, | ||
| }, | ||
| draftVersion: state.draftVersion, | ||
| closeModal: onClose, | ||
| closeAndResetDraft, | ||
| runAsyncAction, | ||
| updateDraft, | ||
| resetDraft, | ||
| resetDraftIfVersion, | ||
| }), | ||
| [ | ||
| closeAndResetDraft, | ||
| onClose, | ||
| resetDraft, | ||
| resetDraftIfVersion, | ||
| runAsyncAction, | ||
| state, | ||
| updateDraft, | ||
| ], | ||
| ); | ||
|
|
||
| return ( | ||
| <V2NewWorkspaceModalDraftContext.Provider value={value}> | ||
| {children} | ||
| </V2NewWorkspaceModalDraftContext.Provider> | ||
| ); | ||
| } | ||
|
|
||
| export function useV2NewWorkspaceModalDraft() { | ||
| const context = useContext(V2NewWorkspaceModalDraftContext); | ||
| if (!context) { | ||
| throw new Error( | ||
| "useV2NewWorkspaceModalDraft must be used within V2NewWorkspaceModalDraftProvider", | ||
| ); | ||
| } | ||
| return context; | ||
| } | ||
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.
Manual dismiss should reset the modal draft.
V2NewWorkspaceModalDraftProvideronly clears state throughcloseAndResetDraft()orrunAsyncAction()inV2NewWorkspaceModalDraftContext.tsx. Here backdrop clicks andEsconly call the store'scloseModal, so reopening the modal reuses the previous project/query/prompt instead of starting fresh.Proposed fix
import { V2NewWorkspaceModalContent } from "./components/V2NewWorkspaceModalContent"; -import { V2NewWorkspaceModalDraftProvider } from "./V2NewWorkspaceModalDraftContext"; +import { + useV2NewWorkspaceModalDraft, + V2NewWorkspaceModalDraftProvider, +} from "./V2NewWorkspaceModalDraftContext"; export function V2NewWorkspaceModal() { const isOpen = useNewWorkspaceModalOpen(); const closeModal = useCloseNewWorkspaceModal(); const preSelectedProjectId = usePreSelectedProjectId(); return ( <V2NewWorkspaceModalDraftProvider onClose={closeModal}> - <Dialog open={isOpen} onOpenChange={(open) => !open && closeModal()}> - <DialogHeader className="sr-only"> - <DialogTitle>New Workspace</DialogTitle> - <DialogDescription> - Create a new workspace from a PR, branch, issue, or prompt. - </DialogDescription> - </DialogHeader> - <DialogContent - showCloseButton={false} - className="bg-popover text-popover-foreground sm:max-w-[560px] max-h-[min(70vh,600px)] !top-[calc(50%-min(35vh,300px))] !-translate-y-0 flex flex-col overflow-hidden p-0" - > - <V2NewWorkspaceModalContent - isOpen={isOpen} - preSelectedProjectId={preSelectedProjectId} - /> - </DialogContent> - </Dialog> + <V2NewWorkspaceModalDialog + isOpen={isOpen} + preSelectedProjectId={preSelectedProjectId} + /> </V2NewWorkspaceModalDraftProvider> ); } + +function V2NewWorkspaceModalDialog({ + isOpen, + preSelectedProjectId, +}: { + isOpen: boolean; + preSelectedProjectId: string | null; +}) { + const { closeAndResetDraft } = useV2NewWorkspaceModalDraft(); + + return ( + <Dialog + open={isOpen} + onOpenChange={(open) => !open && closeAndResetDraft()} + > + <DialogHeader className="sr-only"> + <DialogTitle>New Workspace</DialogTitle> + <DialogDescription> + Create a new workspace from a PR, branch, issue, or prompt. + </DialogDescription> + </DialogHeader> + <DialogContent + showCloseButton={false} + className="bg-popover text-popover-foreground sm:max-w-[560px] max-h-[min(70vh,600px)] !top-[calc(50%-min(35vh,300px))] !-translate-y-0 flex flex-col overflow-hidden p-0" + > + <V2NewWorkspaceModalContent + isOpen={isOpen} + preSelectedProjectId={preSelectedProjectId} + /> + </DialogContent> + </Dialog> + ); +}📝 Committable suggestion
🤖 Prompt for AI Agents