snapshot of https://github.com/makeplane/plane.git @ d868349a55de79979eb8b98701bf1702c67ad634 (2026-09-15)
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 16s
CodeQL / Analyze (python) (push) Failing after 3s
i18n sync check / check:sync (push) Failing after 3s
React Doctor / react-doctor (push) Failing after 16s
Branch Build CE / Build-Push Admin Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Admin Manifest (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Web Manifest (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Proxy Manifest (push) Has been cancelled
Branch Build CE / Merge Space Manifest (push) Has been cancelled
Branch Build CE / Prepare AIO Assets (push) Has been cancelled
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Live Collaboration Manifest (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge API Server Manifest (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge AIO Manifest (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-15 15:14:58 +05:00
commit 23059a1de3
5252 changed files with 573834 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { useEffect } from "react";
import { observer } from "mobx-react";
// plane imports
import { useTranslation } from "@plane/i18n";
import { CyclesOutline } from "@makeplane/propel/icons";
import { cn } from "@plane/utils";
// hooks
import { useCycle } from "@/hooks/store/use-cycle";
export type TReadonlyCycleProps = {
className?: string;
hideIcon?: boolean;
value: string | null;
placeholder?: string;
projectId: string | undefined;
workspaceSlug: string;
};
export const ReadonlyCycle = observer(function ReadonlyCycle(props: TReadonlyCycleProps) {
const { className, hideIcon = false, value, placeholder, projectId, workspaceSlug } = props;
const { t } = useTranslation();
const { getCycleNameById, fetchAllCycles } = useCycle();
const cycleName = value ? getCycleNameById(value) : null;
useEffect(() => {
if (projectId) {
fetchAllCycles(workspaceSlug, projectId);
}
}, [projectId, workspaceSlug]);
return (
<div className={cn("flex items-center gap-1 text-13", className)}>
{!hideIcon && <CyclesOutline className="size-4 flex-shrink-0" />}
<span className="flex-grow truncate">{cycleName ?? placeholder ?? t("common.none")}</span>
</div>
);
});

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
// plane imports
import { useTranslation } from "@plane/i18n";
import { CalendarOutline } from "@makeplane/propel/icons";
import { cn, renderFormattedDate, getDate } from "@plane/utils";
export type TReadonlyDateProps = {
className?: string;
hideIcon?: boolean;
value: Date | string | null;
placeholder?: string;
formatToken?: string;
};
export const ReadonlyDate = observer(function ReadonlyDate(props: TReadonlyDateProps) {
const { className, hideIcon = false, value, placeholder, formatToken } = props;
const { t } = useTranslation();
const formattedDate = value ? renderFormattedDate(getDate(value), formatToken) : null;
return (
<div className={cn("flex items-center gap-1 text-13", className)}>
{!hideIcon && <CalendarOutline className="size-4 flex-shrink-0" />}
<span className="flex-grow truncate">{formattedDate ?? placeholder ?? t("common.none")}</span>
</div>
);
});

View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { useEffect } from "react";
import { observer } from "mobx-react";
import { useTranslation } from "@plane/i18n";
import { EstimateOutline } from "@makeplane/propel/icons";
import { EEstimateSystem } from "@plane/types";
import { cn, convertMinutesToHoursMinutesString } from "@plane/utils";
// hooks
import { useProjectEstimates } from "@/hooks/store/estimates";
import { useEstimate } from "@/hooks/store/estimates/use-estimate";
export type TReadonlyEstimateProps = {
className?: string;
hideIcon?: boolean;
value: string | undefined | null;
placeholder?: string;
projectId: string | undefined;
workspaceSlug: string;
};
export const ReadonlyEstimate = observer(function ReadonlyEstimate(props: TReadonlyEstimateProps) {
const { className, hideIcon = false, value, placeholder, projectId, workspaceSlug } = props;
const { t } = useTranslation();
const { currentActiveEstimateIdByProjectId, getEstimateById, getProjectEstimates } = useProjectEstimates();
const currentActiveEstimateId = projectId ? currentActiveEstimateIdByProjectId(projectId) : undefined;
const currentActiveEstimate = currentActiveEstimateId ? getEstimateById(currentActiveEstimateId) : undefined;
const { estimatePointById } = useEstimate(currentActiveEstimateId);
const estimatePoint = value ? estimatePointById(value) : null;
const displayValue = estimatePoint
? currentActiveEstimate?.type === EEstimateSystem.TIME
? convertMinutesToHoursMinutesString(Number(estimatePoint.value))
: estimatePoint.value
: null;
useEffect(() => {
if (projectId) {
getProjectEstimates(workspaceSlug, projectId);
}
}, [projectId, workspaceSlug]);
return (
<div className={cn("flex items-center gap-1 text-body-xs-regular", className)}>
{!hideIcon && <EstimateOutline className="size-4 flex-shrink-0" />}
<span className="flex-grow truncate">{displayValue ?? placeholder ?? t("common.none")}</span>
</div>
);
});

View File

@@ -0,0 +1,16 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
// Readonly components for displaying single values instead of interactive dropdowns
// These components handle their own data fetching using internal hooks
export { ReadonlyState, type TReadonlyStateProps } from "./state";
export { ReadonlyPriority, type TReadonlyPriorityProps } from "./priority";
export { ReadonlyMember, type TReadonlyMemberProps } from "./member";
export { ReadonlyLabels, type TReadonlyLabelsProps } from "./labels";
export { ReadonlyCycle, type TReadonlyCycleProps } from "./cycle";
export { ReadonlyDate, type TReadonlyDateProps } from "./date";
export { ReadonlyEstimate, type TReadonlyEstimateProps } from "./estimate";
export { ReadonlyModule, type TReadonlyModuleProps } from "./module";

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { useEffect } from "react";
import { observer } from "mobx-react";
// plane imports
import { Tooltip } from "@makeplane/propel/components/tooltip";
import { cn } from "@plane/utils";
// hooks
import { useLabel } from "@/hooks/store/use-label";
import { usePlatformOS } from "@/hooks/use-platform-os";
export type TReadonlyLabelsProps = {
className?: string;
hideIcon?: boolean;
value: string[];
placeholder?: string;
projectId: string | undefined;
workspaceSlug: string;
};
export const ReadonlyLabels = observer(function ReadonlyLabels(props: TReadonlyLabelsProps) {
const { className, value, projectId, workspaceSlug } = props;
const { getLabelById, fetchProjectLabels } = useLabel();
const { isMobile } = usePlatformOS();
const labels = value
.map((labelId) => getLabelById(labelId))
.filter((label): label is NonNullable<typeof label> => Boolean(label));
useEffect(() => {
if (projectId) {
fetchProjectLabels(workspaceSlug?.toString(), projectId);
}
}, [projectId, workspaceSlug]);
return (
<div className={cn("flex items-center gap-2 text-body-xs-regular", className)}>
{labels && (
<>
<Tooltip
label={`Labels: ${labels.map((l) => l?.name).join(", ")}`}
layout="stacked"
disabled={labels.length === 0 || isMobile}
>
<div className="flex h-full items-center gap-1 rounded-sm py-1 text-body-xs-bold">
<span className="h-2 w-2 flex-shrink-0 rounded-full bg-accent-primary" />
<span>{value.length}</span>
<span>Labels</span>
</div>
</Tooltip>
</>
)}
</div>
);
});

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
import type { LucideIcon } from "lucide-react";
// plane imports
import { useTranslation } from "@plane/i18n";
import { cn } from "@plane/utils";
// components
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
// hooks
import { useMember } from "@/hooks/store/use-member";
export type TReadonlyMemberProps = {
className?: string;
icon?: LucideIcon;
hideIcon?: boolean;
value: string | string[];
placeholder?: string;
multiple?: boolean;
projectId?: string;
};
export const ReadonlyMember = observer(function ReadonlyMember(props: TReadonlyMemberProps) {
const { className, icon: Icon, hideIcon = false, value, placeholder, multiple = false } = props;
const { t } = useTranslation();
const { getUserDetails } = useMember();
const memberIds = Array.isArray(value) ? value : value ? [value] : [];
const members = memberIds.map((id) => getUserDetails(id)).filter(Boolean);
if (members.length === 0) {
return (
<div className={cn("flex items-center gap-1 text-body-xs-regular", className)}>
{<ButtonAvatars showTooltip={false} userIds={value} icon={Icon} />}
<span className="flex-grow truncate">{placeholder ?? t("common.none")}</span>
</div>
);
}
if (multiple) {
return (
<div className={cn("flex items-center gap-1 text-body-xs-regular", className)}>
{!hideIcon && Icon && <Icon className="h-3 w-3 flex-shrink-0" />}
<ButtonAvatars showTooltip={false} userIds={memberIds} />
</div>
);
}
const member = members[0];
return (
<div className={cn("flex items-center gap-1 text-body-xs-regular", className)}>
{!hideIcon && Icon && <Icon className="size-4 flex-shrink-0" />}
<div className="flex items-center gap-2">
<div className="flex size-4 items-center justify-center rounded-full bg-layer-1">
<span className="text-13 font-medium">
{member?.display_name?.charAt(0) ?? member?.email?.charAt(0) ?? "?"}
</span>
</div>
<span className="flex-grow truncate">{member?.display_name ?? member?.email}</span>
</div>
</div>
);
});

View File

@@ -0,0 +1,79 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { useEffect } from "react";
import { observer } from "mobx-react";
import { WorkItemsOutline } from "@makeplane/propel/icons";
// plane imports
import { useTranslation } from "@plane/i18n";
import { cn } from "@plane/utils";
// hooks
import { useModule } from "@/hooks/store/use-module";
export type TReadonlyModuleProps = {
className?: string;
hideIcon?: boolean;
value: string | string[] | null;
placeholder?: string;
projectId: string | undefined;
multiple?: boolean;
showCount?: boolean;
workspaceSlug: string;
};
export const ReadonlyModule = observer(function ReadonlyModule(props: TReadonlyModuleProps) {
const {
className,
hideIcon = false,
value,
placeholder,
multiple = false,
showCount = true,
workspaceSlug,
projectId,
} = props;
const { t } = useTranslation();
const { getModuleById, fetchModules } = useModule();
const moduleIds = Array.isArray(value) ? value : value ? [value] : [];
const modules = moduleIds.map((id) => getModuleById(id)).filter(Boolean);
useEffect(() => {
if (moduleIds.length > 0 && projectId) {
fetchModules(workspaceSlug, projectId);
}
}, [value, projectId, workspaceSlug]);
if (modules.length === 0) {
return (
<div className={cn("flex items-center gap-1 text-body-xs-regular", className)}>
{!hideIcon && <WorkItemsOutline className="size-4 flex-shrink-0" />}
<span className="flex-grow truncate">{placeholder ?? t("common.none")}</span>
</div>
);
}
if (multiple) {
const displayText =
showCount && modules.length > 1 ? `${modules[0]?.name} +${modules.length - 1}` : modules[0]?.name;
return (
<div className={cn("flex items-center gap-1 text-body-xs-regular", className)}>
{!hideIcon && <WorkItemsOutline className="size-4 flex-shrink-0" />}
<span className="flex-grow truncate">{displayText}</span>
</div>
);
}
const moduleItem = modules[0];
return (
<div className={cn("flex items-center gap-2 text-body-xs-regular", className)}>
{!hideIcon && <WorkItemsOutline className="size-4 flex-shrink-0" />}
<span className="flex-grow truncate">{moduleItem?.name}</span>
</div>
);
});

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
// plane imports
import { ISSUE_PRIORITIES } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { PriorityIcon } from "@plane/propel/icons";
import type { TIssuePriorities } from "@plane/types";
import { cn } from "@plane/utils";
export type TReadonlyPriorityProps = {
className?: string;
hideIcon?: boolean;
value: TIssuePriorities | undefined | null;
placeholder?: string;
};
export const ReadonlyPriority = observer(function ReadonlyPriority(props: TReadonlyPriorityProps) {
const { className, hideIcon = false, value, placeholder } = props;
const { t } = useTranslation();
const priorityDetails = ISSUE_PRIORITIES.find((p) => p.key === value);
return (
<div className={cn("flex items-center gap-1 text-body-xs-regular", className)}>
{!hideIcon && <PriorityIcon priority={value ?? "none"} size={12} className="flex-shrink-0" withContainer />}
<span className="flex-grow truncate">{priorityDetails?.title ?? placeholder ?? t("common.none")}</span>
</div>
);
});

View File

@@ -0,0 +1,74 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { useEffect, useState } from "react";
import { observer } from "mobx-react";
// plane imports
import { useTranslation } from "@plane/i18n";
import { StateGroupIcon } from "@plane/propel/icons";
import { Loader } from "@plane/ui";
import { cn } from "@plane/utils";
// hooks
import { useProjectState } from "@/hooks/store/use-project-state";
export type TReadonlyStateProps = {
className?: string;
iconSize?: string;
hideIcon?: boolean;
value: string | undefined | null;
placeholder?: string;
projectId: string | undefined;
workspaceSlug: string;
};
export const ReadonlyState = observer(function ReadonlyState(props: TReadonlyStateProps) {
const { className, iconSize = "size-4", hideIcon = false, value, placeholder, projectId, workspaceSlug } = props;
// states
const [stateLoader, setStateLoader] = useState(false);
const { t } = useTranslation();
const { getStateById, getProjectStateIds, fetchProjectStates } = useProjectState();
// derived values
const stateIds = getProjectStateIds(projectId);
const state = getStateById(value);
// fetch states if not provided
const fetchStates = async () => {
if ((stateIds === undefined || stateIds.length === 0) && projectId) {
setStateLoader(true);
try {
await fetchProjectStates(workspaceSlug, projectId);
} finally {
setStateLoader(false);
}
}
};
useEffect(() => {
fetchStates();
}, [projectId, workspaceSlug]);
if (stateLoader) {
return (
<Loader className={cn("flex items-center gap-1 text-body-xs-regular", className)}>
<Loader.Item height="16px" width="16px" className="rounded-full" />
<Loader.Item height="16px" width="50px" />
</Loader>
);
}
return (
<div className={cn("flex items-center gap-1 text-body-xs-regular", className)}>
{!hideIcon && (
<StateGroupIcon
stateGroup={state?.group ?? "backlog"}
className={cn(iconSize, "flex-shrink-0")}
color={state?.color}
/>
)}
<span className="flex-grow truncate">{state?.name ?? placeholder ?? t("common.none")}</span>
</div>
);
});