Skip to content

Commit

Permalink
refactor: command k modal (#2803)
Browse files Browse the repository at this point in the history
* refactor: command palette file structure

* fix: identifier search
  • Loading branch information
aaryan610 authored and sriramveeraghanta committed Dec 7, 2023
1 parent d14ca3a commit aea9a40
Show file tree
Hide file tree
Showing 19 changed files with 741 additions and 754 deletions.
83 changes: 83 additions & 0 deletions web/components/command-palette/actions/help-actions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { Command } from "cmdk";
import { FileText, GithubIcon, MessageSquare, Rocket } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// ui
import { DiscordIcon } from "@plane/ui";

type Props = {
closePalette: () => void;
};

export const CommandPaletteHelpActions: React.FC<Props> = (props) => {
const { closePalette } = props;

const {
commandPalette: { toggleShortcutModal },
} = useMobxStore();

return (
<Command.Group heading="Help">
<Command.Item
onSelect={() => {
closePalette();
toggleShortcutModal(true);
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<Rocket className="h-3.5 w-3.5" />
Open keyboard shortcuts
</div>
</Command.Item>
<Command.Item
onSelect={() => {
closePalette();
window.open("https://docs.plane.so/", "_blank");
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<FileText className="h-3.5 w-3.5" />
Open Plane documentation
</div>
</Command.Item>
<Command.Item
onSelect={() => {
closePalette();
window.open("https://discord.com/invite/A92xrEGCge", "_blank");
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<DiscordIcon className="h-4 w-4" color="rgb(var(--color-text-200))" />
Join our Discord
</div>
</Command.Item>
<Command.Item
onSelect={() => {
closePalette();
window.open("https://github.com/makeplane/plane/issues/new/choose", "_blank");
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<GithubIcon className="h-4 w-4" color="rgb(var(--color-text-200))" />
Report a bug
</div>
</Command.Item>
<Command.Item
onSelect={() => {
closePalette();
(window as any)?.$crisp.push(["do", "chat:open"]);
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<MessageSquare className="h-3.5 w-3.5" />
Chat with us
</div>
</Command.Item>
</Command.Group>
);
};
6 changes: 6 additions & 0 deletions web/components/command-palette/actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from "./issue-actions";
export * from "./help-actions";
export * from "./project-actions";
export * from "./search-results";
export * from "./theme-actions";
export * from "./workspace-settings-actions";
166 changes: 166 additions & 0 deletions web/components/command-palette/actions/issue-actions/actions-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Command } from "cmdk";
import { LinkIcon, Signal, Trash2, UserMinus2, UserPlus2 } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import useToast from "hooks/use-toast";
// ui
import { DoubleCircleIcon, UserGroupIcon } from "@plane/ui";
// helpers
import { copyTextToClipboard } from "helpers/string.helper";
// types
import { IIssue } from "types";

type Props = {
closePalette: () => void;
issueDetails: IIssue | undefined;
pages: string[];
setPages: (pages: string[]) => void;
setPlaceholder: (placeholder: string) => void;
setSearchTerm: (searchTerm: string) => void;
};

export const CommandPaletteIssueActions: React.FC<Props> = observer((props) => {
const { closePalette, issueDetails, pages, setPages, setPlaceholder, setSearchTerm } = props;

const router = useRouter();
const { workspaceSlug, projectId } = router.query;

const {
commandPalette: { toggleCommandPaletteModal, toggleDeleteIssueModal },
issueDetail: { updateIssue },
user: { currentUser },
} = useMobxStore();

const { setToastAlert } = useToast();

const handleUpdateIssue = async (formData: Partial<IIssue>) => {
if (!workspaceSlug || !projectId || !issueDetails) return;

const payload = { ...formData };
await updateIssue(workspaceSlug.toString(), projectId.toString(), issueDetails.id, payload).catch((e) => {
console.error(e);
});
};

const handleIssueAssignees = (assignee: string) => {
if (!issueDetails || !assignee) return;

closePalette();
const updatedAssignees = issueDetails.assignees ?? [];

if (updatedAssignees.includes(assignee)) updatedAssignees.splice(updatedAssignees.indexOf(assignee), 1);
else updatedAssignees.push(assignee);

handleUpdateIssue({ assignees: updatedAssignees });
};

const deleteIssue = () => {
toggleCommandPaletteModal(false);
toggleDeleteIssueModal(true);
};

const copyIssueUrlToClipboard = () => {
if (!router.query.issueId) return;

const url = new URL(window.location.href);
copyTextToClipboard(url.href)
.then(() => {
setToastAlert({
type: "success",
title: "Copied to clipboard",
});
})
.catch(() => {
setToastAlert({
type: "error",
title: "Some error occurred",
});
});
};

return (
<Command.Group heading="Issue actions">
<Command.Item
onSelect={() => {
setPlaceholder("Change state...");
setSearchTerm("");
setPages([...pages, "change-issue-state"]);
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<DoubleCircleIcon className="h-3.5 w-3.5" />
Change state...
</div>
</Command.Item>
<Command.Item
onSelect={() => {
setPlaceholder("Change priority...");
setSearchTerm("");
setPages([...pages, "change-issue-priority"]);
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<Signal className="h-3.5 w-3.5" />
Change priority...
</div>
</Command.Item>
<Command.Item
onSelect={() => {
setPlaceholder("Assign to...");
setSearchTerm("");
setPages([...pages, "change-issue-assignee"]);
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<UserGroupIcon className="h-3.5 w-3.5" />
Assign to...
</div>
</Command.Item>
<Command.Item
onSelect={() => {
handleIssueAssignees(currentUser?.id ?? "");
setSearchTerm("");
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
{issueDetails?.assignees.includes(currentUser?.id ?? "") ? (
<>
<UserMinus2 className="h-3.5 w-3.5" />
Un-assign from me
</>
) : (
<>
<UserPlus2 className="h-3.5 w-3.5" />
Assign to me
</>
)}
</div>
</Command.Item>
<Command.Item onSelect={deleteIssue} className="focus:outline-none">
<div className="flex items-center gap-2 text-custom-text-200">
<Trash2 className="h-3.5 w-3.5" />
Delete issue
</div>
</Command.Item>
<Command.Item
onSelect={() => {
closePalette();
copyIssueUrlToClipboard();
}}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<LinkIcon className="h-3.5 w-3.5" />
Copy issue URL
</div>
</Command.Item>
</Command.Group>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Command } from "cmdk";
import { Check } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// ui
import { Avatar } from "@plane/ui";
// types
import { IIssue } from "types";

type Props = {
closePalette: () => void;
issue: IIssue;
};

export const ChangeIssueAssignee: React.FC<Props> = observer((props) => {
const { closePalette, issue } = props;
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store
const {
issueDetail: { updateIssue },
projectMember: { projectMembers },
} = useMobxStore();

const options =
projectMembers?.map(({ member }) => ({
value: member.id,
query: member.display_name,
content: (
<>
<div className="flex items-center gap-2">
<Avatar name={member.display_name} src={member.avatar} showTooltip={false} />
{member.display_name}
</div>
{issue.assignees.includes(member.id) && (
<div>
<Check className="h-3 w-3" />
</div>
)}
</>
),
})) ?? [];

const handleUpdateIssue = async (formData: Partial<IIssue>) => {
if (!workspaceSlug || !projectId || !issue) return;

const payload = { ...formData };
await updateIssue(workspaceSlug.toString(), projectId.toString(), issue.id, payload).catch((e) => {
console.error(e);
});
};

const handleIssueAssignees = (assignee: string) => {
const updatedAssignees = issue.assignees ?? [];

if (updatedAssignees.includes(assignee)) updatedAssignees.splice(updatedAssignees.indexOf(assignee), 1);
else updatedAssignees.push(assignee);

handleUpdateIssue({ assignees: updatedAssignees });
closePalette();
};

return (
<>
{options.map((option: any) => (
<Command.Item
key={option.value}
onSelect={() => handleIssueAssignees(option.value)}
className="focus:outline-none"
>
{option.content}
</Command.Item>
))}
</>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Command } from "cmdk";
import { Check } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// ui
import { PriorityIcon } from "@plane/ui";
// types
import { IIssue, TIssuePriorities } from "types";
// constants
import { PRIORITIES } from "constants/project";

type Props = {
closePalette: () => void;
issue: IIssue;
};

export const ChangeIssuePriority: React.FC<Props> = observer((props) => {
const { closePalette, issue } = props;

const router = useRouter();
const { workspaceSlug, projectId } = router.query;

const {
issueDetail: { updateIssue },
} = useMobxStore();

const submitChanges = async (formData: Partial<IIssue>) => {
if (!workspaceSlug || !projectId || !issue) return;

const payload = { ...formData };
await updateIssue(workspaceSlug.toString(), projectId.toString(), issue.id, payload).catch((e) => {
console.error(e);
});
};

const handleIssueState = (priority: TIssuePriorities) => {
submitChanges({ priority });
closePalette();
};

return (
<>
{PRIORITIES.map((priority) => (
<Command.Item key={priority} onSelect={() => handleIssueState(priority)} className="focus:outline-none">
<div className="flex items-center space-x-3">
<PriorityIcon priority={priority} />
<span className="capitalize">{priority ?? "None"}</span>
</div>
<div>{priority === issue.priority && <Check className="h-3 w-3" />}</div>
</Command.Item>
))}
</>
);
});
Loading

0 comments on commit aea9a40

Please sign in to comment.