Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ec2c61e0d | |||
| 641ef9fd14 | |||
| 68a4d02847 | |||
| db8c4f92e8 | |||
| a6cc2c93f8 | |||
| 0428ea06f6 | |||
| 7082f7014d | |||
| c7c729d81b | |||
| 97eb8d43d4 | |||
| 1217af1d5f | |||
| 13083a77eb |
@@ -1,5 +1,6 @@
|
||||
# Python imports
|
||||
import json
|
||||
import base64
|
||||
|
||||
# Django imports
|
||||
from django.contrib.postgres.aggregates import ArrayAgg
|
||||
@@ -480,9 +481,7 @@ class IssueViewSet(BaseViewSet):
|
||||
project = Project.objects.get(pk=project_id, workspace__slug=slug)
|
||||
|
||||
issue = (
|
||||
Issue.objects.filter(
|
||||
project_id=self.kwargs.get("project_id")
|
||||
)
|
||||
Issue.objects.filter(project_id=self.kwargs.get("project_id"))
|
||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.select_related("workspace", "project", "state", "parent")
|
||||
.prefetch_related("assignees", "labels", "issue_module__module")
|
||||
@@ -517,7 +516,7 @@ class IssueViewSet(BaseViewSet):
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
)
|
||||
.filter(pk=pk)
|
||||
.annotate(
|
||||
label_ids=Coalesce(
|
||||
@@ -852,9 +851,21 @@ class IssuePaginatedViewSet(BaseViewSet):
|
||||
)
|
||||
).distinct()
|
||||
|
||||
def process_paginated_result(self, fields, results, timezone):
|
||||
def process_paginated_result(
|
||||
self, fields, results, timezone, description_binary_required=False
|
||||
):
|
||||
paginated_data = results.values(*fields)
|
||||
|
||||
# handling the description binary field
|
||||
if description_binary_required:
|
||||
for item in paginated_data:
|
||||
if item["description_binary"]:
|
||||
item["description_binary"] = base64.b64encode(
|
||||
item["description_binary"]
|
||||
).decode("utf-8")
|
||||
else:
|
||||
item["description_binary"] = None
|
||||
|
||||
# converting the datetime fields in paginated data
|
||||
datetime_fields = ["created_at", "updated_at"]
|
||||
paginated_data = user_timezone_converter(
|
||||
@@ -867,6 +878,9 @@ class IssuePaginatedViewSet(BaseViewSet):
|
||||
def list(self, request, slug, project_id):
|
||||
cursor = request.GET.get("cursor", None)
|
||||
is_description_required = request.GET.get("description", "false")
|
||||
is_description_binary_required = request.GET.get(
|
||||
"description_binary", "false"
|
||||
)
|
||||
updated_at = request.GET.get("updated_at__gt", None)
|
||||
|
||||
# required fields
|
||||
@@ -902,6 +916,9 @@ class IssuePaginatedViewSet(BaseViewSet):
|
||||
if str(is_description_required).lower() == "true":
|
||||
required_fields.append("description_html")
|
||||
|
||||
if str(is_description_binary_required).lower() == "true":
|
||||
required_fields.append("description_binary")
|
||||
|
||||
# querying issues
|
||||
base_queryset = Issue.issue_objects.filter(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
@@ -966,12 +983,19 @@ class IssuePaginatedViewSet(BaseViewSet):
|
||||
),
|
||||
)
|
||||
|
||||
is_description_binary_required = False
|
||||
if required_fields.__contains__("description_binary"):
|
||||
is_description_binary_required = True
|
||||
|
||||
paginated_data = paginate(
|
||||
base_queryset=base_queryset,
|
||||
queryset=queryset,
|
||||
cursor=cursor,
|
||||
on_result=lambda results: self.process_paginated_result(
|
||||
required_fields, results, request.user.user_timezone
|
||||
required_fields,
|
||||
results,
|
||||
request.user.user_timezone,
|
||||
is_description_binary_required,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1077,7 +1101,6 @@ class IssueDetailEndpoint(BaseAPIView):
|
||||
|
||||
|
||||
class IssueBulkUpdateDateEndpoint(BaseAPIView):
|
||||
|
||||
def validate_dates(
|
||||
self, current_start, current_target, new_start, new_target
|
||||
):
|
||||
@@ -1093,7 +1116,6 @@ class IssueBulkUpdateDateEndpoint(BaseAPIView):
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def post(self, request, slug, project_id):
|
||||
|
||||
updates = request.data.get("updates", [])
|
||||
|
||||
issue_ids = [update["id"] for update in updates]
|
||||
|
||||
@@ -429,6 +429,9 @@ class ProjectViewSet(BaseViewSet):
|
||||
)
|
||||
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
intake_view = request.data.get(
|
||||
"inbox_view", request.data.get("intake_view", False)
|
||||
)
|
||||
|
||||
project = Project.objects.get(pk=pk)
|
||||
current_instance = json.dumps(
|
||||
@@ -442,14 +445,17 @@ class ProjectViewSet(BaseViewSet):
|
||||
|
||||
serializer = ProjectSerializer(
|
||||
project,
|
||||
data={**request.data},
|
||||
data={
|
||||
**request.data,
|
||||
"intake_view": intake_view,
|
||||
},
|
||||
context={"workspace_id": workspace.id},
|
||||
partial=True,
|
||||
)
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
if serializer.data["intake_view"] or request.data.get("inbox_view", False):
|
||||
if intake_view:
|
||||
intake = Intake.objects.filter(
|
||||
project=project,
|
||||
is_default=True,
|
||||
|
||||
@@ -233,6 +233,7 @@ def filter_assignees(params, issue_filter, method, prefix=""):
|
||||
and params.get("assignees") != "null"
|
||||
):
|
||||
issue_filter[f"{prefix}assignees__in"] = params.get("assignees")
|
||||
issue_filter[f"{prefix}issue_assignee__deleted_at__isnull"] = True
|
||||
return issue_filter
|
||||
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ export const CoreEditorExtensionsWithoutProps = [
|
||||
CustomQuoteExtension,
|
||||
CustomHorizontalRule.configure({
|
||||
HTMLAttributes: {
|
||||
class: "my-4 border-custom-border-400",
|
||||
class: "py-4 border-custom-border-400",
|
||||
},
|
||||
}),
|
||||
CustomLinkExtension.configure({
|
||||
|
||||
@@ -78,7 +78,7 @@ export const CoreEditorExtensions = (args: TArguments) => {
|
||||
DropHandlerExtension(),
|
||||
CustomHorizontalRule.configure({
|
||||
HTMLAttributes: {
|
||||
class: "my-4 border-custom-border-400",
|
||||
class: "py-4 border-custom-border-400",
|
||||
},
|
||||
}),
|
||||
CustomKeymap,
|
||||
|
||||
@@ -67,7 +67,7 @@ export const CoreReadOnlyEditorExtensions = (props: Props) => {
|
||||
CustomQuoteExtension,
|
||||
CustomHorizontalRule.configure({
|
||||
HTMLAttributes: {
|
||||
class: "my-4 border-custom-border-400",
|
||||
class: "py-4 border-custom-border-400",
|
||||
},
|
||||
}),
|
||||
CustomLinkExtension.configure({
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
cursor: text;
|
||||
font-family: var(--font-style);
|
||||
font-size: var(--font-size-regular);
|
||||
line-height: 1.2;
|
||||
font-weight: 400;
|
||||
color: inherit;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
@@ -248,11 +248,6 @@ div[data-type="horizontalRule"] {
|
||||
}
|
||||
}
|
||||
|
||||
/* image resizer */
|
||||
.moveable-control-box {
|
||||
z-index: 10 !important;
|
||||
}
|
||||
|
||||
/* Cursor styles for the inline code blocks */
|
||||
@keyframes blink {
|
||||
49% {
|
||||
@@ -314,13 +309,23 @@ ul[data-type="taskList"] ul[data-type="taskList"] {
|
||||
}
|
||||
/* end numbered, bulleted and to-do lists spacing */
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* tailwind typography */
|
||||
.prose :where(h1):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
&:not(:first-child) {
|
||||
margin-top: 2rem;
|
||||
padding-top: 28px;
|
||||
}
|
||||
|
||||
margin-bottom: 4px;
|
||||
padding-bottom: 4px;
|
||||
font-size: var(--font-size-h1);
|
||||
line-height: var(--line-height-h1);
|
||||
font-weight: 600;
|
||||
@@ -328,10 +333,10 @@ ul[data-type="taskList"] ul[data-type="taskList"] {
|
||||
|
||||
.prose :where(h2):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
&:not(:first-child) {
|
||||
margin-top: 1.4rem;
|
||||
padding-top: 28px;
|
||||
}
|
||||
|
||||
margin-bottom: 1px;
|
||||
padding-bottom: 4px;
|
||||
font-size: var(--font-size-h2);
|
||||
line-height: var(--line-height-h2);
|
||||
font-weight: 600;
|
||||
@@ -339,10 +344,10 @@ ul[data-type="taskList"] ul[data-type="taskList"] {
|
||||
|
||||
.prose :where(h3):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
&:not(:first-child) {
|
||||
margin-top: 1rem;
|
||||
padding-top: 28px;
|
||||
}
|
||||
|
||||
margin-bottom: 1px;
|
||||
padding-bottom: 4px;
|
||||
font-size: var(--font-size-h3);
|
||||
line-height: var(--line-height-h3);
|
||||
font-weight: 600;
|
||||
@@ -350,10 +355,10 @@ ul[data-type="taskList"] ul[data-type="taskList"] {
|
||||
|
||||
.prose :where(h4):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
&:not(:first-child) {
|
||||
margin-top: 1rem;
|
||||
padding-top: 28px;
|
||||
}
|
||||
|
||||
margin-bottom: 1px;
|
||||
padding-bottom: 4px;
|
||||
font-size: var(--font-size-h4);
|
||||
line-height: var(--line-height-h4);
|
||||
font-weight: 600;
|
||||
@@ -361,10 +366,10 @@ ul[data-type="taskList"] ul[data-type="taskList"] {
|
||||
|
||||
.prose :where(h5):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
&:not(:first-child) {
|
||||
margin-top: 1rem;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
margin-bottom: 1px;
|
||||
padding-bottom: 4px;
|
||||
font-size: var(--font-size-h5);
|
||||
line-height: var(--line-height-h5);
|
||||
font-weight: 600;
|
||||
@@ -372,30 +377,40 @@ ul[data-type="taskList"] ul[data-type="taskList"] {
|
||||
|
||||
.prose :where(h6):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
&:not(:first-child) {
|
||||
margin-top: 1rem;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
margin-bottom: 1px;
|
||||
padding-bottom: 4px;
|
||||
font-size: var(--font-size-h6);
|
||||
line-height: var(--line-height-h6);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.prose :where(p):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
&:not(:first-child) {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
margin-bottom: 1px;
|
||||
padding: 3px 0;
|
||||
font-size: var(--font-size-regular);
|
||||
line-height: var(--line-height-regular);
|
||||
}
|
||||
|
||||
p + p {
|
||||
padding-top: 8px !important;
|
||||
}
|
||||
|
||||
.prose :where(ol):not(:where([class~="not-prose"], [class~="not-prose"] *)) li p,
|
||||
.prose :where(ul):not(:where([class~="not-prose"], [class~="not-prose"] *)) li p {
|
||||
font-size: var(--font-size-list);
|
||||
@@ -432,11 +447,6 @@ ul[data-type="taskList"] ul[data-type="taskList"] {
|
||||
[data-text-color="purple"] {
|
||||
color: var(--editor-colors-purple-text);
|
||||
}
|
||||
/* [data-text-color="pink-blue-gradient"] {
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
background-image: linear-gradient(90deg, #a961cd 50%, #e75962 100%);
|
||||
} */
|
||||
/* end text colors */
|
||||
|
||||
/* background colors */
|
||||
|
||||
@@ -46,8 +46,8 @@
|
||||
--font-size-h5: 1.125rem;
|
||||
--font-size-h6: 1rem;
|
||||
--font-size-regular: 1rem;
|
||||
--font-size-code: 0.85rem;
|
||||
--font-size-list: var(--font-size-regular);
|
||||
--font-size-code: var(--font-size-regular);
|
||||
|
||||
--line-height-h1: 2.25rem;
|
||||
--line-height-h2: 2rem;
|
||||
@@ -56,8 +56,8 @@
|
||||
--line-height-h5: 1.5rem;
|
||||
--line-height-h6: 1.5rem;
|
||||
--line-height-regular: 1.5rem;
|
||||
--line-height-code: 1.5rem;
|
||||
--line-height-list: var(--line-height-regular);
|
||||
--line-height-code: var(--line-height-regular);
|
||||
}
|
||||
&.small-font {
|
||||
--font-size-h1: 1.4rem;
|
||||
@@ -67,8 +67,8 @@
|
||||
--font-size-h5: 0.9rem;
|
||||
--font-size-h6: 0.8rem;
|
||||
--font-size-regular: 0.8rem;
|
||||
--font-size-code: 0.8rem;
|
||||
--font-size-list: var(--font-size-regular);
|
||||
--font-size-code: var(--font-size-regular);
|
||||
|
||||
--line-height-h1: 1.8rem;
|
||||
--line-height-h2: 1.6rem;
|
||||
@@ -77,8 +77,8 @@
|
||||
--line-height-h5: 1.2rem;
|
||||
--line-height-h6: 1.2rem;
|
||||
--line-height-regular: 1.2rem;
|
||||
--line-height-code: 1.2rem;
|
||||
--line-height-list: var(--line-height-regular);
|
||||
--line-height-code: var(--line-height-regular);
|
||||
}
|
||||
/* end font sizes and line heights */
|
||||
|
||||
|
||||
@@ -101,6 +101,19 @@
|
||||
--color-sidebar-shadow-2xl: var(--color-shadow-2xl);
|
||||
--color-sidebar-shadow-3xl: var(--color-shadow-3xl);
|
||||
--color-sidebar-shadow-4xl: var(--color-shadow-4xl);
|
||||
|
||||
/* pi */
|
||||
--color-pi-50: var(--color-background-90);
|
||||
--color-pi-100: var(--color-background-90);
|
||||
--color-pi-200: var(--color-primary-200);
|
||||
--color-pi-300: var(--color-primary-200);
|
||||
--color-pi-400: var(--color-primary-200);
|
||||
--color-pi-500: var(--color-primary-200);
|
||||
--color-pi-600: 151, 150, 246;
|
||||
--color-pi-700: var(--color-primary-100);
|
||||
--color-pi-800: 57, 56, 149;
|
||||
--color-pi-900: 30, 29, 78;
|
||||
--color-pi-950: 14, 14, 37;
|
||||
}
|
||||
|
||||
[data-theme="light"],
|
||||
@@ -110,6 +123,19 @@
|
||||
--color-background-100: 255, 255, 255; /* primary bg */
|
||||
--color-background-90: 247, 247, 247; /* secondary bg */
|
||||
--color-background-80: 232, 232, 232; /* tertiary bg */
|
||||
|
||||
/* pi */
|
||||
--color-pi-50: var(--color-background-90);
|
||||
--color-pi-100: var(--color-background-90);
|
||||
--color-pi-200: var(--color-primary-200);
|
||||
--color-pi-300: var(--color-primary-200);
|
||||
--color-pi-400: var(--color-primary-200);
|
||||
--color-pi-500: var(--color-primary-200);
|
||||
--color-pi-600: 151, 150, 246;
|
||||
--color-pi-700: var(--color-primary-100);
|
||||
--color-pi-800: 57, 56, 149;
|
||||
--color-pi-900: 30, 29, 78;
|
||||
--color-pi-950: 14, 14, 37;
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
@@ -200,6 +226,18 @@
|
||||
--color-shadow-xl: 0px 0px 14px 0px rgba(0, 0, 0, 0.25), 0px 6px 10px 0px rgba(0, 0, 0, 0.55);
|
||||
--color-shadow-2xl: 0px 0px 18px 0px rgba(0, 0, 0, 0.25), 0px 8px 12px 0px rgba(0, 0, 0, 0.6);
|
||||
--color-shadow-3xl: 0px 4px 24px 0px rgba(0, 0, 0, 0.3), 0px 12px 40px 0px rgba(0, 0, 0, 0.65);
|
||||
/* pi */
|
||||
--color-pi-50: var(--color-background-90);
|
||||
--color-pi-100: var(--color-background-90);
|
||||
--color-pi-200: var(--color-primary-200);
|
||||
--color-pi-300: var(--color-primary-200);
|
||||
--color-pi-400: var(--color-primary-200);
|
||||
--color-pi-500: var(--color-primary-200);
|
||||
--color-pi-600: 151, 150, 246;
|
||||
--color-pi-700: var(--color-primary-100);
|
||||
--color-pi-800: 57, 56, 149;
|
||||
--color-pi-900: 30, 29, 78;
|
||||
--color-pi-950: 14, 14, 37;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
|
||||
@@ -54,7 +54,7 @@ export const RichTextEditor = forwardRef<EditorRefApi, RichTextEditorWrapperProp
|
||||
suggestions: mentionSuggestions,
|
||||
}}
|
||||
{...rest}
|
||||
containerClassName={cn("relative pl-3 pb-3", containerClassName)}
|
||||
containerClassName={cn("relative pl-3", containerClassName)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -286,7 +286,7 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
|
||||
{shouldRenderDuplicateModal && (
|
||||
<div
|
||||
ref={modalContainerRef}
|
||||
className="relative flex flex-col gap-2.5 h-full px-3 py-4 rounded-lg shadow-xl bg-pi-50"
|
||||
className="relative flex flex-col gap-2.5 px-3 py-4 rounded-lg shadow-xl bg-pi-50"
|
||||
style={{ maxHeight: formRef?.current?.offsetHeight ? `${formRef.current.offsetHeight}px` : "436px" }}
|
||||
>
|
||||
<DuplicateModalRoot
|
||||
|
||||
@@ -91,7 +91,7 @@ export const InboxIssueProperties: FC<TInboxIssueProperties> = observer((props)
|
||||
{/* labels */}
|
||||
<div className="h-7">
|
||||
<IssueLabelSelect
|
||||
setIsOpen={() => { }}
|
||||
setIsOpen={() => {}}
|
||||
value={data?.label_ids || []}
|
||||
onChange={(labelIds) => handleData("label_ids", labelIds)}
|
||||
projectId={projectId}
|
||||
@@ -171,13 +171,13 @@ export const InboxIssueProperties: FC<TInboxIssueProperties> = observer((props)
|
||||
|
||||
{/* add parent */}
|
||||
{isVisible && (
|
||||
<>
|
||||
<div className="h-7">
|
||||
{selectedParentIssue ? (
|
||||
<CustomMenu
|
||||
customButton={
|
||||
<button
|
||||
type="button"
|
||||
className="flex cursor-pointer items-center justify-between gap-1 rounded border-[0.5px] border-custom-border-300 px-2 py-1.5 text-xs hover:bg-custom-background-80"
|
||||
className="flex cursor-pointer items-center justify-between gap-1 h-full rounded border-[0.5px] border-custom-border-300 px-2 py-0.5 text-xs hover:bg-custom-background-80"
|
||||
>
|
||||
<LayoutPanelTop className="h-3 w-3 flex-shrink-0" />
|
||||
<span className="whitespace-nowrap">
|
||||
@@ -188,6 +188,8 @@ export const InboxIssueProperties: FC<TInboxIssueProperties> = observer((props)
|
||||
</button>
|
||||
}
|
||||
placement="bottom-start"
|
||||
className="h-full w-full"
|
||||
customButtonClassName="h-full"
|
||||
tabIndex={getIndex("parent_id")}
|
||||
>
|
||||
<>
|
||||
@@ -208,7 +210,7 @@ export const InboxIssueProperties: FC<TInboxIssueProperties> = observer((props)
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="flex cursor-pointer items-center justify-between gap-1 rounded border-[0.5px] border-custom-border-300 px-2 py-1.5 text-xs hover:bg-custom-background-80"
|
||||
className="flex cursor-pointer items-center justify-between gap-1 h-full rounded border-[0.5px] border-custom-border-300 px-2 py-0.5 text-xs hover:bg-custom-background-80"
|
||||
onClick={() => setParentIssueModalOpen(true)}
|
||||
>
|
||||
<LayoutPanelTop className="h-3 w-3 flex-shrink-0" />
|
||||
@@ -226,7 +228,7 @@ export const InboxIssueProperties: FC<TInboxIssueProperties> = observer((props)
|
||||
projectId={projectId}
|
||||
issueId={undefined}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -151,6 +151,7 @@ export const IssueDetailWidgetModals: FC<Props> = observer((props) => {
|
||||
data={createUpdateModalData}
|
||||
onClose={handleCreateUpdateModalClose}
|
||||
onSubmit={handleCreateUpdateModalOnSubmit}
|
||||
isProjectSelectionDisabled
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -162,7 +163,6 @@ export const IssueDetailWidgetModals: FC<Props> = observer((props) => {
|
||||
handleClose={handleExistingIssuesModalClose}
|
||||
searchParams={existingIssuesModalSearchParams}
|
||||
handleOnSubmit={handleExistingIssuesModalOnSubmit}
|
||||
workspaceLevelToggle
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { IssueCommentCreate } from "@/components/issues";
|
||||
import { IssueActivityCommentRoot } from "@/components/issues/issue-detail";
|
||||
// hooks
|
||||
import { useIssueDetail, useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { useIssueDetail, useProject, useUser, useUserPermissions } from "@/hooks/store";
|
||||
// plane web components
|
||||
import { ActivityFilterRoot, IssueActivityWorklogCreateButton } from "@/plane-web/components/issues/worklog";
|
||||
// plane web constants
|
||||
@@ -38,15 +38,25 @@ export type TActivityOperations = {
|
||||
|
||||
export const IssueActivity: FC<TIssueActivity> = observer((props) => {
|
||||
const { workspaceSlug, projectId, issueId, disabled = false, isIntakeIssue = false } = props;
|
||||
// hooks
|
||||
const { createComment, updateComment, removeComment } = useIssueDetail();
|
||||
const { projectPermissionsByWorkspaceSlugAndProjectId } = useUserPermissions();
|
||||
const { getProjectById } = useProject();
|
||||
//derived values
|
||||
const isGuest = (projectPermissionsByWorkspaceSlugAndProjectId(workspaceSlug, projectId) ?? EUserPermissions.GUEST) === EUserPermissions.GUEST;
|
||||
const isWorklogButtonEnabled = !isIntakeIssue && !isGuest;
|
||||
// state
|
||||
const [selectedFilters, setSelectedFilters] = useState<TActivityFilters[]>(defaultActivityFilters);
|
||||
// hooks
|
||||
const {
|
||||
issue: { getIssueById },
|
||||
createComment,
|
||||
updateComment,
|
||||
removeComment,
|
||||
} = useIssueDetail();
|
||||
const { projectPermissionsByWorkspaceSlugAndProjectId } = useUserPermissions();
|
||||
const { getProjectById } = useProject();
|
||||
const { data: currentUser } = useUser();
|
||||
//derived values
|
||||
const issue = issueId ? getIssueById(issueId) : undefined;
|
||||
const currentUserProjectRole = projectPermissionsByWorkspaceSlugAndProjectId(workspaceSlug, projectId);
|
||||
const isAdmin = (currentUserProjectRole ?? EUserPermissions.GUEST) === EUserPermissions.ADMIN;
|
||||
const isGuest = (currentUserProjectRole ?? EUserPermissions.GUEST) === EUserPermissions.GUEST;
|
||||
const isAssigned = issue?.assignee_ids && currentUser?.id ? issue?.assignee_ids.includes(currentUser?.id) : false;
|
||||
const isWorklogButtonEnabled = !isIntakeIssue && !isGuest && (isAdmin || isAssigned);
|
||||
// toggle filter
|
||||
const toggleFilter = (filter: TActivityFilters) => {
|
||||
setSelectedFilters((prevFilters) => {
|
||||
|
||||
@@ -37,6 +37,7 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
||||
moveToIssue = false,
|
||||
modalTitle,
|
||||
primaryButtonText,
|
||||
isProjectSelectionDisabled = false,
|
||||
} = props;
|
||||
const issueStoreType = useIssueStoreType();
|
||||
|
||||
@@ -361,6 +362,7 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
||||
moveToIssue={moveToIssue}
|
||||
isDuplicateModalOpen={isDuplicateModalOpen}
|
||||
handleDuplicateIssueModal={handleDuplicateIssueModal}
|
||||
isProjectSelectionDisabled={isProjectSelectionDisabled}
|
||||
/>
|
||||
) : (
|
||||
<IssueFormRoot
|
||||
@@ -383,6 +385,7 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
||||
primaryButtonText={primaryButtonText}
|
||||
isDuplicateModalOpen={isDuplicateModalOpen}
|
||||
handleDuplicateIssueModal={handleDuplicateIssueModal}
|
||||
isProjectSelectionDisabled={isProjectSelectionDisabled}
|
||||
/>
|
||||
)}
|
||||
</ModalCore>
|
||||
|
||||
@@ -262,58 +262,62 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{parentId ? (
|
||||
<CustomMenu
|
||||
customButton={
|
||||
<button
|
||||
type="button"
|
||||
className="flex cursor-pointer items-center justify-between gap-1 rounded border-[0.5px] border-custom-border-300 px-2 py-1.5 text-xs hover:bg-custom-background-80"
|
||||
>
|
||||
{selectedParentIssue?.project_id && (
|
||||
<IssueIdentifier
|
||||
projectId={selectedParentIssue.project_id}
|
||||
issueTypeId={selectedParentIssue.type_id}
|
||||
projectIdentifier={selectedParentIssue?.project__identifier}
|
||||
issueSequenceId={selectedParentIssue.sequence_id}
|
||||
textContainerClassName="text-xs"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
}
|
||||
placement="bottom-start"
|
||||
tabIndex={getIndex("parent_id")}
|
||||
>
|
||||
<>
|
||||
<CustomMenu.MenuItem className="!p-1" onClick={() => setParentIssueListModalOpen(true)}>
|
||||
Change parent issue
|
||||
</CustomMenu.MenuItem>
|
||||
<Controller
|
||||
control={control}
|
||||
name="parent_id"
|
||||
render={({ field: { onChange } }) => (
|
||||
<CustomMenu.MenuItem
|
||||
className="!p-1"
|
||||
onClick={() => {
|
||||
onChange(null);
|
||||
handleFormChange();
|
||||
}}
|
||||
>
|
||||
Remove parent issue
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
</CustomMenu>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="flex cursor-pointer items-center justify-between gap-1 rounded border-[0.5px] border-custom-border-300 px-2 py-1.5 text-xs hover:bg-custom-background-80"
|
||||
onClick={() => setParentIssueListModalOpen(true)}
|
||||
>
|
||||
<LayoutPanelTop className="h-3 w-3 flex-shrink-0" />
|
||||
<span className="whitespace-nowrap">Add parent</span>
|
||||
</button>
|
||||
)}
|
||||
<div className="h-7">
|
||||
{parentId ? (
|
||||
<CustomMenu
|
||||
customButton={
|
||||
<button
|
||||
type="button"
|
||||
className="flex cursor-pointer items-center justify-between gap-1 h-full rounded border-[0.5px] border-custom-border-300 px-2 py-0.5 text-xs hover:bg-custom-background-80"
|
||||
>
|
||||
{selectedParentIssue?.project_id && (
|
||||
<IssueIdentifier
|
||||
projectId={selectedParentIssue.project_id}
|
||||
issueTypeId={selectedParentIssue.type_id}
|
||||
projectIdentifier={selectedParentIssue?.project__identifier}
|
||||
issueSequenceId={selectedParentIssue.sequence_id}
|
||||
textContainerClassName="text-xs"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
}
|
||||
placement="bottom-start"
|
||||
className="h-full w-full"
|
||||
customButtonClassName="h-full"
|
||||
tabIndex={getIndex("parent_id")}
|
||||
>
|
||||
<>
|
||||
<CustomMenu.MenuItem className="!p-1" onClick={() => setParentIssueListModalOpen(true)}>
|
||||
Change parent issue
|
||||
</CustomMenu.MenuItem>
|
||||
<Controller
|
||||
control={control}
|
||||
name="parent_id"
|
||||
render={({ field: { onChange } }) => (
|
||||
<CustomMenu.MenuItem
|
||||
className="!p-1"
|
||||
onClick={() => {
|
||||
onChange(null);
|
||||
handleFormChange();
|
||||
}}
|
||||
>
|
||||
Remove parent issue
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
</CustomMenu>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="flex cursor-pointer items-center justify-between gap-1 h-full rounded border-[0.5px] border-custom-border-300 px-2 py-0.5 text-xs hover:bg-custom-background-80"
|
||||
onClick={() => setParentIssueListModalOpen(true)}
|
||||
>
|
||||
<LayoutPanelTop className="h-3 w-3 flex-shrink-0" />
|
||||
<span className="whitespace-nowrap">Add parent</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="parent_id"
|
||||
|
||||
@@ -38,6 +38,7 @@ export interface DraftIssueProps {
|
||||
};
|
||||
isDuplicateModalOpen: boolean;
|
||||
handleDuplicateIssueModal: (isOpen: boolean) => void;
|
||||
isProjectSelectionDisabled?: boolean;
|
||||
}
|
||||
|
||||
export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
|
||||
@@ -58,6 +59,7 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
|
||||
primaryButtonText,
|
||||
isDuplicateModalOpen,
|
||||
handleDuplicateIssueModal,
|
||||
isProjectSelectionDisabled = false,
|
||||
} = props;
|
||||
// states
|
||||
const [issueDiscardModal, setIssueDiscardModal] = useState(false);
|
||||
@@ -179,6 +181,7 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
|
||||
primaryButtonText={primaryButtonText}
|
||||
isDuplicateModalOpen={isDuplicateModalOpen}
|
||||
handleDuplicateIssueModal={handleDuplicateIssueModal}
|
||||
isProjectSelectionDisabled={isProjectSelectionDisabled}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -71,6 +71,7 @@ export interface IssueFormProps {
|
||||
};
|
||||
isDuplicateModalOpen: boolean;
|
||||
handleDuplicateIssueModal: (isOpen: boolean) => void;
|
||||
isProjectSelectionDisabled?: boolean;
|
||||
}
|
||||
|
||||
export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
||||
@@ -93,6 +94,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
||||
},
|
||||
isDuplicateModalOpen,
|
||||
handleDuplicateIssueModal,
|
||||
isProjectSelectionDisabled = false,
|
||||
} = props;
|
||||
|
||||
// states
|
||||
@@ -336,7 +338,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
||||
<div className="flex items-center gap-x-1">
|
||||
<IssueProjectSelect
|
||||
control={control}
|
||||
disabled={!!data?.id || !!data?.sourceIssueId}
|
||||
disabled={!!data?.id || !!data?.sourceIssueId || isProjectSelectionDisabled}
|
||||
handleFormChange={handleFormChange}
|
||||
/>
|
||||
{projectId && (
|
||||
@@ -511,7 +513,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
||||
{shouldRenderDuplicateModal && (
|
||||
<div
|
||||
ref={modalContainerRef}
|
||||
className="relative flex flex-col gap-2.5 h-full px-3 py-4 rounded-lg shadow-xl bg-pi-50"
|
||||
className="relative flex flex-col gap-2.5 px-3 py-4 rounded-lg shadow-xl bg-pi-50"
|
||||
style={{ maxHeight: formRef?.current?.offsetHeight ? `${formRef.current.offsetHeight}px` : "436px" }}
|
||||
>
|
||||
<DuplicateModalRoot
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface IssuesModalProps {
|
||||
default: string;
|
||||
loading: string;
|
||||
};
|
||||
isProjectSelectionDisabled?: boolean;
|
||||
}
|
||||
|
||||
export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer(
|
||||
|
||||
@@ -25,9 +25,9 @@ export const PageEditorTitle: React.FC<Props> = observer((props) => {
|
||||
// page filters
|
||||
const { fontSize } = usePageFilters();
|
||||
// ui
|
||||
const titleClassName = cn("bg-transparent tracking-[-2%] font-semibold", {
|
||||
"text-[1.6rem] leading-[1.8rem]": fontSize === "small-font",
|
||||
"text-[2rem] leading-[2.25rem]": fontSize === "large-font",
|
||||
const titleClassName = cn("bg-transparent tracking-[-2%] font-bold", {
|
||||
"text-[1.6rem] leading-[1.9rem]": fontSize === "small-font",
|
||||
"text-[2rem] leading-[2.375rem]": fontSize === "large-font",
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -120,6 +120,19 @@
|
||||
--color-sidebar-shadow-2xl: var(--color-shadow-2xl);
|
||||
--color-sidebar-shadow-3xl: var(--color-shadow-3xl);
|
||||
--color-sidebar-shadow-4xl: var(--color-shadow-4xl);
|
||||
|
||||
/* pi */
|
||||
--color-pi-50: var(--color-background-90);
|
||||
--color-pi-100: var(--color-background-90);
|
||||
--color-pi-200: var(--color-primary-200);
|
||||
--color-pi-300: var(--color-primary-200);
|
||||
--color-pi-400: var(--color-primary-200);
|
||||
--color-pi-500: var(--color-primary-200);
|
||||
--color-pi-600: 151, 150, 246;
|
||||
--color-pi-700: var(--color-primary-100);
|
||||
--color-pi-800: 57, 56, 149;
|
||||
--color-pi-900: 30, 29, 78;
|
||||
--color-pi-950: 14, 14, 37;
|
||||
}
|
||||
|
||||
[data-theme="light"],
|
||||
@@ -129,6 +142,19 @@
|
||||
--color-background-100: 255, 255, 255; /* primary bg */
|
||||
--color-background-90: 247, 247, 247; /* secondary bg */
|
||||
--color-background-80: 232, 232, 232; /* tertiary bg */
|
||||
|
||||
/* pi */
|
||||
--color-pi-50: var(--color-background-90);
|
||||
--color-pi-100: var(--color-background-90);
|
||||
--color-pi-200: var(--color-primary-200);
|
||||
--color-pi-300: var(--color-primary-200);
|
||||
--color-pi-400: var(--color-primary-200);
|
||||
--color-pi-500: var(--color-primary-200);
|
||||
--color-pi-600: 151, 150, 246;
|
||||
--color-pi-700: var(--color-primary-100);
|
||||
--color-pi-800: 57, 56, 149;
|
||||
--color-pi-900: 30, 29, 78;
|
||||
--color-pi-950: 14, 14, 37;
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
@@ -230,6 +256,19 @@
|
||||
--color-shadow-xl: 0px 0px 14px 0px rgba(0, 0, 0, 0.25), 0px 6px 10px 0px rgba(0, 0, 0, 0.55);
|
||||
--color-shadow-2xl: 0px 0px 18px 0px rgba(0, 0, 0, 0.25), 0px 8px 12px 0px rgba(0, 0, 0, 0.6);
|
||||
--color-shadow-3xl: 0px 4px 24px 0px rgba(0, 0, 0, 0.3), 0px 12px 40px 0px rgba(0, 0, 0, 0.65);
|
||||
|
||||
/* pi */
|
||||
--color-pi-50: var(--color-background-90);
|
||||
--color-pi-100: var(--color-background-90);
|
||||
--color-pi-200: var(--color-primary-200);
|
||||
--color-pi-300: var(--color-primary-200);
|
||||
--color-pi-400: var(--color-primary-200);
|
||||
--color-pi-500: var(--color-primary-200);
|
||||
--color-pi-600: 151, 150, 246;
|
||||
--color-pi-700: var(--color-primary-100);
|
||||
--color-pi-800: 57, 56, 149;
|
||||
--color-pi-900: 30, 29, 78;
|
||||
--color-pi-950: 14, 14, 37;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
|
||||
Reference in New Issue
Block a user