Compare commits

...

2 Commits

Author SHA1 Message Date
Vihar Kurama b5086d764c fix: resolve command palette build errors 2025-09-13 19:28:56 +01:00
Vihar Kurama 2b489180dd feat: add command registry config 2025-09-13 19:07:35 +01:00
9 changed files with 543 additions and 33 deletions
@@ -1,6 +1,6 @@
"use client";
import React, { useEffect, useState } from "react";
import React, { useEffect, useState, useRef, useMemo, useCallback } from "react";
import { Command } from "cmdk";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
@@ -17,9 +17,16 @@ import {
} from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { LayersIcon } from "@plane/propel/icons";
import { IWorkspaceSearchResults } from "@plane/types";
import {
IWorkspaceSearchResults,
ICycle,
TActivityEntityData,
TIssueEntityData,
TIssueSearchResponse,
TPartialProject,
} from "@plane/types";
import { Loader, ToggleSwitch } from "@plane/ui";
import { cn, getTabIndex } from "@plane/utils";
import { cn, getTabIndex, generateWorkItemLink } from "@plane/utils";
// components
import {
ChangeIssueAssignee,
@@ -33,12 +40,20 @@ import {
CommandPaletteWorkspaceSettingsActions,
} from "@/components/command-palette";
import { SimpleEmptyState } from "@/components/empty-state/simple-empty-state-root";
import {
CommandPaletteProjectSelector,
CommandPaletteCycleSelector,
CommandPaletteEntityList,
useKeySequence,
} from "@/components/command-palette";
import { COMMAND_CONFIG } from "@/components/command-palette";
// helpers
// hooks
import { captureClick } from "@/helpers/event-tracker.helper";
import { useCommandPalette } from "@/hooks/store/use-command-palette";
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
import { useProject } from "@/hooks/store/use-project";
import { useCycle } from "@/hooks/store/use-cycle";
import { useUser, useUserPermissions } from "@/hooks/store/user";
import { useAppRouter } from "@/hooks/use-app-router";
import useDebounce from "@/hooks/use-debounce";
@@ -48,6 +63,7 @@ import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
import { IssueIdentifier } from "@/plane-web/components/issues/issue-details/issue-identifier";
// plane web services
import { WorkspaceService } from "@/plane-web/services";
import type { CommandPaletteEntity } from "@/store/base-command-palette.store";
const workspaceService = new WorkspaceService();
@@ -65,6 +81,10 @@ export const CommandModal: React.FC = observer(() => {
const [isWorkspaceLevel, setIsWorkspaceLevel] = useState(false);
const [pages, setPages] = useState<string[]>([]);
const [searchInIssue, setSearchInIssue] = useState(false);
const [recentIssues, setRecentIssues] = useState<TIssueEntityData[]>([]);
const [issueResults, setIssueResults] = useState<TIssueSearchResponse[]>([]);
const [projectSelectionAction, setProjectSelectionAction] = useState<"navigate" | "cycle" | null>(null);
const [selectedProjectId, setSelectedProjectId] = useState<string | null>(null);
// plane hooks
const { t } = useTranslation();
// hooks
@@ -72,11 +92,18 @@ export const CommandModal: React.FC = observer(() => {
issue: { getIssueById },
fetchIssueWithIdentifier,
} = useIssueDetail();
const { workspaceProjectIds } = useProject();
const { workspaceProjectIds, joinedProjectIds, getPartialProjectById } = useProject();
const { getProjectCycleIds, getCycleById, fetchAllCycles } = useCycle();
const { platform, isMobile } = usePlatformOS();
const { canPerformAnyCreateAction } = useUser();
const { isCommandPaletteOpen, toggleCommandPaletteModal, toggleCreateIssueModal, toggleCreateProjectModal } =
useCommandPalette();
const {
isCommandPaletteOpen,
toggleCommandPaletteModal,
toggleCreateIssueModal,
toggleCreateProjectModal,
activeEntity,
clearActiveEntity,
} = useCommandPalette();
const { allowPermissions } = useUserPermissions();
const projectIdentifier = workItem?.toString().split("-")[0];
const sequence_id = workItem?.toString().split("-")[1];
@@ -101,6 +128,106 @@ export const CommandModal: React.FC = observer(() => {
);
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/search/search" });
const openProjectSelection = useCallback(
(action: "navigate" | "cycle") => {
if (!workspaceSlug) return;
setPlaceholder("Search projects...");
setSearchTerm("");
setProjectSelectionAction(action);
setSelectedProjectId(null);
setPages((p) => [...p, "open-project"]);
},
[workspaceSlug]
);
const openProjectList = useCallback(() => openProjectSelection("navigate"), [openProjectSelection]);
const openCycleList = useCallback(() => {
if (!workspaceSlug) return;
const currentProject = projectId ? getPartialProjectById(projectId.toString()) : null;
if (currentProject && currentProject.cycle_view) {
setSelectedProjectId(projectId.toString());
setPlaceholder("Search cycles...");
setSearchTerm("");
setPages((p) => [...p, "open-cycle"]);
fetchAllCycles(workspaceSlug.toString(), projectId.toString());
} else {
openProjectSelection("cycle");
}
}, [workspaceSlug, projectId, getPartialProjectById, fetchAllCycles, openProjectSelection]);
const openIssueList = useCallback(() => {
if (!workspaceSlug) return;
setPlaceholder("Search issues...");
setSearchTerm("");
setPages((p) => [...p, "open-issue"]);
workspaceService
.fetchWorkspaceRecents(workspaceSlug.toString(), "issue")
.then((res) =>
setRecentIssues(res.map((r: TActivityEntityData) => r.entity_data as TIssueEntityData).slice(0, 10))
)
.catch(() => setRecentIssues([]));
}, [workspaceSlug]);
const entityHandlers = useMemo<
Partial<Record<CommandPaletteEntity, () => void>>
>(
() => ({
project: openProjectList,
cycle: openCycleList,
issue: openIssueList,
}),
[openProjectList, openCycleList, openIssueList]
);
const sequenceHandlers = useMemo(() => {
const handlers: Record<string, () => void> = {};
COMMAND_CONFIG.forEach((cmd) => {
if (!cmd.enabled || cmd.enabled()) {
const handler = entityHandlers[cmd.entity];
if (handler) handlers[cmd.sequence] = handler;
}
});
return handlers;
}, [entityHandlers]);
const handleKeySequence = useKeySequence(sequenceHandlers);
useEffect(() => {
if (!isCommandPaletteOpen || !activeEntity) return;
const handler = entityHandlers[activeEntity];
if (handler) handler();
clearActiveEntity();
}, [isCommandPaletteOpen, activeEntity, clearActiveEntity, entityHandlers]);
const projectOptions = useMemo(() => {
const list: TPartialProject[] = [];
joinedProjectIds.forEach((id) => {
const project = getPartialProjectById(id);
if (project) list.push(project);
});
return list.sort((a, b) => new Date(b.updated_at ?? 0).getTime() - new Date(a.updated_at ?? 0).getTime());
}, [joinedProjectIds, getPartialProjectById]);
const cycleOptions = useMemo(() => {
const cycles: ICycle[] = [];
if (selectedProjectId) {
const cycleIds = getProjectCycleIds(selectedProjectId) || [];
cycleIds.forEach((cid) => {
const cycle = getCycleById(cid);
const status = cycle?.status ? cycle.status.toLowerCase() : "";
if (cycle && ["current", "upcoming"].includes(status)) cycles.push(cycle);
});
}
return cycles.sort((a, b) => new Date(b.updated_at ?? 0).getTime() - new Date(a.updated_at ?? 0).getTime());
}, [selectedProjectId, getProjectCycleIds, getCycleById]);
useEffect(() => {
if (page !== "open-cycle" || !workspaceSlug || !selectedProjectId) return;
fetchAllCycles(workspaceSlug.toString(), selectedProjectId);
}, [page, workspaceSlug, selectedProjectId, fetchAllCycles]);
useEffect(() => {
if (issueDetails && isCommandPaletteOpen) {
setSearchInIssue(true);
@@ -117,6 +244,10 @@ export const CommandModal: React.FC = observer(() => {
const closePalette = () => {
toggleCommandPaletteModal(false);
setPages([]);
setPlaceholder("Type a command or search...");
setProjectSelectionAction(null);
setSelectedProjectId(null);
};
const createNewWorkspace = () => {
@@ -124,14 +255,30 @@ export const CommandModal: React.FC = observer(() => {
router.push("/create-workspace");
};
useEffect(
() => {
if (!workspaceSlug) return;
useEffect(() => {
if (!workspaceSlug) return;
setIsLoading(true);
setIsLoading(true);
if (debouncedSearchTerm) {
setIsSearching(true);
if (debouncedSearchTerm) {
setIsSearching(true);
if (page === "open-issue") {
workspaceService
.searchEntity(workspaceSlug.toString(), {
count: 10,
query: debouncedSearchTerm,
query_type: ["issue"],
...(!isWorkspaceLevel && projectId ? { project_id: projectId.toString() } : {}),
})
.then((res) => {
setIssueResults(res.issue || []);
setResultsCount(res.issue?.length || 0);
})
.finally(() => {
setIsLoading(false);
setIsSearching(false);
});
} else {
workspaceService
.searchWorkspace(workspaceSlug.toString(), {
...(projectId ? { project_id: projectId.toString() } : {}),
@@ -150,14 +297,14 @@ export const CommandModal: React.FC = observer(() => {
setIsLoading(false);
setIsSearching(false);
});
} else {
setResults(WORKSPACE_DEFAULT_SEARCH_RESULT);
setIsLoading(false);
setIsSearching(false);
}
},
[debouncedSearchTerm, isWorkspaceLevel, projectId, workspaceSlug] // Only call effect if debounced search term changes
);
} else {
setResults(WORKSPACE_DEFAULT_SEARCH_RESULT);
setIssueResults([]);
setIsLoading(false);
setIsSearching(false);
}
}, [debouncedSearchTerm, isWorkspaceLevel, projectId, workspaceSlug, page]);
return (
<Transition.Root show={isCommandPaletteOpen} afterLeave={() => setSearchTerm("")} as={React.Fragment}>
@@ -203,7 +350,11 @@ export const CommandModal: React.FC = observer(() => {
}}
shouldFilter={searchTerm.length > 0}
onKeyDown={(e: any) => {
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
const key = e.key.toLowerCase();
if (!e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey && !page && searchTerm === "") {
handleKeySequence(e);
}
if ((e.metaKey || e.ctrlKey) && key === "k") {
e.preventDefault();
e.stopPropagation();
closePalette();
@@ -235,20 +386,23 @@ export const CommandModal: React.FC = observer(() => {
}
}
if (e.key === "Escape" && searchTerm) {
if (e.key === "Escape") {
e.preventDefault();
setSearchTerm("");
if (searchTerm) setSearchTerm("");
else closePalette();
return;
}
if (e.key === "Escape" && !page && !searchTerm) {
if (e.key === "Backspace" && !searchTerm && page) {
e.preventDefault();
closePalette();
}
if (e.key === "Escape" || (e.key === "Backspace" && !searchTerm)) {
e.preventDefault();
setPages((pages) => pages.slice(0, -1));
setPlaceholder("Type a command or search...");
const newPages = pages.slice(0, -1);
const newPage = newPages[newPages.length - 1];
setPages(newPages);
if (!newPage) setPlaceholder("Type a command or search...");
else if (newPage === "open-project") setPlaceholder("Search projects...");
else if (newPage === "open-cycle") setPlaceholder("Search cycles...");
if (page === "open-cycle") setSelectedProjectId(null);
if (page === "open-project" && !newPage) setProjectSelectionAction(null);
}
}}
>
@@ -324,7 +478,7 @@ export const CommandModal: React.FC = observer(() => {
</Command.Loading>
)}
{debouncedSearchTerm !== "" && (
{debouncedSearchTerm !== "" && page !== "open-issue" && (
<CommandPaletteSearchResults closePalette={closePalette} results={results} />
)}
@@ -341,6 +495,27 @@ export const CommandModal: React.FC = observer(() => {
setSearchTerm={(newSearchTerm) => setSearchTerm(newSearchTerm)}
/>
)}
{workspaceSlug && joinedProjectIds.length > 0 && (
<Command.Group heading="Navigate">
{COMMAND_CONFIG.filter((cmd) => !cmd.enabled || cmd.enabled()).map((cmd) => (
<Command.Item
key={cmd.id}
onSelect={() => entityHandlers[cmd.entity]?.()}
className="focus:outline-none"
>
<div className="flex items-center gap-2 text-custom-text-200">
<Search className="h-3.5 w-3.5" />
{cmd.title}
</div>
<div className="flex items-center gap-1">
{cmd.keys.map((k) => (
<kbd key={k}>{k}</kbd>
))}
</div>
</Command.Item>
))}
</Command.Group>
)}
{workspaceSlug &&
workspaceProjectIds &&
workspaceProjectIds.length > 0 &&
@@ -431,6 +606,120 @@ export const CommandModal: React.FC = observer(() => {
</>
)}
{page === "open-project" && workspaceSlug && (
<CommandPaletteProjectSelector
projects={projectOptions.filter((p) =>
projectSelectionAction === "cycle" ? p.cycle_view : true
)}
onSelect={(project) => {
if (projectSelectionAction === "navigate") {
closePalette();
router.push(`/${workspaceSlug}/projects/${project.id}/issues`);
} else if (projectSelectionAction === "cycle") {
setSelectedProjectId(project.id);
setPages((p) => [...p, "open-cycle"]);
setPlaceholder("Search cycles...");
fetchAllCycles(workspaceSlug.toString(), project.id);
}
}}
/>
)}
{page === "open-cycle" && workspaceSlug && selectedProjectId && (
<CommandPaletteCycleSelector
cycles={cycleOptions}
onSelect={(cycle) => {
closePalette();
router.push(`/${workspaceSlug}/projects/${cycle.project_id}/cycles/${cycle.id}`);
}}
/>
)}
{page === "open-issue" && workspaceSlug && (
<>
{searchTerm === "" ? (
recentIssues.length > 0 ? (
<CommandPaletteEntityList
heading="Issues"
items={recentIssues}
getKey={(issue) => issue.id}
getLabel={(issue) => `${issue.project_identifier}-${issue.sequence_id} ${issue.name}`}
renderItem={(issue) => (
<div className="flex items-center gap-2">
<IssueIdentifier
projectId={issue.project_id}
projectIdentifier={issue.project_identifier}
issueSequenceId={issue.sequence_id}
textContainerClassName="text-sm text-custom-text-200"
/>
<span className="truncate">{issue.name}</span>
</div>
)}
onSelect={(issue) => {
closePalette();
router.push(
generateWorkItemLink({
workspaceSlug: workspaceSlug.toString(),
projectId: issue.project_id,
issueId: issue.id,
projectIdentifier: issue.project_identifier,
sequenceId: issue.sequence_id,
isEpic: issue.is_epic,
})
);
}}
emptyText="Search for issue id or issue title"
/>
) : (
<div className="px-3 py-8 text-center text-sm text-custom-text-300">
Search for issue id or issue title
</div>
)
) : issueResults.length > 0 ? (
<CommandPaletteEntityList
heading="Issues"
items={issueResults}
getKey={(issue) => issue.id}
getLabel={(issue) => `${issue.project__identifier}-${issue.sequence_id} ${issue.name}`}
renderItem={(issue) => (
<div className="flex items-center gap-2">
<IssueIdentifier
projectId={issue.project_id ?? ""}
projectIdentifier={issue.project__identifier}
issueSequenceId={issue.sequence_id}
textContainerClassName="text-sm text-custom-text-200"
/>
<span className="truncate">{issue.name}</span>
</div>
)}
onSelect={(issue) => {
closePalette();
router.push(
generateWorkItemLink({
workspaceSlug: workspaceSlug.toString(),
projectId: issue.project_id,
issueId: issue.id,
projectIdentifier: issue.project__identifier,
sequenceId: issue.sequence_id,
})
);
}}
emptyText={t("command_k.empty_state.search.title") as string}
/>
) : (
!isLoading &&
!isSearching && (
<div className="flex flex-col items-center justify-center px-3 py-8 text-center">
<SimpleEmptyState
title={t("command_k.empty_state.search.title")}
assetPath={resolvedPath}
/>
</div>
)
)}
</>
)}
{/* workspace settings actions */}
{page === "settings" && workspaceSlug && (
<CommandPaletteWorkspaceSettingsActions closePalette={closePalette} />
@@ -1,6 +1,6 @@
"use client";
import React, { useCallback, useEffect, FC, useMemo } from "react";
import React, { useCallback, useEffect, FC, useMemo, useRef } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import useSWR from "swr";
@@ -10,11 +10,13 @@ import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { copyTextToClipboard } from "@plane/utils";
import { CommandModal, ShortcutsModal } from "@/components/command-palette";
import { COMMAND_CONFIG, CommandConfig } from "@/components/command-palette";
// helpers
// hooks
import { captureClick } from "@/helpers/event-tracker.helper";
import { useAppTheme } from "@/hooks/store/use-app-theme";
import { useCommandPalette } from "@/hooks/store/use-command-palette";
import type { CommandPaletteEntity } from "@/store/base-command-palette.store";
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
import { useUser, useUserPermissions } from "@/hooks/store/user";
import { usePlatformOS } from "@/hooks/use-platform-os";
@@ -41,7 +43,8 @@ export const CommandPalette: FC = observer(() => {
const { toggleSidebar } = useAppTheme();
const { platform } = usePlatformOS();
const { data: currentUser, canPerformAnyCreateAction } = useUser();
const { toggleCommandPaletteModal, isShortcutModalOpen, toggleShortcutModal, isAnyModalOpen } = useCommandPalette();
const { toggleCommandPaletteModal, isShortcutModalOpen, toggleShortcutModal, isAnyModalOpen, activateEntity } =
useCommandPalette();
const { allowPermissions } = useUserPermissions();
// derived values
@@ -158,6 +161,17 @@ export const CommandPalette: FC = observer(() => {
[]
);
const keySequence = useRef("");
const sequenceTimeout = useRef<NodeJS.Timeout | null>(null);
const commandSequenceMap = useMemo(() => {
const map: Record<string, CommandConfig> = {};
COMMAND_CONFIG.forEach((cmd) => {
map[cmd.sequence] = cmd;
});
return map;
}, []);
const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
const { key, ctrlKey, metaKey, altKey, shiftKey } = e;
@@ -186,6 +200,21 @@ export const CommandPalette: FC = observer(() => {
toggleShortcutModal(true);
}
if (!cmdClicked && !altKey && !shiftKey && !isAnyModalOpen) {
keySequence.current = (keySequence.current + keyPressed).slice(-2);
if (sequenceTimeout.current) clearTimeout(sequenceTimeout.current);
sequenceTimeout.current = setTimeout(() => {
keySequence.current = "";
}, 500);
const cmd = commandSequenceMap[keySequence.current];
if (cmd && (!cmd.enabled || cmd.enabled())) {
e.preventDefault();
activateEntity(cmd.entity);
keySequence.current = "";
return;
}
}
if (deleteKey) {
if (performProjectBulkDeleteActions()) {
shortcutsList.project.delete.action();
@@ -240,6 +269,7 @@ export const CommandPalette: FC = observer(() => {
projectId,
shortcutsList,
toggleCommandPaletteModal,
activateEntity,
toggleShortcutModal,
toggleSidebar,
workspaceSlug,
@@ -0,0 +1,52 @@
import type { CommandPaletteEntity } from "@/store/base-command-palette.store";
export interface CommandConfig {
/**
* Unique identifier for the command
*/
id: string;
/**
* Key sequence that triggers the command. Should be lowercase.
*/
sequence: string;
/**
* Display label shown in the command palette.
*/
title: string;
/**
* Keys displayed as shortcut hint.
*/
keys: string[];
/**
* Entity that the command opens.
*/
entity: CommandPaletteEntity;
/**
* Optional predicate controlling command availability
*/
enabled?: () => boolean;
}
export const COMMAND_CONFIG: CommandConfig[] = [
{
id: "open-project",
sequence: "op",
title: "Open project...",
keys: ["O", "P"],
entity: "project",
},
{
id: "open-cycle",
sequence: "oc",
title: "Open cycle...",
keys: ["O", "C"],
entity: "cycle",
},
{
id: "open-issue",
sequence: "oi",
title: "Open issue...",
keys: ["O", "I"],
entity: "issue",
},
];
@@ -0,0 +1,21 @@
"use client";
import React from "react";
import type { ICycle } from "@plane/types";
import { CommandPaletteEntityList } from "./entity-list";
interface Props {
cycles: ICycle[];
onSelect: (cycle: ICycle) => void;
}
export const CommandPaletteCycleSelector: React.FC<Props> = ({ cycles, onSelect }) => (
<CommandPaletteEntityList
heading="Cycles"
items={cycles}
getKey={(cycle) => cycle.id}
getLabel={(cycle) => cycle.name}
onSelect={onSelect}
emptyText="No cycles found"
/>
);
@@ -0,0 +1,42 @@
"use client";
import React from "react";
import { Command } from "cmdk";
import { cn } from "@plane/utils";
interface CommandPaletteEntityListProps<T> {
heading: string;
items: T[];
onSelect: (item: T) => void;
getKey?: (item: T) => string;
getLabel: (item: T) => string;
renderItem?: (item: T) => React.ReactNode;
emptyText?: string;
}
export const CommandPaletteEntityList = <T,>({
heading,
items,
onSelect,
getKey,
getLabel,
renderItem,
emptyText = "No results found",
}: CommandPaletteEntityListProps<T>) => {
if (items.length === 0) return <div className="px-3 py-8 text-center text-sm text-custom-text-300">{emptyText}</div>;
return (
<Command.Group heading={heading}>
{items.map((item) => (
<Command.Item
key={getKey ? getKey(item) : getLabel(item)}
value={getLabel(item)}
onSelect={() => onSelect(item)}
className={cn("focus:outline-none")}
>
{renderItem ? renderItem(item) : getLabel(item)}
</Command.Item>
))}
</Command.Group>
);
};
@@ -2,3 +2,8 @@ export * from "./actions";
export * from "./shortcuts-modal";
export * from "./command-modal";
export * from "./command-palette";
export * from "./project-selector";
export * from "./cycle-selector";
export * from "./entity-list";
export * from "./use-key-sequence";
export * from "./commands";
@@ -0,0 +1,21 @@
"use client";
import React from "react";
import type { TPartialProject } from "@/plane-web/types";
import { CommandPaletteEntityList } from "./entity-list";
interface Props {
projects: TPartialProject[];
onSelect: (project: TPartialProject) => void;
}
export const CommandPaletteProjectSelector: React.FC<Props> = ({ projects, onSelect }) => (
<CommandPaletteEntityList
heading="Projects"
items={projects}
getKey={(project) => project.id}
getLabel={(project) => project.name}
onSelect={onSelect}
emptyText="No projects found"
/>
);
@@ -0,0 +1,25 @@
"use client";
import { useRef } from "react";
export const useKeySequence = (handlers: Record<string, () => void>, timeout = 500) => {
const sequence = useRef("");
const sequenceTimeout = useRef<NodeJS.Timeout | null>(null);
return (e: React.KeyboardEvent) => {
const key = e.key.toLowerCase();
sequence.current = (sequence.current + key).slice(-2);
if (sequenceTimeout.current) clearTimeout(sequenceTimeout.current);
sequenceTimeout.current = setTimeout(() => {
sequence.current = "";
}, timeout);
const action = handlers[sequence.current];
if (action) {
e.preventDefault();
action();
sequence.current = "";
}
};
};
@@ -8,6 +8,8 @@ import {
} from "@plane/constants";
import { EIssuesStoreType } from "@plane/types";
export type CommandPaletteEntity = "project" | "cycle" | "module" | "issue";
export interface ModalData {
store: EIssuesStoreType;
viewId: string;
@@ -30,6 +32,9 @@ export interface IBaseCommandPaletteStore {
allStickiesModal: boolean;
projectListOpenMap: Record<string, boolean>;
getIsProjectListOpen: (projectId: string) => boolean;
activeEntity: CommandPaletteEntity | null;
activateEntity: (entity: CommandPaletteEntity) => void;
clearActiveEntity: () => void;
// toggle actions
toggleCommandPaletteModal: (value?: boolean) => void;
toggleShortcutModal: (value?: boolean) => void;
@@ -61,6 +66,7 @@ export abstract class BaseCommandPaletteStore implements IBaseCommandPaletteStor
createWorkItemAllowedProjectIds: IBaseCommandPaletteStore["createWorkItemAllowedProjectIds"] = undefined;
allStickiesModal: boolean = false;
projectListOpenMap: Record<string, boolean> = {};
activeEntity: CommandPaletteEntity | null = null;
constructor() {
makeObservable(this, {
@@ -79,6 +85,7 @@ export abstract class BaseCommandPaletteStore implements IBaseCommandPaletteStor
createWorkItemAllowedProjectIds: observable,
allStickiesModal: observable,
projectListOpenMap: observable,
activeEntity: observable,
// projectPages: computed,
// toggle actions
toggleCommandPaletteModal: action,
@@ -93,6 +100,8 @@ export abstract class BaseCommandPaletteStore implements IBaseCommandPaletteStor
toggleBulkDeleteIssueModal: action,
toggleAllStickiesModal: action,
toggleProjectListOpen: action,
activateEntity: action,
clearActiveEntity: action,
});
}
@@ -127,6 +136,22 @@ export abstract class BaseCommandPaletteStore implements IBaseCommandPaletteStor
else this.projectListOpenMap[projectId] = !this.projectListOpenMap[projectId];
};
/**
* Opens the command palette with a specific entity pre-selected
* @param entity
*/
activateEntity = (entity: CommandPaletteEntity) => {
this.isCommandPaletteOpen = true;
this.activeEntity = entity;
};
/**
* Clears the active entity trigger
*/
clearActiveEntity = () => {
this.activeEntity = null;
};
/**
* Toggles the command palette modal
* @param value