* refactor: migrate space icons to @makeplane/propel Replace confirmed icon symbols in apps/space with their audited @makeplane/propel/icons targets. Unresolved symbols (StateGroupIcon, PriorityIcon, PlaneLockup, PlaneLogo, Circle, CircleDashed, SignalHigh) stay on their existing imports. Propel icons paint with fill rather than stroke, so stroke-placeholder classes become text-placeholder to preserve the rendered color. Lucide size props become explicit width/height, and one chevron gains an explicit height because Propel defaults to 1em where the legacy IconWrapper defaulted to 16. CycleGroupIcon is deliberately not migrated: it dispatches a different glyph and color per cycle status, which CyclesOutline cannot express. * fix: address icon migration review on space Drop strokeWidth from Propel icons. The generated glyphs paint with fill="currentColor", so the prop reached the svg and changed nothing; keeping it implied a control that no longer exists. Wrap the password and email-clear icon click targets in buttons. These already carried onClick directly on the icon, but Propel bakes aria-hidden="true" onto the svg, so the migration also hid them from assistive tech. The buttons restore an accessible name and put the controls in the tab order, matching the pattern email.tsx already uses.
242 lines
6.6 KiB
TypeScript
242 lines
6.6 KiB
TypeScript
/**
|
|
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
* See the LICENSE file for details.
|
|
*/
|
|
|
|
import { isNil } from "lodash-es";
|
|
// types
|
|
import { EIconSize, ISSUE_PRIORITIES } from "@plane/constants";
|
|
import { CycleGroupIcon, PriorityIcon, StateGroupIcon } from "@plane/propel/icons";
|
|
import { CyclesOutline, ModuleOutline } from "@makeplane/propel/icons";
|
|
import type {
|
|
GroupByColumnTypes,
|
|
IGroupByColumn,
|
|
TCycleGroups,
|
|
IIssueDisplayProperties,
|
|
TGroupedIssues,
|
|
} from "@plane/types";
|
|
// ui
|
|
import { Avatar } from "@plane/ui";
|
|
// components
|
|
// constants
|
|
// stores
|
|
import type { ICycleStore } from "@/store/cycle.store";
|
|
import type { IIssueLabelStore } from "@/store/label.store";
|
|
import type { IIssueMemberStore } from "@/store/members.store";
|
|
import type { IIssueModuleStore } from "@/store/module.store";
|
|
import type { IStateStore } from "@/store/state.store";
|
|
|
|
export const HIGHLIGHT_CLASS = "highlight";
|
|
export const HIGHLIGHT_WITH_LINE = "highlight-with-line";
|
|
|
|
export const getGroupByColumns = (
|
|
groupBy: GroupByColumnTypes | null,
|
|
cycle: ICycleStore,
|
|
module: IIssueModuleStore,
|
|
label: IIssueLabelStore,
|
|
projectState: IStateStore,
|
|
member: IIssueMemberStore,
|
|
includeNone?: boolean
|
|
): IGroupByColumn[] | undefined => {
|
|
switch (groupBy) {
|
|
case "cycle":
|
|
return getCycleColumns(cycle);
|
|
case "module":
|
|
return getModuleColumns(module);
|
|
case "state":
|
|
return getStateColumns(projectState);
|
|
case "priority":
|
|
return getPriorityColumns();
|
|
case "labels":
|
|
return getLabelsColumns(label) as any;
|
|
case "assignees":
|
|
return getAssigneeColumns(member);
|
|
case "created_by":
|
|
return getCreatedByColumns(member) as any;
|
|
default:
|
|
if (includeNone) return [{ id: `All Issues`, name: `All work items`, payload: {}, icon: undefined }];
|
|
}
|
|
};
|
|
|
|
const getCycleColumns = (cycleStore: ICycleStore): IGroupByColumn[] | undefined => {
|
|
const { cycles } = cycleStore;
|
|
|
|
if (!cycles) return;
|
|
|
|
const cycleGroups: IGroupByColumn[] = [];
|
|
|
|
cycles.map((cycle) => {
|
|
if (cycle) {
|
|
const cycleStatus = cycle?.status ? (cycle.status.toLocaleLowerCase() as TCycleGroups) : "draft";
|
|
cycleGroups.push({
|
|
id: cycle.id,
|
|
name: cycle.name,
|
|
icon: <CycleGroupIcon cycleGroup={cycleStatus} className="h-3.5 w-3.5" />,
|
|
payload: { cycle_id: cycle.id },
|
|
});
|
|
}
|
|
});
|
|
cycleGroups.push({
|
|
id: "None",
|
|
name: "None",
|
|
icon: <CyclesOutline className="h-3.5 w-3.5" />,
|
|
payload: { cycle_id: null },
|
|
});
|
|
|
|
return cycleGroups;
|
|
};
|
|
|
|
const getModuleColumns = (moduleStore: IIssueModuleStore): IGroupByColumn[] | undefined => {
|
|
const { modules } = moduleStore;
|
|
|
|
if (!modules) return;
|
|
|
|
const moduleGroups: IGroupByColumn[] = [];
|
|
|
|
modules.map((moduleInfo) => {
|
|
if (moduleInfo)
|
|
moduleGroups.push({
|
|
id: moduleInfo.id,
|
|
name: moduleInfo.name,
|
|
icon: <ModuleOutline className="h-3.5 w-3.5" />,
|
|
payload: { module_ids: [moduleInfo.id] },
|
|
});
|
|
}) as any;
|
|
moduleGroups.push({
|
|
id: "None",
|
|
name: "None",
|
|
icon: <ModuleOutline className="h-3.5 w-3.5" />,
|
|
payload: { module_ids: [] },
|
|
});
|
|
|
|
return moduleGroups as any;
|
|
};
|
|
|
|
const getStateColumns = (projectState: IStateStore): IGroupByColumn[] | undefined => {
|
|
const { sortedStates } = projectState;
|
|
if (!sortedStates) return;
|
|
|
|
return sortedStates.map((state) => ({
|
|
id: state.id,
|
|
name: state.name,
|
|
icon: (
|
|
<div className="h-3.5 w-3.5 rounded-full">
|
|
<StateGroupIcon stateGroup={state.group} color={state.color} size={EIconSize.MD} />
|
|
</div>
|
|
),
|
|
payload: { state_id: state.id },
|
|
})) as any;
|
|
};
|
|
|
|
const getPriorityColumns = () => {
|
|
const priorities = ISSUE_PRIORITIES;
|
|
|
|
return priorities.map((priority) => ({
|
|
id: priority.key,
|
|
name: priority.title,
|
|
icon: <PriorityIcon priority={priority?.key} />,
|
|
payload: { priority: priority.key },
|
|
}));
|
|
};
|
|
|
|
const getLabelsColumns = (label: IIssueLabelStore) => {
|
|
const { labels: storeLabels } = label;
|
|
|
|
if (!storeLabels) return;
|
|
|
|
const labels = [...storeLabels, { id: "None", name: "None", color: "#666" }];
|
|
|
|
return labels.map((label) => ({
|
|
id: label.id,
|
|
name: label.name,
|
|
icon: (
|
|
<div className="h-[12px] w-[12px] rounded-full" style={{ backgroundColor: label.color ? label.color : "#666" }} />
|
|
),
|
|
payload: label?.id === "None" ? {} : { label_ids: [label.id] },
|
|
}));
|
|
};
|
|
|
|
const getAssigneeColumns = (member: IIssueMemberStore) => {
|
|
const { members } = member;
|
|
|
|
if (!members) return;
|
|
|
|
const assigneeColumns: any = members.map((member) => ({
|
|
id: member.id,
|
|
name: member?.member__display_name || "",
|
|
icon: <Avatar name={member?.member__display_name} src={undefined} size="md" />,
|
|
payload: { assignee_ids: [member.id] },
|
|
}));
|
|
|
|
assigneeColumns.push({ id: "None", name: "None", icon: <Avatar size="md" />, payload: {} });
|
|
|
|
return assigneeColumns;
|
|
};
|
|
|
|
const getCreatedByColumns = (member: IIssueMemberStore) => {
|
|
const { members } = member;
|
|
|
|
if (!members) return;
|
|
|
|
return members.map((member) => ({
|
|
id: member.id,
|
|
name: member?.member__display_name || "",
|
|
icon: <Avatar name={member?.member__display_name} src={undefined} size="md" />,
|
|
payload: {},
|
|
}));
|
|
};
|
|
|
|
export const getDisplayPropertiesCount = (
|
|
displayProperties: IIssueDisplayProperties,
|
|
ignoreFields?: (keyof IIssueDisplayProperties)[]
|
|
) => {
|
|
const propertyKeys = Object.keys(displayProperties) as (keyof IIssueDisplayProperties)[];
|
|
|
|
let count = 0;
|
|
|
|
for (const propertyKey of propertyKeys) {
|
|
if (ignoreFields && ignoreFields.includes(propertyKey)) continue;
|
|
if (displayProperties[propertyKey]) count++;
|
|
}
|
|
|
|
return count;
|
|
};
|
|
|
|
export const getIssueBlockId = (issueId: string | undefined, groupId: string | undefined, subGroupId?: string) =>
|
|
`issue_${issueId}_${groupId}_${subGroupId}`;
|
|
|
|
/**
|
|
* returns empty Array if groupId is None
|
|
* @param groupId
|
|
* @returns
|
|
*/
|
|
export const getGroupId = (groupId: string) => {
|
|
if (groupId === "None") return [];
|
|
return [groupId];
|
|
};
|
|
|
|
/**
|
|
* method that removes Null or undefined Keys from object
|
|
* @param obj
|
|
* @returns
|
|
*/
|
|
export const removeNillKeys = <T,>(obj: T) =>
|
|
Object.fromEntries(Object.entries(obj ?? {}).filter(([key, value]) => key && !isNil(value)));
|
|
|
|
/**
|
|
* This Method returns if the grouped values are subGrouped
|
|
* @param groupedIssueIds
|
|
* @returns
|
|
*/
|
|
export const isSubGrouped = (groupedIssueIds: TGroupedIssues) => {
|
|
if (!groupedIssueIds || Array.isArray(groupedIssueIds)) {
|
|
return false;
|
|
}
|
|
|
|
if (Array.isArray(groupedIssueIds[Object.keys(groupedIssueIds)[0]])) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|