mirror of
https://github.com/makeplane/plane.git
synced 2026-07-13 14:01:45 +02:00
Merge pull request #771 from makeplane/sync/ce-ee
sync: community changes
This commit is contained in:
@@ -27,14 +27,11 @@ class ProjectStatesEndpoint(BaseAPIView):
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
states = (
|
||||
State.objects.filter(
|
||||
~Q(name="Triage"),
|
||||
workspace__slug=deploy_board.workspace.slug,
|
||||
project_id=deploy_board.project_id,
|
||||
)
|
||||
.values("name", "group", "color", "id")
|
||||
)
|
||||
states = State.objects.filter(
|
||||
~Q(name="Triage"),
|
||||
workspace__slug=deploy_board.workspace.slug,
|
||||
project_id=deploy_board.project_id,
|
||||
).values("name", "group", "color", "id", "sequence")
|
||||
|
||||
return Response(
|
||||
states,
|
||||
|
||||
13
packages/types/src/favorite/favorite.d.ts
vendored
13
packages/types/src/favorite/favorite.d.ts
vendored
@@ -1,9 +1,22 @@
|
||||
type TLogoProps = {
|
||||
in_use: "emoji" | "icon";
|
||||
emoji?: {
|
||||
value?: string;
|
||||
url?: string;
|
||||
};
|
||||
icon?: {
|
||||
name?: string;
|
||||
color?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type IFavorite = {
|
||||
id: string;
|
||||
name: string;
|
||||
entity_type: string;
|
||||
entity_data: {
|
||||
name: string;
|
||||
logo_props?: TLogoProps | undefined;
|
||||
};
|
||||
is_folder: boolean;
|
||||
sort_order: number;
|
||||
|
||||
@@ -109,10 +109,10 @@ const getModuleColumns = (moduleStore: IIssueModuleStore): IGroupByColumn[] | un
|
||||
};
|
||||
|
||||
const getStateColumns = (projectState: IStateStore): IGroupByColumn[] | undefined => {
|
||||
const { states } = projectState;
|
||||
if (!states) return;
|
||||
const { sortedStates } = projectState;
|
||||
if (!sortedStates) return;
|
||||
|
||||
return states.map((state) => ({
|
||||
return sortedStates.map((state) => ({
|
||||
id: state.id,
|
||||
name: state.name,
|
||||
icon: (
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { action, makeObservable, observable, runInAction } from "mobx";
|
||||
import clone from "lodash/clone";
|
||||
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
||||
import { IState } from "@plane/types";
|
||||
import { sortStates } from "@/helpers/state.helper";
|
||||
import { StateService } from "@/services/state.service";
|
||||
import { CoreRootStore } from "./root.store";
|
||||
|
||||
export interface IStateStore {
|
||||
// observables
|
||||
states: IState[] | undefined;
|
||||
//computed
|
||||
sortedStates: IState[] | undefined;
|
||||
// computed actions
|
||||
getStateById: (stateId: string | undefined) => IState | undefined;
|
||||
// fetch actions
|
||||
@@ -21,6 +25,8 @@ export class StateStore implements IStateStore {
|
||||
makeObservable(this, {
|
||||
// observables
|
||||
states: observable,
|
||||
// computed
|
||||
sortedStates: computed,
|
||||
// fetch action
|
||||
fetchStates: action,
|
||||
});
|
||||
@@ -28,6 +34,11 @@ export class StateStore implements IStateStore {
|
||||
this.rootStore = _rootStore;
|
||||
}
|
||||
|
||||
get sortedStates() {
|
||||
if (!this.states) return;
|
||||
return sortStates(clone(this.states));
|
||||
}
|
||||
|
||||
getStateById = (stateId: string | undefined) => this.states?.find((state) => state.id === stateId);
|
||||
|
||||
fetchStates = async (anchor: string) => {
|
||||
|
||||
13
space/helpers/state.helper.ts
Normal file
13
space/helpers/state.helper.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { IState } from "@plane/types";
|
||||
import { STATE_GROUPS } from "@/constants/state";
|
||||
|
||||
export const sortStates = (states: IState[]) => {
|
||||
if (!states || states.length === 0) return;
|
||||
|
||||
return states.sort((stateA, stateB) => {
|
||||
if (stateA.group === stateB.group) {
|
||||
return stateA.sequence - stateB.sequence;
|
||||
}
|
||||
return Object.keys(STATE_GROUPS).indexOf(stateA.group) - Object.keys(STATE_GROUPS).indexOf(stateB.group);
|
||||
});
|
||||
};
|
||||
@@ -294,7 +294,7 @@ export const CreateProjectForm: FC<Props> = observer((props) => {
|
||||
/>
|
||||
<Tooltip
|
||||
isMobile={isMobile}
|
||||
tooltipContent="Helps you identify issues in the project uniquely, (e.g. APP-123). Max 5 characters."
|
||||
tooltipContent="Helps you identify issues in the project uniquely. Max 5 characters."
|
||||
className="text-sm"
|
||||
position="right-top"
|
||||
>
|
||||
|
||||
@@ -298,7 +298,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
/>
|
||||
<Tooltip
|
||||
isMobile={isMobile}
|
||||
tooltipContent="Helps you identify issues in the project uniquely, (e.g. APP-123). Max 5 characters."
|
||||
tooltipContent="Helps you identify issues in the project uniquely. Max 5 characters."
|
||||
className="text-sm"
|
||||
position="right-top"
|
||||
>
|
||||
|
||||
@@ -9,7 +9,16 @@ import { useParams } from "next/navigation";
|
||||
import { Briefcase, FileText, Layers, MoreHorizontal, Star } from "lucide-react";
|
||||
// ui
|
||||
import { IFavorite } from "@plane/types";
|
||||
import { ContrastIcon, CustomMenu, DiceIcon, DragHandle, FavoriteFolderIcon, LayersIcon, Tooltip } from "@plane/ui";
|
||||
import {
|
||||
ContrastIcon,
|
||||
CustomMenu,
|
||||
DiceIcon,
|
||||
DragHandle,
|
||||
FavoriteFolderIcon,
|
||||
LayersIcon,
|
||||
Logo,
|
||||
Tooltip,
|
||||
} from "@plane/ui";
|
||||
// components
|
||||
import { SidebarNavItem } from "@/components/sidebar";
|
||||
|
||||
@@ -20,6 +29,17 @@ import { useAppTheme } from "@/hooks/store";
|
||||
import useOutsideClickDetector from "@/hooks/use-outside-click-detector";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
||||
const iconClassName = `flex-shrink-0 size-4 stroke-[1.5] m-auto`;
|
||||
const ICONS: Record<string, JSX.Element> = {
|
||||
page: <FileText className={iconClassName} />,
|
||||
project: <Briefcase className={iconClassName} />,
|
||||
view: <Layers className={iconClassName} />,
|
||||
module: <DiceIcon className={iconClassName} />,
|
||||
cycle: <ContrastIcon className={iconClassName} />,
|
||||
issue: <LayersIcon className={iconClassName} />,
|
||||
folder: <FavoriteFolderIcon className={iconClassName} />,
|
||||
};
|
||||
|
||||
export const FavoriteItem = observer(
|
||||
({
|
||||
favoriteMap,
|
||||
@@ -48,28 +68,18 @@ export const FavoriteItem = observer(
|
||||
const dragHandleRef = useRef<HTMLButtonElement | null>(null);
|
||||
const actionSectionRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const getIcon = () => {
|
||||
const className = `flex-shrink-0 size-4 stroke-[1.5] m-auto`;
|
||||
|
||||
switch (favorite.entity_type) {
|
||||
case "page":
|
||||
return <FileText className={className} />;
|
||||
case "project":
|
||||
return <Briefcase className={className} />;
|
||||
case "view":
|
||||
return <Layers className={className} />;
|
||||
case "module":
|
||||
return <DiceIcon className={className} />;
|
||||
case "cycle":
|
||||
return <ContrastIcon className={className} />;
|
||||
case "issue":
|
||||
return <LayersIcon className={className} />;
|
||||
case "folder":
|
||||
return <FavoriteFolderIcon className={className} />;
|
||||
default:
|
||||
return <FileText />;
|
||||
}
|
||||
};
|
||||
const getIcon = () => (
|
||||
<>
|
||||
<div className="hidden group-hover:block">{ICONS[favorite.entity_type] || <FileText />}</div>
|
||||
<div className="block group-hover:hidden">
|
||||
{favorite.entity_data?.logo_props?.in_use ? (
|
||||
<Logo logo={favorite.entity_data?.logo_props} size={16} type="lucide" />
|
||||
) : (
|
||||
ICONS[favorite.entity_type] || <FileText />
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const getLink = () => {
|
||||
switch (favorite.entity_type) {
|
||||
@@ -133,7 +143,10 @@ export const FavoriteItem = observer(
|
||||
<div ref={elementRef} className="group/project-item">
|
||||
<SidebarNavItem
|
||||
key={favorite.id}
|
||||
className={`${sidebarCollapsed ? "p-0 size-8 aspect-square justify-center mx-auto" : ""}`}
|
||||
className={cn({
|
||||
"bg-custom-sidebar-background-90": isMenuActive,
|
||||
"p-0 size-8 aspect-square justify-center mx-auto": sidebarCollapsed,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={cn("flex justify-between items-center gap-1.5 py-[1px]", {
|
||||
|
||||
@@ -133,14 +133,16 @@ export const SidebarFavoritesMenu = observer(() => {
|
||||
<span onClick={() => toggleFavoriteMenu(!isFavoriteMenuOpen)} className="flex-1 text-start">
|
||||
YOUR FAVORITES
|
||||
</span>
|
||||
<span className="flex gap-2 flex-shrink-0 opacity-0 pointer-events-none group-hover/workspace-button:opacity-100 group-hover/workspace-button:pointer-events-auto rounded p-0.5 ">
|
||||
<FolderPlus
|
||||
onClick={() => {
|
||||
setCreateNewFolder(true);
|
||||
!isFavoriteMenuOpen && toggleFavoriteMenu(!isFavoriteMenuOpen);
|
||||
}}
|
||||
className={cn("size-4 flex-shrink-0 text-custom-sidebar-text-400 transition-transform")}
|
||||
/>
|
||||
<span className="flex flex-shrink-0 opacity-0 pointer-events-none group-hover/workspace-button:opacity-100 group-hover/workspace-button:pointer-events-auto rounded p-0.5 ">
|
||||
<Tooltip tooltipHeading="Create folder" tooltipContent="">
|
||||
<FolderPlus
|
||||
onClick={() => {
|
||||
setCreateNewFolder(true);
|
||||
!isFavoriteMenuOpen && toggleFavoriteMenu(!isFavoriteMenuOpen);
|
||||
}}
|
||||
className={cn("size-4 flex-shrink-0 text-custom-sidebar-text-400 transition-transform")}
|
||||
/>
|
||||
</Tooltip>
|
||||
<ChevronRight
|
||||
onClick={() => toggleFavoriteMenu(!isFavoriteMenuOpen)}
|
||||
className={cn("size-4 flex-shrink-0 text-custom-sidebar-text-400 transition-transform", {
|
||||
@@ -168,34 +170,42 @@ export const SidebarFavoritesMenu = observer(() => {
|
||||
static
|
||||
>
|
||||
{createNewFolder && <NewFavoriteFolder setCreateNewFolder={setCreateNewFolder} actionType="create" />}
|
||||
{uniqBy(orderBy(Object.values(favoriteMap), "sequence", "desc"), "id")
|
||||
.filter((fav) => !fav.parent)
|
||||
.map((fav, index) => (
|
||||
<Tooltip
|
||||
key={fav.id}
|
||||
tooltipContent={fav.entity_data ? fav.entity_data.name : fav.name}
|
||||
position="right"
|
||||
className="ml-2"
|
||||
disabled={!sidebarCollapsed}
|
||||
isMobile={isMobile}
|
||||
>
|
||||
{fav.is_folder ? (
|
||||
<FavoriteFolder
|
||||
favorite={fav}
|
||||
isLastChild={index === favoriteIds.length - 1}
|
||||
handleRemoveFromFavorites={handleRemoveFromFavorites}
|
||||
handleRemoveFromFavoritesFolder={handleRemoveFromFavoritesFolder}
|
||||
/>
|
||||
) : (
|
||||
<FavoriteItem
|
||||
favorite={fav}
|
||||
handleRemoveFromFavorites={handleRemoveFromFavorites}
|
||||
handleRemoveFromFavoritesFolder={handleRemoveFromFavoritesFolder}
|
||||
favoriteMap={favoriteMap}
|
||||
/>
|
||||
)}
|
||||
</Tooltip>
|
||||
))}
|
||||
{Object.keys(favoriteMap).length === 0 ? (
|
||||
<>
|
||||
{!sidebarCollapsed && (
|
||||
<span className="text-custom-text-400 text-xs text-center font-medium py-1">No favorites yet</span>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
uniqBy(orderBy(Object.values(favoriteMap), "sequence", "desc"), "id")
|
||||
.filter((fav) => !fav.parent)
|
||||
.map((fav, index) => (
|
||||
<Tooltip
|
||||
key={fav.id}
|
||||
tooltipContent={fav.entity_data ? fav.entity_data.name : fav.name}
|
||||
position="right"
|
||||
className="ml-2"
|
||||
disabled={!sidebarCollapsed}
|
||||
isMobile={isMobile}
|
||||
>
|
||||
{fav.is_folder ? (
|
||||
<FavoriteFolder
|
||||
favorite={fav}
|
||||
isLastChild={index === favoriteIds.length - 1}
|
||||
handleRemoveFromFavorites={handleRemoveFromFavorites}
|
||||
handleRemoveFromFavoritesFolder={handleRemoveFromFavoritesFolder}
|
||||
/>
|
||||
) : (
|
||||
<FavoriteItem
|
||||
favorite={fav}
|
||||
handleRemoveFromFavorites={handleRemoveFromFavorites}
|
||||
handleRemoveFromFavoritesFolder={handleRemoveFromFavoritesFolder}
|
||||
favoriteMap={favoriteMap}
|
||||
/>
|
||||
)}
|
||||
</Tooltip>
|
||||
))
|
||||
)}
|
||||
</Disclosure.Panel>
|
||||
)}
|
||||
</Transition>
|
||||
|
||||
@@ -108,7 +108,7 @@ export const SidebarQuickActions = observer(() => {
|
||||
</button>
|
||||
)}
|
||||
{isDraftButtonOpen && (
|
||||
<div className="absolute mt-0 h-10 w-[220px] pt-2 z-10 top-8 left-0">
|
||||
<div className="absolute mt-0 h-10 w-[220px] pt-2 z-[16] top-8 left-0">
|
||||
<div className="h-full w-full">
|
||||
<button
|
||||
onClick={() => setIsDraftIssueModalOpen(true)}
|
||||
|
||||
@@ -285,7 +285,6 @@ export class ProjectStore implements IProjectStore {
|
||||
project_id: projectId,
|
||||
entity_data: { name: this.projectMap[projectId].name || "" },
|
||||
});
|
||||
await this.fetchProjects(workspaceSlug);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.log("Failed to add project to favorite");
|
||||
@@ -306,12 +305,11 @@ export class ProjectStore implements IProjectStore {
|
||||
try {
|
||||
const currentProject = this.getProjectById(projectId);
|
||||
if (!currentProject.is_favorite) return;
|
||||
const response = await this.rootStore.favorite.removeFavoriteEntity(workspaceSlug.toString(), projectId);
|
||||
|
||||
runInAction(() => {
|
||||
set(this.projectMap, [projectId, "is_favorite"], false);
|
||||
});
|
||||
await this.fetchProjects(workspaceSlug);
|
||||
const response = await this.rootStore.favorite.removeFavoriteEntity(workspaceSlug.toString(), projectId);
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.log("Failed to add project to favorite");
|
||||
|
||||
Reference in New Issue
Block a user