[WEB-1956] fix: Keyboard shortcuts (#5134)

* fix: shortcuts

* fix: naming

* fix: structure optimization
This commit is contained in:
Akshita Goyal 2024-07-19 15:28:48 +05:30 committed by GitHub
parent c2b5464e40
commit e7948eabf2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 90 additions and 19 deletions

View file

@ -46,7 +46,7 @@ export const CommandModal: React.FC = observer(() => {
// hooks // hooks
const { getProjectById, workspaceProjectIds } = useProject(); const { getProjectById, workspaceProjectIds } = useProject();
const { isMobile } = usePlatformOS(); const { isMobile } = usePlatformOS();
const { canPerformWorkspaceCreateActions } = useUser(); const { canPerformAnyCreateAction } = useUser();
// states // states
const [placeholder, setPlaceholder] = useState("Type a command or search..."); const [placeholder, setPlaceholder] = useState("Type a command or search...");
const [resultsCount, setResultsCount] = useState(0); const [resultsCount, setResultsCount] = useState(0);
@ -286,7 +286,7 @@ export const CommandModal: React.FC = observer(() => {
{workspaceSlug && {workspaceSlug &&
workspaceProjectIds && workspaceProjectIds &&
workspaceProjectIds.length > 0 && workspaceProjectIds.length > 0 &&
canPerformWorkspaceCreateActions && ( canPerformAnyCreateAction && (
<Command.Group heading="Issue"> <Command.Group heading="Issue">
<Command.Item <Command.Item
onSelect={() => { onSelect={() => {

View file

@ -41,7 +41,12 @@ export const CommandPalette: FC = observer(() => {
const { toggleSidebar } = useAppTheme(); const { toggleSidebar } = useAppTheme();
const { setTrackElement } = useEventTracker(); const { setTrackElement } = useEventTracker();
const { platform } = usePlatformOS(); const { platform } = usePlatformOS();
const { data: currentUser, canPerformProjectCreateActions, canPerformWorkspaceCreateActions } = useUser(); const {
data: currentUser,
canPerformProjectCreateActions,
canPerformWorkspaceCreateActions,
canPerformAnyCreateAction,
} = useUser();
const { const {
issues: { removeIssue }, issues: { removeIssue },
} = useIssuesStore(); } = useIssuesStore();
@ -120,6 +125,18 @@ export const CommandPalette: FC = observer(() => {
[canPerformWorkspaceCreateActions] [canPerformWorkspaceCreateActions]
); );
const performAnyProjectCreateActions = useCallback(
(showToast: boolean = true) => {
if (!canPerformAnyCreateAction && showToast)
setToast({
type: TOAST_TYPE.ERROR,
title: "You don't have permission to perform this action.",
});
return canPerformAnyCreateAction;
},
[canPerformAnyCreateAction]
);
const shortcutsList: { const shortcutsList: {
global: Record<string, { title: string; description: string; action: () => void }>; global: Record<string, { title: string; description: string; action: () => void }>;
workspace: Record<string, { title: string; description: string; action: () => void }>; workspace: Record<string, { title: string; description: string; action: () => void }>;
@ -222,7 +239,10 @@ export const CommandPalette: FC = observer(() => {
} }
} else if (!isAnyModalOpen) { } else if (!isAnyModalOpen) {
setTrackElement("Shortcut key"); setTrackElement("Shortcut key");
if (Object.keys(shortcutsList.global).includes(keyPressed) && performWorkspaceCreateActions()) if (
Object.keys(shortcutsList.global).includes(keyPressed) &&
((!projectId && performAnyProjectCreateActions()) || performProjectCreateActions())
)
shortcutsList.global[keyPressed].action(); shortcutsList.global[keyPressed].action();
// workspace authorized actions // workspace authorized actions
else if ( else if (
@ -244,6 +264,7 @@ export const CommandPalette: FC = observer(() => {
} }
}, },
[ [
performAnyProjectCreateActions,
performProjectCreateActions, performProjectCreateActions,
performWorkspaceCreateActions, performWorkspaceCreateActions,
copyIssueUrlToClipboard, copyIssueUrlToClipboard,

View file

@ -32,7 +32,7 @@ import { renderFormattedPayloadDate, getDate } from "@/helpers/date-time.helper"
import { getChangedIssuefields, getDescriptionPlaceholder } from "@/helpers/issue.helper"; import { getChangedIssuefields, getDescriptionPlaceholder } from "@/helpers/issue.helper";
import { shouldRenderProject } from "@/helpers/project.helper"; import { shouldRenderProject } from "@/helpers/project.helper";
// hooks // hooks
import { useProjectEstimates, useInstance, useIssueDetail, useProject, useWorkspace } from "@/hooks/store"; import { useProjectEstimates, useInstance, useIssueDetail, useProject, useWorkspace, useUser } from "@/hooks/store";
import useKeypress from "@/hooks/use-keypress"; import useKeypress from "@/hooks/use-keypress";
import { useProjectIssueProperties } from "@/hooks/use-project-issue-properties"; import { useProjectIssueProperties } from "@/hooks/use-project-issue-properties";
// services // services
@ -121,6 +121,8 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
const workspaceStore = useWorkspace(); const workspaceStore = useWorkspace();
const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug?.toString())?.id as string; const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug?.toString())?.id as string;
const { config } = useInstance(); const { config } = useInstance();
const { projectsWithCreatePermissions } = useUser();
const { getProjectById } = useProject(); const { getProjectById } = useProject();
const { areEstimateEnabledByProjectId } = useProjectEstimates(); const { areEstimateEnabledByProjectId } = useProjectEstimates();
@ -341,7 +343,8 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
rules={{ rules={{
required: true, required: true,
}} }}
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange } }) =>
projectsWithCreatePermissions && projectsWithCreatePermissions[value!] ? (
<div className="h-7"> <div className="h-7">
<ProjectDropdown <ProjectDropdown
value={value} value={value}
@ -354,7 +357,10 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
tabIndex={getTabIndex("project_id")} tabIndex={getTabIndex("project_id")}
/> />
</div> </div>
)} ) : (
<></>
)
}
/> />
)} )}
<h3 className="text-xl font-medium text-custom-text-200">{data?.id ? "Update" : "Create"} Issue</h3> <h3 className="text-xl font-medium text-custom-text-200">{data?.id ? "Update" : "Create"} Issue</h3>

View file

@ -44,6 +44,8 @@ export interface IUserStore {
// computed // computed
canPerformProjectCreateActions: boolean; canPerformProjectCreateActions: boolean;
canPerformWorkspaceCreateActions: boolean; canPerformWorkspaceCreateActions: boolean;
canPerformAnyCreateAction: boolean;
projectsWithCreatePermissions: { [projectId: string]: number } | null;
} }
export class UserStore implements IUserStore { export class UserStore implements IUserStore {
@ -91,6 +93,8 @@ export class UserStore implements IUserStore {
// computed // computed
canPerformProjectCreateActions: computed, canPerformProjectCreateActions: computed,
canPerformWorkspaceCreateActions: computed, canPerformWorkspaceCreateActions: computed,
canPerformAnyCreateAction: computed,
projectsWithCreatePermissions: computed,
}); });
} }
@ -136,6 +140,26 @@ export class UserStore implements IUserStore {
} }
}; };
/**
* @description fetches the prjects with write permissions
* @returns {{[projectId: string]: number} || null}
*/
fetchProjectsWithCreatePermissions() {
const allWorkspaceRoles =
this.membership.workspaceProjectsRole &&
this.membership.workspaceProjectsRole[this.membership.router.workspaceSlug || ""];
return (
(allWorkspaceRoles &&
Object.keys(allWorkspaceRoles)
.filter((key) => allWorkspaceRoles[key] >= EUserProjectRoles.MEMBER)
.reduce(
(res: { [projectId: string]: number }, key: string) => ((res[key] = allWorkspaceRoles[key]), res),
{}
)) ||
null
);
}
/** /**
* @description updates the current user * @description updates the current user
* @param data * @param data
@ -229,6 +253,23 @@ export class UserStore implements IUserStore {
this.store.resetOnSignOut(); this.store.resetOnSignOut();
}; };
/**
* @description returns projects where user has permissions
* @returns {{[projectId: string]: number} || null}
*/
get projectsWithCreatePermissions() {
return this.fetchProjectsWithCreatePermissions();
}
/**
* @description returns true if user has permissions to write in any project
* @returns {boolean}
*/
get canPerformAnyCreateAction() {
const filteredProjects = this.fetchProjectsWithCreatePermissions();
return filteredProjects ? Object.keys(filteredProjects).length > 0 : false;
}
/** /**
* @description tells if user has project create actions permissions * @description tells if user has project create actions permissions
* @returns {boolean} * @returns {boolean}

View file

@ -11,6 +11,7 @@ import { ProjectMemberService } from "@/services/project";
import { UserService } from "@/services/user.service"; import { UserService } from "@/services/user.service";
// plane web store // plane web store
import { CoreRootStore } from "../root.store"; import { CoreRootStore } from "../root.store";
import { IRouterStore } from "../router.store";
export interface IUserMembershipStore { export interface IUserMembershipStore {
// observables // observables
@ -47,6 +48,8 @@ export interface IUserMembershipStore {
leaveWorkspace: (workspaceSlug: string) => Promise<void>; leaveWorkspace: (workspaceSlug: string) => Promise<void>;
joinProject: (workspaceSlug: string, projectIds: string[]) => Promise<any>; joinProject: (workspaceSlug: string, projectIds: string[]) => Promise<any>;
leaveProject: (workspaceSlug: string, projectId: string) => Promise<void>; leaveProject: (workspaceSlug: string, projectId: string) => Promise<void>;
router: IRouterStore;
} }
export class UserMembershipStore implements IUserMembershipStore { export class UserMembershipStore implements IUserMembershipStore {