From dd65d03d3383f32de617f2baafac6503efb88ede Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 4 Jun 2024 11:12:24 +0530 Subject: [PATCH 001/307] [WEB-1184] feat: issue bulk operations (#4674) * feat: issue bulk operations * style: bulk operations action bar * chore: remove edition separation --- .../components/admin-sidebar/help-section.tsx | 13 +- web/components/gantt-chart/blocks/block.tsx | 22 +- .../gantt-chart/blocks/blocks-list.tsx | 11 +- .../gantt-chart/chart/main-content.tsx | 97 ++++--- web/components/gantt-chart/chart/root.tsx | 3 + web/components/gantt-chart/constants.ts | 2 + web/components/gantt-chart/root.tsx | 3 + .../gantt-chart/sidebar/cycles/block.tsx | 2 +- .../gantt-chart/sidebar/issues/block.tsx | 64 +++-- .../gantt-chart/sidebar/issues/sidebar.tsx | 12 +- .../gantt-chart/sidebar/modules/block.tsx | 2 +- web/components/gantt-chart/sidebar/root.tsx | 53 +++- .../issues/bulk-operations/index.ts | 2 + .../issues/bulk-operations/root.tsx | 19 ++ .../issues/bulk-operations/upgrade-banner.tsx | 32 +++ web/components/issues/index.ts | 1 + .../issue-layouts/gantt/base-gantt-root.tsx | 1 + .../issue-layouts/list/base-list-root.tsx | 21 +- .../issues/issue-layouts/list/block-root.tsx | 9 +- .../issues/issue-layouts/list/block.tsx | 86 +++++-- .../issues/issue-layouts/list/blocks-list.tsx | 46 ++-- .../issues/issue-layouts/list/default.tsx | 102 +++++--- .../list/headers/group-by-card.tsx | 237 ++++++++++-------- .../issues/issue-layouts/list/list-group.tsx | 9 +- .../spreadsheet/columns/assignee-column.tsx | 4 +- .../spreadsheet/columns/attachment-column.tsx | 4 +- .../spreadsheet/columns/created-on-column.tsx | 5 +- .../spreadsheet/columns/cycle-column.tsx | 15 +- .../spreadsheet/columns/due-date-column.tsx | 13 +- .../spreadsheet/columns/estimate-column.tsx | 6 +- .../spreadsheet/columns/label-column.tsx | 8 +- .../spreadsheet/columns/link-column.tsx | 4 +- .../spreadsheet/columns/module-column.tsx | 15 +- .../spreadsheet/columns/priority-column.tsx | 4 +- .../spreadsheet/columns/start-date-column.tsx | 4 +- .../spreadsheet/columns/state-column.tsx | 4 +- .../spreadsheet/columns/sub-issue-column.tsx | 2 +- .../spreadsheet/columns/updated-on-column.tsx | 5 +- .../spreadsheet/issue-column.tsx | 13 +- .../issue-layouts/spreadsheet/issue-row.tsx | 108 +++++--- .../spreadsheet/spreadsheet-header.tsx | 57 ++++- .../spreadsheet/spreadsheet-table.tsx | 6 + .../spreadsheet/spreadsheet-view.tsx | 68 +++-- web/components/workspace/help-section.tsx | 13 +- web/constants/common.ts | 2 + web/constants/spreadsheet.ts | 14 +- 46 files changed, 817 insertions(+), 406 deletions(-) create mode 100644 web/components/issues/bulk-operations/index.ts create mode 100644 web/components/issues/bulk-operations/root.tsx create mode 100644 web/components/issues/bulk-operations/upgrade-banner.tsx diff --git a/admin/components/admin-sidebar/help-section.tsx b/admin/components/admin-sidebar/help-section.tsx index 56ccbcd8446..38edd06fc23 100644 --- a/admin/components/admin-sidebar/help-section.tsx +++ b/admin/components/admin-sidebar/help-section.tsx @@ -5,9 +5,11 @@ import { observer } from "mobx-react-lite"; import Link from "next/link"; import { ExternalLink, FileText, HelpCircle, MoveLeft } from "lucide-react"; import { Transition } from "@headlessui/react"; +// ui import { DiscordIcon, GithubIcon, Tooltip } from "@plane/ui"; +// helpers +import { WEB_BASE_URL, cn } from "@/helpers/common.helper"; // hooks -import { WEB_BASE_URL } from "@/helpers/common.helper"; import { useTheme } from "@/hooks/store"; // assets import packageJson from "package.json"; @@ -42,9 +44,12 @@ export const HelpSection: FC = observer(() => { return (
diff --git a/web/components/gantt-chart/blocks/block.tsx b/web/components/gantt-chart/blocks/block.tsx index 0897b6b39ea..a33a0fd9f34 100644 --- a/web/components/gantt-chart/blocks/block.tsx +++ b/web/components/gantt-chart/blocks/block.tsx @@ -1,15 +1,16 @@ import { observer } from "mobx-react"; -// hooks -// components // helpers import { cn } from "@/helpers/common.helper"; import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper"; +// hooks import { useIssueDetail } from "@/hooks/store"; -// types +import { TSelectionHelper } from "@/hooks/use-multiple-select"; // constants import { BLOCK_HEIGHT } from "../constants"; +// components import { ChartAddBlock, ChartDraggable } from "../helpers"; import { useGanttChart } from "../hooks"; +// types import { IBlockUpdateData, IGanttBlock } from "../types"; type Props = { @@ -21,6 +22,7 @@ type Props = { enableBlockMove: boolean; enableAddBlock: boolean; ganttContainerRef: React.RefObject; + selectionHelpers: TSelectionHelper; }; export const GanttChartBlock: React.FC = observer((props) => { @@ -33,6 +35,7 @@ export const GanttChartBlock: React.FC = observer((props) => { enableBlockMove, enableAddBlock, ganttContainerRef, + selectionHelpers, } = props; // store hooks const { updateActiveBlockId, isBlockActive } = useGanttChart(); @@ -70,6 +73,10 @@ export const GanttChartBlock: React.FC = observer((props) => { }); }; + const isBlockSelected = selectionHelpers.getIsEntitySelected(block.id); + const isBlockFocused = selectionHelpers.getIsEntityActive(block.id); + const isBlockHoveredOn = isBlockActive(block.id); + return (
= observer((props) => { >
updateActiveBlockId(block.id)} onMouseLeave={() => updateActiveBlockId(null)} diff --git a/web/components/gantt-chart/blocks/blocks-list.tsx b/web/components/gantt-chart/blocks/blocks-list.tsx index 8eb1d877252..6fd22b254e9 100644 --- a/web/components/gantt-chart/blocks/blocks-list.tsx +++ b/web/components/gantt-chart/blocks/blocks-list.tsx @@ -1,10 +1,12 @@ import { FC } from "react"; -// components +// hooks +import { TSelectionHelper } from "@/hooks/use-multiple-select"; +// constants import { HEADER_HEIGHT } from "../constants"; +// types import { IBlockUpdateData, IGanttBlock } from "../types"; +// components import { GanttChartBlock } from "./block"; -// types -// constants export type GanttChartBlocksProps = { itemsContainerWidth: number; @@ -17,6 +19,7 @@ export type GanttChartBlocksProps = { enableAddBlock: boolean; ganttContainerRef: React.RefObject; showAllBlocks: boolean; + selectionHelpers: TSelectionHelper; }; export const GanttChartBlocksList: FC = (props) => { @@ -31,6 +34,7 @@ export const GanttChartBlocksList: FC = (props) => { enableAddBlock, ganttContainerRef, showAllBlocks, + selectionHelpers, } = props; return ( @@ -56,6 +60,7 @@ export const GanttChartBlocksList: FC = (props) => { enableBlockMove={enableBlockMove} enableAddBlock={enableAddBlock} ganttContainerRef={ganttContainerRef} + selectionHelpers={selectionHelpers} /> ); })} diff --git a/web/components/gantt-chart/chart/main-content.tsx b/web/components/gantt-chart/chart/main-content.tsx index 2f5abc8863a..e3b97223790 100644 --- a/web/components/gantt-chart/chart/main-content.tsx +++ b/web/components/gantt-chart/chart/main-content.tsx @@ -2,8 +2,8 @@ import { useEffect, useRef } from "react"; import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element"; import { observer } from "mobx-react"; -// hooks // components +import { MultipleSelectGroup } from "@/components/core"; import { BiWeekChartView, DayChartView, @@ -18,8 +18,12 @@ import { WeekChartView, YearChartView, } from "@/components/gantt-chart"; +import { IssueBulkOperationsRoot } from "@/components/issues"; // helpers import { cn } from "@/helpers/common.helper"; +// constants +import { GANTT_SELECT_GROUP } from "../constants"; +// hooks import { useGanttChart } from "../hooks/use-gantt-chart"; type Props = { @@ -33,6 +37,7 @@ type Props = { enableBlockRightResize: boolean; enableReorder: boolean; enableAddBlock: boolean; + enableSelection: boolean; itemsContainerWidth: number; showAllBlocks: boolean; sidebarToRender: (props: any) => React.ReactNode; @@ -53,6 +58,7 @@ export const GanttChartMainContent: React.FC = observer((props) => { enableBlockRightResize, enableReorder, enableAddBlock, + enableSelection, itemsContainerWidth, showAllBlocks, sidebarToRender, @@ -107,43 +113,58 @@ export const GanttChartMainContent: React.FC = observer((props) => { const ActiveChartView = CHART_VIEW_COMPONENTS[currentView]; return ( -
block.id) ?? [], + }} > - -
- - {currentViewData && ( - - )} -
-
+ {(helpers) => ( + <> +
+ +
+ + {currentViewData && ( + + )} +
+
+ + + )} + ); }); diff --git a/web/components/gantt-chart/chart/root.tsx b/web/components/gantt-chart/chart/root.tsx index 395e0771cd5..d961047e939 100644 --- a/web/components/gantt-chart/chart/root.tsx +++ b/web/components/gantt-chart/chart/root.tsx @@ -32,6 +32,7 @@ type ChartViewRootProps = { enableBlockMove: boolean; enableReorder: boolean; enableAddBlock: boolean; + enableSelection: boolean; bottomSpacing: boolean; showAllBlocks: boolean; quickAdd?: React.JSX.Element | undefined; @@ -51,6 +52,7 @@ export const ChartViewRoot: FC = observer((props) => { enableBlockMove, enableReorder, enableAddBlock, + enableSelection, bottomSpacing, showAllBlocks, quickAdd, @@ -184,6 +186,7 @@ export const ChartViewRoot: FC = observer((props) => { enableBlockRightResize={enableBlockRightResize} enableReorder={enableReorder} enableAddBlock={enableAddBlock} + enableSelection={enableSelection} itemsContainerWidth={itemsContainerWidth} showAllBlocks={showAllBlocks} sidebarToRender={sidebarToRender} diff --git a/web/components/gantt-chart/constants.ts b/web/components/gantt-chart/constants.ts index 958985cf166..52167a49844 100644 --- a/web/components/gantt-chart/constants.ts +++ b/web/components/gantt-chart/constants.ts @@ -3,3 +3,5 @@ export const BLOCK_HEIGHT = 44; export const HEADER_HEIGHT = 60; export const SIDEBAR_WIDTH = 360; + +export const GANTT_SELECT_GROUP = "gantt-issues"; diff --git a/web/components/gantt-chart/root.tsx b/web/components/gantt-chart/root.tsx index 10c1c0d98de..267dfe5c584 100644 --- a/web/components/gantt-chart/root.tsx +++ b/web/components/gantt-chart/root.tsx @@ -18,6 +18,7 @@ type GanttChartRootProps = { enableBlockMove?: boolean; enableReorder?: boolean; enableAddBlock?: boolean; + enableSelection?: boolean; bottomSpacing?: boolean; showAllBlocks?: boolean; }; @@ -36,6 +37,7 @@ export const GanttChartRoot: FC = (props) => { enableBlockMove = false, enableReorder = false, enableAddBlock = false, + enableSelection = false, bottomSpacing = false, showAllBlocks = false, quickAdd, @@ -56,6 +58,7 @@ export const GanttChartRoot: FC = (props) => { enableBlockMove={enableBlockMove} enableReorder={enableReorder} enableAddBlock={enableAddBlock} + enableSelection={enableSelection} bottomSpacing={bottomSpacing} showAllBlocks={showAllBlocks} quickAdd={quickAdd} diff --git a/web/components/gantt-chart/sidebar/cycles/block.tsx b/web/components/gantt-chart/sidebar/cycles/block.tsx index 1119e2e9ca9..7922e86a62d 100644 --- a/web/components/gantt-chart/sidebar/cycles/block.tsx +++ b/web/components/gantt-chart/sidebar/cycles/block.tsx @@ -38,7 +38,7 @@ export const CyclesSidebarBlock: React.FC = observer((props) => {
; + selectionHelpers?: TSelectionHelper; }; export const IssuesSidebarBlock = observer((props: Props) => { - const { block, enableReorder, isDragging, dragHandleRef } = props; + const { block, enableReorder, enableSelection, isDragging, dragHandleRef, selectionHelpers } = props; // store hooks const { updateActiveBlockId, isBlockActive } = useGanttChart(); const { getIsIssuePeeked } = useIssueDetail(); const duration = findTotalDaysInRange(block.start_date, block.target_date); + const isIssueSelected = selectionHelpers?.getIsEntitySelected(block.id); + const isIssueFocused = selectionHelpers?.getIsEntityActive(block.id); + const isBlockHoveredOn = isBlockActive(block.id); + return (
updateActiveBlockId(block.id)} onMouseLeave={() => updateActiveBlockId(null)} >
- {enableReorder && ( - - )} +
+ {enableReorder && ( + + )} + {enableSelection && selectionHelpers && ( + + )} +
diff --git a/web/components/gantt-chart/sidebar/issues/sidebar.tsx b/web/components/gantt-chart/sidebar/issues/sidebar.tsx index 7da30216d4e..f01a12b6ddc 100644 --- a/web/components/gantt-chart/sidebar/issues/sidebar.tsx +++ b/web/components/gantt-chart/sidebar/issues/sidebar.tsx @@ -1,22 +1,26 @@ import { MutableRefObject } from "react"; -// components // ui import { Loader } from "@plane/ui"; -// types +// components import { IGanttBlock, IBlockUpdateData } from "@/components/gantt-chart/types"; +// hooks +import { TSelectionHelper } from "@/hooks/use-multiple-select"; import { GanttDnDHOC } from "../gantt-dnd-HOC"; import { handleOrderChange } from "../utils"; +// types import { IssuesSidebarBlock } from "./block"; type Props = { blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void; blocks: IGanttBlock[] | null; enableReorder: boolean; + enableSelection: boolean; showAllBlocks?: boolean; + selectionHelpers?: TSelectionHelper; }; export const IssueGanttSidebar: React.FC = (props) => { - const { blockUpdateHandler, blocks, enableReorder, showAllBlocks = false } = props; + const { blockUpdateHandler, blocks, enableReorder, enableSelection, showAllBlocks = false, selectionHelpers } = props; const handleOnDrop = ( draggingBlockId: string | undefined, @@ -47,8 +51,10 @@ export const IssueGanttSidebar: React.FC = (props) => { )} diff --git a/web/components/gantt-chart/sidebar/modules/block.tsx b/web/components/gantt-chart/sidebar/modules/block.tsx index e79a6540193..e6b28d54ae5 100644 --- a/web/components/gantt-chart/sidebar/modules/block.tsx +++ b/web/components/gantt-chart/sidebar/modules/block.tsx @@ -38,7 +38,7 @@ export const ModulesSidebarBlock: React.FC = observer((props) => {
void; enableReorder: boolean; + enableSelection: boolean; sidebarToRender: (props: any) => React.ReactNode; title: string; quickAdd?: React.JSX.Element | undefined; + selectionHelpers: TSelectionHelper; }; -export const GanttChartSidebar: React.FC = (props) => { - const { blocks, blockUpdateHandler, enableReorder, sidebarToRender, title, quickAdd } = props; +export const GanttChartSidebar: React.FC = observer((props) => { + const { + blocks, + blockUpdateHandler, + enableReorder, + enableSelection, + sidebarToRender, + title, + quickAdd, + selectionHelpers, + } = props; + + const isGroupSelectionEmpty = selectionHelpers.isGroupSelected(GANTT_SELECT_GROUP) === "empty"; return (
= (props) => { }} >
-
{title}
+
+ {enableSelection && ( +
+ +
+ )} +
{title}
+
Duration
- {sidebarToRender && sidebarToRender({ title, blockUpdateHandler, blocks, enableReorder })} + {sidebarToRender?.({ title, blockUpdateHandler, blocks, enableReorder, enableSelection, selectionHelpers })}
{quickAdd ? quickAdd : null}
); -}; +}); diff --git a/web/components/issues/bulk-operations/index.ts b/web/components/issues/bulk-operations/index.ts new file mode 100644 index 00000000000..f8c733c7ba2 --- /dev/null +++ b/web/components/issues/bulk-operations/index.ts @@ -0,0 +1,2 @@ +export * from "./root"; +export * from "./upgrade-banner"; diff --git a/web/components/issues/bulk-operations/root.tsx b/web/components/issues/bulk-operations/root.tsx new file mode 100644 index 00000000000..957f18609a0 --- /dev/null +++ b/web/components/issues/bulk-operations/root.tsx @@ -0,0 +1,19 @@ +import { observer } from "mobx-react"; +// components +import { BulkOperationsUpgradeBanner } from "@/components/issues"; +// hooks +import { useMultipleSelectStore } from "@/hooks/store"; + +type Props = { + className?: string; +}; + +export const IssueBulkOperationsRoot: React.FC = observer((props) => { + const { className } = props; + // store hooks + const { isSelectionActive } = useMultipleSelectStore(); + + if (!isSelectionActive) return null; + + return ; +}); diff --git a/web/components/issues/bulk-operations/upgrade-banner.tsx b/web/components/issues/bulk-operations/upgrade-banner.tsx new file mode 100644 index 00000000000..c96e6d21049 --- /dev/null +++ b/web/components/issues/bulk-operations/upgrade-banner.tsx @@ -0,0 +1,32 @@ +// ui +import { getButtonStyling } from "@plane/ui"; +// constants +import { MARKETING_PLANE_ONE_PAGE_LINK } from "@/constants/common"; +// helpers +import { cn } from "@/helpers/common.helper"; + +type Props = { + className?: string; +}; + +export const BulkOperationsUpgradeBanner: React.FC = (props) => { + const { className } = props; + + return ( +
+
+

+ Change state, priority, and more for several issues at once. Save three minutes on an average per operation. +

+ + Upgrade to One + +
+
+ ); +}; diff --git a/web/components/issues/index.ts b/web/components/issues/index.ts index 83431f5beb9..454d851109d 100644 --- a/web/components/issues/index.ts +++ b/web/components/issues/index.ts @@ -1,4 +1,5 @@ export * from "./attachment"; +export * from "./bulk-operations"; export * from "./issue-modal"; export * from "./delete-issue-modal"; export * from "./issue-layouts"; diff --git a/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx b/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx index 30aba9e92e9..17df47163f2 100644 --- a/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx +++ b/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx @@ -72,6 +72,7 @@ export const BaseGanttRoot: React.FC = observer((props: IBaseGan enableBlockMove={isAllowed} enableReorder={appliedDisplayFilters?.order_by === "sort_order" && isAllowed} enableAddBlock={isAllowed} + enableSelection={isAllowed} quickAdd={ enableIssueCreation && isAllowed ? ( diff --git a/web/components/issues/issue-layouts/list/base-list-root.tsx b/web/components/issues/issue-layouts/list/base-list-root.tsx index b58bdce2c38..73385a09455 100644 --- a/web/components/issues/issue-layouts/list/base-list-root.tsx +++ b/web/components/issues/issue-layouts/list/base-list-root.tsx @@ -1,16 +1,16 @@ import { FC, useCallback } from "react"; import { observer } from "mobx-react-lite"; -// types +// constants import { EIssuesStoreType } from "@/constants/issue"; import { EUserProjectRoles } from "@/constants/project"; +// hooks import { useIssues, useUser } from "@/hooks/store"; import { useGroupIssuesDragNDrop } from "@/hooks/use-group-dragndrop"; import { useIssuesActions } from "@/hooks/use-issues-actions"; // components import { List } from "./default"; +// types import { IQuickActionProps, TRenderQuickActions } from "./list-view-types"; -// constants -// hooks type ListStoreType = | EIssuesStoreType.PROJECT @@ -37,22 +37,19 @@ export const BaseListRoot = observer((props: IBaseListRoot) => { canEditPropertiesBasedOnProject, isCompletedCycle = false, } = props; - // router - //stores + // store hooks const { issuesFilter, issues } = useIssues(storeType); const { updateIssue, removeIssue, removeIssueFromView, archiveIssue, restoreIssue } = useIssuesActions(storeType); - // mobx store const { membership: { currentProjectRole }, } = useUser(); - const { issueMap } = useIssues(); - - const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; - + // derived values const issueIds = issues?.groupedIssueIds || []; - + // auth + const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; const { enableInlineEditing, enableQuickAdd, enableIssueCreation } = issues?.viewFlags || {}; + const canEditProperties = useCallback( (projectId: string | undefined) => { const isEditingAllowedBasedOnProject = @@ -90,7 +87,7 @@ export const BaseListRoot = observer((props: IBaseListRoot) => { ); return ( -
+
; + selectionHelpers: TSelectionHelper; groupId: string; isDragAllowed: boolean; canDropOverIssue: boolean; @@ -50,6 +52,7 @@ export const IssueBlockRoot: FC = observer((props) => { canDropOverIssue, isParentIssueBeingDragged = false, isLastChild = false, + selectionHelpers, } = props; // states const [isExpanded, setExpanded] = useState(false); @@ -132,6 +135,7 @@ export const IssueBlockRoot: FC = observer((props) => { setExpanded={setExpanded} nestingLevel={nestingLevel} spacingLeft={spacingLeft} + selectionHelpers={selectionHelpers} canDrag={!isSubIssue && isDragAllowed} isCurrentBlockDragging={isParentIssueBeingDragged || isCurrentBlockDragging} setIsCurrentBlockDragging={setIsCurrentBlockDragging} @@ -139,9 +143,7 @@ export const IssueBlockRoot: FC = observer((props) => { {isExpanded && - subIssues && - subIssues.length > 0 && - subIssues.map((subIssueId: string) => ( + subIssues?.map((subIssueId: string) => ( = observer((props) => { nestingLevel={nestingLevel + 1} spacingLeft={spacingLeft + (displayProperties?.key ? 12 : 0)} containerRef={containerRef} + selectionHelpers={selectionHelpers} groupId={groupId} isDragAllowed={isDragAllowed} canDropOverIssue={canDropOverIssue} diff --git a/web/components/issues/issue-layouts/list/block.tsx b/web/components/issues/issue-layouts/list/block.tsx index 2ff31ab1d4c..56d88a7302d 100644 --- a/web/components/issues/issue-layouts/list/block.tsx +++ b/web/components/issues/issue-layouts/list/block.tsx @@ -8,11 +8,13 @@ import { TIssue, IIssueDisplayProperties, TIssueMap } from "@plane/types"; // ui import { Spinner, Tooltip, ControlLink, DragHandle } from "@plane/ui"; // components +import { MultipleSelectEntityAction } from "@/components/core"; import { IssueProperties } from "@/components/issues/issue-layouts/properties"; // helpers import { cn } from "@/helpers/common.helper"; // hooks import { useAppRouter, useIssueDetail, useProject } from "@/hooks/store"; +import { TSelectionHelper } from "@/hooks/use-multiple-select"; import { usePlatformOS } from "@/hooks/use-platform-os"; // types import { TRenderQuickActions } from "./list-view-types"; @@ -29,6 +31,7 @@ interface IssueBlockProps { spacingLeft?: number; isExpanded: boolean; setExpanded: Dispatch>; + selectionHelpers: TSelectionHelper; isCurrentBlockDragging: boolean; setIsCurrentBlockDragging: React.Dispatch>; canDrag: boolean; @@ -47,6 +50,7 @@ export const IssueBlock = observer((props: IssueBlockProps) => { spacingLeft = 14, isExpanded, setExpanded, + selectionHelpers, isCurrentBlockDragging, setIsCurrentBlockDragging, canDrag, @@ -55,7 +59,7 @@ export const IssueBlock = observer((props: IssueBlockProps) => { const issueRef = useRef(null); const dragHandleRef = useRef(null); // hooks - const { workspaceSlug } = useAppRouter(); + const { workspaceSlug, projectId } = useAppRouter(); const { getProjectIdentifierById } = useProject(); const { getIsIssuePeeked, peekIssue, setPeekIssue, subIssues: subIssuesStore } = useIssueDetail(); @@ -98,8 +102,11 @@ export const IssueBlock = observer((props: IssueBlockProps) => { const canEditIssueProperties = canEditProperties(issue.project_id); const projectIdentifier = getProjectIdentifierById(issue.project_id); + const isIssueSelected = selectionHelpers.getIsEntitySelected(issue.id); + const isIssueActive = selectionHelpers.getIsEntityActive(issue.id); + const isSubIssue = nestingLevel !== 0; - const paddingLeft = `${spacingLeft}px`; + const marginLeft = `${spacingLeft}px`; const handleToggleExpand = (e: MouseEvent) => { e.stopPropagation(); @@ -119,39 +126,76 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
-
+
-
+ {/* drag handle */} +
-
- {subIssuesCount > 0 && ( - - )} -
+
+ {/* select checkbox */} + {projectId && canEditIssueProperties && ( + + Only issues within the current +
+ project can be selected. + + } + disabled={issue.project_id === projectId} + > +
+ +
+
+ )} + {/* sub-issues chevron */} +
+ {subIssuesCount > 0 && ( + + )}
{displayProperties && displayProperties?.key && ( -
+
{projectIdentifier}-{issue.sequence_id}
)} @@ -183,7 +227,7 @@ export const IssueBlock = observer((props: IssueBlockProps) => { )}
{!issue?.tempId && ( -
+
{quickActions({ issue, parentRef: issueRef, diff --git a/web/components/issues/issue-layouts/list/blocks-list.tsx b/web/components/issues/issue-layouts/list/blocks-list.tsx index 1d773001eb7..a1d2d009645 100644 --- a/web/components/issues/issue-layouts/list/blocks-list.tsx +++ b/web/components/issues/issue-layouts/list/blocks-list.tsx @@ -2,6 +2,7 @@ import { FC, MutableRefObject } from "react"; // components import { TIssue, IIssueDisplayProperties, TIssueMap, TUnGroupedIssues } from "@plane/types"; import { IssueBlockRoot } from "@/components/issues/issue-layouts/list"; +import { TSelectionHelper } from "@/hooks/use-multiple-select"; // types import { TRenderQuickActions } from "./list-view-types"; @@ -16,6 +17,7 @@ interface Props { containerRef: MutableRefObject; isDragAllowed: boolean; canDropOverIssue: boolean; + selectionHelpers: TSelectionHelper; } export const IssueBlocksList: FC = (props) => { @@ -28,33 +30,33 @@ export const IssueBlocksList: FC = (props) => { displayProperties, canEditProperties, containerRef, + selectionHelpers, isDragAllowed, canDropOverIssue, } = props; return ( -
- {issueIds && - issueIds.length > 0 && - issueIds.map((issueId: string, index) => ( - - ))} +
+ {issueIds?.map((issueId, index) => ( + + ))}
); }; diff --git a/web/components/issues/issue-layouts/list/default.tsx b/web/components/issues/issue-layouts/list/default.tsx index 66bc47c2895..c527276ef47 100644 --- a/web/components/issues/issue-layouts/list/default.tsx +++ b/web/components/issues/issue-layouts/list/default.tsx @@ -1,7 +1,8 @@ import { useEffect, useRef } from "react"; import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element"; -// components +import { observer } from "mobx-react"; +// types import { GroupByColumnTypes, TGroupedIssues, @@ -13,6 +14,9 @@ import { TIssueOrderByOptions, TIssueGroupByOptions, } from "@plane/types"; +// components +import { MultipleSelectGroup } from "@/components/core"; +import { IssueBulkOperationsRoot } from "@/components/issues"; // hooks import { EIssuesStoreType } from "@/constants/issue"; import { useCycle, useLabel, useMember, useModule, useProject, useProjectState } from "@/hooks/store"; @@ -46,7 +50,7 @@ export interface IGroupByList { isCompletedCycle?: boolean; } -const GroupByList: React.FC = (props) => { +const GroupByList: React.FC = observer((props) => { const { issueIds, issuesMap, @@ -113,43 +117,69 @@ const GroupByList: React.FC = (props) => { const is_list = group_by === null ? true : false; + // create groupIds array and entities object for bulk ops + const groupIds = groups.map((g) => g.id); + const orderedGroups: Record = {}; + groupIds.forEach((gID) => { + orderedGroups[gID] = []; + }); + let entities: Record = {}; + + if (is_list) { + entities = Object.assign(orderedGroups, { [groupIds[0]]: issueIds }); + } else { + entities = Object.assign(orderedGroups, { ...issueIds }); + } + return ( -
- {groups && - groups.length > 0 && - groups.map( - (group: IGroupByColumn) => - validateEmptyIssueGroups(is_list ? issueIds : issueIds?.[group.id]) && ( - - ) - )} +
+ {groups && ( + + {(helpers) => ( + <> +
+ {groups.map( + (group: IGroupByColumn) => + validateEmptyIssueGroups(is_list ? issueIds : issueIds?.[group.id]) && ( + + ) + )} +
+ + + )} +
+ )}
); -}; +}); + +GroupByList.displayName = "GroupByList"; export interface IList { issueIds: TGroupedIssues | TUnGroupedIssues | any; diff --git a/web/components/issues/issue-layouts/list/headers/group-by-card.tsx b/web/components/issues/issue-layouts/list/headers/group-by-card.tsx index d479bbeaa1d..feb99a8a523 100644 --- a/web/components/issues/issue-layouts/list/headers/group-by-card.tsx +++ b/web/components/issues/issue-layouts/list/headers/group-by-card.tsx @@ -1,138 +1,169 @@ import { useState } from "react"; import { observer } from "mobx-react"; import { useRouter } from "next/router"; -// lucide icons import { CircleDashed, Plus } from "lucide-react"; +// types import { TIssue, ISearchIssueResponse } from "@plane/types"; -// components +// ui import { CustomMenu, TOAST_TYPE, setToast } from "@plane/ui"; -import { ExistingIssuesListModal } from "@/components/core"; +// components +import { ExistingIssuesListModal, MultipleSelectGroupAction } from "@/components/core"; import { CreateUpdateIssueModal } from "@/components/issues"; -// ui -// mobx -// hooks +// constants import { EIssuesStoreType } from "@/constants/issue"; +// helpers +import { cn } from "@/helpers/common.helper"; +// hooks import { useEventTracker } from "@/hooks/store"; -// types +import { TSelectionHelper } from "@/hooks/use-multiple-select"; interface IHeaderGroupByCard { + groupID: string; icon?: React.ReactNode; title: string; count: number; issuePayload: Partial; + canEditProperties: (projectId: string | undefined) => boolean; disableIssueCreation?: boolean; storeType: EIssuesStoreType; addIssuesToView?: (issueIds: string[]) => Promise; + selectionHelpers: TSelectionHelper; } -export const HeaderGroupByCard = observer( - ({ icon, title, count, issuePayload, disableIssueCreation, storeType, addIssuesToView }: IHeaderGroupByCard) => { - const router = useRouter(); - const { workspaceSlug, projectId, moduleId, cycleId } = router.query; - // hooks - const { setTrackElement } = useEventTracker(); - - const [isOpen, setIsOpen] = useState(false); - - const [openExistingIssueListModal, setOpenExistingIssueListModal] = useState(false); - - const isDraftIssue = router.pathname.includes("draft-issue"); +export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => { + const { + groupID, + icon, + title, + count, + issuePayload, + canEditProperties, + disableIssueCreation, + storeType, + addIssuesToView, + selectionHelpers, + } = props; + // states + const [isOpen, setIsOpen] = useState(false); + const [openExistingIssueListModal, setOpenExistingIssueListModal] = useState(false); + // router + const router = useRouter(); + const { workspaceSlug, projectId, moduleId, cycleId } = router.query; + // hooks + const { setTrackElement } = useEventTracker(); + // derived values + const isDraftIssue = router.pathname.includes("draft-issue"); + const renderExistingIssueModal = moduleId || cycleId; + const existingIssuesListModalPayload = moduleId ? { module: moduleId.toString() } : { cycle: true }; + const isGroupSelectionEmpty = selectionHelpers.isGroupSelected(groupID) === "empty"; + // auth + const canSelectIssues = canEditProperties(projectId?.toString()); - const renderExistingIssueModal = moduleId || cycleId; - const ExistingIssuesListModalPayload = moduleId ? { module: moduleId.toString() } : { cycle: true }; + const handleAddIssuesToView = async (data: ISearchIssueResponse[]) => { + if (!workspaceSlug || !projectId) return; - const handleAddIssuesToView = async (data: ISearchIssueResponse[]) => { - if (!workspaceSlug || !projectId) return; + const issues = data.map((i) => i.id); - const issues = data.map((i) => i.id); + try { + await addIssuesToView?.(issues); - try { - await addIssuesToView?.(issues); + setToast({ + type: TOAST_TYPE.SUCCESS, + title: "Success!", + message: "Issues added to the cycle successfully.", + }); + } catch (error) { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Selected issues could not be added to the cycle. Please try again.", + }); + } + }; - setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: "Issues added to the cycle successfully.", - }); - } catch (error) { - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Selected issues could not be added to the cycle. Please try again.", - }); - } - }; - - return ( - <> -
-
- {icon ? icon : } + return ( + <> +
+ {canSelectIssues && ( +
+
+ )} +
+ {icon ?? } +
-
-
{title}
-
{count || 0}
-
+
+
{title}
+
{count || 0}
+
- {!disableIssueCreation && - (renderExistingIssueModal ? ( - - - - } - > - { - setTrackElement("List layout"); - setIsOpen(true); - }} - > - Create issue - - { - setTrackElement("List layout"); - setOpenExistingIssueListModal(true); - }} - > - Add an existing issue - - - ) : ( -
+ + + } + > + { setTrackElement("List layout"); setIsOpen(true); }} > - -
- ))} + Create issue + + { + setTrackElement("List layout"); + setOpenExistingIssueListModal(true); + }} + > + Add an existing issue + + + ) : ( +
{ + setTrackElement("List layout"); + setIsOpen(true); + }} + > + +
+ ))} - setIsOpen(false)} - data={issuePayload} - storeType={storeType} - isDraft={isDraftIssue} - /> + setIsOpen(false)} + data={issuePayload} + storeType={storeType} + isDraft={isDraftIssue} + /> - {renderExistingIssueModal && ( - setOpenExistingIssueListModal(false)} - searchParams={ExistingIssuesListModalPayload} - handleOnSubmit={handleAddIssuesToView} - /> - )} -
- - ); - } -); + {renderExistingIssueModal && ( + setOpenExistingIssueListModal(false)} + searchParams={existingIssuesListModalPayload} + handleOnSubmit={handleAddIssuesToView} + /> + )} +
+ + ); +}); diff --git a/web/components/issues/issue-layouts/list/list-group.tsx b/web/components/issues/issue-layouts/list/list-group.tsx index 43c5f990e3e..04457327e35 100644 --- a/web/components/issues/issue-layouts/list/list-group.tsx +++ b/web/components/issues/issue-layouts/list/list-group.tsx @@ -19,6 +19,7 @@ import { TOAST_TYPE, setToast } from "@plane/ui"; import { DRAG_ALLOWED_GROUPS, EIssuesStoreType } from "@/constants/issue"; // hooks import { useProjectState } from "@/hooks/store"; +import { TSelectionHelper } from "@/hooks/use-multiple-select"; // components import { GroupDragOverlay } from "../group-drag-overlay"; import { @@ -58,6 +59,7 @@ type Props = { addIssuesToView?: (issueIds: string[]) => Promise; viewId?: string; isCompletedCycle?: boolean; + selectionHelpers: TSelectionHelper; }; export const ListGroup = observer((props: Props) => { @@ -81,6 +83,7 @@ export const ListGroup = observer((props: Props) => { enableIssueQuickAdd, isCompletedCycle, storeType, + selectionHelpers, } = props; const [isDraggingOverColumn, setIsDraggingOverColumn] = useState(false); @@ -190,15 +193,18 @@ export const ListGroup = observer((props: Props) => { "border-custom-error-200": isDraggingOverColumn && !!group.isDropDisabled, })} > -
+
@@ -224,6 +230,7 @@ export const ListGroup = observer((props: Props) => { containerRef={containerRef} isDragAllowed={isDragAllowed} canDropOverIssue={!canOverlayBeVisible} + selectionHelpers={selectionHelpers} /> )} diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/assignee-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/assignee-column.tsx index 9fefd47594c..d0fb5812ddf 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/assignee-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/assignee-column.tsx @@ -1,9 +1,9 @@ import React from "react"; import { observer } from "mobx-react-lite"; +// types import { TIssue } from "@plane/types"; // components import { MemberDropdown } from "@/components/dropdowns"; -// types type Props = { issue: TIssue; @@ -36,7 +36,7 @@ export const SpreadsheetAssigneeColumn: React.FC = observer((props: Props buttonVariant={ issue?.assignee_ids && issue.assignee_ids.length > 0 ? "transparent-without-text" : "transparent-with-text" } - buttonClassName="text-left" + buttonClassName="text-left rounded-none group-[.selected-issue-row]:bg-custom-primary-100/5 group-[.selected-issue-row]:hover:bg-custom-primary-100/10" buttonContainerClassName="w-full" onClose={onClose} /> diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/attachment-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/attachment-column.tsx index 7fb7ef7e2df..0be34526269 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/attachment-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/attachment-column.tsx @@ -1,7 +1,7 @@ import React from "react"; import { observer } from "mobx-react-lite"; -import { TIssue } from "@plane/types"; // types +import { TIssue } from "@plane/types"; type Props = { issue: TIssue; @@ -11,7 +11,7 @@ export const SpreadsheetAttachmentColumn: React.FC = observer((props) => const { issue } = props; return ( -
+
{issue?.attachment_count} {issue?.attachment_count === 1 ? "attachment" : "attachments"}
); diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/created-on-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/created-on-column.tsx index eea39478a52..a7845400c51 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/created-on-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/created-on-column.tsx @@ -1,9 +1,9 @@ import React from "react"; import { observer } from "mobx-react-lite"; +// types import { TIssue } from "@plane/types"; // helpers import { renderFormattedDate } from "@/helpers/date-time.helper"; -// types type Props = { issue: TIssue; @@ -11,8 +11,9 @@ type Props = { export const SpreadsheetCreatedOnColumn: React.FC = observer((props: Props) => { const { issue } = props; + return ( -
+
{renderFormattedDate(issue.created_at)}
); diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/cycle-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/cycle-column.tsx index 8cb2f43fb0b..574ab6feacc 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/cycle-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/cycle-column.tsx @@ -1,14 +1,14 @@ import React, { useCallback } from "react"; import { observer } from "mobx-react"; import { useRouter } from "next/router"; +// types import { TIssue } from "@plane/types"; -// hooks +// components import { CycleDropdown } from "@/components/dropdowns"; +// constants import { EIssuesStoreType } from "@/constants/issue"; +// hooks import { useEventTracker, useIssues } from "@/hooks/store"; -// components -// types -// constants type Props = { issue: TIssue; @@ -17,11 +17,10 @@ type Props = { }; export const SpreadsheetCycleColumn: React.FC = observer((props) => { + const { issue, disabled, onClose } = props; // router const router = useRouter(); const { workspaceSlug } = router.query; - // props - const { issue, disabled, onClose } = props; // hooks const { captureIssueEvent } = useEventTracker(); const { @@ -56,8 +55,8 @@ export const SpreadsheetCycleColumn: React.FC = observer((props) => { disabled={disabled} placeholder="Select cycle" buttonVariant="transparent-with-text" - buttonContainerClassName="w-full relative flex items-center p-2" - buttonClassName="relative leading-4 h-4.5 bg-transparent" + buttonContainerClassName="w-full relative flex items-center p-2 group-[.selected-issue-row]:bg-custom-primary-100/5 group-[.selected-issue-row]:hover:bg-custom-primary-100/10" + buttonClassName="relative leading-4 h-4.5 bg-transparent hover:bg-transparent" onClose={onClose} />
diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/due-date-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/due-date-column.tsx index 58ebac58ecf..f0c43a4573b 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/due-date-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/due-date-column.tsx @@ -1,16 +1,16 @@ import React from "react"; import { observer } from "mobx-react"; import { CalendarCheck2 } from "lucide-react"; +// types import { TIssue } from "@plane/types"; -// hooks // components import { DateDropdown } from "@/components/dropdowns"; // helpers import { cn } from "@/helpers/common.helper"; import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper"; import { shouldHighlightIssueDueDate } from "@/helpers/issue.helper"; +// hooks import { useProjectState } from "@/hooks/store"; -// types type Props = { issue: TIssue; @@ -47,9 +47,12 @@ export const SpreadsheetDueDateColumn: React.FC = observer((props: Props) icon={} buttonVariant="transparent-with-text" buttonContainerClassName="w-full" - buttonClassName={cn("rounded-none text-left", { - "text-red-500": shouldHighlightIssueDueDate(issue.target_date, stateDetails?.group), - })} + buttonClassName={cn( + "rounded-none text-left group-[.selected-issue-row]:bg-custom-primary-100/5 group-[.selected-issue-row]:hover:bg-custom-primary-100/10", + { + "text-red-500": shouldHighlightIssueDueDate(issue.target_date, stateDetails?.group), + } + )} clearIconClassName="!text-custom-text-100" onClose={onClose} /> diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/estimate-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/estimate-column.tsx index 6acc0f6a5e5..2e90cd2ba51 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/estimate-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/estimate-column.tsx @@ -1,8 +1,8 @@ -// components import { observer } from "mobx-react-lite"; +// types import { TIssue } from "@plane/types"; +// components import { EstimateDropdown } from "@/components/dropdowns"; -// types type Props = { issue: TIssue; @@ -25,7 +25,7 @@ export const SpreadsheetEstimateColumn: React.FC = observer((props: Props projectId={issue.project_id} disabled={disabled} buttonVariant="transparent-with-text" - buttonClassName="rounded-none text-left" + buttonClassName="text-left rounded-none group-[.selected-issue-row]:bg-custom-primary-100/5 group-[.selected-issue-row]:hover:bg-custom-primary-100/10" buttonContainerClassName="w-full" onClose={onClose} /> diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/label-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/label-column.tsx index 439abf5f39e..bb409d5637c 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/label-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/label-column.tsx @@ -1,10 +1,10 @@ import React from "react"; import { observer } from "mobx-react-lite"; +// types import { TIssue } from "@plane/types"; -// components // hooks import { useLabel } from "@/hooks/store"; -// types +// components import { IssuePropertyLabels } from "../../properties"; type Props = { @@ -27,8 +27,8 @@ export const SpreadsheetLabelColumn: React.FC = observer((props: Props) = value={issue.label_ids} defaultOptions={defaultLabelOptions} onChange={(data) => onChange(issue, { label_ids: data }, { changed_property: "labels", change_details: data })} - className="h-11 w-full border-b-[0.5px] border-custom-border-200 hover:bg-custom-background-80" - buttonClassName="px-2.5 h-full" + className="h-11 w-full border-b-[0.5px] border-custom-border-200" + buttonClassName="px-2.5 h-full group-[.selected-issue-row]:bg-custom-primary-100/5 group-[.selected-issue-row]:hover:bg-custom-primary-100/10" hideDropdownArrow maxRender={1} disabled={disabled} diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/link-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/link-column.tsx index f2c11ab0f61..f8c63942976 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/link-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/link-column.tsx @@ -1,7 +1,7 @@ import React from "react"; import { observer } from "mobx-react-lite"; -import { TIssue } from "@plane/types"; // types +import { TIssue } from "@plane/types"; type Props = { issue: TIssue; @@ -11,7 +11,7 @@ export const SpreadsheetLinkColumn: React.FC = observer((props: Props) => const { issue } = props; return ( -
+
{issue?.link_count} {issue?.link_count === 1 ? "link" : "links"}
); diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/module-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/module-column.tsx index efae44e840f..2357a6791a7 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/module-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/module-column.tsx @@ -2,14 +2,14 @@ import React, { useCallback } from "react"; import xor from "lodash/xor"; import { observer } from "mobx-react"; import { useRouter } from "next/router"; +// types import { TIssue } from "@plane/types"; -// hooks +// components import { ModuleDropdown } from "@/components/dropdowns"; +// constants import { EIssuesStoreType } from "@/constants/issue"; +// hooks import { useEventTracker, useIssues } from "@/hooks/store"; -// components -// types -// constants type Props = { issue: TIssue; @@ -18,11 +18,10 @@ type Props = { }; export const SpreadsheetModuleColumn: React.FC = observer((props) => { + const { issue, disabled, onClose } = props; // router const router = useRouter(); const { workspaceSlug } = router.query; - // props - const { issue, disabled, onClose } = props; // hooks const { captureIssueEvent } = useEventTracker(); const { @@ -65,8 +64,8 @@ export const SpreadsheetModuleColumn: React.FC = observer((props) => { disabled={disabled} placeholder="Select modules" buttonVariant="transparent-with-text" - buttonContainerClassName="w-full relative flex items-center p-2" - buttonClassName="relative leading-4 h-4.5 bg-transparent" + buttonContainerClassName="w-full relative flex items-center p-2 group-[.selected-issue-row]:bg-custom-primary-100/5 group-[.selected-issue-row]:hover:bg-custom-primary-100/10" + buttonClassName="relative leading-4 h-4.5 bg-transparent hover:bg-transparent" onClose={onClose} multiple showCount diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/priority-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/priority-column.tsx index 8058b70236a..1e072a736a5 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/priority-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/priority-column.tsx @@ -1,9 +1,9 @@ import React from "react"; import { observer } from "mobx-react-lite"; +// types import { TIssue } from "@plane/types"; // components import { PriorityDropdown } from "@/components/dropdowns"; -// types type Props = { issue: TIssue; @@ -22,7 +22,7 @@ export const SpreadsheetPriorityColumn: React.FC = observer((props: Props onChange={(data) => onChange(issue, { priority: data }, { changed_property: "priority", change_details: data })} disabled={disabled} buttonVariant="transparent-with-text" - buttonClassName="rounded-none text-left" + buttonClassName="text-left rounded-none group-[.selected-issue-row]:bg-custom-primary-100/5 group-[.selected-issue-row]:hover:bg-custom-primary-100/10" buttonContainerClassName="w-full" onClose={onClose} /> diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/start-date-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/start-date-column.tsx index aff8c7dfa76..9a17d34d4d2 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/start-date-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/start-date-column.tsx @@ -1,12 +1,12 @@ import React from "react"; import { observer } from "mobx-react"; import { CalendarClock } from "lucide-react"; +// types import { TIssue } from "@plane/types"; // components import { DateDropdown } from "@/components/dropdowns"; // helpers import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper"; -// types type Props = { issue: TIssue; @@ -38,7 +38,7 @@ export const SpreadsheetStartDateColumn: React.FC = observer((props: Prop placeholder="Start date" icon={} buttonVariant="transparent-with-text" - buttonClassName="rounded-none text-left" + buttonClassName="text-left rounded-none group-[.selected-issue-row]:bg-custom-primary-100/5 group-[.selected-issue-row]:hover:bg-custom-primary-100/10" buttonContainerClassName="w-full" onClose={onClose} /> diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/state-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/state-column.tsx index 20158572843..f50ab4fced6 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/state-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/state-column.tsx @@ -1,9 +1,9 @@ import React from "react"; import { observer } from "mobx-react-lite"; +// types import { TIssue } from "@plane/types"; // components import { StateDropdown } from "@/components/dropdowns"; -// types type Props = { issue: TIssue; @@ -23,7 +23,7 @@ export const SpreadsheetStateColumn: React.FC = observer((props) => { onChange={(data) => onChange(issue, { state_id: data }, { changed_property: "state", change_details: data })} disabled={disabled} buttonVariant="transparent-with-text" - buttonClassName="rounded-none text-left" + buttonClassName="text-left rounded-none group-[.selected-issue-row]:bg-custom-primary-100/5 group-[.selected-issue-row]:hover:bg-custom-primary-100/10" buttonContainerClassName="w-full" onClose={onClose} /> diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/sub-issue-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/sub-issue-column.tsx index 8a6d26ac6c1..70595454123 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/sub-issue-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/sub-issue-column.tsx @@ -34,7 +34,7 @@ export const SpreadsheetSubIssueColumn: React.FC = observer((props: Props
{}} className={cn( - "flex h-11 w-full items-center border-b-[0.5px] border-custom-border-200 px-2.5 py-1 text-xs hover:bg-custom-background-80", + "flex h-11 w-full items-center border-b-[0.5px] border-custom-border-200 px-2.5 py-1 text-xs hover:bg-custom-background-80 group-[.selected-issue-row]:bg-custom-primary-100/5 group-[.selected-issue-row]:hover:bg-custom-primary-100/10", { "cursor-pointer": subIssueCount, } diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/updated-on-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/updated-on-column.tsx index 60a0e6e5368..08d7162d72c 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/updated-on-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/updated-on-column.tsx @@ -1,9 +1,9 @@ import React from "react"; import { observer } from "mobx-react-lite"; +// types import { TIssue } from "@plane/types"; // helpers import { renderFormattedDate } from "@/helpers/date-time.helper"; -// types type Props = { issue: TIssue; @@ -11,8 +11,9 @@ type Props = { export const SpreadsheetUpdatedOnColumn: React.FC = observer((props: Props) => { const { issue } = props; + return ( -
+
{renderFormattedDate(issue.updated_at)}
); diff --git a/web/components/issues/issue-layouts/spreadsheet/issue-column.tsx b/web/components/issues/issue-layouts/spreadsheet/issue-column.tsx index 161dd6514f4..086d0fe3e0f 100644 --- a/web/components/issues/issue-layouts/spreadsheet/issue-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/issue-column.tsx @@ -1,13 +1,14 @@ import { useRef } from "react"; import { observer } from "mobx-react"; import { useRouter } from "next/router"; -import { IIssueDisplayProperties, TIssue } from "@plane/types"; // types +import { IIssueDisplayProperties, TIssue } from "@plane/types"; +// constants import { SPREADSHEET_PROPERTY_DETAILS } from "@/constants/spreadsheet"; +// hooks import { useEventTracker } from "@/hooks/store"; -import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC"; -// constants // components +import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC"; type Props = { displayProperties: IIssueDisplayProperties; @@ -37,7 +38,7 @@ export const IssueColumn = observer((props: Props) => { > { }) } disabled={disableUserActions} - onClose={() => { - tableCellRef?.current?.focus(); - }} + onClose={() => tableCellRef?.current?.focus()} /> diff --git a/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx b/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx index eb33a13f35a..771871bda50 100644 --- a/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx @@ -1,20 +1,23 @@ import { Dispatch, MouseEvent, MutableRefObject, SetStateAction, useRef, useState } from "react"; import { observer } from "mobx-react"; import { useRouter } from "next/router"; -// icons import { ChevronRight, MoreHorizontal } from "lucide-react"; +// types import { IIssueDisplayProperties, TIssue } from "@plane/types"; // ui import { ControlLink, Tooltip } from "@plane/ui"; // components +import { MultipleSelectEntityAction } from "@/components/core"; import RenderIfVisible from "@/components/core/render-if-visible-HOC"; +// constants +import { SPREADSHEET_SELECT_GROUP } from "@/constants/spreadsheet"; // helper import { cn } from "@/helpers/common.helper"; // hooks import { useIssueDetail, useProject } from "@/hooks/store"; +import { TSelectionHelper } from "@/hooks/use-multiple-select"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; import { usePlatformOS } from "@/hooks/use-platform-os"; -// types // local components import { TRenderQuickActions } from "../list/list-view-types"; import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC"; @@ -34,6 +37,7 @@ interface Props { issueIds: string[]; spreadsheetColumnsList: (keyof IIssueDisplayProperties)[]; spacingLeft?: number; + selectionHelpers: TSelectionHelper; } export const SpreadsheetIssueRow = observer((props: Props) => { @@ -51,12 +55,16 @@ export const SpreadsheetIssueRow = observer((props: Props) => { issueIds, spreadsheetColumnsList, spacingLeft = 6, + selectionHelpers, } = props; - + // states const [isExpanded, setExpanded] = useState(false); + // store hooks const { subIssues: subIssuesStore } = useIssueDetail(); - + // derived values const subIssues = subIssuesStore.subIssuesByIssueId(issueId); + const isIssueSelected = selectionHelpers.getIsEntitySelected(issueId); + const isIssueActive = selectionHelpers.getIsEntityActive(issueId); return ( <> @@ -65,7 +73,13 @@ export const SpreadsheetIssueRow = observer((props: Props) => { as="tr" defaultHeight="calc(2.75rem - 1px)" root={containerRef} - placeholderChildren={} + placeholderChildren={ + + } + classNames={cn("bg-custom-background-100 transition-[background-color]", { + "group selected-issue-row": isIssueSelected, + "border-[0.5px] border-custom-border-400": isIssueActive, + })} > { isExpanded={isExpanded} setExpanded={setExpanded} spreadsheetColumnsList={spreadsheetColumnsList} + selectionHelpers={selectionHelpers} /> {isExpanded && - subIssues && - subIssues.length > 0 && - subIssues.map((subIssueId: string) => ( + subIssues?.map((subIssueId: string) => ( { containerRef={containerRef} issueIds={issueIds} spreadsheetColumnsList={spreadsheetColumnsList} + selectionHelpers={selectionHelpers} /> ))} @@ -123,6 +137,7 @@ interface IssueRowDetailsProps { setExpanded: Dispatch>; spreadsheetColumnsList: (keyof IIssueDisplayProperties)[]; spacingLeft?: number; + selectionHelpers: TSelectionHelper; } const IssueRowDetails = observer((props: IssueRowDetailsProps) => { @@ -140,6 +155,7 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => { setExpanded, spreadsheetColumnsList, spacingLeft = 6, + selectionHelpers, } = props; // states const [isMenuActive, setIsMenuActive] = useState(false); @@ -148,7 +164,7 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => { const menuActionRef = useRef(null); // router const router = useRouter(); - const { workspaceSlug } = router.query; + const { workspaceSlug, projectId } = router.query; // hooks const { getProjectIdentifierById } = useProject(); const { getIsIssuePeeked, peekIssue, setPeekIssue } = useIssueDetail(); @@ -171,7 +187,7 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => { const issueDetail = issue.getIssueById(issueId); - const paddingLeft = `${spacingLeft}px`; + const marginLeft = `${spacingLeft}px`; useOutsideClickDetector(menuActionRef, () => setIsMenuActive(false)); @@ -204,16 +220,22 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => { const disableUserActions = !canEditProperties(issueDetail.project_id); const subIssuesCount = issueDetail?.sub_issues_count ?? 0; + const isIssueSelected = selectionHelpers.getIsEntitySelected(issueDetail.id); return ( <> - + handleIssuePeekOverview(issueDetail)} className={cn( - "group clickable cursor-pointer h-11 w-[28rem] flex items-center bg-custom-background-100 text-sm after:absolute border-r-[0.5px] z-10 border-custom-border-200", + "group clickable cursor-pointer h-11 w-[28rem] flex items-center text-sm after:absolute border-r-[0.5px] z-10 border-custom-border-200 bg-transparent group-[.selected-issue-row]:bg-custom-primary-100/5 group-[.selected-issue-row]:hover:bg-custom-primary-100/10", { "border-b-[0.5px]": !getIsIssuePeeked(issueDetail.id), "border border-custom-primary-70 hover:border-custom-primary-70": @@ -223,23 +245,51 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => { )} disabled={!!issueDetail?.tempId} > -
-
- {/* bulk ops */} - -
- {subIssuesCount > 0 && ( - - )} -
+
+ {/* select checkbox */} + {projectId && !disableUserActions && ( + + Only issues within the current +
+ project can be selected. + + } + disabled={issueDetail.project_id === projectId} + > +
+ +
+
+ )} + {/* sub-issues chevron */} +
+ {subIssuesCount > 0 && ( + + )}
diff --git a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-header.tsx b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-header.tsx index 63017f0e72e..9de9a68e7fa 100644 --- a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-header.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-header.tsx @@ -1,33 +1,68 @@ +import { observer } from "mobx-react"; +import { useRouter } from "next/router"; // ui import { IIssueDisplayFilterOptions, IIssueDisplayProperties } from "@plane/types"; -// types -import { LayersIcon } from "@plane/ui"; // components +import { MultipleSelectGroupAction } from "@/components/core"; import { SpreadsheetHeaderColumn } from "@/components/issues/issue-layouts"; +// constants +import { SPREADSHEET_SELECT_GROUP } from "@/constants/spreadsheet"; +// helpers +import { cn } from "@/helpers/common.helper"; +// hooks +import { TSelectionHelper } from "@/hooks/use-multiple-select"; interface Props { displayProperties: IIssueDisplayProperties; displayFilters: IIssueDisplayFilterOptions; handleDisplayFilterUpdate: (data: Partial) => void; + canEditProperties: (projectId: string | undefined) => boolean; isEstimateEnabled: boolean; spreadsheetColumnsList: (keyof IIssueDisplayProperties)[]; + selectionHelpers: TSelectionHelper; } -export const SpreadsheetHeader = (props: Props) => { - const { displayProperties, displayFilters, handleDisplayFilterUpdate, isEstimateEnabled, spreadsheetColumnsList } = - props; +export const SpreadsheetHeader = observer((props: Props) => { + const { + displayProperties, + displayFilters, + handleDisplayFilterUpdate, + canEditProperties, + isEstimateEnabled, + spreadsheetColumnsList, + selectionHelpers, + } = props; + // router + const router = useRouter(); + const { projectId } = router.query; + // derived values + const isGroupSelectionEmpty = selectionHelpers.isGroupSelected(SPREADSHEET_SELECT_GROUP) === "empty"; + // auth + const canSelectIssues = canEditProperties(projectId?.toString()); return ( - - - Issue - + {canSelectIssues && ( +
+ +
+ )} +
+ Issues {spreadsheetColumnsList.map((property) => ( @@ -43,4 +78,4 @@ export const SpreadsheetHeader = (props: Props) => { ); -}; +}); diff --git a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-table.tsx b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-table.tsx index f548c69a5f5..67bc99182f5 100644 --- a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-table.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-table.tsx @@ -2,6 +2,7 @@ import { MutableRefObject, useCallback, useEffect, useRef } from "react"; import { observer } from "mobx-react-lite"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, TIssue } from "@plane/types"; //types +import { TSelectionHelper } from "@/hooks/use-multiple-select"; import { useTableKeyboardNavigation } from "@/hooks/use-table-keyboard-navigation"; //components import { TRenderQuickActions } from "../list/list-view-types"; @@ -20,6 +21,7 @@ type Props = { portalElement: React.MutableRefObject; containerRef: MutableRefObject; spreadsheetColumnsList: (keyof IIssueDisplayProperties)[]; + selectionHelpers: TSelectionHelper; }; export const SpreadsheetTable = observer((props: Props) => { @@ -35,6 +37,7 @@ export const SpreadsheetTable = observer((props: Props) => { canEditProperties, containerRef, spreadsheetColumnsList, + selectionHelpers, } = props; // states @@ -81,8 +84,10 @@ export const SpreadsheetTable = observer((props: Props) => { displayProperties={displayProperties} displayFilters={displayFilters} handleDisplayFilterUpdate={handleDisplayFilterUpdate} + canEditProperties={canEditProperties} isEstimateEnabled={isEstimateEnabled} spreadsheetColumnsList={spreadsheetColumnsList} + selectionHelpers={selectionHelpers} /> {issueIds.map((id) => ( @@ -100,6 +105,7 @@ export const SpreadsheetTable = observer((props: Props) => { isScrolled={isScrolled} issueIds={issueIds} spreadsheetColumnsList={spreadsheetColumnsList} + selectionHelpers={selectionHelpers} /> ))} diff --git a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx index a1f44d7f1e9..c4b6e7d69a7 100644 --- a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx @@ -1,15 +1,18 @@ import React, { useRef } from "react"; import { observer } from "mobx-react-lite"; +// types import { TIssue, IIssueDisplayFilterOptions, IIssueDisplayProperties } from "@plane/types"; // components import { LogoSpinner } from "@/components/common"; -import { SpreadsheetQuickAddIssueForm } from "@/components/issues"; -import { SPREADSHEET_PROPERTY_LIST } from "@/constants/spreadsheet"; +import { MultipleSelectGroup } from "@/components/core"; +import { IssueBulkOperationsRoot, SpreadsheetQuickAddIssueForm } from "@/components/issues"; +// constants +import { SPREADSHEET_PROPERTY_LIST, SPREADSHEET_SELECT_GROUP } from "@/constants/spreadsheet"; +// hooks import { useProject } from "@/hooks/store"; +// types import { TRenderQuickActions } from "../list/list-view-types"; import { SpreadsheetTable } from "./spreadsheet-table"; -// types -//hooks type Props = { displayProperties: IIssueDisplayProperties; @@ -73,28 +76,41 @@ export const SpreadsheetView: React.FC = observer((props) => { return (
-
- -
-
-
- {enableQuickCreateIssue && !disableIssueCreation && ( - - )} -
-
+ + {(helpers) => ( + <> +
+ +
+
+
+ {enableQuickCreateIssue && !disableIssueCreation && ( + + )} +
+
+ + + )} +
); }); diff --git a/web/components/workspace/help-section.tsx b/web/components/workspace/help-section.tsx index 130a62c8733..513be22a51a 100644 --- a/web/components/workspace/help-section.tsx +++ b/web/components/workspace/help-section.tsx @@ -1,12 +1,12 @@ import React, { useRef, useState } from "react"; import { observer } from "mobx-react"; import Link from "next/link"; -// headless ui import { FileText, HelpCircle, MessagesSquare, MoveLeft, Zap } from "lucide-react"; import { Transition } from "@headlessui/react"; -// icons // ui import { DiscordIcon, GithubIcon, Tooltip } from "@plane/ui"; +// helpers +import { cn } from "@/helpers/common.helper"; // hooks import { useAppTheme, useCommandPalette } from "@/hooks/store"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; @@ -59,9 +59,12 @@ export const WorkspaceHelpSection: React.FC = observe return ( <>
{!isCollapsed && ( diff --git a/web/constants/common.ts b/web/constants/common.ts index 71d65765c5f..0c1431740d4 100644 --- a/web/constants/common.ts +++ b/web/constants/common.ts @@ -3,3 +3,5 @@ export const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB export const MARKETING_PRICING_PAGE_LINK = "https://plane.so/pricing"; export const MARKETING_CONTACT_US_PAGE_LINK = "https://plane.so/contact"; + +export const MARKETING_PLANE_ONE_PAGE_LINK = "https://plane.so/one"; diff --git a/web/constants/spreadsheet.ts b/web/constants/spreadsheet.ts index d70a603a24e..c45a1bf7c0c 100644 --- a/web/constants/spreadsheet.ts +++ b/web/constants/spreadsheet.ts @@ -1,6 +1,16 @@ import { FC } from "react"; // icons -import { CalendarDays, Link2, Signal, Tag, Triangle, Paperclip, CalendarCheck2, CalendarClock, Users } from "lucide-react"; +import { + CalendarDays, + Link2, + Signal, + Tag, + Triangle, + Paperclip, + CalendarCheck2, + CalendarClock, + Users, +} from "lucide-react"; // types import { IIssueDisplayProperties, TIssue, TIssueOrderByOptions } from "@plane/types"; // ui @@ -184,3 +194,5 @@ export const SPREADSHEET_PROPERTY_LIST: (keyof IIssueDisplayProperties)[] = [ "attachment_count", "sub_issue_count", ]; + +export const SPREADSHEET_SELECT_GROUP = "spreadsheet-issues"; From 87582604f725ed233cc4609f34bec13028ebcd2e Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Tue, 4 Jun 2024 11:22:40 +0530 Subject: [PATCH 002/307] style: fix overlapping of response container in AI popover. (#4684) --- web/components/core/modals/gpt-assistant-popover.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/web/components/core/modals/gpt-assistant-popover.tsx b/web/components/core/modals/gpt-assistant-popover.tsx index 5fd992a28c2..099b90254e4 100644 --- a/web/components/core/modals/gpt-assistant-popover.tsx +++ b/web/components/core/modals/gpt-assistant-popover.tsx @@ -210,11 +210,7 @@ export const GptAssistantPopover: React.FC = (props) => { {response !== "" && (
Response: - ${response}

`} - containerClassName={response ? "-mx-3 -my-3" : ""} - ref={responseRef} - /> + ${response}

`} ref={responseRef} />
)} {invalidResponse && ( From cad55f323487712940e155a51a83536654533735 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Tue, 4 Jun 2024 13:11:49 +0530 Subject: [PATCH 003/307] [WEB-1498] style: fix comments reaction alignment. (#4686) --- .../issues/issue-detail/reactions/reaction-selector.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/components/issues/issue-detail/reactions/reaction-selector.tsx b/web/components/issues/issue-detail/reactions/reaction-selector.tsx index da517e975b5..e3d58b50482 100644 --- a/web/components/issues/issue-detail/reactions/reaction-selector.tsx +++ b/web/components/issues/issue-detail/reactions/reaction-selector.tsx @@ -44,11 +44,11 @@ export const ReactionSelector: React.FC = (props) => { leaveTo="opacity-0 translate-y-1" > -
+
{reactionEmojis.map((emoji) => (
)} - {inboxIssuePaginationInfo?.next_page_results && ( -
+
+ {inboxIssuePaginationInfo?.next_page_results && ( + )}
- )}
)}
diff --git a/web/hooks/use-intersection-observer.tsx b/web/hooks/use-intersection-observer.tsx index 71c5f4b7f0d..5ed56051135 100644 --- a/web/hooks/use-intersection-observer.tsx +++ b/web/hooks/use-intersection-observer.tsx @@ -9,12 +9,12 @@ export type UseIntersectionObserverProps = { export const useIntersectionObserver = ( containerRef: RefObject, - elementRef: RefObject, + elementRef: HTMLDivElement | null, callback: () => void, rootMargin?: string ) => { useEffect(() => { - if (elementRef.current) { + if (elementRef) { const observer = new IntersectionObserver( (entries) => { if (entries[entries.length - 1].isIntersecting) { @@ -26,16 +26,16 @@ export const useIntersectionObserver = ( rootMargin, } ); - observer.observe(elementRef.current); + observer.observe(elementRef); return () => { - if (elementRef.current) { + if (elementRef) { // eslint-disable-next-line react-hooks/exhaustive-deps - observer.unobserve(elementRef.current); + observer.unobserve(elementRef); } }; } // while removing the current from the refs, the observer is not not working as expected // fix this eslint warning with caution // eslint-disable-next-line react-hooks/exhaustive-deps - }, [rootMargin, callback, elementRef.current, containerRef.current]); + }, [rootMargin, callback, elementRef, containerRef.current]); }; diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/inbox/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/inbox/index.tsx index f32adaf5d86..fc28c266c8c 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/inbox/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/inbox/index.tsx @@ -1,4 +1,4 @@ -import { ReactElement, useEffect } from "react"; +import { ReactElement } from "react"; import { observer } from "mobx-react"; import { useRouter } from "next/router"; // components @@ -11,7 +11,7 @@ import { EmptyStateType } from "@/constants/empty-state"; // helpers import { EInboxIssueCurrentTab } from "@/helpers/inbox.helper"; // hooks -import { useProject, useProjectInbox } from "@/hooks/store"; +import { useProject } from "@/hooks/store"; // layouts import { AppLayout } from "@/layouts/app-layout"; // types @@ -23,12 +23,6 @@ const ProjectInboxPage: NextPageWithLayout = observer(() => { const { workspaceSlug, projectId, currentTab: navigationTab, inboxIssueId } = router.query; // hooks const { currentProjectDetails } = useProject(); - const { currentTab, handleCurrentTab } = useProjectInbox(); - - useEffect(() => { - if (navigationTab && currentTab != navigationTab) - handleCurrentTab(navigationTab === "open" ? EInboxIssueCurrentTab.OPEN : EInboxIssueCurrentTab.CLOSED); - }, [currentTab, navigationTab, handleCurrentTab]); // No access to inbox if (currentProjectDetails?.inbox_view === false) @@ -44,6 +38,12 @@ const ProjectInboxPage: NextPageWithLayout = observer(() => { // derived values const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Inbox` : "Plane - Inbox"; + const currentNavigationTab = navigationTab + ? navigationTab === "open" + ? EInboxIssueCurrentTab.OPEN + : EInboxIssueCurrentTab.CLOSED + : undefined; + if (!workspaceSlug || !projectId) return <>; return ( @@ -55,6 +55,7 @@ const ProjectInboxPage: NextPageWithLayout = observer(() => { projectId={projectId.toString()} inboxIssueId={inboxIssueId?.toString() || undefined} inboxAccessible={currentProjectDetails?.inbox_view || false} + navigationTab={currentNavigationTab} />
diff --git a/web/store/inbox/project-inbox.store.ts b/web/store/inbox/project-inbox.store.ts index 2aecb17cbe5..2557f79797b 100644 --- a/web/store/inbox/project-inbox.store.ts +++ b/web/store/inbox/project-inbox.store.ts @@ -321,8 +321,6 @@ export class ProjectInboxStore implements IProjectInboxStore { (this.inboxIssuePaginationInfo?.total_results && this.inboxIssueIds.length < this.inboxIssuePaginationInfo?.total_results)) ) { - this.loader = "pagination-loading"; - const queryParams = this.inboxIssueQueryParams( this.inboxFilters, this.inboxSorting, @@ -332,7 +330,6 @@ export class ProjectInboxStore implements IProjectInboxStore { const { results, ...paginationInfo } = await this.inboxIssueService.list(workspaceSlug, projectId, queryParams); runInAction(() => { - this.loader = undefined; set(this, "inboxIssuePaginationInfo", paginationInfo); if (results && results.length > 0) { const issueIds = results.map((value) => value?.issue?.id); @@ -343,7 +340,6 @@ export class ProjectInboxStore implements IProjectInboxStore { } else set(this, ["inboxIssuePaginationInfo", "next_page_results"], false); } catch (error) { console.error("Error fetching the inbox issues", error); - this.loader = undefined; this.error = { message: "Error fetching the paginated inbox issues please try again later.", status: "pagination-error", From 188f8ff83f108fd5cc2f384e73456d66b91603fc Mon Sep 17 00:00:00 2001 From: Satish Gandham Date: Tue, 4 Jun 2024 14:17:35 +0530 Subject: [PATCH 007/307] feat: Add components required for estimates (#4690) * Add sortable, radio and typography components * Remove stray css classes * Prevent drag of items from other draggable * Minor cleanup * Update yarn.lock * Remove radio input component as it was build on headless ui v2.0.0 and now we are using v1.7.0 * Fix build errors * Update dependencies in use memo. --- packages/ui/src/index.ts | 3 +- packages/ui/src/sortable/draggable.tsx | 62 +++++++++++++++ packages/ui/src/sortable/index.ts | 2 + packages/ui/src/sortable/sortable.stories.tsx | 32 ++++++++ packages/ui/src/sortable/sortable.tsx | 79 +++++++++++++++++++ packages/ui/src/typography/index.tsx | 1 + packages/ui/src/typography/sub-heading.tsx | 15 ++++ web/lib/app-provider.tsx | 4 +- yarn.lock | 2 +- 9 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 packages/ui/src/sortable/draggable.tsx create mode 100644 packages/ui/src/sortable/index.ts create mode 100644 packages/ui/src/sortable/sortable.stories.tsx create mode 100644 packages/ui/src/sortable/sortable.tsx create mode 100644 packages/ui/src/typography/index.tsx create mode 100644 packages/ui/src/typography/sub-heading.tsx diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index dae01238162..23fc7ed62fd 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -13,4 +13,5 @@ export * from "./loader"; export * from "./control-link"; export * from "./toast"; export * from "./drag-handle"; -export * from "./drop-indicator"; \ No newline at end of file +export * from "./drop-indicator"; +export * from "./sortable"; diff --git a/packages/ui/src/sortable/draggable.tsx b/packages/ui/src/sortable/draggable.tsx new file mode 100644 index 00000000000..7fded837e6f --- /dev/null +++ b/packages/ui/src/sortable/draggable.tsx @@ -0,0 +1,62 @@ +import React, { useEffect, useRef, useState } from "react"; +import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; +import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; +import { isEqual } from "lodash"; +import { cn } from "../../helpers"; +import { attachClosestEdge, extractClosestEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge"; +import { DropIndicator } from "../drop-indicator"; + +type Props = { + children: React.ReactNode; + data: any; //@todo make this generic + className?: string; +}; +const Draggable = ({ children, data, className }: Props) => { + const ref = useRef(null); + const [dragging, setDragging] = useState(false); // NEW + const [isDraggedOver, setIsDraggedOver] = useState(false); + + const [closestEdge, setClosestEdge] = useState(null); + useEffect(() => { + const el = ref.current; + + if (el) { + combine( + draggable({ + element: el, + onDragStart: () => setDragging(true), // NEW + onDrop: () => setDragging(false), // NEW + getInitialData: () => data, + }), + dropTargetForElements({ + element: el, + onDragEnter: (args) => { + setIsDraggedOver(true); + setClosestEdge(extractClosestEdge(args.self.data)); + }, + onDragLeave: () => setIsDraggedOver(false), + onDrop: () => { + setIsDraggedOver(false); + }, + canDrop: ({ source }) => !isEqual(source.data, data) && source.data.__uuid__ === data.__uuid__, + getData: ({ input, element }) => + attachClosestEdge(data, { + input, + element, + allowedEdges: ["top", "bottom"], + }), + }) + ); + } + }, [data]); + + return ( +
+ {} + {children} + {} +
+ ); +}; + +export { Draggable }; diff --git a/packages/ui/src/sortable/index.ts b/packages/ui/src/sortable/index.ts new file mode 100644 index 00000000000..9dde5a40474 --- /dev/null +++ b/packages/ui/src/sortable/index.ts @@ -0,0 +1,2 @@ +export * from "./sortable"; +export * from "./draggable"; diff --git a/packages/ui/src/sortable/sortable.stories.tsx b/packages/ui/src/sortable/sortable.stories.tsx new file mode 100644 index 00000000000..6d40ddc2ee1 --- /dev/null +++ b/packages/ui/src/sortable/sortable.stories.tsx @@ -0,0 +1,32 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import React from "react"; +import { Draggable } from "./draggable"; +import { Sortable } from "./sortable"; + +const meta: Meta = { + title: "Sortable", + component: Sortable, +}; + +export default meta; +type Story = StoryObj; + +const data = [ + { id: "1", name: "John Doe" }, + { id: "2", name: "Jane Doe 2" }, + { id: "3", name: "Alice" }, + { id: "4", name: "Bob" }, + { id: "5", name: "Charlie" }, +]; +export const Default: Story = { + args: { + data, + render: (item: any) => ( + // +
{item.name}
+ //
+ ), + onChange: (data) => console.log(data.map(({ id }) => id)), + keyExtractor: (item: any) => item.id, + }, +}; diff --git a/packages/ui/src/sortable/sortable.tsx b/packages/ui/src/sortable/sortable.tsx new file mode 100644 index 00000000000..9d79a8f5900 --- /dev/null +++ b/packages/ui/src/sortable/sortable.tsx @@ -0,0 +1,79 @@ +import React, { Fragment, useEffect, useMemo } from "react"; +import { monitorForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; +import { Draggable } from "./draggable"; + +type Props = { + data: T[]; + render: (item: T, index: number) => React.ReactNode; + onChange: (data: T[]) => void; + keyExtractor: (item: T, index: number) => string; + containerClassName?: string; + id: string; +}; + +const moveItem = ( + data: T[], + source: T, + destination: T & Record, + keyExtractor: (item: T, index: number) => string +) => { + const sourceIndex = data.indexOf(source); + if (sourceIndex === -1) return data; + + const destinationIndex = data.findIndex((item, index) => keyExtractor(item, index) === keyExtractor(destination, 0)); + + if (destinationIndex === -1) return data; + + const symbolKey = Reflect.ownKeys(destination).find((key) => key.toString() === "Symbol(closestEdge)"); + const position = symbolKey ? destination[symbolKey as symbol] : "bottom"; // Add 'as symbol' to cast symbolKey to symbol + const newData = [...data]; + const [movedItem] = newData.splice(sourceIndex, 1); + + let adjustedDestinationIndex = destinationIndex; + if (position === "bottom") { + adjustedDestinationIndex++; + } + + // Prevent moving item out of bounds + if (adjustedDestinationIndex > newData.length) { + adjustedDestinationIndex = newData.length; + } + + newData.splice(adjustedDestinationIndex, 0, movedItem); + + return newData; +}; + +export const Sortable = ({ data, render, onChange, keyExtractor, containerClassName, id }: Props) => { + useEffect(() => { + const unsubscribe = monitorForElements({ + onDrop({ source, location }) { + const destination = location?.current?.dropTargets[0]; + if (!destination) return; + onChange(moveItem(data, source.data as T, destination.data as T & { closestEdge: string }, keyExtractor)); + }, + }); + + // Clean up the subscription on unmount + return () => { + if (unsubscribe) unsubscribe(); + }; + }, [data, keyExtractor, onChange]); + + const enhancedData = useMemo(() => { + const uuid = id ? id : Math.random().toString(36).substring(7); + return data.map((item) => ({ ...item, __uuid__: uuid })); + }, [data, id]); + + return ( + <> + {enhancedData.map((item, index) => ( + + {render(item, index)} + + ))} + + ); +}; + +export default Sortable; diff --git a/packages/ui/src/typography/index.tsx b/packages/ui/src/typography/index.tsx new file mode 100644 index 00000000000..0b1b7ffe1c4 --- /dev/null +++ b/packages/ui/src/typography/index.tsx @@ -0,0 +1 @@ +export * from "./sub-heading"; diff --git a/packages/ui/src/typography/sub-heading.tsx b/packages/ui/src/typography/sub-heading.tsx new file mode 100644 index 00000000000..9e7075583cc --- /dev/null +++ b/packages/ui/src/typography/sub-heading.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import { cn } from "../../helpers"; + +type Props = { + children: React.ReactNode; + className?: string; + noMargin?: boolean; +}; +const SubHeading = ({ children, className, noMargin }: Props) => ( +

+ {children} +

+); + +export { SubHeading }; diff --git a/web/lib/app-provider.tsx b/web/lib/app-provider.tsx index 1044af012cb..692a43b9fd3 100644 --- a/web/lib/app-provider.tsx +++ b/web/lib/app-provider.tsx @@ -43,8 +43,6 @@ export const AppProvider: FC = observer((props) => { return ( <> - {/* TODO: Need to handle custom themes for toast */} - @@ -56,6 +54,8 @@ export const AppProvider: FC = observer((props) => { posthogAPIKey={config?.posthog_api_key || undefined} posthogHost={config?.posthog_host || undefined} > + {/* TODO: Need to handle custom themes for toast */} + {children} diff --git a/yarn.lock b/yarn.lock index b807736a8d8..e1fee6c52b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13945,4 +13945,4 @@ yocto-queue@^0.1.0: zxcvbn@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/zxcvbn/-/zxcvbn-4.4.2.tgz#28ec17cf09743edcab056ddd8b1b06262cc73c30" - integrity sha512-Bq0B+ixT/DMyG8kgX2xWcI5jUvCwqrMxSFam7m0lAf78nf04hv6lNCsyLYdyYTrCVMqNDY/206K7eExYCeSyUQ== + integrity sha512-Bq0B+ixT/DMyG8kgX2xWcI5jUvCwqrMxSFam7m0lAf78nf04hv6lNCsyLYdyYTrCVMqNDY/206K7eExYCeSyUQ== \ No newline at end of file From e503c901ae806aa99eb083b8a572ebf605fd7b6f Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Tue, 4 Jun 2024 15:35:20 +0530 Subject: [PATCH 008/307] [WEB-1521] chore: add configuration to enable/disable sign-ups. (#4697) --- admin/app/authentication/page.tsx | 35 +++++++++++++++++-- .../components/admin-sidebar/help-section.tsx | 2 +- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/admin/app/authentication/page.tsx b/admin/app/authentication/page.tsx index d1e6fb0ba18..c44b74b4907 100644 --- a/admin/app/authentication/page.tsx +++ b/admin/app/authentication/page.tsx @@ -7,12 +7,12 @@ import { useTheme } from "next-themes"; import useSWR from "swr"; import { Mails, KeyRound } from "lucide-react"; import { TInstanceConfigurationKeys } from "@plane/types"; -import { Loader, setPromiseToast } from "@plane/ui"; +import { Loader, ToggleSwitch, setPromiseToast } from "@plane/ui"; // components import { PageHeader } from "@/components/core"; // hooks // helpers -import { resolveGeneralTheme } from "@/helpers/common.helper"; +import { cn, resolveGeneralTheme } from "@/helpers/common.helper"; import { useInstance } from "@/hooks/store"; // images import githubLightModeImage from "@/public/logos/github-black.png"; @@ -45,6 +45,8 @@ const InstanceAuthenticationPage = observer(() => { const [isSubmitting, setIsSubmitting] = useState(false); // theme const { resolvedTheme } = useTheme(); + // derived values + const enableSignUpConfig = formattedConfig?.ENABLE_SIGNUP ?? ""; const updateConfig = async (key: TInstanceConfigurationKeys, value: string) => { setIsSubmitting(true); @@ -129,7 +131,34 @@ const InstanceAuthenticationPage = observer(() => {
{formattedConfig ? (
-
Authentication modes
+
Sign-up configuration
+
+
+
+
+ Allow anyone to sign up without invite +
+
+ Toggling this off will disable self sign ups. +
+
+
+
+
+ { + Boolean(parseInt(enableSignUpConfig)) === true + ? updateConfig("ENABLE_SIGNUP", "0") + : updateConfig("ENABLE_SIGNUP", "1"); + }} + size="sm" + disabled={isSubmitting} + /> +
+
+
+
Authentication modes
{authenticationMethodsCard.map((method) => ( { return (
Date: Tue, 4 Jun 2024 18:40:41 +0530 Subject: [PATCH 009/307] fix: regenerating lock file --- yarn.lock | 2786 +++++++++++++++++++++++++---------------------------- 1 file changed, 1314 insertions(+), 1472 deletions(-) diff --git a/yarn.lock b/yarn.lock index e1fee6c52b3..00f56ce5c76 100644 --- a/yarn.lock +++ b/yarn.lock @@ -46,9 +46,9 @@ "@babel/runtime" "^7.0.0" "@atlaskit/pragmatic-drag-and-drop@^1.1.0", "@atlaskit/pragmatic-drag-and-drop@^1.1.3": - version "1.1.9" - resolved "https://registry.yarnpkg.com/@atlaskit/pragmatic-drag-and-drop/-/pragmatic-drag-and-drop-1.1.9.tgz#1fc8e4c0af209cd9201125773a1fe4d49f2428b7" - integrity sha512-HZkT0mStizzneobq7vINEnEsm7DTu70gaEu0HHSNKv6lkX4tI0h1HoAyDUZqH3xFc5nuhvjdFxygZRzMhs5w+g== + version "1.1.10" + resolved "https://registry.yarnpkg.com/@atlaskit/pragmatic-drag-and-drop/-/pragmatic-drag-and-drop-1.1.10.tgz#b7671bb6329adec6603c1805310a5ec16ff6fb7c" + integrity sha512-imiaN7bfZJM92Xq+qgbzIaKgLeHpd56w5/ECrBW1HMIXTRFauLdEo1w6PCcI4k1r18pA19E1mNhSb5+WvhMkOg== dependencies: "@babel/runtime" "^7.0.0" bind-event-listener "^3.0.0" @@ -61,15 +61,7 @@ dependencies: default-browser-id "3.0.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.2": - version "7.24.2" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" - integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== - dependencies: - "@babel/highlight" "^7.24.2" - picocolors "^1.0.0" - -"@babel/code-frame@^7.24.6": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.24.6": version "7.24.6" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.6.tgz#ab88da19344445c3d8889af2216606d3329f3ef2" integrity sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA== @@ -77,38 +69,12 @@ "@babel/highlight" "^7.24.6" picocolors "^1.0.0" -"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.23.5", "@babel/compat-data@^7.24.4": - version "7.24.4" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.4.tgz#6f102372e9094f25d908ca0d34fc74c74606059a" - integrity sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ== - -"@babel/compat-data@^7.24.6": +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.24.6": version "7.24.6" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.6.tgz#b3600217688cabb26e25f8e467019e66d71b7ae2" integrity sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ== -"@babel/core@^7.11.1", "@babel/core@^7.18.9", "@babel/core@^7.23.0", "@babel/core@^7.24.4": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.5.tgz#15ab5b98e101972d171aeef92ac70d8d6718f06a" - integrity sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.24.2" - "@babel/generator" "^7.24.5" - "@babel/helper-compilation-targets" "^7.23.6" - "@babel/helper-module-transforms" "^7.24.5" - "@babel/helpers" "^7.24.5" - "@babel/parser" "^7.24.5" - "@babel/template" "^7.24.0" - "@babel/traverse" "^7.24.5" - "@babel/types" "^7.24.5" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/core@^7.18.5": +"@babel/core@^7.11.1", "@babel/core@^7.18.5", "@babel/core@^7.18.9", "@babel/core@^7.23.0", "@babel/core@^7.24.4": version "7.24.6" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.6.tgz#8650e0e4b03589ebe886c4e4a60398db0a7ec787" integrity sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ== @@ -129,17 +95,7 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.24.4", "@babel/generator@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.5.tgz#e5afc068f932f05616b66713e28d0f04e99daeb3" - integrity sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA== - dependencies: - "@babel/types" "^7.24.5" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^2.5.1" - -"@babel/generator@^7.24.6": +"@babel/generator@^7.24.4", "@babel/generator@^7.24.6": version "7.24.6" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.6.tgz#dfac82a228582a9d30c959fe50ad28951d4737a7" integrity sha512-S7m4eNa6YAPJRHmKsLHIDJhNAGNKoWNiWefz1MBbpnt8g9lvMDl1hir4P9bo/57bQEmuwEhnRU/AMWsD0G/Fbg== @@ -149,32 +105,21 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" -"@babel/helper-annotate-as-pure@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" - integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-builder-binary-assignment-operator-visitor@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz#5426b109cf3ad47b91120f8328d8ab1be8b0b956" - integrity sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw== +"@babel/helper-annotate-as-pure@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.6.tgz#517af93abc77924f9b2514c407bbef527fb8938d" + integrity sha512-DitEzDfOMnd13kZnDqns1ccmftwJTS9DMkyn9pYTxulS7bZxUxpMly3Nf23QQ6NwA4UB8lAqjbqWtyvElEMAkg== dependencies: - "@babel/types" "^7.22.15" + "@babel/types" "^7.24.6" -"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.23.6": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" - integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== +"@babel/helper-builder-binary-assignment-operator-visitor@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.6.tgz#19e9089ee87b0d0928012c83961a8deef4b0223f" + integrity sha512-+wnfqc5uHiMYtvRX7qu80Toef8BXeh4HHR1SPeonGb1SKPniNEd4a/nlaJJMv/OIEYvIVavvo0yR7u10Gqz0Iw== dependencies: - "@babel/compat-data" "^7.23.5" - "@babel/helper-validator-option" "^7.23.5" - browserslist "^4.22.2" - lru-cache "^5.1.1" - semver "^6.3.1" + "@babel/types" "^7.24.6" -"@babel/helper-compilation-targets@^7.24.6": +"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.24.6": version "7.24.6" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.6.tgz#4a51d681f7680043d38e212715e2a7b1ad29cb51" integrity sha512-VZQ57UsDGlX/5fFA7GkVPplZhHsVc+vuErWgdOiysI9Ksnw0Pbbd6pnPiR/mmJyKHgyIW0c7KT32gmhiF+cirg== @@ -185,27 +130,27 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.24.1", "@babel/helper-create-class-features-plugin@^7.24.4", "@babel/helper-create-class-features-plugin@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz#7d19da92c7e0cd8d11c09af2ce1b8e7512a6e723" - integrity sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-member-expression-to-functions" "^7.24.5" - "@babel/helper-optimise-call-expression" "^7.22.5" - "@babel/helper-replace-supers" "^7.24.1" - "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.24.5" +"@babel/helper-create-class-features-plugin@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.6.tgz#c50b86fa1c4ca9b7a890dc21884f097b6c4b5286" + integrity sha512-djsosdPJVZE6Vsw3kk7IPRWethP94WHGOhQTc67SNXE0ZzMhHgALw8iGmYS0TD1bbMM0VDROy43od7/hN6WYcA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.6" + "@babel/helper-environment-visitor" "^7.24.6" + "@babel/helper-function-name" "^7.24.6" + "@babel/helper-member-expression-to-functions" "^7.24.6" + "@babel/helper-optimise-call-expression" "^7.24.6" + "@babel/helper-replace-supers" "^7.24.6" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.6" + "@babel/helper-split-export-declaration" "^7.24.6" semver "^6.3.1" -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.15", "@babel/helper-create-regexp-features-plugin@^7.22.5": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1" - integrity sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w== +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.6.tgz#47d382dec0d49e74ca1b6f7f3b81f5968022a3c8" + integrity sha512-C875lFBIWWwyv6MHZUG9HmRrlTDgOsLWZfYR0nW69gaKJNe0/Mpxx5r0EID2ZdHQkdUmQo2t0uNckTL08/1BgA== dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-annotate-as-pure" "^7.24.6" regexpu-core "^5.3.1" semver "^6.3.1" @@ -220,24 +165,11 @@ lodash.debounce "^4.0.8" resolve "^1.14.2" -"@babel/helper-environment-visitor@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" - integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== - "@babel/helper-environment-visitor@^7.24.6": version "7.24.6" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz#ac7ad5517821641550f6698dd5468f8cef78620d" integrity sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g== -"@babel/helper-function-name@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" - integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== - dependencies: - "@babel/template" "^7.22.15" - "@babel/types" "^7.23.0" - "@babel/helper-function-name@^7.24.6": version "7.24.6" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.24.6.tgz#cebdd063386fdb95d511d84b117e51fc68fec0c8" @@ -246,13 +178,6 @@ "@babel/template" "^7.24.6" "@babel/types" "^7.24.6" -"@babel/helper-hoist-variables@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" - integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== - dependencies: - "@babel/types" "^7.22.5" - "@babel/helper-hoist-variables@^7.24.6": version "7.24.6" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.6.tgz#8a7ece8c26756826b6ffcdd0e3cf65de275af7f9" @@ -260,38 +185,20 @@ dependencies: "@babel/types" "^7.24.6" -"@babel/helper-member-expression-to-functions@^7.23.0", "@babel/helper-member-expression-to-functions@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz#5981e131d5c7003c7d1fa1ad49e86c9b097ec475" - integrity sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA== - dependencies: - "@babel/types" "^7.24.5" - -"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.24.1", "@babel/helper-module-imports@^7.24.3": - version "7.24.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz#6ac476e6d168c7c23ff3ba3cf4f7841d46ac8128" - integrity sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg== +"@babel/helper-member-expression-to-functions@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.6.tgz#86084f3e0e4e2169a134754df3870bc7784db71e" + integrity sha512-OTsCufZTxDUsv2/eDXanw/mUZHWOxSbEmC3pP8cgjcy5rgeVPWWMStnv274DV60JtHxTk0adT0QrCzC4M9NWGg== dependencies: - "@babel/types" "^7.24.0" + "@babel/types" "^7.24.6" -"@babel/helper-module-imports@^7.24.6": +"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.24.6": version "7.24.6" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.6.tgz#65e54ffceed6a268dc4ce11f0433b82cfff57852" integrity sha512-a26dmxFJBF62rRO9mmpgrfTLsAuyHk4e1hKTUkD/fcMfynt8gvEKwQPQDVxWhca8dHoDck+55DFt42zV0QMw5g== dependencies: "@babel/types" "^7.24.6" -"@babel/helper-module-transforms@^7.23.3", "@babel/helper-module-transforms@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz#ea6c5e33f7b262a0ae762fd5986355c45f54a545" - integrity sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A== - dependencies: - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-module-imports" "^7.24.3" - "@babel/helper-simple-access" "^7.24.5" - "@babel/helper-split-export-declaration" "^7.24.5" - "@babel/helper-validator-identifier" "^7.24.5" - "@babel/helper-module-transforms@^7.24.6": version "7.24.6" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.6.tgz#22346ed9df44ce84dee850d7433c5b73fab1fe4e" @@ -303,42 +210,35 @@ "@babel/helper-split-export-declaration" "^7.24.6" "@babel/helper-validator-identifier" "^7.24.6" -"@babel/helper-optimise-call-expression@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" - integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw== +"@babel/helper-optimise-call-expression@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.6.tgz#f7836e3ccca3dfa02f15d2bc8b794efe75a5256e" + integrity sha512-3SFDJRbx7KuPRl8XDUr8O7GAEB8iGyWPjLKJh/ywP/Iy9WOmEfMrsWbaZpvBu2HSYn4KQygIsz0O7m8y10ncMA== dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.0", "@babel/helper-plugin-utils@^7.24.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz#a924607dd254a65695e5bd209b98b902b3b2f11a" - integrity sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ== + "@babel/types" "^7.24.6" -"@babel/helper-remap-async-to-generator@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz#7b68e1cb4fa964d2996fd063723fb48eca8498e0" - integrity sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-wrap-function" "^7.22.20" +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.6", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.6.tgz#fa02a32410a15a6e8f8185bcbf608f10528d2a24" + integrity sha512-MZG/JcWfxybKwsA9N9PmtF2lOSFSEMVCpIRrbxccZFLJPrJciJdG/UhSh5W96GEteJI2ARqm5UAHxISwRDLSNg== -"@babel/helper-replace-supers@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz#7085bd19d4a0b7ed8f405c1ed73ccb70f323abc1" - integrity sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ== +"@babel/helper-remap-async-to-generator@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.6.tgz#c96ceb9846e877d806ce82a1521230ea7e0fc354" + integrity sha512-1Qursq9ArRZPAMOZf/nuzVW8HgJLkTB9y9LfP4lW2MVp4e9WkLJDovfKBxoDcCk6VuzIxyqWHyBoaCtSRP10yg== dependencies: - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-member-expression-to-functions" "^7.23.0" - "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-annotate-as-pure" "^7.24.6" + "@babel/helper-environment-visitor" "^7.24.6" + "@babel/helper-wrap-function" "^7.24.6" -"@babel/helper-simple-access@^7.22.5", "@babel/helper-simple-access@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz#50da5b72f58c16b07fbd992810be6049478e85ba" - integrity sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ== +"@babel/helper-replace-supers@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.24.6.tgz#3ea87405a2986a49ab052d10e540fe036d747c71" + integrity sha512-mRhfPwDqDpba8o1F8ESxsEkJMQkUF8ZIWrAc0FtWhxnjfextxMWxr22RtFizxxSYLjVHDeMgVsRq8BBZR2ikJQ== dependencies: - "@babel/types" "^7.24.5" + "@babel/helper-environment-visitor" "^7.24.6" + "@babel/helper-member-expression-to-functions" "^7.24.6" + "@babel/helper-optimise-call-expression" "^7.24.6" "@babel/helper-simple-access@^7.24.6": version "7.24.6" @@ -347,19 +247,12 @@ dependencies: "@babel/types" "^7.24.6" -"@babel/helper-skip-transparent-expression-wrappers@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" - integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-split-export-declaration@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz#b9a67f06a46b0b339323617c8c6213b9055a78b6" - integrity sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q== +"@babel/helper-skip-transparent-expression-wrappers@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.6.tgz#c47e9b33b7ea50d1073e125ebc26661717cb7040" + integrity sha512-jhbbkK3IUKc4T43WadP96a27oYti9gEf1LdyGSP2rHGH77kwLwfhO7TgwnWvxxQVmke0ImmCSS47vcuxEMGD3Q== dependencies: - "@babel/types" "^7.24.5" + "@babel/types" "^7.24.6" "@babel/helper-split-export-declaration@^7.24.6": version "7.24.6" @@ -368,53 +261,29 @@ dependencies: "@babel/types" "^7.24.6" -"@babel/helper-string-parser@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e" - integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== - "@babel/helper-string-parser@^7.24.6": version "7.24.6" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.6.tgz#28583c28b15f2a3339cfafafeaad42f9a0e828df" integrity sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q== -"@babel/helper-validator-identifier@^7.22.20", "@babel/helper-validator-identifier@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz#918b1a7fa23056603506370089bd990d8720db62" - integrity sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA== - "@babel/helper-validator-identifier@^7.24.6": version "7.24.6" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz#08bb6612b11bdec78f3feed3db196da682454a5e" integrity sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw== -"@babel/helper-validator-option@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" - integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== - "@babel/helper-validator-option@^7.24.6": version "7.24.6" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.6.tgz#59d8e81c40b7d9109ab7e74457393442177f460a" integrity sha512-Jktc8KkF3zIkePb48QO+IapbXlSapOW9S+ogZZkcO6bABgYAxtZcjZ/O005111YLf+j4M84uEgwYoidDkXbCkQ== -"@babel/helper-wrap-function@^7.22.20": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.24.5.tgz#335f934c0962e2c1ed1fb9d79e06a56115067c09" - integrity sha512-/xxzuNvgRl4/HLNKvnFwdhdgN3cpLxgLROeLDl83Yx0AJ1SGvq1ak0OszTOjDfiB8Vx03eJbeDWh9r+jCCWttw== - dependencies: - "@babel/helper-function-name" "^7.23.0" - "@babel/template" "^7.24.0" - "@babel/types" "^7.24.5" - -"@babel/helpers@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.5.tgz#fedeb87eeafa62b621160402181ad8585a22a40a" - integrity sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q== +"@babel/helper-wrap-function@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.24.6.tgz#c27af1006e310683fdc76b668a0a1f6003e36217" + integrity sha512-f1JLrlw/jbiNfxvdrfBgio/gRBk3yTAEJWirpAkiJG2Hb22E7cEYKHWo0dFPTv/niPovzIdPdEDetrv6tC6gPQ== dependencies: - "@babel/template" "^7.24.0" - "@babel/traverse" "^7.24.5" - "@babel/types" "^7.24.5" + "@babel/helper-function-name" "^7.24.6" + "@babel/template" "^7.24.6" + "@babel/types" "^7.24.6" "@babel/helpers@^7.24.6": version "7.24.6" @@ -424,16 +293,6 @@ "@babel/template" "^7.24.6" "@babel/types" "^7.24.6" -"@babel/highlight@^7.24.2": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.5.tgz#bc0613f98e1dd0720e99b2a9ee3760194a704b6e" - integrity sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw== - dependencies: - "@babel/helper-validator-identifier" "^7.24.5" - chalk "^2.4.2" - js-tokens "^4.0.0" - picocolors "^1.0.0" - "@babel/highlight@^7.24.6": version "7.24.6" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.6.tgz#6d610c1ebd2c6e061cade0153bf69b0590b7b3df" @@ -444,47 +303,42 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.23.0", "@babel/parser@^7.24.0", "@babel/parser@^7.24.4", "@babel/parser@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.5.tgz#4a4d5ab4315579e5398a82dcf636ca80c3392790" - integrity sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg== - -"@babel/parser@^7.24.6": +"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.23.0", "@babel/parser@^7.24.4", "@babel/parser@^7.24.6": version "7.24.6" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.6.tgz#5e030f440c3c6c78d195528c3b688b101a365328" integrity sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q== -"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.5.tgz#4c3685eb9cd790bcad2843900fe0250c91ccf895" - integrity sha512-LdXRi1wEMTrHVR4Zc9F8OewC3vdm5h4QB6L71zy6StmYeqGi1b3ttIO8UC+BfZKcH9jdr4aI249rBkm+3+YvHw== +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.6.tgz#283a74ef365b1e954cda6b2724c678a978215e88" + integrity sha512-bYndrJ6Ph6Ar+GaB5VAc0JPoP80bQCm4qon6JEzXfRl5QZyQ8Ur1K6k7htxWmPA5z+k7JQvaMUrtXlqclWYzKw== dependencies: - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-plugin-utils" "^7.24.5" + "@babel/helper-environment-visitor" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz#b645d9ba8c2bc5b7af50f0fe949f9edbeb07c8cf" - integrity sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg== +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.6.tgz#f9f5ae4d6fb72f5950262cb6f0b2482c3bc684ef" + integrity sha512-iVuhb6poq5ikqRq2XWU6OQ+R5o9wF+r/or9CeUyovgptz0UlnK4/seOQ1Istu/XybYjAhQv1FRSSfHHufIku5Q== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz#da8261f2697f0f41b0855b91d3a20a1fbfd271d3" - integrity sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ== +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.6.tgz#ab9be6edfffa127bd5ec4317c76c5af0f8fc7e6c" + integrity sha512-c8TER5xMDYzzFcGqOEp9l4hvB7dcbhcGjcLVwxWfe4P5DOafdwjsBJZKsmv+o3aXh7NhopvayQIovHrh2zSRUQ== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" - "@babel/plugin-transform-optional-chaining" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.6" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.6" + "@babel/plugin-transform-optional-chaining" "^7.24.6" -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz#1181d9685984c91d657b8ddf14f0487a6bab2988" - integrity sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw== +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.6.tgz#0faf879249ec622d7f1c42eaebf7d11197401b2c" + integrity sha512-z8zEjYmwBUHN/pCF3NuWBhHQjJCrd33qAi8MgANfMrAvn72k2cImT8VjK9LJFu4ysOLJqhfkYYb3MvwANRUNZQ== dependencies: - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-environment-visitor" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" "@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": version "7.21.0-placeholder-for-preset-env.2" @@ -526,26 +380,26 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-syntax-flow@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.24.1.tgz#875c25e3428d7896c87589765fc8b9d32f24bd8d" - integrity sha512-sxi2kLTI5DeW5vDtMUsk4mTPwvlUDbjOnoWayhynCwrw4QXRld4QEYwqzY8JmQXaJUtgUuCIurtSRH5sn4c7mA== +"@babel/plugin-syntax-flow@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.24.6.tgz#1102a710771326b8e2f0c85ac2aecb6f52eb601e" + integrity sha512-gNkksSdV8RbsCoHF9sjVYrHfYACMl/8U32UfUhJ9+84/ASXw8dlx+eHyyF0m6ncQJ9IBSxfuCkB36GJqYdXTOA== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-syntax-import-assertions@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz#db3aad724153a00eaac115a3fb898de544e34971" - integrity sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ== +"@babel/plugin-syntax-import-assertions@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.6.tgz#52521c1c1698fc2dd9cf88f7a4dd86d4d041b9e1" + integrity sha512-BE6o2BogJKJImTmGpkmOic4V0hlRRxVtzqxiSPa8TIFxyhi4EFjHm08nq1M4STK4RytuLMgnSz0/wfflvGFNOg== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-syntax-import-attributes@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz#c66b966c63b714c4eec508fcf5763b1f2d381093" - integrity sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA== +"@babel/plugin-syntax-import-attributes@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.6.tgz#12aba325534129584672920274fefa4dc2d5f68e" + integrity sha512-D+CfsVZousPXIdudSII7RGy52+dYRtbyKAZcvtQKq/NpsivyMVduepzcLqG5pMBugtMdedxdC8Ramdpcne9ZWQ== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" "@babel/plugin-syntax-import-meta@^7.10.4": version "7.10.4" @@ -561,12 +415,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz#3f6ca04b8c841811dbc3c5c5f837934e0d626c10" - integrity sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA== +"@babel/plugin-syntax-jsx@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.6.tgz#bcca2964150437f88f65e3679e3d68762287b9c8" + integrity sha512-lWfvAIFNWMlCsU0DRUun2GpFwZdGTukLaHJqRh1JRb80NdAP5Sb1HDHB5X9P9OtgZHQl089UzQkpYlBq2VTPRw== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" "@babel/plugin-syntax-logical-assignment-operators@^7.10.4": version "7.10.4" @@ -624,12 +478,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz#b3bcc51f396d15f3591683f90239de143c076844" - integrity sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw== +"@babel/plugin-syntax-typescript@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.6.tgz#769daf2982d60308bc83d8936eaecb7582463c87" + integrity sha512-TzCtxGgVTEJWWwcYwQhCIQ6WaKlo80/B+Onsk4RRCcYqpYGFcG9etPW94VToGte5AAcxRrhjPUFvUS3Y2qKi4A== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" "@babel/plugin-syntax-unicode-sets-regex@^7.18.6": version "7.18.6" @@ -639,432 +493,432 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-arrow-functions@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz#2bf263617060c9cc45bcdbf492b8cc805082bf27" - integrity sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw== +"@babel/plugin-transform-arrow-functions@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.6.tgz#93607d1ef5b81c70af174aff3532d57216367492" + integrity sha512-jSSSDt4ZidNMggcLx8SaKsbGNEfIl0PHx/4mFEulorE7bpYLbN0d3pDW3eJ7Y5Z3yPhy3L3NaPCYyTUY7TuugQ== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-async-generator-functions@^7.24.3": - version "7.24.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.3.tgz#8fa7ae481b100768cc9842c8617808c5352b8b89" - integrity sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg== +"@babel/plugin-transform-async-generator-functions@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.6.tgz#fa4a9e5c3a7f60f697ba36587b6c41b04f507d84" + integrity sha512-VEP2o4iR2DqQU6KPgizTW2mnMx6BG5b5O9iQdrW9HesLkv8GIA8x2daXBQxw1MrsIkFQGA/iJ204CKoQ8UcnAA== dependencies: - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/helper-remap-async-to-generator" "^7.22.20" + "@babel/helper-environment-visitor" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" + "@babel/helper-remap-async-to-generator" "^7.24.6" "@babel/plugin-syntax-async-generators" "^7.8.4" -"@babel/plugin-transform-async-to-generator@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz#0e220703b89f2216800ce7b1c53cb0cf521c37f4" - integrity sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw== +"@babel/plugin-transform-async-to-generator@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.6.tgz#eb11434b11d73d8c0cf9f71a6f4f1e6ba441df35" + integrity sha512-NTBA2SioI3OsHeIn6sQmhvXleSl9T70YY/hostQLveWs0ic+qvbA3fa0kwAwQ0OA/XGaAerNZRQGJyRfhbJK4g== dependencies: - "@babel/helper-module-imports" "^7.24.1" - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/helper-remap-async-to-generator" "^7.22.20" + "@babel/helper-module-imports" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" + "@babel/helper-remap-async-to-generator" "^7.24.6" -"@babel/plugin-transform-block-scoped-functions@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz#1c94799e20fcd5c4d4589523bbc57b7692979380" - integrity sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg== +"@babel/plugin-transform-block-scoped-functions@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.6.tgz#975555b5bfa9870b1218da536d1528735f1f8c56" + integrity sha512-XNW7jolYHW9CwORrZgA/97tL/k05qe/HL0z/qqJq1mdWhwwCM6D4BJBV7wAz9HgFziN5dTOG31znkVIzwxv+vw== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-block-scoping@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.5.tgz#89574191397f85661d6f748d4b89ee4d9ee69a2a" - integrity sha512-sMfBc3OxghjC95BkYrYocHL3NaOplrcaunblzwXhGmlPwpmfsxr4vK+mBBt49r+S240vahmv+kUxkeKgs+haCw== +"@babel/plugin-transform-block-scoping@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.6.tgz#a03ec8a4591c2b43cf7798bc633e698293fda179" + integrity sha512-S/t1Xh4ehW7sGA7c1j/hiOBLnEYCp/c2sEG4ZkL8kI1xX9tW2pqJTCHKtdhe/jHKt8nG0pFCrDHUXd4DvjHS9w== dependencies: - "@babel/helper-plugin-utils" "^7.24.5" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-class-properties@^7.22.5", "@babel/plugin-transform-class-properties@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz#bcbf1aef6ba6085cfddec9fc8d58871cf011fc29" - integrity sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g== +"@babel/plugin-transform-class-properties@^7.22.5", "@babel/plugin-transform-class-properties@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.6.tgz#d9f394e97e88ef905d5a1e5e7a16238621b7982e" + integrity sha512-j6dZ0Z2Z2slWLR3kt9aOmSIrBvnntWjMDN/TVcMPxhXMLmJVqX605CBRlcGI4b32GMbfifTEsdEjGjiE+j/c3A== dependencies: - "@babel/helper-create-class-features-plugin" "^7.24.1" - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-create-class-features-plugin" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-class-static-block@^7.24.4": - version "7.24.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz#1a4653c0cf8ac46441ec406dece6e9bc590356a4" - integrity sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg== +"@babel/plugin-transform-class-static-block@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.6.tgz#f43f29286f6f0dca33d18fd5033b817d6c3fa816" + integrity sha512-1QSRfoPI9RoLRa8Mnakc6v3e0gJxiZQTYrMfLn+mD0sz5+ndSzwymp2hDcYJTyT0MOn0yuWzj8phlIvO72gTHA== dependencies: - "@babel/helper-create-class-features-plugin" "^7.24.4" - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-create-class-features-plugin" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" "@babel/plugin-syntax-class-static-block" "^7.14.5" -"@babel/plugin-transform-classes@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.5.tgz#05e04a09df49a46348299a0e24bfd7e901129339" - integrity sha512-gWkLP25DFj2dwe9Ck8uwMOpko4YsqyfZJrOmqqcegeDYEbp7rmn4U6UQZNj08UF6MaX39XenSpKRCvpDRBtZ7Q== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-compilation-targets" "^7.23.6" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-plugin-utils" "^7.24.5" - "@babel/helper-replace-supers" "^7.24.1" - "@babel/helper-split-export-declaration" "^7.24.5" +"@babel/plugin-transform-classes@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.6.tgz#0cc198c02720d4eeb091004843477659c6b37977" + integrity sha512-+fN+NO2gh8JtRmDSOB6gaCVo36ha8kfCW1nMq2Gc0DABln0VcHN4PrALDvF5/diLzIRKptC7z/d7Lp64zk92Fg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.6" + "@babel/helper-compilation-targets" "^7.24.6" + "@babel/helper-environment-visitor" "^7.24.6" + "@babel/helper-function-name" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" + "@babel/helper-replace-supers" "^7.24.6" + "@babel/helper-split-export-declaration" "^7.24.6" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz#bc7e787f8e021eccfb677af5f13c29a9934ed8a7" - integrity sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw== +"@babel/plugin-transform-computed-properties@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.6.tgz#7a1765c01cdfe59c320d2d0f37a4dc4aecd14df1" + integrity sha512-cRzPobcfRP0ZtuIEkA8QzghoUpSB3X3qSH5W2+FzG+VjWbJXExtx0nbRqwumdBN1x/ot2SlTNQLfBCnPdzp6kg== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/template" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" + "@babel/template" "^7.24.6" -"@babel/plugin-transform-destructuring@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.5.tgz#80843ee6a520f7362686d1a97a7b53544ede453c" - integrity sha512-SZuuLyfxvsm+Ah57I/i1HVjveBENYK9ue8MJ7qkc7ndoNjqquJiElzA7f5yaAXjyW2hKojosOTAQQRX50bPSVg== +"@babel/plugin-transform-destructuring@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.6.tgz#bdd1a6c90ffb2bfd13b6007b13316eeafc97cb53" + integrity sha512-YLW6AE5LQpk5npNXL7i/O+U9CE4XsBCuRPgyjl1EICZYKmcitV+ayuuUGMJm2lC1WWjXYszeTnIxF/dq/GhIZQ== dependencies: - "@babel/helper-plugin-utils" "^7.24.5" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-dotall-regex@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz#d56913d2f12795cc9930801b84c6f8c47513ac13" - integrity sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw== +"@babel/plugin-transform-dotall-regex@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.6.tgz#5a6b3148ec5f4f274ff48cebea90565087cad126" + integrity sha512-rCXPnSEKvkm/EjzOtLoGvKseK+dS4kZwx1HexO3BtRtgL0fQ34awHn34aeSHuXtZY2F8a1X8xqBBPRtOxDVmcA== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.15" - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-create-regexp-features-plugin" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-duplicate-keys@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz#5347a797fe82b8d09749d10e9f5b83665adbca88" - integrity sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA== +"@babel/plugin-transform-duplicate-keys@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.6.tgz#2716301227cf7cd4fdadcbe4353ce191f8b3dc8a" + integrity sha512-/8Odwp/aVkZwPFJMllSbawhDAO3UJi65foB00HYnK/uXvvCPm0TAXSByjz1mpRmp0q6oX2SIxpkUOpPFHk7FLA== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-dynamic-import@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz#2a5a49959201970dd09a5fca856cb651e44439dd" - integrity sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA== +"@babel/plugin-transform-dynamic-import@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.6.tgz#b477177761d56b15a4ba42a83be31cf72d757acf" + integrity sha512-vpq8SSLRTBLOHUZHSnBqVo0AKX3PBaoPs2vVzYVWslXDTDIpwAcCDtfhUcHSQQoYoUvcFPTdC8TZYXu9ZnLT/w== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" "@babel/plugin-syntax-dynamic-import" "^7.8.3" -"@babel/plugin-transform-exponentiation-operator@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz#6650ebeb5bd5c012d5f5f90a26613a08162e8ba4" - integrity sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw== +"@babel/plugin-transform-exponentiation-operator@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.6.tgz#011e9e1a429f91b024af572530873ca571f9ef06" + integrity sha512-EemYpHtmz0lHE7hxxxYEuTYOOBZ43WkDgZ4arQ4r+VX9QHuNZC+WH3wUWmRNvR8ECpTRne29aZV6XO22qpOtdA== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.15" - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-export-namespace-from@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz#f033541fc036e3efb2dcb58eedafd4f6b8078acd" - integrity sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ== +"@babel/plugin-transform-export-namespace-from@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.6.tgz#b64ded74d9afb3db5d47d93996c4df69f15ac97c" + integrity sha512-inXaTM1SVrIxCkIJ5gqWiozHfFMStuGbGJAxZFBoHcRRdDP0ySLb3jH6JOwmfiinPwyMZqMBX+7NBDCO4z0NSA== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" -"@babel/plugin-transform-flow-strip-types@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.24.1.tgz#fa8d0a146506ea195da1671d38eed459242b2dcc" - integrity sha512-iIYPIWt3dUmUKKE10s3W+jsQ3icFkw0JyRVyY1B7G4yK/nngAOHLVx8xlhA6b/Jzl/Y0nis8gjqhqKtRDQqHWQ== +"@babel/plugin-transform-flow-strip-types@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.24.6.tgz#dfd9d1c90e74335bc68d82f41ad9224960a4de84" + integrity sha512-1l8b24NoCpaQ13Vi6FtLG1nv6kNoi8PWvQb1AYO7GHZDpFfBYc3lbXArx1lP2KRt8b4pej1eWc/zrRmsQTfOdQ== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/plugin-syntax-flow" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.6" + "@babel/plugin-syntax-flow" "^7.24.6" -"@babel/plugin-transform-for-of@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz#67448446b67ab6c091360ce3717e7d3a59e202fd" - integrity sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg== +"@babel/plugin-transform-for-of@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.6.tgz#7f31780bd0c582b546372c0c0da9d9d56731e0a2" + integrity sha512-n3Sf72TnqK4nw/jziSqEl1qaWPbCRw2CziHH+jdRYvw4J6yeCzsj4jdw8hIntOEeDGTmHVe2w4MVL44PN0GMzg== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.6" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.6" -"@babel/plugin-transform-function-name@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz#8cba6f7730626cc4dfe4ca2fa516215a0592b361" - integrity sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA== +"@babel/plugin-transform-function-name@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.6.tgz#60d1de3f6fd816a3e3bf9538578a64527e1b9c97" + integrity sha512-sOajCu6V0P1KPljWHKiDq6ymgqB+vfo3isUS4McqW1DZtvSVU2v/wuMhmRmkg3sFoq6GMaUUf8W4WtoSLkOV/Q== dependencies: - "@babel/helper-compilation-targets" "^7.23.6" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-compilation-targets" "^7.24.6" + "@babel/helper-function-name" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-json-strings@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz#08e6369b62ab3e8a7b61089151b161180c8299f7" - integrity sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ== +"@babel/plugin-transform-json-strings@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.6.tgz#a84639180ea1f9001bb5e6dc01921235ab05ad8b" + integrity sha512-Uvgd9p2gUnzYJxVdBLcU0KurF8aVhkmVyMKW4MIY1/BByvs3EBpv45q01o7pRTVmTvtQq5zDlytP3dcUgm7v9w== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" "@babel/plugin-syntax-json-strings" "^7.8.3" -"@babel/plugin-transform-literals@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz#0a1982297af83e6b3c94972686067df588c5c096" - integrity sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g== +"@babel/plugin-transform-literals@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.6.tgz#7f44f2871d7a4456030b0540858046f0b7bc6b18" + integrity sha512-f2wHfR2HF6yMj+y+/y07+SLqnOSwRp8KYLpQKOzS58XLVlULhXbiYcygfXQxJlMbhII9+yXDwOUFLf60/TL5tw== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-logical-assignment-operators@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz#719d8aded1aa94b8fb34e3a785ae8518e24cfa40" - integrity sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w== +"@babel/plugin-transform-logical-assignment-operators@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.6.tgz#9cc7baa5629866566562c159dc1eae7569810f33" + integrity sha512-EKaWvnezBCMkRIHxMJSIIylzhqK09YpiJtDbr2wsXTwnO0TxyjMUkaw4RlFIZMIS0iDj0KyIg7H7XCguHu/YDA== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" -"@babel/plugin-transform-member-expression-literals@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz#896d23601c92f437af8b01371ad34beb75df4489" - integrity sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg== +"@babel/plugin-transform-member-expression-literals@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.6.tgz#5d3681ca201ac6909419cc51ac082a6ba4c5c756" + integrity sha512-9g8iV146szUo5GWgXpRbq/GALTnY+WnNuRTuRHWWFfWGbP9ukRL0aO/jpu9dmOPikclkxnNsjY8/gsWl6bmZJQ== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-modules-amd@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz#b6d829ed15258536977e9c7cc6437814871ffa39" - integrity sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ== +"@babel/plugin-transform-modules-amd@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.6.tgz#09aeac7acb7913496aaaafdc64f40683e0db7e41" + integrity sha512-eAGogjZgcwqAxhyFgqghvoHRr+EYRQPFjUXrTYKBRb5qPnAVxOOglaxc4/byHqjvq/bqO2F3/CGwTHsgKJYHhQ== dependencies: - "@babel/helper-module-transforms" "^7.23.3" - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-module-transforms" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-modules-commonjs@^7.23.0", "@babel/plugin-transform-modules-commonjs@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz#e71ba1d0d69e049a22bf90b3867e263823d3f1b9" - integrity sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw== +"@babel/plugin-transform-modules-commonjs@^7.23.0", "@babel/plugin-transform-modules-commonjs@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.6.tgz#1b8269902f25bd91ca6427230d4735ddd1e1283e" + integrity sha512-JEV8l3MHdmmdb7S7Cmx6rbNEjRCgTQMZxllveHO0mx6uiclB0NflCawlQQ6+o5ZrwjUBYPzHm2XoK4wqGVUFuw== dependencies: - "@babel/helper-module-transforms" "^7.23.3" - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-module-transforms" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" + "@babel/helper-simple-access" "^7.24.6" -"@babel/plugin-transform-modules-systemjs@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz#2b9625a3d4e445babac9788daec39094e6b11e3e" - integrity sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA== +"@babel/plugin-transform-modules-systemjs@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.6.tgz#c54eb53fe16f9b82d320abd76762d0320e3f9393" + integrity sha512-xg1Z0J5JVYxtpX954XqaaAT6NpAY6LtZXvYFCJmGFJWwtlz2EmJoR8LycFRGNE8dBKizGWkGQZGegtkV8y8s+w== dependencies: - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-module-transforms" "^7.23.3" - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/helper-validator-identifier" "^7.22.20" + "@babel/helper-hoist-variables" "^7.24.6" + "@babel/helper-module-transforms" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" + "@babel/helper-validator-identifier" "^7.24.6" -"@babel/plugin-transform-modules-umd@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz#69220c66653a19cf2c0872b9c762b9a48b8bebef" - integrity sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg== +"@babel/plugin-transform-modules-umd@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.6.tgz#c4ef8b6d4da230b8dc87e81cd66986728952f89b" + integrity sha512-esRCC/KsSEUvrSjv5rFYnjZI6qv4R1e/iHQrqwbZIoRJqk7xCvEUiN7L1XrmW5QSmQe3n1XD88wbgDTWLbVSyg== dependencies: - "@babel/helper-module-transforms" "^7.23.3" - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-module-transforms" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-named-capturing-groups-regex@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f" - integrity sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ== +"@babel/plugin-transform-named-capturing-groups-regex@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.6.tgz#352ee2861ab8705320029f80238cf26a92ba65d5" + integrity sha512-6DneiCiu91wm3YiNIGDWZsl6GfTTbspuj/toTEqLh9d4cx50UIzSdg+T96p8DuT7aJOBRhFyaE9ZvTHkXrXr6Q== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.5" - "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-create-regexp-features-plugin" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-new-target@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz#29c59988fa3d0157de1c871a28cd83096363cc34" - integrity sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug== +"@babel/plugin-transform-new-target@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.6.tgz#fc024294714705113720d5e3dc0f9ad7abdbc289" + integrity sha512-f8liz9JG2Va8A4J5ZBuaSdwfPqN6axfWRK+y66fjKYbwf9VBLuq4WxtinhJhvp1w6lamKUwLG0slK2RxqFgvHA== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-nullish-coalescing-operator@^7.22.11", "@babel/plugin-transform-nullish-coalescing-operator@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz#0cd494bb97cb07d428bd651632cb9d4140513988" - integrity sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw== +"@babel/plugin-transform-nullish-coalescing-operator@^7.22.11", "@babel/plugin-transform-nullish-coalescing-operator@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.6.tgz#12b83b3cdfd1cd2066350e36e4fb912ab194545e" + integrity sha512-+QlAiZBMsBK5NqrBWFXCYeXyiU1y7BQ/OYaiPAcQJMomn5Tyg+r5WuVtyEuvTbpV7L25ZSLfE+2E9ywj4FD48A== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" -"@babel/plugin-transform-numeric-separator@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz#5bc019ce5b3435c1cadf37215e55e433d674d4e8" - integrity sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw== +"@babel/plugin-transform-numeric-separator@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.6.tgz#d9115669cc85aa91fbfb15f88f2226332cf4946a" + integrity sha512-6voawq8T25Jvvnc4/rXcWZQKKxUNZcKMS8ZNrjxQqoRFernJJKjE3s18Qo6VFaatG5aiX5JV1oPD7DbJhn0a4Q== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-transform-object-rest-spread@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.5.tgz#f91bbcb092ff957c54b4091c86bda8372f0b10ef" - integrity sha512-7EauQHszLGM3ay7a161tTQH7fj+3vVM/gThlz5HpFtnygTxjrlvoeq7MPVA1Vy9Q555OB8SnAOsMkLShNkkrHA== +"@babel/plugin-transform-object-rest-spread@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.6.tgz#68d763f69955f9e599c405c6c876f5be46b47d8a" + integrity sha512-OKmi5wiMoRW5Smttne7BwHM8s/fb5JFs+bVGNSeHWzwZkWXWValR1M30jyXo1s/RaqgwwhEC62u4rFH/FBcBPg== dependencies: - "@babel/helper-compilation-targets" "^7.23.6" - "@babel/helper-plugin-utils" "^7.24.5" + "@babel/helper-compilation-targets" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.24.5" + "@babel/plugin-transform-parameters" "^7.24.6" -"@babel/plugin-transform-object-super@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz#e71d6ab13483cca89ed95a474f542bbfc20a0520" - integrity sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ== +"@babel/plugin-transform-object-super@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.6.tgz#9cbe6f995bed343a7ab8daf0416dac057a9c3e27" + integrity sha512-N/C76ihFKlZgKfdkEYKtaRUtXZAgK7sOY4h2qrbVbVTXPrKGIi8aww5WGe/+Wmg8onn8sr2ut6FXlsbu/j6JHg== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/helper-replace-supers" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.6" + "@babel/helper-replace-supers" "^7.24.6" -"@babel/plugin-transform-optional-catch-binding@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz#92a3d0efe847ba722f1a4508669b23134669e2da" - integrity sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA== +"@babel/plugin-transform-optional-catch-binding@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.6.tgz#c81e90a971aad898e56f2b75a358e6c4855aeba3" + integrity sha512-L5pZ+b3O1mSzJ71HmxSCmTVd03VOT2GXOigug6vDYJzE5awLI7P1g0wFcdmGuwSDSrQ0L2rDOe/hHws8J1rv3w== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-transform-optional-chaining@^7.23.0", "@babel/plugin-transform-optional-chaining@^7.24.1", "@babel/plugin-transform-optional-chaining@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.5.tgz#a6334bebd7f9dd3df37447880d0bd64b778e600f" - integrity sha512-xWCkmwKT+ihmA6l7SSTpk8e4qQl/274iNbSKRRS8mpqFR32ksy36+a+LWY8OXCCEefF8WFlnOHVsaDI2231wBg== +"@babel/plugin-transform-optional-chaining@^7.23.0", "@babel/plugin-transform-optional-chaining@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.6.tgz#3d636b3ed8b5a506f93e4d4675fc95754d7594f5" + integrity sha512-cHbqF6l1QP11OkYTYQ+hhVx1E017O5ZcSPXk9oODpqhcAD1htsWG2NpHrrhthEO2qZomLK0FXS+u7NfrkF5aOQ== dependencies: - "@babel/helper-plugin-utils" "^7.24.5" - "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.6" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.6" "@babel/plugin-syntax-optional-chaining" "^7.8.3" -"@babel/plugin-transform-parameters@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.5.tgz#5c3b23f3a6b8fed090f9b98f2926896d3153cc62" - integrity sha512-9Co00MqZ2aoky+4j2jhofErthm6QVLKbpQrvz20c3CH9KQCLHyNB+t2ya4/UrRpQGR+Wrwjg9foopoeSdnHOkA== +"@babel/plugin-transform-parameters@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.6.tgz#7aee86dfedd2fc0136fecbe6f7649fc02d86ab22" + integrity sha512-ST7guE8vLV+vI70wmAxuZpIKzVjvFX9Qs8bl5w6tN/6gOypPWUmMQL2p7LJz5E63vEGrDhAiYetniJFyBH1RkA== dependencies: - "@babel/helper-plugin-utils" "^7.24.5" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-private-methods@^7.22.5", "@babel/plugin-transform-private-methods@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz#a0faa1ae87eff077e1e47a5ec81c3aef383dc15a" - integrity sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw== +"@babel/plugin-transform-private-methods@^7.22.5", "@babel/plugin-transform-private-methods@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.6.tgz#258e1f859a52ff7b30ad556598224c192defcda7" + integrity sha512-T9LtDI0BgwXOzyXrvgLTT8DFjCC/XgWLjflczTLXyvxbnSR/gpv0hbmzlHE/kmh9nOvlygbamLKRo6Op4yB6aw== dependencies: - "@babel/helper-create-class-features-plugin" "^7.24.1" - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-create-class-features-plugin" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-private-property-in-object@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.5.tgz#f5d1fcad36e30c960134cb479f1ca98a5b06eda5" - integrity sha512-JM4MHZqnWR04jPMujQDTBVRnqxpLLpx2tkn7iPn+Hmsc0Gnb79yvRWOkvqFOx3Z7P7VxiRIR22c4eGSNj87OBQ== +"@babel/plugin-transform-private-property-in-object@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.6.tgz#59ff09a099f62213112cf348e96b6b11957d1f28" + integrity sha512-Qu/ypFxCY5NkAnEhCF86Mvg3NSabKsh/TPpBVswEdkGl7+FbsYHy1ziRqJpwGH4thBdQHh8zx+z7vMYmcJ7iaQ== dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-create-class-features-plugin" "^7.24.5" - "@babel/helper-plugin-utils" "^7.24.5" + "@babel/helper-annotate-as-pure" "^7.24.6" + "@babel/helper-create-class-features-plugin" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" -"@babel/plugin-transform-property-literals@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz#d6a9aeab96f03749f4eebeb0b6ea8e90ec958825" - integrity sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA== +"@babel/plugin-transform-property-literals@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.6.tgz#243c4faabe811c405e9443059a58e834bf95dfd1" + integrity sha512-oARaglxhRsN18OYsnPTpb8TcKQWDYNsPNmTnx5++WOAsUJ0cSC/FZVlIJCKvPbU4yn/UXsS0551CFKJhN0CaMw== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-regenerator@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz#625b7545bae52363bdc1fbbdc7252b5046409c8c" - integrity sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw== +"@babel/plugin-transform-regenerator@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.6.tgz#ed10cf0c13619365e15459f88d1b915ac57ffc24" + integrity sha512-SMDxO95I8WXRtXhTAc8t/NFQUT7VYbIWwJCJgEli9ml4MhqUMh4S6hxgH6SmAC3eAQNWCDJFxcFeEt9w2sDdXg== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" regenerator-transform "^0.15.2" -"@babel/plugin-transform-reserved-words@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz#8de729f5ecbaaf5cf83b67de13bad38a21be57c1" - integrity sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg== +"@babel/plugin-transform-reserved-words@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.6.tgz#9eb16cbf339fcea0a46677716c775afb5ef14245" + integrity sha512-DcrgFXRRlK64dGE0ZFBPD5egM2uM8mgfrvTMOSB2yKzOtjpGegVYkzh3s1zZg1bBck3nkXiaOamJUqK3Syk+4A== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-shorthand-properties@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz#ba9a09144cf55d35ec6b93a32253becad8ee5b55" - integrity sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA== +"@babel/plugin-transform-shorthand-properties@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.6.tgz#ef734ebccc428d2174c7bb36015d0800faf5381e" + integrity sha512-xnEUvHSMr9eOWS5Al2YPfc32ten7CXdH7Zwyyk7IqITg4nX61oHj+GxpNvl+y5JHjfN3KXE2IV55wAWowBYMVw== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-spread@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz#a1acf9152cbf690e4da0ba10790b3ac7d2b2b391" - integrity sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g== +"@babel/plugin-transform-spread@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.6.tgz#a56cecbd8617675531d1b79f5b755b7613aa0822" + integrity sha512-h/2j7oIUDjS+ULsIrNZ6/TKG97FgmEk1PXryk/HQq6op4XUUUwif2f69fJrzK0wza2zjCS1xhXmouACaWV5uPA== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/helper-plugin-utils" "^7.24.6" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.6" -"@babel/plugin-transform-sticky-regex@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz#f03e672912c6e203ed8d6e0271d9c2113dc031b9" - integrity sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw== +"@babel/plugin-transform-sticky-regex@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.6.tgz#1a78127731fea87d954bed193840986a38f04327" + integrity sha512-fN8OcTLfGmYv7FnDrsjodYBo1DhPL3Pze/9mIIE2MGCT1KgADYIOD7rEglpLHZj8PZlC/JFX5WcD+85FLAQusw== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-template-literals@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz#15e2166873a30d8617e3e2ccadb86643d327aab7" - integrity sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g== +"@babel/plugin-transform-template-literals@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.6.tgz#aaf2ae157acd0e5c9265dba8ac0a439f8d2a6303" + integrity sha512-BJbEqJIcKwrqUP+KfUIkxz3q8VzXe2R8Wv8TaNgO1cx+nNavxn/2+H8kp9tgFSOL6wYPPEgFvU6IKS4qoGqhmg== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-typeof-symbol@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.5.tgz#703cace5ef74155fb5eecab63cbfc39bdd25fe12" - integrity sha512-UTGnhYVZtTAjdwOTzT+sCyXmTn8AhaxOS/MjG9REclZ6ULHWF9KoCZur0HSGU7hk8PdBFKKbYe6+gqdXWz84Jg== +"@babel/plugin-transform-typeof-symbol@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.6.tgz#3d02da23ebcc8f1982ddcd1f2581cf3ee4e58762" + integrity sha512-IshCXQ+G9JIFJI7bUpxTE/oA2lgVLAIK8q1KdJNoPXOpvRaNjMySGuvLfBw/Xi2/1lLo953uE8hyYSDW3TSYig== dependencies: - "@babel/helper-plugin-utils" "^7.24.5" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-typescript@^7.24.1": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.5.tgz#bcba979e462120dc06a75bd34c473a04781931b8" - integrity sha512-E0VWu/hk83BIFUWnsKZ4D81KXjN5L3MobvevOHErASk9IPwKHOkTgvqzvNo1yP/ePJWqqK2SpUR5z+KQbl6NVw== +"@babel/plugin-transform-typescript@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.6.tgz#339c6127a783c32e28a5b591e6c666f899b57db0" + integrity sha512-H0i+hDLmaYYSt6KU9cZE0gb3Cbssa/oxWis7PX4ofQzbvsfix9Lbh8SRk7LCPDlLWJHUiFeHU0qRRpF/4Zv7mQ== dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-create-class-features-plugin" "^7.24.5" - "@babel/helper-plugin-utils" "^7.24.5" - "@babel/plugin-syntax-typescript" "^7.24.1" + "@babel/helper-annotate-as-pure" "^7.24.6" + "@babel/helper-create-class-features-plugin" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" + "@babel/plugin-syntax-typescript" "^7.24.6" -"@babel/plugin-transform-unicode-escapes@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz#fb3fa16676549ac7c7449db9b342614985c2a3a4" - integrity sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw== +"@babel/plugin-transform-unicode-escapes@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.6.tgz#c8ddca8fd5bacece837a4e27bd3b7ed64580d1a8" + integrity sha512-bKl3xxcPbkQQo5eX9LjjDpU2xYHeEeNQbOhj0iPvetSzA+Tu9q/o5lujF4Sek60CM6MgYvOS/DJuwGbiEYAnLw== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-unicode-property-regex@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz#56704fd4d99da81e5e9f0c0c93cabd91dbc4889e" - integrity sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng== +"@babel/plugin-transform-unicode-property-regex@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.6.tgz#e66297d5d452db0b0be56515e3d0e10b7d33fb32" + integrity sha512-8EIgImzVUxy15cZiPii9GvLZwsy7Vxc+8meSlR3cXFmBIl5W5Tn9LGBf7CDKkHj4uVfNXCJB8RsVfnmY61iedA== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.15" - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-create-regexp-features-plugin" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-unicode-regex@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz#57c3c191d68f998ac46b708380c1ce4d13536385" - integrity sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g== +"@babel/plugin-transform-unicode-regex@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.6.tgz#2001e7d87ed709eea145e0b65fb5f93c3c0e225b" + integrity sha512-pssN6ExsvxaKU638qcWb81RrvvgZom3jDgU/r5xFZ7TONkZGFf4MhI2ltMb8OcQWhHyxgIavEU+hgqtbKOmsPA== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.15" - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-create-regexp-features-plugin" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" -"@babel/plugin-transform-unicode-sets-regex@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz#c1ea175b02afcffc9cf57a9c4658326625165b7f" - integrity sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA== +"@babel/plugin-transform-unicode-sets-regex@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.6.tgz#f18b7292222aee85c155258ceb345a146a070a46" + integrity sha512-quiMsb28oXWIDK0gXLALOJRXLgICLiulqdZGOaPPd0vRT7fQp74NtdADAVu+D8s00C+0Xs0MxVP0VKF/sZEUgw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.22.15" - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-create-regexp-features-plugin" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" "@babel/preset-env@^7.11.0", "@babel/preset-env@^7.24.4": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.5.tgz#6a9ac90bd5a5a9dae502af60dfc58c190551bbcd" - integrity sha512-UGK2ifKtcC8i5AI4cH+sbLLuLc2ktYSFJgBAXorKAsHUZmrQ1q6aQ6i3BvU24wWs2AAKqQB6kq3N9V9Gw1HiMQ== - dependencies: - "@babel/compat-data" "^7.24.4" - "@babel/helper-compilation-targets" "^7.23.6" - "@babel/helper-plugin-utils" "^7.24.5" - "@babel/helper-validator-option" "^7.23.5" - "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.24.5" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.24.1" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.24.1" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.24.1" + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.6.tgz#a5a55bc70e5ff1ed7f872067e2a9d65ff917ad6f" + integrity sha512-CrxEAvN7VxfjOG8JNF2Y/eMqMJbZPZ185amwGUBp8D9USK90xQmv7dLdFSa+VbD7fdIqcy/Mfv7WtzG8+/qxKg== + dependencies: + "@babel/compat-data" "^7.24.6" + "@babel/helper-compilation-targets" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.6" + "@babel/helper-validator-option" "^7.24.6" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.24.6" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.24.6" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.24.6" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.24.6" "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-class-properties" "^7.12.13" "@babel/plugin-syntax-class-static-block" "^7.14.5" "@babel/plugin-syntax-dynamic-import" "^7.8.3" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-syntax-import-assertions" "^7.24.1" - "@babel/plugin-syntax-import-attributes" "^7.24.1" + "@babel/plugin-syntax-import-assertions" "^7.24.6" + "@babel/plugin-syntax-import-attributes" "^7.24.6" "@babel/plugin-syntax-import-meta" "^7.10.4" "@babel/plugin-syntax-json-strings" "^7.8.3" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" @@ -1076,54 +930,54 @@ "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-syntax-top-level-await" "^7.14.5" "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" - "@babel/plugin-transform-arrow-functions" "^7.24.1" - "@babel/plugin-transform-async-generator-functions" "^7.24.3" - "@babel/plugin-transform-async-to-generator" "^7.24.1" - "@babel/plugin-transform-block-scoped-functions" "^7.24.1" - "@babel/plugin-transform-block-scoping" "^7.24.5" - "@babel/plugin-transform-class-properties" "^7.24.1" - "@babel/plugin-transform-class-static-block" "^7.24.4" - "@babel/plugin-transform-classes" "^7.24.5" - "@babel/plugin-transform-computed-properties" "^7.24.1" - "@babel/plugin-transform-destructuring" "^7.24.5" - "@babel/plugin-transform-dotall-regex" "^7.24.1" - "@babel/plugin-transform-duplicate-keys" "^7.24.1" - "@babel/plugin-transform-dynamic-import" "^7.24.1" - "@babel/plugin-transform-exponentiation-operator" "^7.24.1" - "@babel/plugin-transform-export-namespace-from" "^7.24.1" - "@babel/plugin-transform-for-of" "^7.24.1" - "@babel/plugin-transform-function-name" "^7.24.1" - "@babel/plugin-transform-json-strings" "^7.24.1" - "@babel/plugin-transform-literals" "^7.24.1" - "@babel/plugin-transform-logical-assignment-operators" "^7.24.1" - "@babel/plugin-transform-member-expression-literals" "^7.24.1" - "@babel/plugin-transform-modules-amd" "^7.24.1" - "@babel/plugin-transform-modules-commonjs" "^7.24.1" - "@babel/plugin-transform-modules-systemjs" "^7.24.1" - "@babel/plugin-transform-modules-umd" "^7.24.1" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5" - "@babel/plugin-transform-new-target" "^7.24.1" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.24.1" - "@babel/plugin-transform-numeric-separator" "^7.24.1" - "@babel/plugin-transform-object-rest-spread" "^7.24.5" - "@babel/plugin-transform-object-super" "^7.24.1" - "@babel/plugin-transform-optional-catch-binding" "^7.24.1" - "@babel/plugin-transform-optional-chaining" "^7.24.5" - "@babel/plugin-transform-parameters" "^7.24.5" - "@babel/plugin-transform-private-methods" "^7.24.1" - "@babel/plugin-transform-private-property-in-object" "^7.24.5" - "@babel/plugin-transform-property-literals" "^7.24.1" - "@babel/plugin-transform-regenerator" "^7.24.1" - "@babel/plugin-transform-reserved-words" "^7.24.1" - "@babel/plugin-transform-shorthand-properties" "^7.24.1" - "@babel/plugin-transform-spread" "^7.24.1" - "@babel/plugin-transform-sticky-regex" "^7.24.1" - "@babel/plugin-transform-template-literals" "^7.24.1" - "@babel/plugin-transform-typeof-symbol" "^7.24.5" - "@babel/plugin-transform-unicode-escapes" "^7.24.1" - "@babel/plugin-transform-unicode-property-regex" "^7.24.1" - "@babel/plugin-transform-unicode-regex" "^7.24.1" - "@babel/plugin-transform-unicode-sets-regex" "^7.24.1" + "@babel/plugin-transform-arrow-functions" "^7.24.6" + "@babel/plugin-transform-async-generator-functions" "^7.24.6" + "@babel/plugin-transform-async-to-generator" "^7.24.6" + "@babel/plugin-transform-block-scoped-functions" "^7.24.6" + "@babel/plugin-transform-block-scoping" "^7.24.6" + "@babel/plugin-transform-class-properties" "^7.24.6" + "@babel/plugin-transform-class-static-block" "^7.24.6" + "@babel/plugin-transform-classes" "^7.24.6" + "@babel/plugin-transform-computed-properties" "^7.24.6" + "@babel/plugin-transform-destructuring" "^7.24.6" + "@babel/plugin-transform-dotall-regex" "^7.24.6" + "@babel/plugin-transform-duplicate-keys" "^7.24.6" + "@babel/plugin-transform-dynamic-import" "^7.24.6" + "@babel/plugin-transform-exponentiation-operator" "^7.24.6" + "@babel/plugin-transform-export-namespace-from" "^7.24.6" + "@babel/plugin-transform-for-of" "^7.24.6" + "@babel/plugin-transform-function-name" "^7.24.6" + "@babel/plugin-transform-json-strings" "^7.24.6" + "@babel/plugin-transform-literals" "^7.24.6" + "@babel/plugin-transform-logical-assignment-operators" "^7.24.6" + "@babel/plugin-transform-member-expression-literals" "^7.24.6" + "@babel/plugin-transform-modules-amd" "^7.24.6" + "@babel/plugin-transform-modules-commonjs" "^7.24.6" + "@babel/plugin-transform-modules-systemjs" "^7.24.6" + "@babel/plugin-transform-modules-umd" "^7.24.6" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.24.6" + "@babel/plugin-transform-new-target" "^7.24.6" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.24.6" + "@babel/plugin-transform-numeric-separator" "^7.24.6" + "@babel/plugin-transform-object-rest-spread" "^7.24.6" + "@babel/plugin-transform-object-super" "^7.24.6" + "@babel/plugin-transform-optional-catch-binding" "^7.24.6" + "@babel/plugin-transform-optional-chaining" "^7.24.6" + "@babel/plugin-transform-parameters" "^7.24.6" + "@babel/plugin-transform-private-methods" "^7.24.6" + "@babel/plugin-transform-private-property-in-object" "^7.24.6" + "@babel/plugin-transform-property-literals" "^7.24.6" + "@babel/plugin-transform-regenerator" "^7.24.6" + "@babel/plugin-transform-reserved-words" "^7.24.6" + "@babel/plugin-transform-shorthand-properties" "^7.24.6" + "@babel/plugin-transform-spread" "^7.24.6" + "@babel/plugin-transform-sticky-regex" "^7.24.6" + "@babel/plugin-transform-template-literals" "^7.24.6" + "@babel/plugin-transform-typeof-symbol" "^7.24.6" + "@babel/plugin-transform-unicode-escapes" "^7.24.6" + "@babel/plugin-transform-unicode-property-regex" "^7.24.6" + "@babel/plugin-transform-unicode-regex" "^7.24.6" + "@babel/plugin-transform-unicode-sets-regex" "^7.24.6" "@babel/preset-modules" "0.1.6-no-external-plugins" babel-plugin-polyfill-corejs2 "^0.4.10" babel-plugin-polyfill-corejs3 "^0.10.4" @@ -1132,13 +986,13 @@ semver "^6.3.1" "@babel/preset-flow@^7.22.15": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.24.1.tgz#da7196c20c2d7dd4e98cfd8b192fe53b5eb6f0bb" - integrity sha512-sWCV2G9pcqZf+JHyv/RyqEIpFypxdCSxWIxQjpdaQxenNog7cN1pr76hg8u0Fz8Qgg0H4ETkGcJnXL8d4j0PPA== + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.24.6.tgz#df09ee46558577bea49bc71d597604c03c9bf7a6" + integrity sha512-huoe0T1Qs9fQhMWbmqE/NHUeZbqmHDsN6n/jYvPcUUHfuKiPV32C9i8tDhMbQ1DEKTjbBP7Rjm3nSLwlB2X05g== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/helper-validator-option" "^7.23.5" - "@babel/plugin-transform-flow-strip-types" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.6" + "@babel/helper-validator-option" "^7.24.6" + "@babel/plugin-transform-flow-strip-types" "^7.24.6" "@babel/preset-modules@0.1.6-no-external-plugins": version "0.1.6-no-external-plugins" @@ -1150,20 +1004,20 @@ esutils "^2.0.2" "@babel/preset-typescript@^7.23.0": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz#89bdf13a3149a17b3b2a2c9c62547f06db8845ec" - integrity sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ== + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.24.6.tgz#27057470fb981c31338bdb897fc3d9aa0cb7dab2" + integrity sha512-U10aHPDnokCFRXgyT/MaIRTivUu2K/mu0vJlwRS9LxJmJet+PFQNKpggPyFCUtC6zWSBPjvxjnpNkAn3Uw2m5w== dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/helper-validator-option" "^7.23.5" - "@babel/plugin-syntax-jsx" "^7.24.1" - "@babel/plugin-transform-modules-commonjs" "^7.24.1" - "@babel/plugin-transform-typescript" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.6" + "@babel/helper-validator-option" "^7.24.6" + "@babel/plugin-syntax-jsx" "^7.24.6" + "@babel/plugin-transform-modules-commonjs" "^7.24.6" + "@babel/plugin-transform-typescript" "^7.24.6" "@babel/register@^7.22.15": - version "7.23.7" - resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.23.7.tgz#485a5e7951939d21304cae4af1719fdb887bc038" - integrity sha512-EjJeB6+kvpk+Y5DAkEAmbOBEFkh9OASx0huoEkqYTFxAZHzOAX2Oh5uwAUuL2rUddqfM0SA+KPXV2TbzoZ2kvQ== + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.24.6.tgz#59e21dcc79e1d04eed5377633b0f88029a6bef9e" + integrity sha512-WSuFCc2wCqMeXkz/i3yfAAsxwWflEgbVkZzivgAmXl/MxrXeoYFZOOPllbC8R8WTF7u61wSRQtDVZ1879cdu6w== dependencies: clone-deep "^4.0.1" find-cache-dir "^2.0.0" @@ -1177,21 +1031,12 @@ integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.9", "@babel/runtime@^7.24.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.5.tgz#230946857c053a36ccc66e1dd03b17dd0c4ed02c" - integrity sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g== + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.6.tgz#5b76eb89ad45e2e4a0a8db54c456251469a3358e" + integrity sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw== dependencies: regenerator-runtime "^0.14.0" -"@babel/template@^7.22.15", "@babel/template@^7.24.0": - version "7.24.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" - integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== - dependencies: - "@babel/code-frame" "^7.23.5" - "@babel/parser" "^7.24.0" - "@babel/types" "^7.24.0" - "@babel/template@^7.24.6": version "7.24.6" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.6.tgz#048c347b2787a6072b24c723664c8d02b67a44f9" @@ -1201,23 +1046,7 @@ "@babel/parser" "^7.24.6" "@babel/types" "^7.24.6" -"@babel/traverse@^7.18.9", "@babel/traverse@^7.24.1", "@babel/traverse@^7.24.5": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.5.tgz#972aa0bc45f16983bf64aa1f877b2dd0eea7e6f8" - integrity sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA== - dependencies: - "@babel/code-frame" "^7.24.2" - "@babel/generator" "^7.24.5" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.24.5" - "@babel/parser" "^7.24.5" - "@babel/types" "^7.24.5" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/traverse@^7.24.6": +"@babel/traverse@^7.18.9", "@babel/traverse@^7.24.1", "@babel/traverse@^7.24.6": version "7.24.6" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.6.tgz#0941ec50cdeaeacad0911eb67ae227a4f8424edc" integrity sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw== @@ -1233,16 +1062,7 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.18.9", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.24.0", "@babel/types@^7.24.5", "@babel/types@^7.4.4": - version "7.24.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.5.tgz#7661930afc638a5383eb0c4aee59b74f38db84d7" - integrity sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ== - dependencies: - "@babel/helper-string-parser" "^7.24.1" - "@babel/helper-validator-identifier" "^7.24.5" - to-fast-properties "^2.0.0" - -"@babel/types@^7.24.6": +"@babel/types@^7.0.0", "@babel/types@^7.18.9", "@babel/types@^7.20.7", "@babel/types@^7.24.0", "@babel/types@^7.24.6", "@babel/types@^7.4.4": version "7.24.6" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.6.tgz#ba4e1f59870c10dc2fa95a274ac4feec23b21912" integrity sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ== @@ -1310,11 +1130,11 @@ "@egjs/component" "^3.0.2" "@chromatic-com/storybook@^1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@chromatic-com/storybook/-/storybook-1.4.0.tgz#5cb1c68ecf32c55fe4ab8a8e3271022845169c00" - integrity sha512-CpskwN1RsgaDMSe7mnwrmst9XeLfvrSbCJOc/eaHIDzhSiKhdbbEF83cYjMYnvODPMW8QNVdw9gWMh+yzBQtSw== + version "1.5.0" + resolved "https://registry.yarnpkg.com/@chromatic-com/storybook/-/storybook-1.5.0.tgz#a1312b7fc8ac0df2d43c51e48014e024fd4b9561" + integrity sha512-LkLKv7SWu/6kGep1ft2HA1T/cm14wU0zoW71gE4cZRcgUoRQJtyhITFTLHrjqAxz6bVqNgqzQtd5oBZ2nK3L3g== dependencies: - chromatic "^11.3.2" + chromatic "^11.4.0" filesize "^10.0.12" jsonfile "^6.1.0" react-confetti "^6.1.0" @@ -1707,9 +1527,9 @@ eslint-visitor-keys "^3.3.0" "@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.6.1": - version "4.10.0" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" - integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== + version "4.10.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.1.tgz#361461e5cb3845d874e61731c11cfedd664d83a0" + integrity sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA== "@eslint/eslintrc@^2.1.4": version "2.1.4" @@ -1896,19 +1716,19 @@ clsx "^2.1.0" prop-types "^15.8.1" -"@mui/core-downloads-tracker@^5.15.18": - version "5.15.18" - resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.18.tgz#0f9426409a82f78423bdf504b2a5a3788656d612" - integrity sha512-/9pVk+Al8qxAjwFUADv4BRZgMpZM4m5E+2Q/20qhVPuIJWqKp4Ie4tGExac6zu93rgPTYVQGgu+1vjiT0E+cEw== +"@mui/core-downloads-tracker@^5.15.19": + version "5.15.19" + resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.19.tgz#7af0025c871f126367a55219486681954e4821d7" + integrity sha512-tCHSi/Tomez9ERynFhZRvFO6n9ATyrPs+2N80DMDzp6xDVirbBjEwhPcE+x7Lj+nwYw0SqFkOxyvMP0irnm55w== "@mui/material@^5.14.1": - version "5.15.18" - resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.15.18.tgz#1184e88cebb5c58625ca799531c0611c1fd9a2a8" - integrity sha512-n+/dsiqux74fFfcRUJjok+ieNQ7+BEk6/OwX9cLcLvriZrZb+/7Y8+Fd2HlUUbn5N0CDurgAHm0VH1DqyJ9HAw== + version "5.15.19" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.15.19.tgz#a5bd50b6e68cee4ed39ea91dbecede5a020aaa97" + integrity sha512-lp5xQBbcRuxNtjpWU0BWZgIrv2XLUz4RJ0RqFXBdESIsKoGCQZ6P3wwU5ZPuj5TjssNiKv9AlM+vHopRxZhvVQ== dependencies: "@babel/runtime" "^7.23.9" "@mui/base" "5.0.0-beta.40" - "@mui/core-downloads-tracker" "^5.15.18" + "@mui/core-downloads-tracker" "^5.15.19" "@mui/system" "^5.15.15" "@mui/types" "^7.2.14" "@mui/utils" "^5.15.14" @@ -2228,7 +2048,7 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.8.0.tgz#5aa7abb48f23f693068ed2999ae627d2f7d902ec" integrity sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w== -"@opentelemetry/context-async-hooks@^1.23.0": +"@opentelemetry/context-async-hooks@^1.24.1": version "1.24.1" resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-1.24.1.tgz#1db7116d78f60e993e0d337bd497885a53deba1a" integrity sha512-R5r6DO4kgEOVBxFXhXjwospLQkv+sYxwCfjvoZBe7Zm6KKXAV9kDSJhi/D1BweowdZmO+sdbENLs374gER8hpQ== @@ -2240,14 +2060,14 @@ dependencies: "@opentelemetry/semantic-conventions" "1.24.1" -"@opentelemetry/instrumentation-connect@0.36.0": - version "0.36.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.36.0.tgz#6a83722f0cb22a7f9b3bd8185f940308bbed0e50" - integrity sha512-k9++bmJZ9zDEs3u3DnKTn2l7QTiNFg3gPx7G9rW0TPnP+xZoBSBTrEcGYBaqflQlrFG23Q58+X1sM2ayWPv5Fg== +"@opentelemetry/instrumentation-connect@0.36.1": + version "0.36.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.36.1.tgz#4ef93ade4c7224213d9e4411190c22d753166a18" + integrity sha512-xI5Q/CMmzBmHshPnzzjD19ptFaYO/rQWzokpNio4QixZYWhJsa35QgRvN9FhPkwgtuJIbt/CWWAufJ3egJNHEA== dependencies: "@opentelemetry/core" "^1.8.0" "@opentelemetry/instrumentation" "^0.51.0" - "@opentelemetry/semantic-conventions" "^1.0.0" + "@opentelemetry/semantic-conventions" "^1.22.0" "@types/connect" "3.4.36" "@opentelemetry/instrumentation-express@0.39.0": @@ -2817,61 +2637,61 @@ dependencies: "@daybrush/utils" "^1.4.0" -"@sentry-internal/browser-utils@8.4.0": - version "8.4.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.4.0.tgz#5b108878e93713757d75e7e8ae7780297d36ad17" - integrity sha512-Mfm3TK3KUlghhuKM3rjTeD4D5kAiB7iVNFoaDJIJBVKa67M9BvlNTnNJMDi7+9rV4RuLQYxXn0p5HEZJFYp3Zw== +"@sentry-internal/browser-utils@8.7.0": + version "8.7.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.7.0.tgz#8a75560c80c50e023db58faf055dacde670e9d78" + integrity sha512-RFBK1sYBwV5qGMEwWF0rjOTqQpp4/SvE+qHkOJNRUTVYmfjM+Y9lcxwn4B6lu3aboxePpBw/i1PlP6XwX4UnGA== dependencies: - "@sentry/core" "8.4.0" - "@sentry/types" "8.4.0" - "@sentry/utils" "8.4.0" + "@sentry/core" "8.7.0" + "@sentry/types" "8.7.0" + "@sentry/utils" "8.7.0" -"@sentry-internal/feedback@8.4.0": - version "8.4.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.4.0.tgz#81067dadda249b354b72f5adba20374dea43fdf4" - integrity sha512-1/WshI2X9seZAQXrOiv6/LU08fbSSvJU0b1ZWMhn+onb/FWPomsL/UN0WufCYA65S5JZGdaWC8fUcJxWC8PATQ== +"@sentry-internal/feedback@8.7.0": + version "8.7.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.7.0.tgz#fa632c70de93b9c6626951f62c732984242097d8" + integrity sha512-qcGtWCtRB4eP7NVQoxW936oPkU4qu9otMLYELPGmOJPnuAG0lujlJXW7BucaM7ADyJgJTE75hG849bHecfnbmQ== dependencies: - "@sentry/core" "8.4.0" - "@sentry/types" "8.4.0" - "@sentry/utils" "8.4.0" + "@sentry/core" "8.7.0" + "@sentry/types" "8.7.0" + "@sentry/utils" "8.7.0" -"@sentry-internal/replay-canvas@8.4.0": - version "8.4.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.4.0.tgz#cf5e903d8935ba6b60a5027d0055902987353920" - integrity sha512-g+U4IPQdODCg7fQQVNvH6ix05Tl1mOQXXRexgtp+tXdys4sHQSBUYraJYZy+mY3OGnLRgKFqELM0fnffJSpuyQ== +"@sentry-internal/replay-canvas@8.7.0": + version "8.7.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.7.0.tgz#78316619cc57b8d81cacabacdf0f5167eb805ef9" + integrity sha512-FOnvBPbq6MJVHPduc0hcsdE3PeeovQ2z5WJnZDGhvp/Obehxqe+XgX7K/595vRIknv4EokRn/3Kw0mFwG8E+ZQ== dependencies: - "@sentry-internal/replay" "8.4.0" - "@sentry/core" "8.4.0" - "@sentry/types" "8.4.0" - "@sentry/utils" "8.4.0" + "@sentry-internal/replay" "8.7.0" + "@sentry/core" "8.7.0" + "@sentry/types" "8.7.0" + "@sentry/utils" "8.7.0" -"@sentry-internal/replay@8.4.0": - version "8.4.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.4.0.tgz#8fc4a6bf1d5f480fcde2d56cd75042953e44efda" - integrity sha512-RSzQwCF/QTi5/5XAuj0VJImAhu4MheeHYvAbr/PuMSF4o1j89gBA7e3boA4u8633IqUeu5w3S5sb6jVrKaVifg== +"@sentry-internal/replay@8.7.0": + version "8.7.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.7.0.tgz#2898303529bb2273129e2e86c861d03f69e95622" + integrity sha512-bQzOkWplaWTe3u+aDBhxWY3Qy0aT7ss2A3VR8iC6N8ZIEP9PxqyJwTNoouhinfgmlnCguI7RDOO4f3r3e2M80Q== dependencies: - "@sentry-internal/browser-utils" "8.4.0" - "@sentry/core" "8.4.0" - "@sentry/types" "8.4.0" - "@sentry/utils" "8.4.0" + "@sentry-internal/browser-utils" "8.7.0" + "@sentry/core" "8.7.0" + "@sentry/types" "8.7.0" + "@sentry/utils" "8.7.0" "@sentry/babel-plugin-component-annotate@2.16.0": version "2.16.0" resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.16.0.tgz#c831713b85516fb3f9da2985836ddf444dc634e6" integrity sha512-+uy1qPkA5MSNgJ0L9ur/vNTydfdHwHnBX2RQ+0thsvkqf90fU788YjkkXwUiBBNuqNyI69JiOW6frixAWy7oUg== -"@sentry/browser@8.4.0": - version "8.4.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.4.0.tgz#f4aa381eab212432d71366884693a36c2e3a1675" - integrity sha512-hmXeIZBdN0A6yCuoMTcigGxLl42nbeb205fXtouwE7Maa0qM2HM+Ijq0sHzbhxR3zU0JXDtcJh1k6wtJOREJ3g== +"@sentry/browser@8.7.0": + version "8.7.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.7.0.tgz#78844ca196315327979ef7cfbad71788e95c9888" + integrity sha512-4EEp+PlcktsMN0p+MdCPl/lghTkq7eOtZjQG9NGhWzfyWrJ3tuL1nsDr2SSivJ1V277F01KtKYo6BFwP2NtBZA== dependencies: - "@sentry-internal/browser-utils" "8.4.0" - "@sentry-internal/feedback" "8.4.0" - "@sentry-internal/replay" "8.4.0" - "@sentry-internal/replay-canvas" "8.4.0" - "@sentry/core" "8.4.0" - "@sentry/types" "8.4.0" - "@sentry/utils" "8.4.0" + "@sentry-internal/browser-utils" "8.7.0" + "@sentry-internal/feedback" "8.7.0" + "@sentry-internal/replay" "8.7.0" + "@sentry-internal/replay-canvas" "8.7.0" + "@sentry/core" "8.7.0" + "@sentry/types" "8.7.0" + "@sentry/utils" "8.7.0" "@sentry/bundler-plugin-core@2.16.0": version "2.16.0" @@ -2887,45 +2707,45 @@ magic-string "0.27.0" unplugin "1.0.1" -"@sentry/cli-darwin@2.31.2": - version "2.31.2" - resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.31.2.tgz#faeb87d09d8b21b8b8dd2e2aa848b538f01ddd26" - integrity sha512-BHA/JJXj1dlnoZQdK4efRCtHRnbBfzbIZUKAze7oRR1RfNqERI84BVUQeKateD3jWSJXQfEuclIShc61KOpbKw== - -"@sentry/cli-linux-arm64@2.31.2": - version "2.31.2" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.31.2.tgz#669c9c3f7f9130d26f5db732f793378863d58869" - integrity sha512-FLVKkJ/rWvPy/ka7OrUdRW63a/z8HYI1Gt8Pr6rWs50hb7YJja8lM8IO10tYmcFE/tODICsnHO9HTeUg2g2d1w== - -"@sentry/cli-linux-arm@2.31.2": - version "2.31.2" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.31.2.tgz#3e36ed7db09e922f00221281252e58dfd8755ea5" - integrity sha512-W8k5mGYYZz/I/OxZH65YAK7dCkQAl+wbuoASGOQjUy5VDgqH0QJ8kGJufXvFPM+f3ZQGcKAnVsZ6tFqZXETBAw== - -"@sentry/cli-linux-i686@2.31.2": - version "2.31.2" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.31.2.tgz#02b7da274369b78a5676c20bb26cc37caed5244b" - integrity sha512-A64QtzaPi3MYFpZ+Fwmi0mrSyXgeLJ0cWr4jdeTGrzNpeowSteKgd6tRKU+LVq0k5shKE7wdnHk+jXnoajulMA== - -"@sentry/cli-linux-x64@2.31.2": - version "2.31.2" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.31.2.tgz#54f74a9e5925db9ddafebc0efd4056c5377be5fd" - integrity sha512-YL/r+15R4mOEiU3mzn7iFQOeFEUB6KxeKGTTrtpeOGynVUGIdq4nV5rHow5JDbIzOuBS3SpOmcIMluvo1NCh0g== - -"@sentry/cli-win32-i686@2.31.2": - version "2.31.2" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.31.2.tgz#5dab845a824be0927566171aa05f015e887fe82d" - integrity sha512-Az/2bmW+TFI059RE0mSBIxTBcoShIclz7BDebmIoCkZ+retrwAzpmBnBCDAHow+Yi43utOow+3/4idGa2OxcLw== - -"@sentry/cli-win32-x64@2.31.2": - version "2.31.2" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.31.2.tgz#e12fec0a54f6d9cced5235fbc68ba8f94165634b" - integrity sha512-XIzyRnJu539NhpFa+JYkotzVwv3NrZ/4GfHB/JWA2zReRvsk39jJG8D5HOmm0B9JA63QQT7Dt39RW8g3lkmb6w== +"@sentry/cli-darwin@2.32.1": + version "2.32.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.32.1.tgz#9cb3b8cfb7068d40979514dee72e2bb3ad2c6d0a" + integrity sha512-z/lEwANTYPCzbWTZ2+eeeNYxRLllC8knd0h+vtAKlhmGw/fyc/N39cznIFyFu+dLJ6tTdjOWOeikHtKuS/7onw== + +"@sentry/cli-linux-arm64@2.32.1": + version "2.32.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.32.1.tgz#785a5d5d3d2919c581bf5b4efc638c3695d8c3bf" + integrity sha512-hsGqHYuecUl1Yhq4MhiRejfh1gNlmhyNPcQEoO/DDRBnGnJyEAdiDpKXJcc2e/lT9k40B55Ob2CP1SeY040T2w== + +"@sentry/cli-linux-arm@2.32.1": + version "2.32.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.32.1.tgz#7f9e8292850311bab263e7b84800eb407ff37998" + integrity sha512-m0lHkn+o4YKBq8KptGZvpT64FAwSl9mYvHZO9/ChnEGIJ/WyJwiN1X1r9JHVaW4iT5lD0Y5FAyq3JLkk0m0XHg== + +"@sentry/cli-linux-i686@2.32.1": + version "2.32.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.32.1.tgz#8e85fa58dee042e6a4642e960d226788f8e7288b" + integrity sha512-SuMLN1/ceFd3Q/B0DVyh5igjetTAF423txiABAHASenEev0lG0vZkRDXFclfgDtDUKRPmOXW7VDMirM3yZWQHQ== + +"@sentry/cli-linux-x64@2.32.1": + version "2.32.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.32.1.tgz#b68ed9c4ba163b6730d386dbeca828114f1c979b" + integrity sha512-x4FGd6xgvFddz8V/dh6jii4wy9qjWyvYLBTz8Fhi9rIP+b8wQ3oxwHIdzntareetZP7C1ggx+hZheiYocNYVwA== + +"@sentry/cli-win32-i686@2.32.1": + version "2.32.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.32.1.tgz#e2532893f87f5d180f6e56f49904d4ac141c8788" + integrity sha512-i6aZma9mFzR+hqMY5VliQZEX6ypP/zUjPK0VtIMYWs5cC6PsQLRmuoeJmy3Z7d4nlh0CdK5NPC813Ej6RY6/vg== + +"@sentry/cli-win32-x64@2.32.1": + version "2.32.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.32.1.tgz#6b60607cbba243f3708779443cd3f16e09d4289c" + integrity sha512-B58w/lRHLb4MUSjJNfMMw2cQykfimDCMLMmeK+1EiT2RmSeNQliwhhBxYcKk82a8kszH6zg3wT2vCea7LyPUyA== "@sentry/cli@^2.22.3": - version "2.31.2" - resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.31.2.tgz#39df8e52966aa8db4f9c51f4bc77abd62b6a630e" - integrity sha512-2aKyUx6La2P+pplL8+2vO67qJ+c1C79KYWAyQBE0JIT5kvKK9JpwtdNoK1F0/2mRpwhhYPADCz3sVIRqmL8cQQ== + version "2.32.1" + resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.32.1.tgz#80932d3d58e6d3b52e2bd705673e08deeb9cb5b0" + integrity sha512-MWkbkzZfnlE7s2pPbg4VozRSAeMlIObfZlTIou9ye6XnPt6ZmmxCLOuOgSKMv4sXg6aeqKNzMNiadThxCWyvPg== dependencies: https-proxy-agent "^5.0.0" node-fetch "^2.6.7" @@ -2933,52 +2753,52 @@ proxy-from-env "^1.1.0" which "^2.0.2" optionalDependencies: - "@sentry/cli-darwin" "2.31.2" - "@sentry/cli-linux-arm" "2.31.2" - "@sentry/cli-linux-arm64" "2.31.2" - "@sentry/cli-linux-i686" "2.31.2" - "@sentry/cli-linux-x64" "2.31.2" - "@sentry/cli-win32-i686" "2.31.2" - "@sentry/cli-win32-x64" "2.31.2" - -"@sentry/core@8.4.0": - version "8.4.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.4.0.tgz#ab3f7202f3cae82daf4c3c408f50d2c6fb913620" - integrity sha512-0eACPlJvKloFIlcT1c/vjGnvqxLxpGyGuSsU7uonrkmBqIRwLYXWtR4PoHapysKtjPVoHAn9au50ut6ymC2V8Q== - dependencies: - "@sentry/types" "8.4.0" - "@sentry/utils" "8.4.0" + "@sentry/cli-darwin" "2.32.1" + "@sentry/cli-linux-arm" "2.32.1" + "@sentry/cli-linux-arm64" "2.32.1" + "@sentry/cli-linux-i686" "2.32.1" + "@sentry/cli-linux-x64" "2.32.1" + "@sentry/cli-win32-i686" "2.32.1" + "@sentry/cli-win32-x64" "2.32.1" + +"@sentry/core@8.7.0": + version "8.7.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.7.0.tgz#c98bc47020cd48d899806baaebc7a4b6fe10b9ab" + integrity sha512-Sq/46B+5nWmgnCD6dEMZ6HTkKbV/KAdgaSvT8oXDb9OWoPy1jJ/gbLrhLs62KbjuDQk4/vWnOgHiKQbcslSzMw== + dependencies: + "@sentry/types" "8.7.0" + "@sentry/utils" "8.7.0" "@sentry/nextjs@^8": - version "8.4.0" - resolved "https://registry.yarnpkg.com/@sentry/nextjs/-/nextjs-8.4.0.tgz#c1b80256f003bf0c63d6b5220b51adff9e6cab7e" - integrity sha512-g0C/vDrK3NeJhw/xXpUZCS/NuuTluSixlS3tZOd82AJVXGhepNlzm+RAbuMv2R9CVfvdHvZYDtZf7WxWDrhrrg== + version "8.7.0" + resolved "https://registry.yarnpkg.com/@sentry/nextjs/-/nextjs-8.7.0.tgz#aa949f013d2a7a6c981234c280fe363dd0f58d42" + integrity sha512-aD+iDPUy5eC1XqPiFfRU09mlK3iCwxSGMg3y/z3GXrUlR9DLI7lMz90l6qaDGFFnjsCT75JrG37dYWVqILAx3g== dependencies: "@opentelemetry/instrumentation-http" "0.51.1" "@rollup/plugin-commonjs" "24.0.0" - "@sentry/core" "8.4.0" - "@sentry/node" "8.4.0" - "@sentry/opentelemetry" "8.4.0" - "@sentry/react" "8.4.0" - "@sentry/types" "8.4.0" - "@sentry/utils" "8.4.0" - "@sentry/vercel-edge" "8.4.0" + "@sentry/core" "8.7.0" + "@sentry/node" "8.7.0" + "@sentry/opentelemetry" "8.7.0" + "@sentry/react" "8.7.0" + "@sentry/types" "8.7.0" + "@sentry/utils" "8.7.0" + "@sentry/vercel-edge" "8.7.0" "@sentry/webpack-plugin" "2.16.0" chalk "3.0.0" resolve "1.22.8" rollup "3.29.4" stacktrace-parser "^0.1.10" -"@sentry/node@8.4.0": - version "8.4.0" - resolved "https://registry.yarnpkg.com/@sentry/node/-/node-8.4.0.tgz#342a92c0937aa149fb428928f9ea7e0c3e8d2158" - integrity sha512-k0uqG2F8BQWATIEghA1jQ0tBAr9mJsyS+ZiruXjbixy8kd7+ZM1CCiqeqqrYaanS0hI0mvtg9uxTQzBa1SMQsA== +"@sentry/node@8.7.0": + version "8.7.0" + resolved "https://registry.yarnpkg.com/@sentry/node/-/node-8.7.0.tgz#dd8993c6060870ad80b8c45091d4ae91c1c7b7c2" + integrity sha512-El1LmXGVe8Ahi5oUdlrE5s3Or23/iGnnntNvaYymXk4BmL4dJtv7ttlQ94ZrI9QWs8VnfM7eHqCd+OPjTh0XJQ== dependencies: "@opentelemetry/api" "^1.8.0" - "@opentelemetry/context-async-hooks" "^1.23.0" + "@opentelemetry/context-async-hooks" "^1.24.1" "@opentelemetry/core" "^1.24.1" "@opentelemetry/instrumentation" "^0.51.1" - "@opentelemetry/instrumentation-connect" "0.36.0" + "@opentelemetry/instrumentation-connect" "0.36.1" "@opentelemetry/instrumentation-express" "0.39.0" "@opentelemetry/instrumentation-fastify" "0.36.1" "@opentelemetry/instrumentation-graphql" "0.40.0" @@ -2996,53 +2816,53 @@ "@opentelemetry/sdk-trace-base" "^1.23.0" "@opentelemetry/semantic-conventions" "^1.23.0" "@prisma/instrumentation" "5.14.0" - "@sentry/core" "8.4.0" - "@sentry/opentelemetry" "8.4.0" - "@sentry/types" "8.4.0" - "@sentry/utils" "8.4.0" + "@sentry/core" "8.7.0" + "@sentry/opentelemetry" "8.7.0" + "@sentry/types" "8.7.0" + "@sentry/utils" "8.7.0" optionalDependencies: opentelemetry-instrumentation-fetch-node "1.2.0" -"@sentry/opentelemetry@8.4.0": - version "8.4.0" - resolved "https://registry.yarnpkg.com/@sentry/opentelemetry/-/opentelemetry-8.4.0.tgz#0f7a0f197031bf387421f78487a3767e724828b3" - integrity sha512-1YXLuRHMhzPzoiD8Pzts5GlZY4V5GSXGn5aBmFlJ13vSrUK6C4qhPfZMboppntPihOxupCPg3XP76ZMj6+XuOg== +"@sentry/opentelemetry@8.7.0": + version "8.7.0" + resolved "https://registry.yarnpkg.com/@sentry/opentelemetry/-/opentelemetry-8.7.0.tgz#9f6ba1c081918fd2f82b4811aefd94b2362c5c0f" + integrity sha512-I9JEXnqXDBPr5MtgEYRvmcolmpugSgH1QV+SFnfOPc40Mu/npNsJq7oqbGzhlCe4H45XD6LJzFlc7BfoCzwAsQ== dependencies: - "@sentry/core" "8.4.0" - "@sentry/types" "8.4.0" - "@sentry/utils" "8.4.0" + "@sentry/core" "8.7.0" + "@sentry/types" "8.7.0" + "@sentry/utils" "8.7.0" -"@sentry/react@8.4.0": - version "8.4.0" - resolved "https://registry.yarnpkg.com/@sentry/react/-/react-8.4.0.tgz#95f4fed03709b231770a4f32d3c960c544b0dc3c" - integrity sha512-YnDN+szKFm1fQ9311nAulsRbboeMbqNmosMLA6PweBDEwD0HEJsovQT+ZJxXiOL220qsgWVJzk+aTPtf+oY4wA== +"@sentry/react@8.7.0": + version "8.7.0" + resolved "https://registry.yarnpkg.com/@sentry/react/-/react-8.7.0.tgz#fc3f4303abb2ccd1e23f75f9f5bb6e648e095fa0" + integrity sha512-JFo8QW8JB4eaFC8RdkOBO96JvlGgstywmyMZ39qWfFbD735vGl8PnOa0AnrC/5Auc86dZ98/I4OEPboqUE9q1w== dependencies: - "@sentry/browser" "8.4.0" - "@sentry/core" "8.4.0" - "@sentry/types" "8.4.0" - "@sentry/utils" "8.4.0" + "@sentry/browser" "8.7.0" + "@sentry/core" "8.7.0" + "@sentry/types" "8.7.0" + "@sentry/utils" "8.7.0" hoist-non-react-statics "^3.3.2" -"@sentry/types@8.4.0": - version "8.4.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.4.0.tgz#42500005a198ff8c247490434ed55e0a9f975ad1" - integrity sha512-mHUaaYEQCNukzYsTLp4rP2NNO17vUf+oSGS6qmhrsGqmGNICKw2CIwJlPPGeAkq9Y4tiUOye2m5OT1xsOtxLIw== +"@sentry/types@8.7.0": + version "8.7.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.7.0.tgz#92731af32318d6abb8759216cf6c3c5035894e6e" + integrity sha512-11KLOKumP6akugVGLvSoEig+JlP0ZEzW3nN9P+ppgdIx9HAxMIh6UvumbieG4/DWjAh2kh6NPNfUw3gk2Gfq1A== -"@sentry/utils@8.4.0": - version "8.4.0" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.4.0.tgz#1b816e65d8dbf055c5e1554361aaf9a8a8a94102" - integrity sha512-oDF0RVWW0AyEnsP1x4McHUvQSAxJgx3G6wM9Sb4wc1F8rwsHnCtGHc+WRZ5Gd2AXC5EGkfbg5919+1ku/L4Dww== +"@sentry/utils@8.7.0": + version "8.7.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.7.0.tgz#26893acc5bca9bfd4998d2eafe724491e3ca78a2" + integrity sha512-aWmcbSoOmrbzll/FkNQFJcCtLAuJLvTYbRKiCSkV3FScA7UaA742HkTZAPFiioALFIESWk/fcGZqtN0s4I281Q== dependencies: - "@sentry/types" "8.4.0" + "@sentry/types" "8.7.0" -"@sentry/vercel-edge@8.4.0": - version "8.4.0" - resolved "https://registry.yarnpkg.com/@sentry/vercel-edge/-/vercel-edge-8.4.0.tgz#64905348220a90fb9f1a747aea26e84735518ae0" - integrity sha512-iT/lZYYHziAQH0OdSCjqA1WJ1gkwAiE/Aosn3h+ddktqoU7DG/bknzwD+QSt4EQjTn3GvuXFj/JonEfbA1wlow== +"@sentry/vercel-edge@8.7.0": + version "8.7.0" + resolved "https://registry.yarnpkg.com/@sentry/vercel-edge/-/vercel-edge-8.7.0.tgz#78e4d41affa4bb29fb387e5c6eb0947116583088" + integrity sha512-Ggx/en7Agzi4PHsgT91nbEXuv1LIHT2BVSY2ggkD6Uy0xBkwRy2zfSlMaK98BEmVf+pPEGZTEuoYFuBtUsShHw== dependencies: - "@sentry/core" "8.4.0" - "@sentry/types" "8.4.0" - "@sentry/utils" "8.4.0" + "@sentry/core" "8.7.0" + "@sentry/types" "8.7.0" + "@sentry/utils" "8.7.0" "@sentry/webpack-plugin@2.16.0": version "2.16.0" @@ -3063,55 +2883,55 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz#719df7fb41766bc143369eaa0dd56d8dc87c9958" integrity sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg== -"@storybook/addon-actions@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-8.1.2.tgz#d3fc0d19581092dd51f93b58c4a0725d20a51ec5" - integrity sha512-EW8sYgA1vpn67pTheBjfLCfPO82w0jMkKYFDFMMvxVoEDbJ3fgsUDDd058zBBFiDtnngv3VUrXASPTxK51F2mQ== +"@storybook/addon-actions@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-8.1.5.tgz#c89d2027cbac0e6b4391db23a79e20a407b949d7" + integrity sha512-XbCUGGXQ4XX/zTRgUsR1l1yZJQIWR33P/M1OEAn0HbsfwS+P87GqfApkj4N7QrMfLkUkoLtdfprp5BZul98AKA== dependencies: - "@storybook/core-events" "8.1.2" + "@storybook/core-events" "8.1.5" "@storybook/global" "^5.0.0" "@types/uuid" "^9.0.1" dequal "^2.0.2" polished "^4.2.2" uuid "^9.0.0" -"@storybook/addon-backgrounds@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-backgrounds/-/addon-backgrounds-8.1.2.tgz#b26edf16300396808d59a9309f7b33620f3b42bc" - integrity sha512-zCSDQ6a0od1Na1fzBcwCtxykKBLi97eKdVBTCVWQ3IPztl+WODO306PfE4vC9QV8icq8BPgPp4MsKuNLx1uFxQ== +"@storybook/addon-backgrounds@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/addon-backgrounds/-/addon-backgrounds-8.1.5.tgz#dd77d8fac2df534bab2d8550fd3adf0892807e88" + integrity sha512-osAM4U8DCcKe/JGBBHoFYQi0oorNzFPwcETTy4SAc8LVqsv73SN7CyNnqCrN9Kjom9klJqB/tngvjdJ1XLu4WQ== dependencies: "@storybook/global" "^5.0.0" memoizerific "^1.11.3" ts-dedent "^2.0.0" -"@storybook/addon-controls@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-controls/-/addon-controls-8.1.2.tgz#21eded36d46acc1919718b8722ec15725810db0e" - integrity sha512-2+dy3wRfGjiXmbw/3Fg8wnkO5kLycl9g37m/kCKsJZ10pFjq7xInNP6QKUz0yCM7BMdO+NlDii/clUAHYQ/puA== +"@storybook/addon-controls@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/addon-controls/-/addon-controls-8.1.5.tgz#c0b7272e9aaaab9db299e73a2053c04f1694cd43" + integrity sha512-O0796G3+772kohYOsR98puROgkEakNXZ9n3FXVsQQ57Ww/CIP7gFRv5VM5z+Jw0a+HQI5be6504hDeAOHrd8qQ== dependencies: - "@storybook/blocks" "8.1.2" + "@storybook/blocks" "8.1.5" dequal "^2.0.2" lodash "^4.17.21" ts-dedent "^2.0.0" -"@storybook/addon-docs@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-8.1.2.tgz#8d6e6d0b634d4753d90a3261b34641455d027b2a" - integrity sha512-wwzvcE/d2ZBmUQY1gMJMb/cNw4CvckFl4pFq3cGipn3QnZzYIad9oQxMIUtYXCyHw3oguajaw6qYLZzkJEAvWg== +"@storybook/addon-docs@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-8.1.5.tgz#c837283b5e7bf782d2c7fa7cce5d6344e3f91ee7" + integrity sha512-D3kDWjOGAthbwQOnouauOmywiTnuvI4KS0E9TDBYspcufimoNve5nOlr/oo9SLS1O2Psmhi6MDJephaDDo+5Dw== dependencies: "@babel/core" "^7.24.4" "@mdx-js/react" "^3.0.0" - "@storybook/blocks" "8.1.2" - "@storybook/client-logger" "8.1.2" - "@storybook/components" "8.1.2" - "@storybook/csf-plugin" "8.1.2" - "@storybook/csf-tools" "8.1.2" + "@storybook/blocks" "8.1.5" + "@storybook/client-logger" "8.1.5" + "@storybook/components" "8.1.5" + "@storybook/csf-plugin" "8.1.5" + "@storybook/csf-tools" "8.1.5" "@storybook/global" "^5.0.0" - "@storybook/node-logger" "8.1.2" - "@storybook/preview-api" "8.1.2" - "@storybook/react-dom-shim" "8.1.2" - "@storybook/theming" "8.1.2" - "@storybook/types" "8.1.2" + "@storybook/node-logger" "8.1.5" + "@storybook/preview-api" "8.1.5" + "@storybook/react-dom-shim" "8.1.5" + "@storybook/theming" "8.1.5" + "@storybook/types" "8.1.5" "@types/react" "^16.8.0 || ^17.0.0 || ^18.0.0" fs-extra "^11.1.0" react "^16.8.0 || ^17.0.0 || ^18.0.0" @@ -3121,72 +2941,72 @@ ts-dedent "^2.0.0" "@storybook/addon-essentials@^8.1.1": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-essentials/-/addon-essentials-8.1.2.tgz#53f297360e604509cda76e38303ca4acd9435d73" - integrity sha512-mX8Loni7bRQ6IVkhJzIcgBtpaZKAxF8OGPcqG/J4DMulH0wv58vKBCygyCc2XXOG3K1fyggGN5KgWa+w6X3Anw== - dependencies: - "@storybook/addon-actions" "8.1.2" - "@storybook/addon-backgrounds" "8.1.2" - "@storybook/addon-controls" "8.1.2" - "@storybook/addon-docs" "8.1.2" - "@storybook/addon-highlight" "8.1.2" - "@storybook/addon-measure" "8.1.2" - "@storybook/addon-outline" "8.1.2" - "@storybook/addon-toolbars" "8.1.2" - "@storybook/addon-viewport" "8.1.2" - "@storybook/core-common" "8.1.2" - "@storybook/manager-api" "8.1.2" - "@storybook/node-logger" "8.1.2" - "@storybook/preview-api" "8.1.2" + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/addon-essentials/-/addon-essentials-8.1.5.tgz#978bc9c60c80d8f3708f0a384341bc6c966d1334" + integrity sha512-0k2D5+j2N6hso3y+rSqTlQECZ/Z/Q85eit0exx2/Rk/TI5F5HceLveA1YXyC0J291nexdF9RvjP7aCtee3WSYg== + dependencies: + "@storybook/addon-actions" "8.1.5" + "@storybook/addon-backgrounds" "8.1.5" + "@storybook/addon-controls" "8.1.5" + "@storybook/addon-docs" "8.1.5" + "@storybook/addon-highlight" "8.1.5" + "@storybook/addon-measure" "8.1.5" + "@storybook/addon-outline" "8.1.5" + "@storybook/addon-toolbars" "8.1.5" + "@storybook/addon-viewport" "8.1.5" + "@storybook/core-common" "8.1.5" + "@storybook/manager-api" "8.1.5" + "@storybook/node-logger" "8.1.5" + "@storybook/preview-api" "8.1.5" ts-dedent "^2.0.0" -"@storybook/addon-highlight@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-highlight/-/addon-highlight-8.1.2.tgz#462c434263ef21d6c087b4063cd586bd34b82aa9" - integrity sha512-1PDE2RRV33oMGZJ4ZJ9GbLlsWEIq/opEfbSa9cy1C4K0mcfLtdSbDk5bT23wfU78HFYF2q5+aJ7jVFwHJjueTA== +"@storybook/addon-highlight@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/addon-highlight/-/addon-highlight-8.1.5.tgz#40c067b87327e7e5c0b592e783442a04481748bd" + integrity sha512-E31yrV7lmE82T57tLSm8mg50BX3lBbA4qozaVKyWohw0NrZPcrS3Z6Iyjl0dp7heoUFpE3rljHwMxADRA25HkQ== dependencies: "@storybook/global" "^5.0.0" "@storybook/addon-interactions@^8.1.1": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-interactions/-/addon-interactions-8.1.2.tgz#e1ff6cd4036d31b417b8831a96f519676692b64f" - integrity sha512-x8TCWVMOqcwQEIgkWeDaPQs4pYFNf2qvoq5E8xLweCbHY6ciwU/IOYNs4p7r4qwzQvUteyHCBYnKIXSbH8xhiA== + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/addon-interactions/-/addon-interactions-8.1.5.tgz#d7711936aae5b1a1b9833b4b40d96e425435d765" + integrity sha512-jhDpqttch0XhRiCY9rfrs8xQpAH5KcAGAesqfaHaCnCZnZs6jqlGfJgCJAJWzA5PM+IdsK/RJ6abIgD1GAzNyw== dependencies: "@storybook/global" "^5.0.0" - "@storybook/instrumenter" "8.1.2" - "@storybook/test" "8.1.2" - "@storybook/types" "8.1.2" + "@storybook/instrumenter" "8.1.5" + "@storybook/test" "8.1.5" + "@storybook/types" "8.1.5" polished "^4.2.2" ts-dedent "^2.2.0" "@storybook/addon-links@^8.1.1": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-8.1.2.tgz#b967858178d1bcc80083bb2dc314307c6e23b35f" - integrity sha512-l+Q1LqyPhQ1n4vGQF95vkH9ImJUt1eY0MU+mlwlMHoMDdcFkUqzBxx5NwhrhbvdZK/yfxt9gjsx62k9RYDNKjA== + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-8.1.5.tgz#1fd7bbc6579fd1562ea3d9b9afa62e8b57e17162" + integrity sha512-cRarzAI27K1JijDmFtNqr7khyg/l1JyOLXvLUDZRI6NBFGQo2oA42iHuR8jzje4tlUEh/8svGz52YR4TUvsDtQ== dependencies: "@storybook/csf" "^0.1.7" "@storybook/global" "^5.0.0" ts-dedent "^2.0.0" -"@storybook/addon-measure@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-measure/-/addon-measure-8.1.2.tgz#9d0c0e59f115e067a868ad3c5bfa613aa2fa969d" - integrity sha512-Wer5TQ6smPiJpevvnUr6DELxd7bNvCL1xOeaAI8/0g186TKlaaSam2GapIErWXQfzuh7dYnCVXxiTOiayUv3UQ== +"@storybook/addon-measure@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/addon-measure/-/addon-measure-8.1.5.tgz#d197498bcd74837ea0ef32fda08ee9839cac0844" + integrity sha512-kHiv2qq9Ws0lGQ8p7FfMKFtXO4hrRiYStG8CCp9i1IfPzLpY8S9Kl9bwnoyVyI5bwqZP1wjFQVw8sjumV6FMFw== dependencies: "@storybook/global" "^5.0.0" tiny-invariant "^1.3.1" "@storybook/addon-onboarding@^8.1.1": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-onboarding/-/addon-onboarding-8.1.2.tgz#bf1703ecd9db56d673f1e73cc49c3785e47c7aed" - integrity sha512-z5jokyfCA4tJMv/kl7MeimxezHibcX+ICH2tausV3rRmtkDxVqp8LmbCktH5hPST7VX+qBVsQyqGDmpEXqWkog== + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/addon-onboarding/-/addon-onboarding-8.1.5.tgz#595f200ab81f3d03e3b2561477f97265dd690036" + integrity sha512-/rb5gQjQ1xOApELFj4Jg/809zR7OmGM9P4NsyVrZaL2Jhlwx8yKfu97IpgqbVX+KSfiBDJbySC9FdqVxSjGCZg== dependencies: react-confetti "^6.1.0" -"@storybook/addon-outline@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-outline/-/addon-outline-8.1.2.tgz#645c18cd948cc72bb36de277d97ba3a97377e449" - integrity sha512-4jnUIU4dMDGbgevPQTXoe11WVAOxwhMkFgq9URZjV553Rhpu6g2DfXLJEB45j3uh7AksyD6gX2/v8tXoYeKUoQ== +"@storybook/addon-outline@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/addon-outline/-/addon-outline-8.1.5.tgz#dc3452824d9c3a8189b7aa1667f8416b7876fbd0" + integrity sha512-eCXnGN24ewfvUKKpzTJP7HtPJkAexIBnQdJCw9R9Jk8IyHh7xPWsrz+haY1FQHTXZGAevoBcI4/tpG2XOumBlw== dependencies: "@storybook/global" "^5.0.0" ts-dedent "^2.0.0" @@ -3198,43 +3018,43 @@ dependencies: "@storybook/node-logger" "^8.0.0-alpha.10" -"@storybook/addon-toolbars@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-toolbars/-/addon-toolbars-8.1.2.tgz#8e6a85338b9fec8777d08f23ffef218ce220d874" - integrity sha512-Nim7ndZLEzt2NtSzZFZIZ4EKSxAzSnIgX8U3gMDyVPhITXOxVy2kPeYYCcRynZOjDa7MXbin7NaV+GH82RfZKw== +"@storybook/addon-toolbars@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/addon-toolbars/-/addon-toolbars-8.1.5.tgz#1ca4e87c347a150596f29de36e1dfb165c4999cd" + integrity sha512-UxEtb4ii0FORqUuPgLycPQ0MQ4Bq2YWBft6yT00xMjUuwkld27BlrvnpaBlx+disgWwOKGKVd02f/4dbZr2s1g== -"@storybook/addon-viewport@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-viewport/-/addon-viewport-8.1.2.tgz#ed489721867c04097dc73c714230637ab82b8c71" - integrity sha512-a+VgyH5lWQzB+e9kzg06lKxDFFrAksdDszmeTWG5d6CG9uXWQ95LqFTfGp25sQRRpcBth5i0VJnGN8fNiSKblQ== +"@storybook/addon-viewport@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/addon-viewport/-/addon-viewport-8.1.5.tgz#86bba48b62d5bc3a9dd8cfaa0ef3c11eeeaff557" + integrity sha512-kHaYdaAiv7107GSi4TsS1wEDN4I7cdYWSaCBBSvJlvvYvULKFVMkhsDJlSioskICx6OchkIKY5LJgLZ72fxdVA== dependencies: memoizerific "^1.11.3" "@storybook/addon-webpack5-compiler-swc@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@storybook/addon-webpack5-compiler-swc/-/addon-webpack5-compiler-swc-1.0.2.tgz#f5ab6e68d15dbff0cd6fafd76fd0d5d15dbb21a9" - integrity sha512-o8PPyFCl48bkqmcwiX6RNIMBdXe96EqmB1JuMzInQX77f7lEAN6sAE17/pdlZmmRmzIEADqgumOXLKL3ViObzw== + version "1.0.3" + resolved "https://registry.yarnpkg.com/@storybook/addon-webpack5-compiler-swc/-/addon-webpack5-compiler-swc-1.0.3.tgz#e0d193858de4e0931a359f2837e0cbbe64b9422f" + integrity sha512-ahemZdpFN7Ikz2WTy0sLJ38t6OWLLKweeTNOfUh2ARd3x0CqJxAqWLBAFXJke+2KDFUQg9MTE+1rwHEFCPYUvQ== dependencies: - "@swc/core" "^1.3.102" + "@swc/core" "1.5.7" swc-loader "^0.2.3" -"@storybook/blocks@8.1.2", "@storybook/blocks@^8.1.1": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/blocks/-/blocks-8.1.2.tgz#1fb868499d3d62390264aa1018e650df95984b8c" - integrity sha512-Juk4xkRdjxVDeLdx0w1t6BafxqF6R64QVoehcjO50zHUixQEJdmFopxUgBFFRPTdisz7uNNFSDH3LOYCrKdMVQ== +"@storybook/blocks@8.1.5", "@storybook/blocks@^8.1.1": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/blocks/-/blocks-8.1.5.tgz#a491e6f88b908f08906733a537949cc54256d909" + integrity sha512-rq8Ej5feS2BlfXOpNLDwdASkIIZJtKzLy9cUpuGftTiu06HiWAk3wpNpnn/kuunDYlZUa+qHEOSiIkTrdduwYw== dependencies: - "@storybook/channels" "8.1.2" - "@storybook/client-logger" "8.1.2" - "@storybook/components" "8.1.2" - "@storybook/core-events" "8.1.2" + "@storybook/channels" "8.1.5" + "@storybook/client-logger" "8.1.5" + "@storybook/components" "8.1.5" + "@storybook/core-events" "8.1.5" "@storybook/csf" "^0.1.7" - "@storybook/docs-tools" "8.1.2" + "@storybook/docs-tools" "8.1.5" "@storybook/global" "^5.0.0" "@storybook/icons" "^1.2.5" - "@storybook/manager-api" "8.1.2" - "@storybook/preview-api" "8.1.2" - "@storybook/theming" "8.1.2" - "@storybook/types" "8.1.2" + "@storybook/manager-api" "8.1.5" + "@storybook/preview-api" "8.1.5" + "@storybook/theming" "8.1.5" + "@storybook/types" "8.1.5" "@types/lodash" "^4.14.167" color-convert "^2.0.1" dequal "^2.0.2" @@ -3248,15 +3068,15 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/builder-manager@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/builder-manager/-/builder-manager-8.1.2.tgz#ea51be988b223cf7dcf90fea8f77ce5594f6ff9a" - integrity sha512-cOdfSUY6vtZvJaSK1htx4aNmCLldPS7gFTBoooj3oMv7SyP3c3T53NuB+RcYpMqAUtngjLnTcl+tQ9JR/H5meA== +"@storybook/builder-manager@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/builder-manager/-/builder-manager-8.1.5.tgz#fa72bed0bf92ebe11a2e433f810c8d1dbb0fab1a" + integrity sha512-wDiHLV+UPaUN+765WwXkocVRB2QnJ61CjLHbpWaLiJvryFJt+JQ6nAvgSalCRnZxI046ztbS9T6okhpFI011IA== dependencies: "@fal-works/esbuild-plugin-global-externals" "^2.1.2" - "@storybook/core-common" "8.1.2" - "@storybook/manager" "8.1.2" - "@storybook/node-logger" "8.1.2" + "@storybook/core-common" "8.1.5" + "@storybook/manager" "8.1.5" + "@storybook/node-logger" "8.1.5" "@types/ejs" "^3.1.1" "@yarnpkg/esbuild-plugin-pnp" "^3.0.0-rc.10" browser-assert "^1.2.1" @@ -3268,19 +3088,19 @@ process "^0.11.10" util "^0.12.4" -"@storybook/builder-webpack5@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/builder-webpack5/-/builder-webpack5-8.1.2.tgz#6b245548f2b7c54f83bdf5294a63d9e306289805" - integrity sha512-viLRvTXvXUj3MeO7CNk9nOde5Q/QLxE4EuTExIaJK3OB637bptabtVnbNsOCyhilHK7g1Jww9rdirXVosPUXjg== - dependencies: - "@storybook/channels" "8.1.2" - "@storybook/client-logger" "8.1.2" - "@storybook/core-common" "8.1.2" - "@storybook/core-events" "8.1.2" - "@storybook/core-webpack" "8.1.2" - "@storybook/node-logger" "8.1.2" - "@storybook/preview" "8.1.2" - "@storybook/preview-api" "8.1.2" +"@storybook/builder-webpack5@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/builder-webpack5/-/builder-webpack5-8.1.5.tgz#ffa8914d63589d3dd848e8fa3ea5459c288ec80a" + integrity sha512-gGVlApa0JVu0q7Ws37Kubh9e8wDKoJh23DXGIeK3EHVloL2XU9+wgP2NcUoiySvTIKPtDB7Zljg1/BXgqeOJ4w== + dependencies: + "@storybook/channels" "8.1.5" + "@storybook/client-logger" "8.1.5" + "@storybook/core-common" "8.1.5" + "@storybook/core-events" "8.1.5" + "@storybook/core-webpack" "8.1.5" + "@storybook/node-logger" "8.1.5" + "@storybook/preview" "8.1.5" + "@storybook/preview-api" "8.1.5" "@types/node" "^18.0.0" "@types/semver" "^7.3.4" browser-assert "^1.2.1" @@ -3308,33 +3128,33 @@ webpack-hot-middleware "^2.25.1" webpack-virtual-modules "^0.5.0" -"@storybook/channels@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-8.1.2.tgz#f166393214be061f074ab88b3d240d67b21d7b8c" - integrity sha512-ChWKPCDZ4VVBpulJsZ+RQiPi4NVm6tb0FJwjEcMskxl4Nx2x4+rxLrZHrsZHWXsH5uJctSEjmmvEn1QdjVKMPQ== +"@storybook/channels@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-8.1.5.tgz#d00d033d318cf202ece1de728e55e85f82242e74" + integrity sha512-R+puP4tWYzQUbpIp8sX6U5oI+ZUevVOaFxXGaAN3PRXjIRC38oKTVWzj/G6GdziVFzN6rDn+JsYPmiRMYo1sYg== dependencies: - "@storybook/client-logger" "8.1.2" - "@storybook/core-events" "8.1.2" + "@storybook/client-logger" "8.1.5" + "@storybook/core-events" "8.1.5" "@storybook/global" "^5.0.0" telejson "^7.2.0" tiny-invariant "^1.3.1" -"@storybook/cli@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/cli/-/cli-8.1.2.tgz#ee6e5d04b5f3cd8c301115cbf0c989c06f7cda1f" - integrity sha512-gynn8LjmqJmAfaMNAf8h0V+RFdTo10+x4x6yiCy/eI/Yct0B5ke9MPzdKHocMhBLv9EDNz1ealwjsvR7dmjy2w== +"@storybook/cli@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/cli/-/cli-8.1.5.tgz#17bc7014100b1bb227433ecec71137115c5937ff" + integrity sha512-VEYluZEMleNEnD5wTD90KTh03pwjvQwEEmzHAJQJdLbWTAcgBxZ3Gb45nbUPauSqBL+HdJx0QXF8Ielk+iBttw== dependencies: "@babel/core" "^7.24.4" "@babel/types" "^7.24.0" "@ndelangen/get-tarball" "^3.0.7" - "@storybook/codemod" "8.1.2" - "@storybook/core-common" "8.1.2" - "@storybook/core-events" "8.1.2" - "@storybook/core-server" "8.1.2" - "@storybook/csf-tools" "8.1.2" - "@storybook/node-logger" "8.1.2" - "@storybook/telemetry" "8.1.2" - "@storybook/types" "8.1.2" + "@storybook/codemod" "8.1.5" + "@storybook/core-common" "8.1.5" + "@storybook/core-events" "8.1.5" + "@storybook/core-server" "8.1.5" + "@storybook/csf-tools" "8.1.5" + "@storybook/node-logger" "8.1.5" + "@storybook/telemetry" "8.1.5" + "@storybook/types" "8.1.5" "@types/semver" "^7.3.4" "@yarnpkg/fslib" "2.10.3" "@yarnpkg/libzip" "2.3.0" @@ -3357,29 +3177,29 @@ read-pkg-up "^7.0.1" semver "^7.3.7" strip-json-comments "^3.0.1" - tempy "^1.0.1" + tempy "^3.1.0" tiny-invariant "^1.3.1" ts-dedent "^2.0.0" -"@storybook/client-logger@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-8.1.2.tgz#32bca1651892b18edd627d2835f44b50bee0d19c" - integrity sha512-2kiXh0CE2IJpV++r0sGknMVMjAiT/tQe16FlGHOh52XppUz7slQVy/W/nPhVKvcbdThSQd0kYFR9r3XmAT1LSQ== +"@storybook/client-logger@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-8.1.5.tgz#aa4a6ce4ca46fdfe12539e571f9059a479c8ae43" + integrity sha512-zd+aENXnOHsxBATppELmhw/UywLzCxQjz/8i/xkUjeTRB4Ggp0hJlOUdJUEdIJz631ydyytfvM70ktBj9gMl1w== dependencies: "@storybook/global" "^5.0.0" -"@storybook/codemod@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/codemod/-/codemod-8.1.2.tgz#7a6055d41a38d226ae6c6b5dc2542aba4b5dbf13" - integrity sha512-kcJjq5BBxxUBVsOxBnwwkqVPm/zdWMbr5VOjmZinqPtPA1slFQxbfRenD77l6icT15InZqbRXwkp1+G8oYDVGA== +"@storybook/codemod@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/codemod/-/codemod-8.1.5.tgz#ee8e69834ec9cf3f543f5ba0ed5afdd9c26b57dc" + integrity sha512-eGoYozT2XPfsIFrzm4cJo9tRTX0yuK1y4uTYmKvnomezHu5kiY8qo2fUzQa5DHxiAzRDTpGlQTzb0PsxHOxYoA== dependencies: "@babel/core" "^7.24.4" "@babel/preset-env" "^7.24.4" "@babel/types" "^7.24.0" "@storybook/csf" "^0.1.7" - "@storybook/csf-tools" "8.1.2" - "@storybook/node-logger" "8.1.2" - "@storybook/types" "8.1.2" + "@storybook/csf-tools" "8.1.5" + "@storybook/node-logger" "8.1.5" + "@storybook/types" "8.1.5" "@types/cross-spawn" "^6.0.2" cross-spawn "^7.0.3" globby "^14.0.1" @@ -3389,31 +3209,31 @@ recast "^0.23.5" tiny-invariant "^1.3.1" -"@storybook/components@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/components/-/components-8.1.2.tgz#e4bd892206f8fc22ac5321818afe5ac95ad954e5" - integrity sha512-jv/oSyD9usXeVa278NOD8sGe4J2HM1O7JBvCwQdjw/QPAe/TeDNei80qwiQDYuU6z3R5ELXbLt1MhxZxQXEVAQ== +"@storybook/components@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/components/-/components-8.1.5.tgz#43504e04525b94ed750bf941016b5c68d5a12c9e" + integrity sha512-IxoT2pH7V98gF0zDAMUuq9sUZPg0vvQ9Y+A13HeYHvaY25XdesXVMbdzEd6SpeLYmfPykMPIAEcADfqeM6eXfA== dependencies: "@radix-ui/react-dialog" "^1.0.5" "@radix-ui/react-slot" "^1.0.2" - "@storybook/client-logger" "8.1.2" + "@storybook/client-logger" "8.1.5" "@storybook/csf" "^0.1.7" "@storybook/global" "^5.0.0" "@storybook/icons" "^1.2.5" - "@storybook/theming" "8.1.2" - "@storybook/types" "8.1.2" + "@storybook/theming" "8.1.5" + "@storybook/types" "8.1.5" memoizerific "^1.11.3" util-deprecate "^1.0.2" -"@storybook/core-common@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-8.1.2.tgz#5bc286fcf2bb06ebfa6b32cbc6263f9aa0604c49" - integrity sha512-Wt9xMXVSXDA7Kzk6II6SISvRVHeAtOVuJOu3VnpsFFd3tBFiXMA7jD25rHVMB3VlZDT8iaoAgdZDYnq4xYTRJg== +"@storybook/core-common@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-8.1.5.tgz#273ad15cb35705e46f1806d0cc733a1a62d79cd5" + integrity sha512-1QDOT6KPZ9KV7Gs1yyqzvSwGBmNSUB33gckUldSBF4aqP+tZ7W5JIQ6/YTtp3V02sEokZGdL9Ud4LczQxTgy3A== dependencies: - "@storybook/core-events" "8.1.2" - "@storybook/csf-tools" "8.1.2" - "@storybook/node-logger" "8.1.2" - "@storybook/types" "8.1.2" + "@storybook/core-events" "8.1.5" + "@storybook/csf-tools" "8.1.5" + "@storybook/node-logger" "8.1.5" + "@storybook/types" "8.1.5" "@yarnpkg/fslib" "2.10.3" "@yarnpkg/libzip" "2.3.0" chalk "^4.1.0" @@ -3435,42 +3255,42 @@ pretty-hrtime "^1.0.3" resolve-from "^5.0.0" semver "^7.3.7" - tempy "^1.0.1" + tempy "^3.1.0" tiny-invariant "^1.3.1" ts-dedent "^2.0.0" util "^0.12.4" -"@storybook/core-events@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-8.1.2.tgz#60830a530bc63fe40012a6934ea18658067f3f56" - integrity sha512-GKsvo/eeEQYDEhAw5YkUIZHYNurAJjzW3+uzThUuC1r0CGcfE+twJVfQXynAyOgL6hFdqy7879/3augf3v3cJQ== +"@storybook/core-events@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-8.1.5.tgz#d921984e12b27aaaa623499a7ac0c3eea5e96264" + integrity sha512-fgwbrHoLtSX6kfmamTGJqD+KfuEgun8cc4mWKZK094ByaqbSjhnOyeYO1sfVk8qst7QTFlOfhLAUe4cz1z149A== dependencies: "@storybook/csf" "^0.1.7" ts-dedent "^2.0.0" -"@storybook/core-server@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-8.1.2.tgz#389718a501066b9b07b5bcc5dceafcf08c59700c" - integrity sha512-I2DR6rWv3BYCu1of2rAo8fcgqsW2NdKRm3omPqW7VRtICYiV9EnOUqV8wbEmvWtFJkuweH5o/P3rSKre2j2scw== +"@storybook/core-server@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-8.1.5.tgz#24a6054149f450c795d68c23790613c13f041881" + integrity sha512-y16W2sg5KIHG6qgbd+a0nBUYHAgiUpPDFF7cdcIpbeOIoqFn+6ECp93MVefukumiSj3sQiJFU/tSm2A8apGltw== dependencies: "@aw-web-design/x-default-browser" "1.4.126" "@babel/core" "^7.24.4" "@babel/parser" "^7.24.4" "@discoveryjs/json-ext" "^0.5.3" - "@storybook/builder-manager" "8.1.2" - "@storybook/channels" "8.1.2" - "@storybook/core-common" "8.1.2" - "@storybook/core-events" "8.1.2" + "@storybook/builder-manager" "8.1.5" + "@storybook/channels" "8.1.5" + "@storybook/core-common" "8.1.5" + "@storybook/core-events" "8.1.5" "@storybook/csf" "^0.1.7" - "@storybook/csf-tools" "8.1.2" + "@storybook/csf-tools" "8.1.5" "@storybook/docs-mdx" "3.1.0-next.0" "@storybook/global" "^5.0.0" - "@storybook/manager" "8.1.2" - "@storybook/manager-api" "8.1.2" - "@storybook/node-logger" "8.1.2" - "@storybook/preview-api" "8.1.2" - "@storybook/telemetry" "8.1.2" - "@storybook/types" "8.1.2" + "@storybook/manager" "8.1.5" + "@storybook/manager-api" "8.1.5" + "@storybook/node-logger" "8.1.5" + "@storybook/preview-api" "8.1.5" + "@storybook/telemetry" "8.1.5" + "@storybook/types" "8.1.5" "@types/detect-port" "^1.3.0" "@types/diff" "^5.0.9" "@types/node" "^18.0.0" @@ -3500,44 +3320,44 @@ watchpack "^2.2.0" ws "^8.2.3" -"@storybook/core-webpack@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/core-webpack/-/core-webpack-8.1.2.tgz#c9891598885eddd5c9b0945bdf2be0f857aea7be" - integrity sha512-VZBqqTbz9XpCvmYjfRWfCQfa+QDWPV7rjs0dEFiKklB0HkUgAsmqnRBlWHsWUXIJWhdL9IvxIU0NgA+v161zIQ== +"@storybook/core-webpack@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/core-webpack/-/core-webpack-8.1.5.tgz#2dcdb0c7d9d3549aca925ccd4aebb09aea94a2c4" + integrity sha512-yXixldqg6gGT0OGWuWd52YZycgTrqiPlVHsi91SPtQJSaj3YRS2cM/Giq+gPTE0Zb9+Izq8QEnkyr8B4MfvGbQ== dependencies: - "@storybook/core-common" "8.1.2" - "@storybook/node-logger" "8.1.2" - "@storybook/types" "8.1.2" + "@storybook/core-common" "8.1.5" + "@storybook/node-logger" "8.1.5" + "@storybook/types" "8.1.5" "@types/node" "^18.0.0" ts-dedent "^2.0.0" -"@storybook/csf-plugin@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/csf-plugin/-/csf-plugin-8.1.2.tgz#b702dd831080086acafe8e1bb5635ce80d01bb7e" - integrity sha512-MSWt4/bypBjfbPqna7FvIMLuvghmCfLkHMRFzZC5stUcEqPQavpTyxw8Kw0xFMTLaiBlDgi58EzDlGAXYKVZVw== +"@storybook/csf-plugin@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/csf-plugin/-/csf-plugin-8.1.5.tgz#38a2a04c8010bde0eede907b1bf323e23c16f2c8" + integrity sha512-p6imdhlcm2iEeCU+3BDDR1fuw+u9sOQDlQQbTLYhBDvjy3lydp3W0erWo5aUANhQRU2uobZf4wZ52MLrENt+dQ== dependencies: - "@storybook/csf-tools" "8.1.2" + "@storybook/csf-tools" "8.1.5" unplugin "^1.3.1" -"@storybook/csf-tools@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/csf-tools/-/csf-tools-8.1.2.tgz#dd15fd544251c73c6879eb7da41de3ea3f49254c" - integrity sha512-IGK6wx8qBuAmnPii2iSJ+ry3mGwd4iksHYypeKfrrkbmzKFG2Qh8QbytPrJz0FkTHI22kXPoQW2Ar9b0PyeqIA== +"@storybook/csf-tools@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/csf-tools/-/csf-tools-8.1.5.tgz#779a8158cf3ab40da1a68e3d1d3499cc86494ccc" + integrity sha512-jOfUo0arlaG4LlsdWaRfZCS0I1FhUnkf06ThzRBrrp8mFAPtOpf9iW16J3fYMS5vAdE/v+Z1RxuTRich4/JGdQ== dependencies: "@babel/generator" "^7.24.4" "@babel/parser" "^7.24.4" "@babel/traverse" "^7.24.1" "@babel/types" "^7.24.0" "@storybook/csf" "^0.1.7" - "@storybook/types" "8.1.2" + "@storybook/types" "8.1.5" fs-extra "^11.1.0" recast "^0.23.5" ts-dedent "^2.0.0" "@storybook/csf@^0.1.7": - version "0.1.7" - resolved "https://registry.yarnpkg.com/@storybook/csf/-/csf-0.1.7.tgz#dcc6c16a353bc09c8c619ba1a23ba93b2aab0b9d" - integrity sha512-53JeLZBibjQxi0Ep+/AJTfxlofJlxy1jXcSKENlnKxHjWEYyHQCumMP5yTFjf7vhNnMjEpV3zx6t23ssFiGRyw== + version "0.1.8" + resolved "https://registry.yarnpkg.com/@storybook/csf/-/csf-0.1.8.tgz#63a83dc493c462d84e0f333e3f3264d319bec716" + integrity sha512-Ntab9o7LjBCbFIao5l42itFiaSh/Qu+l16l/r/9qmV9LnYZkO+JQ7tzhdlwpgJfhs+B5xeejpdAtftDRyXNajw== dependencies: type-fest "^2.19.0" @@ -3546,15 +3366,15 @@ resolved "https://registry.yarnpkg.com/@storybook/docs-mdx/-/docs-mdx-3.1.0-next.0.tgz#9567c6eb621110dcf6554923a975238953d06305" integrity sha512-t4syFIeSyufieNovZbLruPt2DmRKpbwL4fERCZ1MifWDRIORCKLc4NCEHy+IqvIqd71/SJV2k4B51nF7vlJfmQ== -"@storybook/docs-tools@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/docs-tools/-/docs-tools-8.1.2.tgz#1b25eba9cbadd0ef0a1409c87e12e2458ecf4b61" - integrity sha512-FWhs/ZN0Fc4Qke5zdA6l0UJhIP5kdrrRjEXe0IppP7UIvtRUgrIhiS4Kz/Re110cOkjO5/K0Hr6yQPLSCPnPKA== +"@storybook/docs-tools@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/docs-tools/-/docs-tools-8.1.5.tgz#fd6fa4db0aa6e08cbf60bc1d41bd4d0e74139e74" + integrity sha512-zlHv8fi1Bw8RbjkGGBJoO/RbM41bwxU1kV76TPQUyqQmzqPRsHi3zt+8bdddQLNrC6rhTF+Cj3yEdPfTZrB0aA== dependencies: - "@storybook/core-common" "8.1.2" - "@storybook/core-events" "8.1.2" - "@storybook/preview-api" "8.1.2" - "@storybook/types" "8.1.2" + "@storybook/core-common" "8.1.5" + "@storybook/core-events" "8.1.5" + "@storybook/preview-api" "8.1.5" + "@storybook/types" "8.1.5" "@types/doctrine" "^0.0.3" assert "^2.1.0" doctrine "^3.0.0" @@ -3570,33 +3390,33 @@ resolved "https://registry.yarnpkg.com/@storybook/icons/-/icons-1.2.9.tgz#bb4a51a79e186b62e2dd0e04928b8617ac573838" integrity sha512-cOmylsz25SYXaJL/gvTk/dl3pyk7yBFRfeXTsHvTA3dfhoU/LWSq0NKL9nM7WBasJyn6XPSGnLS4RtKXLw5EUg== -"@storybook/instrumenter@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/instrumenter/-/instrumenter-8.1.2.tgz#ae7d01ba2c09fa21e6a7a225734b5d19c8dac639" - integrity sha512-TWldk4QTtraVaCizQrsnf4y23IPz1OqUOvzY5bA8Aje9FzzX7YHo9yEGEvd4Nx6c2GaBnInn/gWmqucmWngJDg== +"@storybook/instrumenter@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/instrumenter/-/instrumenter-8.1.5.tgz#7e2f4d28486b226ec483f89da206e418643315ee" + integrity sha512-pyOg0YeL06bIFw8J3y0E1xyaJEVX5dtyvFZ31xi7jcElhsO/uPTbrJzSfMFtv3kDXU3hKDpeI2pbxpkFUVSvsQ== dependencies: - "@storybook/channels" "8.1.2" - "@storybook/client-logger" "8.1.2" - "@storybook/core-events" "8.1.2" + "@storybook/channels" "8.1.5" + "@storybook/client-logger" "8.1.5" + "@storybook/core-events" "8.1.5" "@storybook/global" "^5.0.0" - "@storybook/preview-api" "8.1.2" + "@storybook/preview-api" "8.1.5" "@vitest/utils" "^1.3.1" util "^0.12.4" -"@storybook/manager-api@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/manager-api/-/manager-api-8.1.2.tgz#f090cf9d371ee2b3592fc7090d5ccf998e973e1f" - integrity sha512-6Ge7EEx94YJm3HcfjkDfRPMnr5qVdF2RzhvyflJapBxMV0X44mkGPUWyzft3cf+vUCQlXXrmtBeZauB6JTJuiA== +"@storybook/manager-api@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/manager-api/-/manager-api-8.1.5.tgz#1f1a8875cbc19fad5435f670943207158dc76551" + integrity sha512-iVP7FOKDf9L7zWCb8C2XeZjWSILS3hHeNwILvd9YSX9dg9du41kJYahsAHxDCR/jp/gv0ZM/V0vuHzi+naVPkQ== dependencies: - "@storybook/channels" "8.1.2" - "@storybook/client-logger" "8.1.2" - "@storybook/core-events" "8.1.2" + "@storybook/channels" "8.1.5" + "@storybook/client-logger" "8.1.5" + "@storybook/core-events" "8.1.5" "@storybook/csf" "^0.1.7" "@storybook/global" "^5.0.0" "@storybook/icons" "^1.2.5" - "@storybook/router" "8.1.2" - "@storybook/theming" "8.1.2" - "@storybook/types" "8.1.2" + "@storybook/router" "8.1.5" + "@storybook/theming" "8.1.5" + "@storybook/types" "8.1.5" dequal "^2.0.2" lodash "^4.17.21" memoizerific "^1.11.3" @@ -3604,25 +3424,25 @@ telejson "^7.2.0" ts-dedent "^2.0.0" -"@storybook/manager@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/manager/-/manager-8.1.2.tgz#8dd31513f1e42ded4ef82aa404f097f2249f0a02" - integrity sha512-RMAyvXkKId3C1ryYjcp8aSajTdtFc1+B+HvrehszrCWklj7H7sBJ5ZQFhw0WASDwyF/fzEnR7epb2kDjIKeCHA== - -"@storybook/node-logger@8.1.2", "@storybook/node-logger@^8.0.0-alpha.10": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-8.1.2.tgz#47309d08022554a6c987f7c8dae77a804a3d6f49" - integrity sha512-qdCzcDKsRiEqy7FeD0p0ZvIMqaXWn+GY0q0VkrGGhSwPJ3cxyIuyA+GA9FkXUMzxVJKgR7Gh9dOlTEaCe5ZjfA== - -"@storybook/preset-react-webpack@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/preset-react-webpack/-/preset-react-webpack-8.1.2.tgz#3d325e4ba264dc520726508ca6708af8d240e6b0" - integrity sha512-6khGxusOxGdfwTxIfIOPmFcYam27K1rFBB1ux4mfRWlc2pGs1AJVFBZkXTYumWBCpT74qd7EV0o9PoWX7eNlIw== - dependencies: - "@storybook/core-webpack" "8.1.2" - "@storybook/docs-tools" "8.1.2" - "@storybook/node-logger" "8.1.2" - "@storybook/react" "8.1.2" +"@storybook/manager@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/manager/-/manager-8.1.5.tgz#56cf0b93485c2d12ca71d4e3c90ca9bf519d3126" + integrity sha512-qMYwD1cXW0hJ3pMmdMlbsqktVBlsjsqwMH5PBzAN4FoWiCQ/yHeAnDXRUgFFaLcORS72h9H/cQuJ+p//RdeURg== + +"@storybook/node-logger@8.1.5", "@storybook/node-logger@^8.0.0-alpha.10": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-8.1.5.tgz#c0c064b3ebdc0b3c97b7f449ed96ab59c484cab6" + integrity sha512-9qwPX/uGhdHaVjeVUSwJUSbKX7g9goyhGYdKVuCEyl7vHR9Kp7Zkag2sEHmVdd9ixTea3jk2GZQEbnBDNQNGnw== + +"@storybook/preset-react-webpack@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/preset-react-webpack/-/preset-react-webpack-8.1.5.tgz#25d9f65b2ef7a5217bc7994fe43f27d456c327e8" + integrity sha512-OiizVxDT5b7dORO8IYtNjQnrke+vgRgRPw/JSfIzWoYakDCFgui86BZ4Zx/1eecztXtQOem4bOfc7GLep5VkpA== + dependencies: + "@storybook/core-webpack" "8.1.5" + "@storybook/docs-tools" "8.1.5" + "@storybook/node-logger" "8.1.5" + "@storybook/react" "8.1.5" "@storybook/react-docgen-typescript-plugin" "1.0.6--canary.9.0c3f3b7.0" "@types/node" "^18.0.0" "@types/semver" "^7.3.4" @@ -3635,17 +3455,17 @@ tsconfig-paths "^4.2.0" webpack "5" -"@storybook/preview-api@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/preview-api/-/preview-api-8.1.2.tgz#614de0a39a775c32894500bda7a5027d6a44441f" - integrity sha512-94xFDtyz8l2TdyHQiew2RZ8YLkHAgD/qGgosrLUTX9w+Pe0stU+SyiyinMVof/cac8lPzQoK60fwTABHkv8Gpg== +"@storybook/preview-api@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/preview-api/-/preview-api-8.1.5.tgz#2577f95022922dd3e9a75445756d21591e58de5f" + integrity sha512-pv0aT5WbnSYR7KWQgy3jLfuBM0ocYG6GTcmZLREW5554oiBPHhzNFv+ZrBI47RzbrbFxq1h5dj4v8lkEcKIrbA== dependencies: - "@storybook/channels" "8.1.2" - "@storybook/client-logger" "8.1.2" - "@storybook/core-events" "8.1.2" + "@storybook/channels" "8.1.5" + "@storybook/client-logger" "8.1.5" + "@storybook/core-events" "8.1.5" "@storybook/csf" "^0.1.7" "@storybook/global" "^5.0.0" - "@storybook/types" "8.1.2" + "@storybook/types" "8.1.5" "@types/qs" "^6.9.5" dequal "^2.0.2" lodash "^4.17.21" @@ -3655,10 +3475,10 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/preview@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/preview/-/preview-8.1.2.tgz#c370adaa4589b3057dfa2ba3dd278c1bab32b47c" - integrity sha512-8VjDPF2sTU7iYigISV47ug90LI1u9PLCMY71hWqHAyrAMfPd6GIz0bO5scfZ1eOwN7jIgFZVFoSBYq8kmA4iiQ== +"@storybook/preview@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/preview/-/preview-8.1.5.tgz#3d1e91d3596b0e0736da80ba6f8a5ffb323f7d18" + integrity sha512-8qNzK/5fCjfWcup5w3UxJXMAUp4+iOdh+vO+vDIJWSbPXRPtuarSM/tv/12N7hz/zvCpGLGBql0BE+oyC0bmhw== "@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0": version "1.0.6--canary.9.0c3f3b7.0" @@ -3673,33 +3493,33 @@ react-docgen-typescript "^2.2.2" tslib "^2.0.0" -"@storybook/react-dom-shim@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/react-dom-shim/-/react-dom-shim-8.1.2.tgz#654c0cab71940ba6613e3b96ca4980e24b956ce1" - integrity sha512-Djcq9MBYfefwGdk3k3jmZrLxSRM0y0RQQ0M5c8zMkM6Hbqo0MKd/UXnAKoQ8TadNnNUNaVpkanRv7KWlF0FFUQ== +"@storybook/react-dom-shim@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/react-dom-shim/-/react-dom-shim-8.1.5.tgz#5f97c0b278d784c4eb736dc463425b81c7e75bb8" + integrity sha512-eyHSngIBHeFT4vVkQTN2+c/mSKCPrb8uPpWbrc3ihGBKvL/656erWNmiUVnY3zuQvCBPz2q2Vy3v2Pr+nvfOTw== "@storybook/react-webpack5@^8.1.1": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/react-webpack5/-/react-webpack5-8.1.2.tgz#fcebd1e7fb2766e1760c67716f0347358e35710e" - integrity sha512-VCvLFjnfZpIFRg5NoOyyR+fGmBc7tJrJ2pag3x4TbzzOGG6plx6dZ6YaB4EgLz5/kMs2dUOnONlcFwLrAS7EoQ== - dependencies: - "@storybook/builder-webpack5" "8.1.2" - "@storybook/preset-react-webpack" "8.1.2" - "@storybook/react" "8.1.2" - "@storybook/types" "8.1.2" + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/react-webpack5/-/react-webpack5-8.1.5.tgz#2dc4fe138aa7bc476a76b24527af3d352d68796d" + integrity sha512-XWHfSco08KmwjBbxFxi1WuG5bMipPkdJEUGyJqqqcVAP6BPFeYsO0PPai9CRJHlFSdQ3MGyUdY/Wy42JmRUocg== + dependencies: + "@storybook/builder-webpack5" "8.1.5" + "@storybook/preset-react-webpack" "8.1.5" + "@storybook/react" "8.1.5" + "@storybook/types" "8.1.5" "@types/node" "^18.0.0" -"@storybook/react@8.1.2", "@storybook/react@^8.1.1": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/react/-/react-8.1.2.tgz#082255f0071f7ac803f0115ef4437bda73dd6cae" - integrity sha512-Gg/dm3GT0wsK+4TdNsbu6CLsPShfkTujlDEKRBxeGMsLsQOwIayZp67fYjSj3OE6EachMAQToNgtDweIMMXhEg== +"@storybook/react@8.1.5", "@storybook/react@^8.1.1": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/react/-/react-8.1.5.tgz#fb852039f5eb732e479912fe8bb01655d83a0a5e" + integrity sha512-Yr0Z1FQPKFnc3jI7UbNYyi5K6zoFRZlac7xzBMT4q+bUtl0g3fmYTDFisCwK8I30qE6r01EjzNvaTU75PqXkMw== dependencies: - "@storybook/client-logger" "8.1.2" - "@storybook/docs-tools" "8.1.2" + "@storybook/client-logger" "8.1.5" + "@storybook/docs-tools" "8.1.5" "@storybook/global" "^5.0.0" - "@storybook/preview-api" "8.1.2" - "@storybook/react-dom-shim" "8.1.2" - "@storybook/types" "8.1.2" + "@storybook/preview-api" "8.1.5" + "@storybook/react-dom-shim" "8.1.5" + "@storybook/types" "8.1.5" "@types/escodegen" "^0.0.6" "@types/estree" "^0.0.51" "@types/node" "^18.0.0" @@ -3716,38 +3536,38 @@ type-fest "~2.19" util-deprecate "^1.0.2" -"@storybook/router@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/router/-/router-8.1.2.tgz#875ec8d61c2cd427844578d256bc3d2141230818" - integrity sha512-Lu05lDqoIinda1z43Danxhaq9t8k5jSKHJZXEIDsiLPUkGQCW3syzSE03c7qzOAbN/gmd/cu9MkmvGW+HBnWSA== +"@storybook/router@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/router/-/router-8.1.5.tgz#e1dd831136e874df833286fd76554958af6132fa" + integrity sha512-DCwvAswlbLhQu6REPV04XNRhtPvsrRqHjMHKzjlfs+qYJWY7Egkofy05qlegqjkMDve33czfnRGBm0C16IydkA== dependencies: - "@storybook/client-logger" "8.1.2" + "@storybook/client-logger" "8.1.5" memoizerific "^1.11.3" qs "^6.10.0" -"@storybook/telemetry@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/telemetry/-/telemetry-8.1.2.tgz#549a7d813c0c501f5b882b7d040a5b183cee54f7" - integrity sha512-FoJUDtRPNM5pTvnbQOPTGBIsmW2r/XC//DtHj84B8g7V+Ww+TToMrQWWgPTQwD1epL+ihtxbAV47EUlSB9mMDw== +"@storybook/telemetry@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/telemetry/-/telemetry-8.1.5.tgz#5fa3dae7f85a5749733928acc1e7deab5e3ca1cf" + integrity sha512-QbB1Ox7oBaCvIF2TacFjPLi1XYeHxSPeZUuFXeE+tSMdvvWZzYLnXfj/oISmV6Q+X5VZfyJVMrZ2LfeW9CuFNg== dependencies: - "@storybook/client-logger" "8.1.2" - "@storybook/core-common" "8.1.2" - "@storybook/csf-tools" "8.1.2" + "@storybook/client-logger" "8.1.5" + "@storybook/core-common" "8.1.5" + "@storybook/csf-tools" "8.1.5" chalk "^4.1.0" detect-package-manager "^2.0.1" fetch-retry "^5.0.2" fs-extra "^11.1.0" read-pkg-up "^7.0.1" -"@storybook/test@8.1.2", "@storybook/test@^8.1.1": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/test/-/test-8.1.2.tgz#ead03dad1c76b2c905e223c0e864f2047e6fb5d8" - integrity sha512-wSsO7VSF9dM4NdiOu7EMQd326HpmkKzCarpIHm+FsxRemYfB6wCjbNRIrXe81PLSfyzFte8/ZvMPo0LeMefwHA== +"@storybook/test@8.1.5", "@storybook/test@^8.1.1": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/test/-/test-8.1.5.tgz#264b9c9e14fb4b97eeea20156689020795c56e67" + integrity sha512-BuxzWWS7BIJrOTuwH5WTj3nGQ+xNCvinJBQsV+MRAdH+kltgPYbntd/NBceuHmYeUrX0t8id5VUapNaG4SHw1A== dependencies: - "@storybook/client-logger" "8.1.2" - "@storybook/core-events" "8.1.2" - "@storybook/instrumenter" "8.1.2" - "@storybook/preview-api" "8.1.2" + "@storybook/client-logger" "8.1.5" + "@storybook/core-events" "8.1.5" + "@storybook/instrumenter" "8.1.5" + "@storybook/preview-api" "8.1.5" "@testing-library/dom" "^9.3.4" "@testing-library/jest-dom" "^6.4.2" "@testing-library/user-event" "^14.5.2" @@ -3755,22 +3575,22 @@ "@vitest/spy" "^1.3.1" util "^0.12.4" -"@storybook/theming@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-8.1.2.tgz#fa7ddc2eee788d8916f982ce46af2d42b47b1042" - integrity sha512-4ayCoSluaReGfj+wDa/KKwX5mEReVsjDhQ3nCusKsPQJK5zgT4Vt4CZekU7IK0cYmRPF4nMgVH3m3jmMD4x4ng== +"@storybook/theming@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-8.1.5.tgz#8eb0718907ec443cfca1b73491f5e99df65930af" + integrity sha512-E4z1t49fMbVvd/t2MSL0Ecp5zbqsU/QfWBX/eorJ+m+Xc9skkwwG5qf/FnP9x4RZ9KaX8U8+862t0eafVvf4Tw== dependencies: "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" - "@storybook/client-logger" "8.1.2" + "@storybook/client-logger" "8.1.5" "@storybook/global" "^5.0.0" memoizerific "^1.11.3" -"@storybook/types@8.1.2": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@storybook/types/-/types-8.1.2.tgz#7924596d4dfc6c2c156c52af0906856da0a278fd" - integrity sha512-8P3rB/6UxHlui0/Gdh2/qQvyLy6efJxVUt0g7iANQorqdYMxRNS/OtxNjI8rxFB3sIvl4owG7iYh3j2RCCEqaQ== +"@storybook/types@8.1.5": + version "8.1.5" + resolved "https://registry.yarnpkg.com/@storybook/types/-/types-8.1.5.tgz#627cac55e8034deed4b763327ff938c84c541a05" + integrity sha512-/PfAZh1xtXN2MvAZZKpiL/nPkC3bZj8BQ7P7z5a/aQarP+y7qdXuoitYQ6oOH3rkaiYywmkWzA/y4iW70KXLKg== dependencies: - "@storybook/channels" "8.1.2" + "@storybook/channels" "8.1.5" "@types/express" "^4.7.0" file-system-cache "2.3.0" @@ -3834,7 +3654,7 @@ resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.5.7.tgz#f84641393b5223450d00d97bfff877b8b69d7c9b" integrity sha512-tp43WfJLCsKLQKBmjmY/0vv1slVywR5Q4qKjF5OIY8QijaEW7/8VwPyUyVoJZEnDgv9jKtUTG5PzqtIYPZGnyg== -"@swc/core@^1.3.102": +"@swc/core@1.5.7": version "1.5.7" resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.5.7.tgz#e1db7b9887d5f34eb4a3256a738d0c5f1b018c33" integrity sha512-U4qJRBefIJNJDRCCiVtkfa/hpiZ7w0R6kASea+/KLp+vkus3zcLSB8Ub8SvKgTIxjWpwsKcZlPf5nrv4ls46SQ== @@ -4280,9 +4100,9 @@ integrity sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg== "@types/emscripten@^1.39.6": - version "1.39.12" - resolved "https://registry.yarnpkg.com/@types/emscripten/-/emscripten-1.39.12.tgz#e43b4fdd4b389861897d6cbb9665532f3afd5abd" - integrity sha512-AQImDBgudQfMqUBfrjZYilRxoHDzTBp+ejh+g1fY67eSMalwIKtBXofjpyI0JBgNpHGzxeGAR2QDya0wxW9zbA== + version "1.39.13" + resolved "https://registry.yarnpkg.com/@types/emscripten/-/emscripten-1.39.13.tgz#afeb1648648dc096efe57983e20387627306e2aa" + integrity sha512-cFq+fO/isvhvmuP/+Sl4K4jtU6E23DoivtbO4r50e3odaxAiVdbfSYRDdJ4gCdxx+3aRjhphS5ZMwIH4hFy/Cw== "@types/escodegen@^0.0.6": version "0.0.6" @@ -4321,9 +4141,9 @@ integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== "@types/express-serve-static-core@^4.17.33": - version "4.19.1" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.1.tgz#57d34698bb580720fd6e3c360d4b2fdef579b979" - integrity sha512-ej0phymbFLoCB26dbbq5PGScsf2JAJ4IJHjG10LalgUV36XKTmA4GdA+PVllKvRk0sEKt64X8975qFnkSi0hqA== + version "4.19.3" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.3.tgz#e469a13e4186c9e1c0418fb17be8bc8ff1b19a7a" + integrity sha512-KOzM7MhcBFlmnlr/fzISFF5vGWVSvN6fTd4T+ExOt08bA/dA5kpSzY52nMsI1KDFmUREpJelPYyuslLRSjjgCg== dependencies: "@types/node" "*" "@types/qs" "*" @@ -4497,9 +4317,9 @@ "@types/node" "*" "@types/node@*", "@types/node@^20.5.2": - version "20.12.12" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.12.tgz#7cbecdf902085cec634fdb362172dfe12b8f2050" - integrity sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw== + version "20.14.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.1.tgz#2434dbcb1f039e31f2c0e9969da93f52cf6348f3" + integrity sha512-T2MzSGEu+ysB/FkWfqmhV3PLyQlowdptmmgD20C6QxsS8Fmv5SjpZ1ayXaEC0S21/h5UJ9iA6W/5vSNU5l00OA== dependencies: undici-types "~5.26.4" @@ -4524,9 +4344,9 @@ integrity sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA== "@types/node@^18.0.0": - version "18.19.33" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.33.tgz#98cd286a1b8a5e11aa06623210240bcc28e95c48" - integrity sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A== + version "18.19.34" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.34.tgz#c3fae2bbbdb94b4a52fe2d229d0dccce02ef3d27" + integrity sha512-eXF4pfBNV5DAMKGbI02NnDtWrQ40hAN558/2vvS4gMpMIxaf6JmD7YjnZbq0Q9TDSSkKBamime8ewRoomHdt4g== dependencies: undici-types "~5.26.4" @@ -4719,15 +4539,15 @@ tsutils "^3.21.0" "@typescript-eslint/eslint-plugin@^7.1.1": - version "7.10.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.10.0.tgz#07854a236f107bb45cbf4f62b89474cbea617f50" - integrity sha512-PzCr+a/KAef5ZawX7nbyNwBDtM1HdLIT53aSA2DDlxmxMngZ43O8SIePOeX8H5S+FHXeI6t97mTt/dDdzY4Fyw== + version "7.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.12.0.tgz#f87a32e8972b8a60024f2f8f12205e7c8108bc41" + integrity sha512-7F91fcbuDf/d3S8o21+r3ZncGIke/+eWk0EpO21LXhDfLahriZF9CGj4fbAetEjlaBdjdSm9a6VeXbpbT6Z40Q== dependencies: "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "7.10.0" - "@typescript-eslint/type-utils" "7.10.0" - "@typescript-eslint/utils" "7.10.0" - "@typescript-eslint/visitor-keys" "7.10.0" + "@typescript-eslint/scope-manager" "7.12.0" + "@typescript-eslint/type-utils" "7.12.0" + "@typescript-eslint/utils" "7.12.0" + "@typescript-eslint/visitor-keys" "7.12.0" graphemer "^1.4.0" ignore "^5.3.1" natural-compare "^1.4.0" @@ -4745,14 +4565,14 @@ debug "^4.3.4" "@typescript-eslint/parser@^7.1.1": - version "7.10.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.10.0.tgz#e6ac1cba7bc0400a4459e7eb5b23115bd71accfb" - integrity sha512-2EjZMA0LUW5V5tGQiaa2Gys+nKdfrn2xiTIBLR4fxmPmVSvgPcKNW+AE/ln9k0A4zDUti0J/GZXMDupQoI+e1w== - dependencies: - "@typescript-eslint/scope-manager" "7.10.0" - "@typescript-eslint/types" "7.10.0" - "@typescript-eslint/typescript-estree" "7.10.0" - "@typescript-eslint/visitor-keys" "7.10.0" + version "7.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.12.0.tgz#8761df3345528b35049353db80010b385719b1c3" + integrity sha512-dm/J2UDY3oV3TKius2OUZIFHsomQmpHtsV0FTh1WO8EKgHLQ1QCADUqscPgTpU+ih1e21FQSRjXckHn3txn6kQ== + dependencies: + "@typescript-eslint/scope-manager" "7.12.0" + "@typescript-eslint/types" "7.12.0" + "@typescript-eslint/typescript-estree" "7.12.0" + "@typescript-eslint/visitor-keys" "7.12.0" debug "^4.3.4" "@typescript-eslint/scope-manager@5.62.0": @@ -4763,13 +4583,13 @@ "@typescript-eslint/types" "5.62.0" "@typescript-eslint/visitor-keys" "5.62.0" -"@typescript-eslint/scope-manager@7.10.0": - version "7.10.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.10.0.tgz#054a27b1090199337a39cf755f83d9f2ce26546b" - integrity sha512-7L01/K8W/VGl7noe2mgH0K7BE29Sq6KAbVmxurj8GGaPDZXPr8EEQ2seOeAS+mEV9DnzxBQB6ax6qQQ5C6P4xg== +"@typescript-eslint/scope-manager@7.12.0": + version "7.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.12.0.tgz#259c014362de72dd34f995efe6bd8dda486adf58" + integrity sha512-itF1pTnN6F3unPak+kutH9raIkL3lhH1YRPGgt7QQOh43DQKVJXmWkpb+vpc/TiDHs6RSd9CTbDsc/Y+Ygq7kg== dependencies: - "@typescript-eslint/types" "7.10.0" - "@typescript-eslint/visitor-keys" "7.10.0" + "@typescript-eslint/types" "7.12.0" + "@typescript-eslint/visitor-keys" "7.12.0" "@typescript-eslint/scope-manager@7.2.0": version "7.2.0" @@ -4789,13 +4609,13 @@ debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/type-utils@7.10.0": - version "7.10.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.10.0.tgz#8a75accce851d0a331aa9331268ef64e9b300270" - integrity sha512-D7tS4WDkJWrVkuzgm90qYw9RdgBcrWmbbRkrLA4d7Pg3w0ttVGDsvYGV19SH8gPR5L7OtcN5J1hTtyenO9xE9g== +"@typescript-eslint/type-utils@7.12.0": + version "7.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.12.0.tgz#9dfaaa1972952f395ec5be4f5bbfc4d3cdc63908" + integrity sha512-lib96tyRtMhLxwauDWUp/uW3FMhLA6D0rJ8T7HmH7x23Gk1Gwwu8UZ94NMXBvOELn6flSPiBrCKlehkiXyaqwA== dependencies: - "@typescript-eslint/typescript-estree" "7.10.0" - "@typescript-eslint/utils" "7.10.0" + "@typescript-eslint/typescript-estree" "7.12.0" + "@typescript-eslint/utils" "7.12.0" debug "^4.3.4" ts-api-utils "^1.3.0" @@ -4804,10 +4624,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== -"@typescript-eslint/types@7.10.0": - version "7.10.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.10.0.tgz#da92309c97932a3a033762fd5faa8b067de84e3b" - integrity sha512-7fNj+Ya35aNyhuqrA1E/VayQX9Elwr8NKZ4WueClR3KwJ7Xx9jcCdOrLW04h51de/+gNbyFMs+IDxh5xIwfbNg== +"@typescript-eslint/types@7.12.0": + version "7.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.12.0.tgz#bf208f971a8da1e7524a5d9ae2b5f15192a37981" + integrity sha512-o+0Te6eWp2ppKY3mLCU+YA9pVJxhUJE15FV7kxuD9jgwIAa+w/ycGJBMrYDTpVGUM/tgpa9SeMOugSabWFq7bg== "@typescript-eslint/types@7.2.0": version "7.2.0" @@ -4827,13 +4647,13 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@7.10.0": - version "7.10.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.10.0.tgz#6dcdc5de3149916a6a599fa89dde5c471b88b8bb" - integrity sha512-LXFnQJjL9XIcxeVfqmNj60YhatpRLt6UhdlFwAkjNc6jSUlK8zQOl1oktAP8PlWFzPQC1jny/8Bai3/HPuvN5g== +"@typescript-eslint/typescript-estree@7.12.0": + version "7.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.12.0.tgz#e6c1074f248b3db6573ab6a7c47a39c4cd498ff9" + integrity sha512-5bwqLsWBULv1h6pn7cMW5dXX/Y2amRqLaKqsASVwbBHMZSnHqE/HN4vT4fE0aFsiwxYvr98kqOWh1a8ZKXalCQ== dependencies: - "@typescript-eslint/types" "7.10.0" - "@typescript-eslint/visitor-keys" "7.10.0" + "@typescript-eslint/types" "7.12.0" + "@typescript-eslint/visitor-keys" "7.12.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" @@ -4869,15 +4689,15 @@ eslint-scope "^5.1.1" semver "^7.3.7" -"@typescript-eslint/utils@7.10.0": - version "7.10.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.10.0.tgz#8ee43e5608c9f439524eaaea8de5b358b15c51b3" - integrity sha512-olzif1Fuo8R8m/qKkzJqT7qwy16CzPRWBvERS0uvyc+DHd8AKbO4Jb7kpAvVzMmZm8TrHnI7hvjN4I05zow+tg== +"@typescript-eslint/utils@7.12.0": + version "7.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.12.0.tgz#c6e58fd7f724cdccc848f71e388ad80cbdb95dd0" + integrity sha512-Y6hhwxwDx41HNpjuYswYp6gDbkiZ8Hin9Bf5aJQn1bpTs3afYY4GX+MPYxma8jtoIV2GRwTM/UJm/2uGCVv+DQ== dependencies: "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "7.10.0" - "@typescript-eslint/types" "7.10.0" - "@typescript-eslint/typescript-estree" "7.10.0" + "@typescript-eslint/scope-manager" "7.12.0" + "@typescript-eslint/types" "7.12.0" + "@typescript-eslint/typescript-estree" "7.12.0" "@typescript-eslint/visitor-keys@5.62.0": version "5.62.0" @@ -4887,12 +4707,12 @@ "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" -"@typescript-eslint/visitor-keys@7.10.0": - version "7.10.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.10.0.tgz#2af2e91e73a75dd6b70b4486c48ae9d38a485a78" - integrity sha512-9ntIVgsi6gg6FIq9xjEO4VQJvwOqA3jaBFQJ/6TK5AvEup2+cECI6Fh7QiBxmfMHXU0V0J4RyPeOU1VDNzl9cg== +"@typescript-eslint/visitor-keys@7.12.0": + version "7.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.12.0.tgz#c053b55a996679528beeedd8e565710ce1ae1ad3" + integrity sha512-uZk7DevrQLL3vSnfFl5bj4sL75qC9D6EdjemIdbtkuUmIheWpuiiylSY01JxJE7+zGrOWDZrp1WxOuDntvKrHQ== dependencies: - "@typescript-eslint/types" "7.10.0" + "@typescript-eslint/types" "7.12.0" eslint-visitor-keys "^3.4.3" "@typescript-eslint/visitor-keys@7.2.0": @@ -5155,14 +4975,6 @@ agent-base@6: dependencies: debug "4" -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - ajv-formats@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" @@ -5193,14 +5005,14 @@ ajv@^6.12.4, ajv@^6.12.5: uri-js "^4.2.2" ajv@^8.0.0, ajv@^8.6.0, ajv@^8.9.0: - version "8.13.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.13.0.tgz#a3939eaec9fb80d217ddf0c3376948c023f28c91" - integrity sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA== + version "8.15.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.15.0.tgz#d918c661e3e820bbbc65a320e182ee56a1aa978a" + integrity sha512-15BTtQUOsSrmHCy+B4VnAiJAJxJ8IFgu6fcjFQF3jQYZ78nLSQthlFg4ehp+NLIyfvFgOlxNsjKIEhydtFPVHQ== dependencies: fast-deep-equal "^3.1.3" + fast-uri "^2.3.0" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" - uri-js "^4.4.1" ansi-escapes@^6.2.0: version "6.2.1" @@ -5308,7 +5120,7 @@ array-flatten@1.1.1: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== -array-includes@^3.1.6, array-includes@^3.1.7: +array-includes@^3.1.6, array-includes@^3.1.7, array-includes@^3.1.8: version "3.1.8" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== @@ -5337,7 +5149,7 @@ array-uniq@^1.0.1: resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" integrity sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q== -array.prototype.findlast@^1.2.4: +array.prototype.findlast@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904" integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== @@ -5392,14 +5204,14 @@ array.prototype.toreversed@^1.1.2: es-shim-unscopables "^1.0.0" array.prototype.tosorted@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz#c8c89348337e51b8a3c48a9227f9ce93ceedcba8" - integrity sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg== + version "1.1.4" + resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz#fe954678ff53034e717ea3352a03f0b0b86f7ffc" + integrity sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA== dependencies: - call-bind "^1.0.5" + call-bind "^1.0.7" define-properties "^1.2.1" - es-abstract "^1.22.3" - es-errors "^1.1.0" + es-abstract "^1.23.3" + es-errors "^1.3.0" es-shim-unscopables "^1.0.2" arraybuffer.prototype.slice@^1.0.3: @@ -5580,18 +5392,18 @@ balanced-match@^1.0.0: integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== bare-events@^2.0.0, bare-events@^2.2.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.2.2.tgz#a98a41841f98b2efe7ecc5c5468814469b018078" - integrity sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ== + version "2.3.1" + resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.3.1.tgz#5af2ee0be9578f81e3c1aa9bc3a6a2bcf22307ce" + integrity sha512-sJnSOTVESURZ61XgEleqmP255T6zTYwHPwE4r6SssIh0U9/uDvfpdoJYpVUerJJZH2fueO+CdT8ZT+OC/7aZDA== bare-fs@^2.1.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-2.3.0.tgz#0872f8e33cf291c9fd527d827154f156a298d402" - integrity sha512-TNFqa1B4N99pds2a5NYHR15o0ZpdNKbAeKTE/+G6ED/UeOavv8RY3dr/Fu99HW3zU3pXpo2kDNO8Sjsm2esfOw== + version "2.3.1" + resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-2.3.1.tgz#cdbd63dac7a552dfb2b87d18c822298d1efd213d" + integrity sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA== dependencies: bare-events "^2.0.0" bare-path "^2.0.0" - bare-stream "^1.0.0" + bare-stream "^2.0.0" bare-os@^2.1.0: version "2.3.0" @@ -5599,18 +5411,18 @@ bare-os@^2.1.0: integrity sha512-oPb8oMM1xZbhRQBngTgpcQ5gXw6kjOaRsSWsIeNyRxGed2w/ARyP7ScBYpWR1qfX2E5rS3gBw6OWcSQo+s+kUg== bare-path@^2.0.0, bare-path@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/bare-path/-/bare-path-2.1.2.tgz#7a0940d34ebe65f7e179fa61ed8d49d9dc151d67" - integrity sha512-o7KSt4prEphWUHa3QUwCxUI00R86VdjiuxmJK0iNVDHYPGo+HsDaVCnqCmPbf/MiW1ok8F4p3m8RTHlWk8K2ig== + version "2.1.3" + resolved "https://registry.yarnpkg.com/bare-path/-/bare-path-2.1.3.tgz#594104c829ef660e43b5589ec8daef7df6cedb3e" + integrity sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA== dependencies: bare-os "^2.1.0" -bare-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/bare-stream/-/bare-stream-1.0.0.tgz#25c3e56198d922187320c3f8c52d75c4051178b4" - integrity sha512-KhNUoDL40iP4gFaLSsoGE479t0jHijfYdIcxRn/XtezA2BaUD0NRf/JGRpsMq6dMNM+SrCrB0YSSo/5wBY4rOQ== +bare-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/bare-stream/-/bare-stream-2.0.1.tgz#c8608b3669c36541e2da8cffe886adec2d6d9c0a" + integrity sha512-ubLyoDqPnUf5o0kSFp709HC0WRZuxVuh4pbte5eY95Xvx5bdvz07c2JFmXBfqqe60q+9PJ8S4X5GRvmcNSKMxg== dependencies: - streamx "^2.16.1" + streamx "^2.18.0" base64-js@^1.3.1: version "1.5.1" @@ -5753,9 +5565,9 @@ bundle-require@^3.0.2: load-tsconfig "^0.2.0" bundle-require@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bundle-require/-/bundle-require-4.1.0.tgz#3d5fcd19d5160d4cbac5e95ed5a394d1ecd40ce6" - integrity sha512-FeArRFM+ziGkRViKRnSTbHZc35dgmR9yNog05Kn0+ItI59pOAISGvnnIwW1WgFZQW59IxD9QpJnUPkdIPfZuXg== + version "4.2.1" + resolved "https://registry.yarnpkg.com/bundle-require/-/bundle-require-4.2.1.tgz#4c450a5807381d20ade987bde8ac391544257919" + integrity sha512-7Q/6vkyYAwOmQNRw75x+4yRtZCZJXUDmHHlFdkiV0wgv/reNjtJwpu1jPJ0w2kbEpIM0uoKI3S4/f39dU7AjSA== dependencies: load-tsconfig "^0.2.3" @@ -5811,9 +5623,9 @@ camelcase-css@^2.0.1: integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001599: - version "1.0.30001621" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001621.tgz#4adcb443c8b9c8303e04498318f987616b8fea2e" - integrity sha512-+NLXZiviFFKX0fk8Piwv3PfLPGtRqJeq2TiNoUff/qB5KJgwecJTvCXDpmlyP/eCI/GUEmp/h/y5j0yckiiZrA== + version "1.0.30001627" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001627.tgz#8071c42d468e06ed2fb2c545efe79a663fd326ab" + integrity sha512-4zgNiB8nTyV/tHhwZrFs88ryjls/lHiqFhrxCW4qSTeuRByBVnPYpDInchOIySWknznucaf31Z4KYqjfbrecVw== capital-case@^1.0.4: version "1.0.4" @@ -5850,11 +5662,6 @@ chalk@3.0.0, chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" - integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== - chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -5872,6 +5679,11 @@ chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@~5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" + integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + change-case@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/change-case/-/change-case-4.1.2.tgz#fedfc5f136045e2398c0410ee441f95704641e12" @@ -5927,15 +5739,15 @@ chownr@^2.0.0: resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== -chromatic@^11.3.2: - version "11.4.0" - resolved "https://registry.yarnpkg.com/chromatic/-/chromatic-11.4.0.tgz#411a51e82599472b2131a08895faf000e0f9a0fa" - integrity sha512-/O6OwEUckqKTBGbm9KvYsR/eKCXy4s2eelO38yyfimBIJiL8+TS/pVnBqdtzUqO2hVK4GjrFiea9CnZUG9Akzw== +chromatic@^11.4.0: + version "11.5.1" + resolved "https://registry.yarnpkg.com/chromatic/-/chromatic-11.5.1.tgz#1f9df5078c6725a449eb6890cf07dbdb2c8cf35b" + integrity sha512-JMLih17sOwdD8h1w7XRTrs0g1DUicJxWkHzq0/nGB0CMIgfoylHq7uXpPEByu7l78lZLqcgneCPUuVEEfEWJDg== chrome-trace-event@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" - integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + version "1.0.4" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" + integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== citty@^0.1.6: version "0.1.6" @@ -5968,11 +5780,6 @@ clean-css@^5.2.2: dependencies: source-map "~0.6.0" -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - clean-webpack-plugin@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/clean-webpack-plugin/-/clean-webpack-plugin-4.0.0.tgz#72947d4403d452f38ed61a9ff0ada8122aacd729" @@ -6124,11 +5931,6 @@ comma-separated-tokens@^2.0.0: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== -commander@12.1.0: - version "12.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" - integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== - commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -6149,6 +5951,11 @@ commander@^8.3.0: resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== +commander@~12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" + integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== + common-tags@^1.8.0: version "1.8.2" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6" @@ -6282,6 +6089,13 @@ crypto-random-string@^2.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== +crypto-random-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-4.0.0.tgz#5a3cc53d7dd86183df5da0312816ceeeb5bb1fc2" + integrity sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA== + dependencies: + type-fest "^1.0.1" + css-loader@^6.7.1: version "6.11.0" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.11.0.tgz#33bae3bf6363d0a7c2cf9031c96c744ff54d85ba" @@ -6474,10 +6288,10 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== +debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.4: + version "4.3.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== dependencies: ms "2.1.2" @@ -6621,20 +6435,6 @@ del@^4.1.1: pify "^4.0.1" rimraf "^2.6.3" -del@^6.0.0: - version "6.1.1" - resolved "https://registry.yarnpkg.com/del/-/del-6.1.1.tgz#3b70314f1ec0aa325c6b14eb36b95786671edb7a" - integrity sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg== - dependencies: - globby "^11.0.1" - graceful-fs "^4.2.4" - is-glob "^4.0.1" - is-path-cwd "^2.2.0" - is-path-inside "^3.0.2" - p-map "^4.0.0" - rimraf "^3.0.2" - slash "^3.0.0" - delaunator@4: version "4.0.1" resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-4.0.1.tgz#3d779687f57919a7a418f8ab947d3bddb6846957" @@ -6795,9 +6595,9 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: domelementtype "^2.2.0" dompurify@^3.0.11: - version "3.1.4" - resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.1.4.tgz#42121304b2b3a6bae22f80131ff8a8f3f3c56be2" - integrity sha512-2gnshi6OshmuKil8rMZuQCGiUF3cUxHY3NGDzUAdUx/NPEe5DVnO8BDoAQouvgwnx0R/+a6jUn36Z0FSdq8vww== + version "3.1.5" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.1.5.tgz#2c6a113fc728682a0f55684b1388c58ddb79dc38" + integrity sha512-lwG+n5h8QNpxtyrJW/gJWckL+1/DQiYMX8f7t8Z2AZTPw1esVrqjI63i7Zc2Gz0aKzLVMYC1V1PL/ky+aY/NgA== domutils@^2.5.2, domutils@^2.8.0: version "2.8.0" @@ -6859,16 +6659,16 @@ ejs@^3.1.10, ejs@^3.1.6: jake "^10.8.5" electron-to-chromium@^1.4.668: - version "1.4.777" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.777.tgz#f846fbba23fd11b3c6f97848cdda94896fdb8baf" - integrity sha512-n02NCwLJ3wexLfK/yQeqfywCblZqLcXphzmid5e8yVPdtEcida7li0A5WQKghHNG0FeOMCzeFOzEbtAh5riXFw== + version "1.4.789" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.789.tgz#fec941cb753ee139da562a5a8ff31fc3e828b411" + integrity sha512-0VbyiaXoT++Fi2vHGo2ThOeS6X3vgRCWrjPeO2FeIAWL6ItiSJ9BqlH8LfCXe3X1IdcG+S0iLoNaxQWhfZoGzQ== emoji-picker-react@^4.5.16: - version "4.9.3" - resolved "https://registry.yarnpkg.com/emoji-picker-react/-/emoji-picker-react-4.9.3.tgz#ea6b87fd93021922a1787ddfdded581d755d950f" - integrity sha512-1O38tt6Yty8MiAB/3BWBpjyKr6sKVIl7Uc/fcATBxgz43fN31j1gVAsbY0+S2Yolaxgro838tFd2iADV2Ot2nA== + version "4.9.4" + resolved "https://registry.yarnpkg.com/emoji-picker-react/-/emoji-picker-react-4.9.4.tgz#869256913545405a3bd6d52f5a319ba3580101d0" + integrity sha512-u/3IsmMPr3l9Tcsuwpw4VgHb34W0BjC6iE06EXNHNOnNjBtW0BUN8upba8V3piUht00Sa+/L1+hs/xVmdWsswA== dependencies: - flairup "0.0.38" + flairup "0.0.39" emoji-regex@^10.3.0: version "10.3.0" @@ -7000,7 +6800,7 @@ es-define-property@^1.0.0: dependencies: get-intrinsic "^1.2.4" -es-errors@^1.1.0, es-errors@^1.2.1, es-errors@^1.3.0: +es-errors@^1.2.1, es-errors@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== @@ -7020,7 +6820,7 @@ es-get-iterator@^1.1.3: isarray "^2.0.5" stop-iteration-iterator "^1.0.0" -es-iterator-helpers@^1.0.15, es-iterator-helpers@^1.0.17: +es-iterator-helpers@^1.0.15, es-iterator-helpers@^1.0.19: version "1.0.19" resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz#117003d0e5fec237b4b5c08aded722e0c6d50ca8" integrity sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw== @@ -7420,28 +7220,28 @@ eslint-plugin-prettier@^5.1.3: integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ== eslint-plugin-react@^7.33.2: - version "7.34.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz#6806b70c97796f5bbfb235a5d3379ece5f4da997" - integrity sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw== + version "7.34.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.34.2.tgz#2780a1a35a51aca379d86d29b9a72adc6bfe6b66" + integrity sha512-2HCmrU+/JNigDN6tg55cRDKCQWicYAPB38JGSFDQt95jDm8rrvSUo7YPkOIm5l6ts1j1zCvysNcasvfTMQzUOw== dependencies: - array-includes "^3.1.7" - array.prototype.findlast "^1.2.4" + array-includes "^3.1.8" + array.prototype.findlast "^1.2.5" array.prototype.flatmap "^1.3.2" array.prototype.toreversed "^1.1.2" array.prototype.tosorted "^1.1.3" doctrine "^2.1.0" - es-iterator-helpers "^1.0.17" + es-iterator-helpers "^1.0.19" estraverse "^5.3.0" jsx-ast-utils "^2.4.1 || ^3.0.0" minimatch "^3.1.2" - object.entries "^1.1.7" - object.fromentries "^2.0.7" - object.hasown "^1.1.3" - object.values "^1.1.7" + object.entries "^1.1.8" + object.fromentries "^2.0.8" + object.hasown "^1.1.4" + object.values "^1.2.0" prop-types "^15.8.1" resolve "^2.0.0-next.5" semver "^6.3.1" - string.prototype.matchall "^4.0.10" + string.prototype.matchall "^4.0.11" eslint-plugin-turbo@1.13.3: version "1.13.3" @@ -7590,21 +7390,6 @@ events@^3.2.0: resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -execa@8.0.1, execa@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" - integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^8.0.1" - human-signals "^5.0.0" - is-stream "^3.0.0" - merge-stream "^2.0.0" - npm-run-path "^5.1.0" - onetime "^6.0.0" - signal-exit "^4.1.0" - strip-final-newline "^3.0.0" - execa@^5.0.0, execa@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" @@ -7620,6 +7405,21 @@ execa@^5.0.0, execa@^5.1.1: signal-exit "^3.0.3" strip-final-newline "^2.0.0" +execa@^8.0.1, execa@~8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" + integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^8.0.1" + human-signals "^5.0.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^4.1.0" + strip-final-newline "^3.0.0" + expand-template@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" @@ -7677,7 +7477,7 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== -fast-fifo@^1.1.0, fast-fifo@^1.2.0: +fast-fifo@^1.2.0, fast-fifo@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== @@ -7708,6 +7508,11 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-uri@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-2.3.0.tgz#bdae493942483d299e7285dcb4627767d42e2793" + integrity sha512-eel5UKGn369gGEWOqBShmFJWfq/xSJvsgDzgLYC845GneayWvXBf0lJCBn5qTABfewy1ZDPoaR5OZCP+kssfuw== + fastq@^1.6.0: version "1.17.1" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" @@ -7832,10 +7637,10 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -flairup@0.0.38: - version "0.0.38" - resolved "https://registry.yarnpkg.com/flairup/-/flairup-0.0.38.tgz#62216990a8317a1b07d1d816033624c5b2130f31" - integrity sha512-W9QA5TM7eYNlGoBYwfVn/o6v4yWBCxfq4+EJ5w774oFeyWvVWnYq6Dgt4CJltjG9y/lPwbOqz3jSSr8K66ToGg== +flairup@0.0.39: + version "0.0.39" + resolved "https://registry.yarnpkg.com/flairup/-/flairup-0.0.39.tgz#edc3a0f27548f0a0a0cb3569f4971ae4c0b73d46" + integrity sha512-UVPkzZmZeBWBx1+Ovo++kYKk9Wi32Jxt+c7HsxnEY80ExwFV54w+NyquFziqMLS0BnGVE43yGD4OvIwaAm/WiQ== flat-cache@^3.0.4: version "3.2.0" @@ -7852,9 +7657,9 @@ flatted@^3.2.9: integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== flow-parser@0.*: - version "0.236.0" - resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.236.0.tgz#8e8e6c59ff7e8d196c0ed215b3919320a1c6e332" - integrity sha512-0OEk9Gr+Yj7wjDW2KgaNYUypKau71jAfFyeLQF5iVtxqc6uJHag/MT7pmaEApf4qM7u86DkBcd4ualddYMfbLw== + version "0.237.2" + resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.237.2.tgz#f3e86ab582db57e4437796e7048632646a21a46f" + integrity sha512-mvI/kdfr3l1waaPbThPA8dJa77nHXrfZIun+SWvFwSwDjmeByU7mGJGRmv1+7guU6ccyLV8e1lqZA1lD4iMGnQ== follow-redirects@^1.15.6: version "1.15.6" @@ -8152,15 +7957,15 @@ glob@10.3.10: path-scurry "^1.10.1" glob@^10.0.0, glob@^10.3.10: - version "10.3.16" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.16.tgz#bf6679d5d51279c8cfae4febe0d051d2a4bf4c6f" - integrity sha512-JDKXl1DiuuHJ6fVS2FXjownaavciiHNUU4mOvV/B793RLh05vZL1rcPnCSaOgv1hDT6RDlY7AB7ZUvFYAtPgAw== + version "10.4.1" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.1.tgz#0cfb01ab6a6b438177bfe6a58e2576f6efe909c2" + integrity sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw== dependencies: foreground-child "^3.1.0" jackspeak "^3.1.2" - minimatch "^9.0.1" - minipass "^7.0.4" - path-scurry "^1.11.0" + minimatch "^9.0.4" + minipass "^7.1.2" + path-scurry "^1.11.1" glob@^7.0.3, glob@^7.1.3, glob@^7.1.6: version "7.2.3" @@ -8215,7 +8020,7 @@ globalthis@^1.0.3: define-properties "^1.2.1" gopd "^1.0.1" -globby@^11.0.1, globby@^11.0.3, globby@^11.0.4, globby@^11.1.0: +globby@^11.0.3, globby@^11.0.4, globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -8794,7 +8599,7 @@ is-obj@^1.0.1: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" integrity sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg== -is-path-cwd@^2.0.0, is-path-cwd@^2.2.0: +is-path-cwd@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== @@ -8813,7 +8618,7 @@ is-path-inside@^2.1.0: dependencies: path-is-inside "^1.0.2" -is-path-inside@^3.0.2, is-path-inside@^3.0.3: +is-path-inside@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== @@ -8976,9 +8781,9 @@ jackspeak@^2.3.5: "@pkgjs/parseargs" "^0.11.0" jackspeak@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.1.2.tgz#eada67ea949c6b71de50f1b09c92a961897b90ab" - integrity sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ== + version "3.2.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.2.3.tgz#33e8c44f7858d199fc5684f4ab62d1fd873eb10d" + integrity sha512-htOzIMPbpLid/Gq9/zaz9SfExABxqRe1sSCdxntlO/aMD6u0issZQiY25n2GKQUtJ02j7z5sfptlAOMpWWOmvw== dependencies: "@isaacs/cliui" "^8.0.2" optionalDependencies: @@ -9220,22 +9025,22 @@ levn@^0.4.1: type-check "~0.4.0" lib0@^0.2.42, lib0@^0.2.74, lib0@^0.2.85, lib0@^0.2.86: - version "0.2.93" - resolved "https://registry.yarnpkg.com/lib0/-/lib0-0.2.93.tgz#95487c2a97657313cb1d91fbcf9f6d64b7fcd062" - integrity sha512-M5IKsiFJYulS+8Eal8f+zAqf5ckm1vffW0fFDxfgxJ+uiVopvDdd3PxJmz0GsVi3YNO7QCFSq0nAsiDmNhLj9Q== + version "0.2.94" + resolved "https://registry.yarnpkg.com/lib0/-/lib0-0.2.94.tgz#fc28b4b65f816599f1e2f59d3401e231709535b3" + integrity sha512-hZ3p54jL4Wpu7IOg26uC7dnEWiMyNlUrb9KoG7+xYs45WkQwpVvKFndVq2+pqLYKe1u8Fp3+zAfZHVvTK34PvQ== dependencies: isomorphic.js "^0.2.4" -lilconfig@3.1.1, lilconfig@^3.0.0, lilconfig@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.1.tgz#9d8a246fa753106cfc205fd2d77042faca56e5e3" - integrity sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ== - lilconfig@^2.0.5, lilconfig@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== +lilconfig@^3.0.0, lilconfig@^3.1.1, lilconfig@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.1.tgz#9d8a246fa753106cfc205fd2d77042faca56e5e3" + integrity sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ== + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -9254,22 +9059,22 @@ linkifyjs@^4.1.3: integrity sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg== lint-staged@^15.2.2: - version "15.2.4" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.2.4.tgz#b8376b459a308d40e5dfdae302f333fa4c4bf126" - integrity sha512-3F9KRQIS2fVDGtCkBp4Bx0jswjX7zUcKx6OF0ZeY1prksUyKPRIIUqZhIUYAstJfvj6i48VFs4dwVIbCYwvTYQ== - dependencies: - chalk "5.3.0" - commander "12.1.0" - debug "4.3.4" - execa "8.0.1" - lilconfig "3.1.1" - listr2 "8.2.1" - micromatch "4.0.6" - pidtree "0.6.0" - string-argv "0.3.2" - yaml "2.4.2" - -listr2@8.2.1: + version "15.2.5" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.2.5.tgz#8c342f211bdb34ffd3efd1311248fa6b50b43b50" + integrity sha512-j+DfX7W9YUvdzEZl3Rk47FhDF6xwDBV5wwsCPw6BwWZVPYJemusQmvb9bRsW23Sqsaa+vRloAWogbK4BUuU2zA== + dependencies: + chalk "~5.3.0" + commander "~12.1.0" + debug "~4.3.4" + execa "~8.0.1" + lilconfig "~3.1.1" + listr2 "~8.2.1" + micromatch "~4.0.7" + pidtree "~0.6.0" + string-argv "~0.3.2" + yaml "~2.4.2" + +listr2@~8.2.1: version "8.2.1" resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.2.1.tgz#06a1a6efe85f23c5324180d7c1ddbd96b5eefd6d" integrity sha512-irTfvpib/rNiD637xeevjO2l3Z5loZmuaRi0L0YE5LfijwVY96oyVn0DFD3o/teAok7nfobMG1THvvcHh/BP6g== @@ -9437,6 +9242,11 @@ lucide-react@^0.378.0: resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.378.0.tgz#232acb99c6baedfa90959a2c0dd11327b058bde8" integrity sha512-u6EPU8juLUk9ytRcyapkWI18epAv3RU+6+TC23ivjR0e+glWKBobFeSgRwOIJihzktILQuy6E0E80P2jVTDR5g== +lucide-react@^0.379.0: + version "0.379.0" + resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.379.0.tgz#29e34eeffae7fb241b64b09868cbe3ab888ef7cc" + integrity sha512-KcdeVPqmhRldldAAgptb8FjIunM2x2Zy26ZBh1RsEUcdLIvsEmbcw7KpzFYUy5BbpGeWhPu9Z9J5YXfStiXwhg== + lz-string@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" @@ -9796,15 +9606,7 @@ micromark@^3.0.0: micromark-util-types "^1.0.1" uvu "^0.5.0" -micromatch@4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.6.tgz#ab4e37c42726b9cd788181ba4a2a4fead8e394a3" - integrity sha512-Y4Ypn3oujJYxJcMacVgcs92wofTHxp9FzfDpQON4msDefoC0lb3ETvQLOdLcbhSwU1bz8HrL/1sygfBIHudrkQ== - dependencies: - braces "^3.0.3" - picomatch "^4.0.2" - -micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5, micromatch@~4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== @@ -9906,10 +9708,10 @@ minipass@^5.0.0: resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.4: - version "7.1.1" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.1.tgz#f7f85aff59aa22f110b20e27692465cf3bf89481" - integrity sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA== +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== minizlib@^2.1.1: version "2.1.2" @@ -10066,9 +9868,9 @@ no-case@^3.0.4: tslib "^2.0.3" node-abi@^3.3.0: - version "3.62.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.62.0.tgz#017958ed120f89a3a14a7253da810f5d724e3f36" - integrity sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g== + version "3.63.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.63.0.tgz#9bfbe68b87357f8b508554608b323e9b1052d045" + integrity sha512-vAszCsOUrUxjGAmdnM/pq7gUgie0IRteCQMX6d4A534fQCR93EJU5qgzBvU6EkFfK27s0T3HEV3BOyJIr7OMYw== dependencies: semver "^7.3.5" @@ -10206,7 +10008,7 @@ object.assign@^4.1.4, object.assign@^4.1.5: has-symbols "^1.0.3" object-keys "^1.1.1" -object.entries@^1.1.7: +object.entries@^1.1.7, object.entries@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== @@ -10215,7 +10017,7 @@ object.entries@^1.1.7: define-properties "^1.2.1" es-object-atoms "^1.0.0" -object.fromentries@^2.0.7: +object.fromentries@^2.0.7, object.fromentries@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== @@ -10234,7 +10036,7 @@ object.groupby@^1.0.1: define-properties "^1.2.1" es-abstract "^1.23.2" -object.hasown@^1.1.3: +object.hasown@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.4.tgz#e270ae377e4c120cdcb7656ce66884a6218283dc" integrity sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg== @@ -10243,7 +10045,7 @@ object.hasown@^1.1.3: es-abstract "^1.23.2" es-object-atoms "^1.0.0" -object.values@^1.1.6, object.values@^1.1.7: +object.values@^1.1.6, object.values@^1.1.7, object.values@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== @@ -10397,13 +10199,6 @@ p-map@^2.0.0: resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -10500,7 +10295,7 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-scurry@^1.10.1, path-scurry@^1.11.0, path-scurry@^1.6.1: +path-scurry@^1.10.1, path-scurry@^1.11.1, path-scurry@^1.6.1: version "1.11.1" resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== @@ -10591,12 +10386,7 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.0, picomatc resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -picomatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" - integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== - -pidtree@0.6.0: +pidtree@~0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== @@ -10776,9 +10566,9 @@ postcss-selector-parser@6.0.10: util-deprecate "^1.0.2" postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: - version "6.0.16" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz#3b88b9f5c5abd989ef4e2fc9ec8eedd34b20fb04" - integrity sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw== + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz#49694cb4e7c649299fea510a29fa6577104bcf53" + integrity sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" @@ -10856,9 +10646,9 @@ postgres-range@^1.1.1: integrity sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w== posthog-js@^1.131.3: - version "1.132.2" - resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.132.2.tgz#6d776fbba0db699517a04eaaf5278da4914a4efd" - integrity sha512-GkiulyjQU7Ez48jcAeEXgj5zUFE2/D1nKIB83sJk/fD+8sLIYrK6ksR34ZkolEq6lRkFfuSoai/+Zgj79QbyXA== + version "1.136.5" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.136.5.tgz#df89ec9990fe3509799b516a97c30a948464eecf" + integrity sha512-jLmR9HX7/zR/EfJ+7Fl6ivvBKJdmaFiB8mhdEITIPWpXVgGRvp3u5JuvlNXPW9r1pfux8qoPJCtsqHP+uv73gw== dependencies: fflate "^0.4.8" preact "^10.19.3" @@ -10891,10 +10681,10 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -"prettier-fallback@npm:prettier@^3", prettier@^3.1.1, prettier@^3.2.5, prettier@latest: - version "3.2.5" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368" - integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== +"prettier-fallback@npm:prettier@^3": + version "3.3.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.0.tgz#d173ea0524a691d4c0b1181752f2b46724328cdf" + integrity sha512-J9odKxERhCQ10OC2yb93583f6UnYutOeiV5i0zEDS7UGTdUt0u+y8erxl3lBKvwo/JHyyoEdXjwp4dke9oyZ/g== prettier-linter-helpers@^1.0.0: version "1.0.0" @@ -10918,6 +10708,11 @@ prettier@^2.8.8: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== +prettier@^3.1.1, prettier@^3.2.5, prettier@latest: + version "3.3.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.0.tgz#d173ea0524a691d4c0b1181752f2b46724328cdf" + integrity sha512-J9odKxERhCQ10OC2yb93583f6UnYutOeiV5i0zEDS7UGTdUt0u+y8erxl3lBKvwo/JHyyoEdXjwp4dke9oyZ/g== + pretty-bytes@^5.3.0, pretty-bytes@^5.4.1: version "5.6.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" @@ -11083,9 +10878,9 @@ prosemirror-menu@^1.2.4: prosemirror-state "^1.0.0" prosemirror-model@^1.0.0, prosemirror-model@^1.19.0, prosemirror-model@^1.19.4, prosemirror-model@^1.20.0, prosemirror-model@^1.21.0, prosemirror-model@^1.8.1: - version "1.21.0" - resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.21.0.tgz#2d69ed04b4e7c441c3eb87c1c964fab4f9b217df" - integrity sha512-zLpS1mVCZLA7VTp82P+BfMiYVPcX1/z0Mf3gsjKZtzMWubwn2pN7CceMV0DycjlgE5JeXPR7UF4hJPbBV98oWA== + version "1.21.1" + resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.21.1.tgz#023b804cfc7942bc9b3104e65f59b18bf18514c6" + integrity sha512-IVBAuMqOfltTr7yPypwpfdGT+6rGAteVOw2FO6GEvCGGa1ZwxLseqC1Eax/EChDvG/xGquB2d/hLdgh3THpsYg== dependencies: orderedmap "^2.0.0" @@ -11141,9 +10936,9 @@ prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transfor prosemirror-model "^1.21.0" prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.27.0, prosemirror-view@^1.31.0, prosemirror-view@^1.32.7: - version "1.33.6" - resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.33.6.tgz#85804eb922411af8e300a07f4f376722b15900b9" - integrity sha512-zRLUNgLIQfd8IfGprsXxWTjdA8xEAFJe8cDNrOptj6Mop9sj+BMeVbJvceyAYCm5G2dOdT2prctH7K9dfnpIMw== + version "1.33.7" + resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.33.7.tgz#fd9841a79a4bc517914a57456370b941bd655729" + integrity sha512-jo6eMQCtPRwcrA2jISBCnm0Dd2B+szS08BU1Ay+XGiozHo5EZMHfLQE8R5nO4vb1spTH2RW1woZIYXRiQsuP8g== dependencies: prosemirror-model "^1.20.0" prosemirror-state "^1.0.0" @@ -11564,9 +11359,9 @@ readdirp@~3.6.0: picomatch "^2.2.1" recast@^0.23.3, recast@^0.23.5: - version "0.23.7" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.23.7.tgz#1e08f164e10402b075c904a2b01022b3da039c72" - integrity sha512-MpQlLZVpqbbxYcqEjwpRWo88sGvjOYoXptySz710RuddNMHx+wPkoNX6YyLZJlXAh5VZr1qmPrTwcTuFMh0Lag== + version "0.23.9" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.23.9.tgz#587c5d3a77c2cfcb0c18ccce6da4361528c2587b" + integrity sha512-Hx/BGIbwj+Des3+xy5uAtAbdCyqK9y9wbBcDFDYanLS9JnMqf7OeF87HQwUimE87OEc72mr6tkKUKMBBL+hF9Q== dependencies: ast-types "^0.16.1" esprima "~4.0.0" @@ -12256,9 +12051,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.17" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz#887da8aa73218e51a1d917502d79863161a93f9c" - integrity sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg== + version "3.0.18" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz#22aa922dcf2f2885a6494a261f2d8b75345d0326" + integrity sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ== stacktrace-parser@^0.1.10: version "0.1.10" @@ -12285,11 +12080,11 @@ store2@^2.14.2: integrity sha512-4QcZ+yx7nzEFiV4BMLnr/pRa5HYzNITX2ri0Zh6sT9EyQHbBHacC6YigllUPU9X3D0f/22QCgfokpKs52YRrUg== storybook@^8.1.1: - version "8.1.2" - resolved "https://registry.yarnpkg.com/storybook/-/storybook-8.1.2.tgz#cb24e549d56050543d290188cad014712fa59d1e" - integrity sha512-6yJ7e320AmT6x8IGeNd3P79RDZI+8v9Pp3AZaHLgBnxdp4qehSfBZxW9MtZunVnXlgpI+HXOogwMJMWayg428A== + version "8.1.5" + resolved "https://registry.yarnpkg.com/storybook/-/storybook-8.1.5.tgz#079248bdb099b4edb8cde94246b62bec448a1e21" + integrity sha512-v4o8AfTvxWpdGa9Pa9x8EAmqbN5yJc+2fW8b6ZaCsDOTh2t5Y3EUHbIzdtvX+1Gb6ALsOs5e2Q9GlCAzjz+WNQ== dependencies: - "@storybook/cli" "8.1.2" + "@storybook/cli" "8.1.5" stream-shift@^1.0.0: version "1.0.3" @@ -12301,23 +12096,32 @@ streamsearch@^1.1.0: resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== -streamx@^2.15.0, streamx@^2.16.1: - version "2.16.1" - resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.16.1.tgz#2b311bd34832f08aa6bb4d6a80297c9caef89614" - integrity sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ== +streamx@^2.15.0, streamx@^2.18.0: + version "2.18.0" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.18.0.tgz#5bc1a51eb412a667ebfdcd4e6cf6a6fc65721ac7" + integrity sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ== dependencies: - fast-fifo "^1.1.0" + fast-fifo "^1.3.2" queue-tick "^1.0.1" + text-decoder "^1.1.0" optionalDependencies: bare-events "^2.2.0" -string-argv@0.3.2: +string-argv@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - name string-width-cjs +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -12344,7 +12148,7 @@ string-width@^7.0.0: get-east-asian-width "^1.0.0" strip-ansi "^7.1.0" -string.prototype.matchall@^4.0.10, string.prototype.matchall@^4.0.6: +string.prototype.matchall@^4.0.11, string.prototype.matchall@^4.0.6: version "4.0.11" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a" integrity sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg== @@ -12413,7 +12217,14 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -12706,6 +12517,11 @@ temp-dir@^2.0.0: resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== +temp-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-3.0.0.tgz#7f147b42ee41234cc6ba3138cd8e8aa2302acffa" + integrity sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw== + temp@^0.8.4: version "0.8.4" resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.4.tgz#8c97a33a4770072e0a05f919396c7665a7dd59f2" @@ -12723,16 +12539,15 @@ tempy@^0.6.0: type-fest "^0.16.0" unique-string "^2.0.0" -tempy@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/tempy/-/tempy-1.0.1.tgz#30fe901fd869cfb36ee2bd999805aa72fbb035de" - integrity sha512-biM9brNqxSc04Ee71hzFbryD11nX7VPhQQY32AdDmjFvodsRFz/3ufeoTZ6uYkRFfGo188tENcASNs3vTdsM0w== +tempy@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/tempy/-/tempy-3.1.0.tgz#00958b6df85db8589cb595465e691852aac038e9" + integrity sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g== dependencies: - del "^6.0.0" - is-stream "^2.0.0" - temp-dir "^2.0.0" - type-fest "^0.16.0" - unique-string "^2.0.0" + is-stream "^3.0.0" + temp-dir "^3.0.0" + type-fest "^2.12.2" + unique-string "^3.0.0" terser-webpack-plugin@^5.3.1, terser-webpack-plugin@^5.3.10, terser-webpack-plugin@^5.3.3: version "5.3.10" @@ -12755,6 +12570,13 @@ terser@^5.0.0, terser@^5.10.0, terser@^5.26.0: commander "^2.20.0" source-map-support "~0.5.20" +text-decoder@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.1.0.tgz#3379e728fcf4d3893ec1aea35e8c2cac215ef190" + integrity sha512-TmLJNj6UgX8xcUZo4UDStGQtDiTzF7BzWlzn9g7UWrjkpHr5uJTK1ld16wZ3LXb2vb6jH8qU89dW5whuMdXYdw== + dependencies: + b4a "^1.6.4" + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -12832,9 +12654,9 @@ to-regex-range@^5.0.1: is-number "^7.0.0" tocbot@^4.20.1: - version "4.28.0" - resolved "https://registry.yarnpkg.com/tocbot/-/tocbot-4.28.0.tgz#fb5ffb1e342d21d244404885d2f8ac33d37161c4" - integrity sha512-E1RJiEOBKxFvSezbPsFW3z+K7faYgcTp+LwBRpJj1bZ8XTAobh5Y9TaVjW6ND3mAc9BDOmqwhH7xpF4on6R++w== + version "4.28.2" + resolved "https://registry.yarnpkg.com/tocbot/-/tocbot-4.28.2.tgz#5a51b34cefd39f6b556b936b380a838a0a8c49ea" + integrity sha512-/MaSa9xI6mIo84IxqqliSCtPlH0oy7sLcY9s26qPMyH/2CxtZ2vNAXYlIdEQ7kjAkCQnc0rbLygf//F5c663oQ== toidentifier@1.0.1: version "1.0.1" @@ -13055,7 +12877,12 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-fest@^2.19.0, type-fest@~2.19: +type-fest@^1.0.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" + integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== + +type-fest@^2.12.2, type-fest@^2.19.0, type-fest@~2.19: version "2.19.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== @@ -13210,6 +13037,13 @@ unique-string@^2.0.0: dependencies: crypto-random-string "^2.0.0" +unique-string@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-3.0.0.tgz#84a1c377aff5fd7a8bc6b55d8244b2bd90d75b9a" + integrity sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ== + dependencies: + crypto-random-string "^4.0.0" + unist-util-generated@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-2.0.1.tgz#e37c50af35d3ed185ac6ceacb6ca0afb28a85cae" @@ -13339,7 +13173,7 @@ upper-case@^2.0.2: dependencies: tslib "^2.0.3" -uri-js@^4.2.2, uri-js@^4.4.1: +uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== @@ -13814,8 +13648,16 @@ workbox-window@6.6.1, workbox-window@^6.5.4: "@types/trusted-types" "^2.0.2" workbox-core "6.6.1" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: - name wrap-ansi-cjs +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -13902,16 +13744,16 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@2.4.2, yaml@^2.3.4, yaml@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.2.tgz#7a2b30f2243a5fc299e1f14ca58d475ed4bc5362" - integrity sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA== - yaml@^1.10.0, yaml@^1.10.2: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yaml@^2.3.4, yaml@^2.4.2, yaml@~2.4.2: + version "2.4.3" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.3.tgz#0777516b8c7880bcaa0f426a5410e8d6b0be1f3d" + integrity sha512-sntgmxj8o7DE7g/Qi60cqpLBA3HG3STcDA0kO+WfB05jEKhZMbY7umNm2rBpQvsmZ16/lPXCJGW2672dgOUkrg== + yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" @@ -13945,4 +13787,4 @@ yocto-queue@^0.1.0: zxcvbn@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/zxcvbn/-/zxcvbn-4.4.2.tgz#28ec17cf09743edcab056ddd8b1b06262cc73c30" - integrity sha512-Bq0B+ixT/DMyG8kgX2xWcI5jUvCwqrMxSFam7m0lAf78nf04hv6lNCsyLYdyYTrCVMqNDY/206K7eExYCeSyUQ== \ No newline at end of file + integrity sha512-Bq0B+ixT/DMyG8kgX2xWcI5jUvCwqrMxSFam7m0lAf78nf04hv6lNCsyLYdyYTrCVMqNDY/206K7eExYCeSyUQ== From 4d176166707903e008541f8287f3e540b815e635 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Tue, 4 Jun 2024 18:50:28 +0530 Subject: [PATCH 010/307] fix: docker image build errors --- packages/ui/package.json | 3 +++ yarn.lock | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 5444b673e98..84a655bf747 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -20,12 +20,15 @@ "postcss": "postcss styles/globals.css -o styles/output.css --watch" }, "dependencies": { + "@atlaskit/pragmatic-drag-and-drop": "^1.1.10", + "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3", "@blueprintjs/core": "^4.16.3", "@blueprintjs/popover2": "^1.13.3", "@headlessui/react": "^1.7.17", "@popperjs/core": "^2.11.8", "clsx": "^2.0.0", "emoji-picker-react": "^4.5.16", + "lodash": "^4.17.21", "lucide-react": "^0.379.0", "react-color": "^2.19.3", "react-dom": "^18.2.0", diff --git a/yarn.lock b/yarn.lock index 00f56ce5c76..05aec74bf30 100644 --- a/yarn.lock +++ b/yarn.lock @@ -45,7 +45,7 @@ "@atlaskit/pragmatic-drag-and-drop" "^1.1.0" "@babel/runtime" "^7.0.0" -"@atlaskit/pragmatic-drag-and-drop@^1.1.0", "@atlaskit/pragmatic-drag-and-drop@^1.1.3": +"@atlaskit/pragmatic-drag-and-drop@^1.1.0", "@atlaskit/pragmatic-drag-and-drop@^1.1.10", "@atlaskit/pragmatic-drag-and-drop@^1.1.3": version "1.1.10" resolved "https://registry.yarnpkg.com/@atlaskit/pragmatic-drag-and-drop/-/pragmatic-drag-and-drop-1.1.10.tgz#b7671bb6329adec6603c1805310a5ec16ff6fb7c" integrity sha512-imiaN7bfZJM92Xq+qgbzIaKgLeHpd56w5/ECrBW1HMIXTRFauLdEo1w6PCcI4k1r18pA19E1mNhSb5+WvhMkOg== @@ -4432,7 +4432,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@18.2.48", "@types/react@^16.8.0 || ^17.0.0 || ^18.0.0", "@types/react@^18.2.42", "@types/react@^18.2.48": +"@types/react@*", "@types/react@^16.8.0 || ^17.0.0 || ^18.0.0", "@types/react@^18.2.42", "@types/react@^18.2.48": version "18.2.48" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.48.tgz#11df5664642d0bd879c1f58bc1d37205b064e8f1" integrity sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w== From 8c5f6932143d1676380c5eeea392afded36e3fc5 Mon Sep 17 00:00:00 2001 From: "M. Palanikannan" <73993394+Palanikannan1437@users.noreply.github.com> Date: Wed, 5 Jun 2024 12:41:56 +0530 Subject: [PATCH 011/307] regression: focus changing issue with the peek overview editor (#4700) --- packages/editor/core/src/hooks/use-editor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/core/src/hooks/use-editor.tsx b/packages/editor/core/src/hooks/use-editor.tsx index 76071791b6a..563cb5122e8 100644 --- a/packages/editor/core/src/hooks/use-editor.tsx +++ b/packages/editor/core/src/hooks/use-editor.tsx @@ -112,7 +112,7 @@ export const useEditor = ({ if (value === null || value === undefined) return; if (editor && !editor.isDestroyed && !editor.storage.image.uploadInProgress) { try { - editor.commands.setContent(value); + editor.commands.setContent(value, false, { preserveWhitespace: "full" }); const currentSavedSelection = savedSelectionRef.current; if (currentSavedSelection) { const docLength = editor.state.doc.content.size; From 453459d271136818e96a76180a5706f4bc75e6a8 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Wed, 5 Jun 2024 12:47:16 +0530 Subject: [PATCH 012/307] [WEB-1459] chore: save users all / favorite project list collapse state into localstorage. (#4701) --- web/components/project/sidebar-list.tsx | 192 ++++++++++++++---------- 1 file changed, 111 insertions(+), 81 deletions(-) diff --git a/web/components/project/sidebar-list.tsx b/web/components/project/sidebar-list.tsx index b6eb0c70820..698869f98a7 100644 --- a/web/components/project/sidebar-list.tsx +++ b/web/components/project/sidebar-list.tsx @@ -5,22 +5,30 @@ import { observer } from "mobx-react"; import { useRouter } from "next/router"; import { ChevronDown, ChevronRight, Plus } from "lucide-react"; import { Disclosure, Transition } from "@headlessui/react"; +// types import { IProject } from "@plane/types"; -// hooks +// ui import { TOAST_TYPE, setToast } from "@plane/ui"; +// components import { CreateProjectModal, ProjectSidebarListItem } from "@/components/project"; +// constants import { EUserWorkspaceRoles } from "@/constants/workspace"; +// helpers import { cn } from "@/helpers/common.helper"; import { orderJoinedProjects } from "@/helpers/project.helper"; import { copyUrlToClipboard } from "@/helpers/string.helper"; +// hooks import { useAppTheme, useCommandPalette, useEventTracker, useProject, useUser } from "@/hooks/store"; -// ui -// components -// helpers -// constants export const ProjectSidebarList: FC = observer(() => { + // get local storage data for isFavoriteProjectsListOpen and isAllProjectsListOpen + const isFavProjectsListOpenInLocalStorage = localStorage.getItem("isFavoriteProjectsListOpen"); + const isAllProjectsListOpenInLocalStorage = localStorage.getItem("isAllProjectsListOpen"); // states + const [isFavoriteProjectsListOpen, setIsFavoriteProjectsListOpen] = useState( + isFavProjectsListOpenInLocalStorage === "true" + ); + const [isAllProjectsListOpen, setIsAllProjectsListOpen] = useState(isAllProjectsListOpenInLocalStorage === "true"); const [isFavoriteProjectCreate, setIsFavoriteProjectCreate] = useState(false); const [isProjectModalOpen, setIsProjectModalOpen] = useState(false); const [isScrolled, setIsScrolled] = useState(false); // scroll animation state @@ -122,6 +130,16 @@ export const ProjectSidebarList: FC = observer(() => { ); }, [containerRef]); + const toggleListDisclosure = (isOpen: boolean, type: "all" | "favorite") => { + if (type === "all") { + setIsAllProjectsListOpen(isOpen); + localStorage.setItem("isAllProjectsListOpen", isOpen.toString()); + } else { + setIsFavoriteProjectsListOpen(isOpen); + localStorage.setItem("isFavoriteProjectsListOpen", isOpen.toString()); + } + }; + return ( <> {workspaceSlug && ( @@ -147,42 +165,48 @@ export const ProjectSidebarList: FC = observer(() => { >
{favoriteProjects && favoriteProjects.length > 0 && ( - - {({ open }) => ( - <> - {!isCollapsed && ( -
- - Favorites - {open ? : } - - {isAuthorizedUser && ( - + + <> + {!isCollapsed && ( +
+ toggleListDisclosure(!isFavoriteProjectsListOpen, "favorite")} + > + Favorites + {isFavoriteProjectsListOpen ? ( + + ) : ( + )} -
- )} - - + + {isAuthorizedUser && ( + + )} +
+ )} + + {isFavoriteProjectsListOpen && ( + {favoriteProjects.map((projectId, index) => ( { /> ))} - - - )} + )} + +
)}
{joinedProjects && joinedProjects.length > 0 && ( - - {({ open }) => ( - <> - {!isCollapsed && ( -
- - Your projects - {open ? : } - - {isAuthorizedUser && ( - + + <> + {!isCollapsed && ( +
+ toggleListDisclosure(!isAllProjectsListOpen, "all")} + > + Your projects + {isAllProjectsListOpen ? ( + + ) : ( + )} -
- )} - - + + {isAuthorizedUser && ( + + )} +
+ )} + + {isAllProjectsListOpen && ( + {joinedProjects.map((projectId, index) => ( { /> ))} - - - )} + )} + +
)}
From 93a22034bd61a934bd9ea1157d2505fd03d14143 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Wed, 5 Jun 2024 12:48:50 +0530 Subject: [PATCH 013/307] [WEB-1501] chore: update selected entity details on entities list change (#4702) * chore: update selected entity detials on entities list change * chore: addd selectionHelpers as a prop --- .../gantt-chart/chart/main-content.tsx | 2 +- .../issues/bulk-operations/root.tsx | 2 ++ .../issues/issue-layouts/list/default.tsx | 2 +- .../spreadsheet/spreadsheet-view.tsx | 2 +- web/hooks/use-multiple-select.ts | 19 +++++++++++++++---- web/store/multiple_select.store.ts | 16 +++++++++++++++- 6 files changed, 35 insertions(+), 8 deletions(-) diff --git a/web/components/gantt-chart/chart/main-content.tsx b/web/components/gantt-chart/chart/main-content.tsx index e3b97223790..e5bd7afbfb8 100644 --- a/web/components/gantt-chart/chart/main-content.tsx +++ b/web/components/gantt-chart/chart/main-content.tsx @@ -162,7 +162,7 @@ export const GanttChartMainContent: React.FC = observer((props) => { )}
- + )} diff --git a/web/components/issues/bulk-operations/root.tsx b/web/components/issues/bulk-operations/root.tsx index 957f18609a0..f92e0227965 100644 --- a/web/components/issues/bulk-operations/root.tsx +++ b/web/components/issues/bulk-operations/root.tsx @@ -3,9 +3,11 @@ import { observer } from "mobx-react"; import { BulkOperationsUpgradeBanner } from "@/components/issues"; // hooks import { useMultipleSelectStore } from "@/hooks/store"; +import { TSelectionHelper } from "@/hooks/use-multiple-select"; type Props = { className?: string; + selectionHelpers: TSelectionHelper; }; export const IssueBulkOperationsRoot: React.FC = observer((props) => { diff --git a/web/components/issues/issue-layouts/list/default.tsx b/web/components/issues/issue-layouts/list/default.tsx index c527276ef47..daddc572f33 100644 --- a/web/components/issues/issue-layouts/list/default.tsx +++ b/web/components/issues/issue-layouts/list/default.tsx @@ -170,7 +170,7 @@ const GroupByList: React.FC = observer((props) => { ) )}
- + )} diff --git a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx index c4b6e7d69a7..8287ea74600 100644 --- a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx @@ -107,7 +107,7 @@ export const SpreadsheetView: React.FC = observer((props) => { )}
- + )} diff --git a/web/hooks/use-multiple-select.ts b/web/hooks/use-multiple-select.ts index 9dcc0e17c3c..47f67362423 100644 --- a/web/hooks/use-multiple-select.ts +++ b/web/hooks/use-multiple-select.ts @@ -33,6 +33,7 @@ export const useMultipleSelect = (props: Props) => { const router = useRouter(); // store hooks const { + selectedEntityIds, updateSelectedEntityDetails, bulkUpdateSelectedEntityDetails, getActiveEntityDetails, @@ -45,6 +46,7 @@ export const useMultipleSelect = (props: Props) => { clearSelection, getIsEntitySelected, getIsEntityActive, + getEntityDetailsFromEntityID, } = useMultipleSelectStore(); const groups = useMemo(() => Object.keys(entities), [entities]); @@ -248,10 +250,6 @@ export const useMultipleSelect = (props: Props) => { (groupID: string) => { const groupEntities = entitiesList.filter((entity) => entity.groupID === groupID); const groupSelectionStatus = isGroupSelected(groupID); - // groupEntities.map((entity) => { - // console.log("group click"); - // handleEntitySelection(entity, false, groupSelectionStatus === "empty" ? "force-add" : "force-remove"); - // }); handleEntitySelection(groupEntities, false, groupSelectionStatus === "empty" ? "force-add" : "force-remove"); }, [entitiesList, handleEntitySelection, isGroupSelected] @@ -346,6 +344,19 @@ export const useMultipleSelect = (props: Props) => { }; }, [clearSelection, router.events]); + // when entities list change, remove entityIds from the selected entities array, which are not present in the new list + useEffect(() => { + selectedEntityIds.map((entityID) => { + const isEntityPresent = entitiesList.find((en) => en.entityID === entityID); + if (!isEntityPresent) { + const entityDetails = getEntityDetailsFromEntityID(entityID); + if (entityDetails) { + handleEntitySelection(entityDetails); + } + } + }); + }, [entitiesList, getEntityDetailsFromEntityID, handleEntitySelection, selectedEntityIds]); + /** * @description helper functions for selection */ diff --git a/web/store/multiple_select.store.ts b/web/store/multiple_select.store.ts index 14750f31a59..c573cec5b9a 100644 --- a/web/store/multiple_select.store.ts +++ b/web/store/multiple_select.store.ts @@ -19,6 +19,7 @@ export type IMultipleSelectStore = { getPreviousActiveEntity: () => TEntityDetails | null; getNextActiveEntity: () => TEntityDetails | null; getActiveEntityDetails: () => TEntityDetails | null; + getEntityDetailsFromEntityID: (entityID: string) => TEntityDetails | null; // entity actions updateSelectedEntityDetails: (entityDetails: TEntityDetails, action: "add" | "remove") => void; bulkUpdateSelectedEntityDetails: (entitiesList: TEntityDetails[], action: "add" | "remove") => void; @@ -119,6 +120,16 @@ export class MultipleSelectStore implements IMultipleSelectStore { */ getActiveEntityDetails = computedFn(() => this.activeEntityDetails); + /** + * @description get the entity details from entityID + * @param {string} entityID + * @returns {TEntityDetails | null} + */ + getEntityDetailsFromEntityID = computedFn( + (entityID: string): TEntityDetails | null => + this.selectedEntityDetails.find((en) => en.entityID === entityID) ?? null + ); + // entity actions /** * @description add or remove entities @@ -159,8 +170,11 @@ export class MultipleSelectStore implements IMultipleSelectStore { if (entitiesList.length > 0) this.updateLastSelectedEntityDetails(entitiesList[entitiesList.length - 1]); }); } else { + const newEntities = differenceWith(this.selectedEntityDetails, entitiesList, (obj1, obj2) => + isEqual(obj1.entityID, obj2.entityID) + ); runInAction(() => { - this.selectedEntityDetails = differenceWith(this.selectedEntityDetails, entitiesList, isEqual); + this.selectedEntityDetails = newEntities; }); } }; From 52d8d6e7ce57aabf552959158c920ffd1c5d875b Mon Sep 17 00:00:00 2001 From: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Date: Wed, 5 Jun 2024 14:03:49 +0530 Subject: [PATCH 014/307] [WEB-1517] chore: remove drag handle from list drag block (#4698) * remove drag handle from list drag block * align list group header with list item * rearrange chevron for list subissues and rearrange spaces * adding default draggable property to control link * remove unnecessary dependencies for useEffect --- packages/ui/src/control-link/control-link.tsx | 13 ++++- .../issues/issue-layouts/list/block.tsx | 52 ++++++++++--------- .../list/headers/group-by-card.tsx | 4 +- .../issues/issue-layouts/list/list-group.tsx | 2 +- .../list/quick-add-issue-form.tsx | 2 +- 5 files changed, 42 insertions(+), 31 deletions(-) diff --git a/packages/ui/src/control-link/control-link.tsx b/packages/ui/src/control-link/control-link.tsx index df195847656..83f3157cc50 100644 --- a/packages/ui/src/control-link/control-link.tsx +++ b/packages/ui/src/control-link/control-link.tsx @@ -7,10 +7,11 @@ export type TControlLink = React.AnchorHTMLAttributes & { target?: string; disabled?: boolean; className?: string; + draggable?: boolean; }; export const ControlLink = React.forwardRef((props, ref) => { - const { href, onClick, children, target = "_self", disabled = false, className, ...rest } = props; + const { href, onClick, children, target = "_self", disabled = false, className, draggable = false, ...rest } = props; const LEFT_CLICK_EVENT_CODE = 0; const handleOnClick = (event: React.MouseEvent) => { @@ -33,7 +34,15 @@ export const ControlLink = React.forwardRef((pr if (disabled) return <>{children}; return ( - + {children} ); diff --git a/web/components/issues/issue-layouts/list/block.tsx b/web/components/issues/issue-layouts/list/block.tsx index 56d88a7302d..358c7218953 100644 --- a/web/components/issues/issue-layouts/list/block.tsx +++ b/web/components/issues/issue-layouts/list/block.tsx @@ -6,7 +6,7 @@ import { ChevronRight } from "lucide-react"; // types import { TIssue, IIssueDisplayProperties, TIssueMap } from "@plane/types"; // ui -import { Spinner, Tooltip, ControlLink, DragHandle } from "@plane/ui"; +import { Spinner, Tooltip, ControlLink, setToast, TOAST_TYPE } from "@plane/ui"; // components import { MultipleSelectEntityAction } from "@/components/core"; import { IssueProperties } from "@/components/issues/issue-layouts/properties"; @@ -57,7 +57,6 @@ export const IssueBlock = observer((props: IssueBlockProps) => { } = props; // ref const issueRef = useRef(null); - const dragHandleRef = useRef(null); // hooks const { workspaceSlug, projectId } = useAppRouter(); const { getProjectIdentifierById } = useProject(); @@ -78,14 +77,12 @@ export const IssueBlock = observer((props: IssueBlockProps) => { useEffect(() => { const element = issueRef.current; - const dragHandleElement = dragHandleRef.current; - if (!element || !dragHandleElement) return; + if (!element) return; return combine( draggable({ element, - dragHandle: dragHandleElement, canDrag: () => canDrag, getInitialData: () => ({ id: issueId, type: "ISSUE", groupId }), onDragStart: () => { @@ -96,7 +93,7 @@ export const IssueBlock = observer((props: IssueBlockProps) => { }, }) ); - }, [issueRef?.current, canDrag, issueId, groupId, dragHandleRef?.current, setIsCurrentBlockDragging]); + }, [canDrag, issueId, groupId, setIsCurrentBlockDragging]); if (!issue) return null; @@ -135,20 +132,19 @@ export const IssueBlock = observer((props: IssueBlockProps) => { "bg-custom-background-80": isCurrentBlockDragging, } )} + onDragStart={() => { + if (!canDrag) { + setToast({ + type: TOAST_TYPE.WARNING, + title: "Cannot move issue", + message: "Drag and drop is disabled for the current grouping", + }); + } + }} >
-
-
- {/* drag handle */} -
- -
+
+
{/* select checkbox */} {projectId && canEditIssueProperties && ( {
)} + {displayProperties && displayProperties?.key && ( +
+ {projectIdentifier}-{issue.sequence_id} +
+ )} + {/* sub-issues chevron */} -
+
{subIssuesCount > 0 && ( )}
- {displayProperties && displayProperties?.key && ( -
- {projectIdentifier}-{issue.sequence_id} -
- )} {issue?.tempId !== undefined && (
@@ -206,7 +203,12 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
{issue?.is_draft ? ( - +

{issue.name}

) : ( diff --git a/web/components/issues/issue-layouts/list/headers/group-by-card.tsx b/web/components/issues/issue-layouts/list/headers/group-by-card.tsx index feb99a8a523..cfda9161371 100644 --- a/web/components/issues/issue-layouts/list/headers/group-by-card.tsx +++ b/web/components/issues/issue-layouts/list/headers/group-by-card.tsx @@ -83,7 +83,7 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => { return ( <> -
+
{canSelectIssues && (
{ />
)} -
+
{icon ?? }
diff --git a/web/components/issues/issue-layouts/list/list-group.tsx b/web/components/issues/issue-layouts/list/list-group.tsx index 04457327e35..64cbf807bb7 100644 --- a/web/components/issues/issue-layouts/list/list-group.tsx +++ b/web/components/issues/issue-layouts/list/list-group.tsx @@ -193,7 +193,7 @@ export const ListGroup = observer((props: Props) => { "border-custom-error-200": isDraggingOverColumn && !!group.isDropDisabled, })} > -
+
= observer((props
) : (
setIsOpen(true)} > From 249e71e424e9ccb0baee6ac2dc98d0f0a85b10ca Mon Sep 17 00:00:00 2001 From: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Date: Wed, 5 Jun 2024 14:20:57 +0530 Subject: [PATCH 015/307] fix: email validation (#4707) * fix: email validation on complete login or sign up functionality * dev: add try catch block * dev: split up code * dev: empty return --- .../plane/authentication/adapter/base.py | 137 +++++++++++++----- .../plane/authentication/adapter/error.py | 2 + .../plane/authentication/adapter/oauth.py | 10 +- 3 files changed, 107 insertions(+), 42 deletions(-) diff --git a/apiserver/plane/authentication/adapter/base.py b/apiserver/plane/authentication/adapter/base.py index 7b899e63cab..5876e934f30 100644 --- a/apiserver/plane/authentication/adapter/base.py +++ b/apiserver/plane/authentication/adapter/base.py @@ -4,6 +4,8 @@ # Django imports from django.utils import timezone +from django.core.validators import validate_email +from django.core.exceptions import ValidationError # Third party imports from zxcvbn import zxcvbn @@ -46,53 +48,115 @@ def create_update_account(self, user): def authenticate(self): raise NotImplementedError + def sanitize_email(self, email): + # Check if email is present + if not email: + raise AuthenticationException( + error_code=AUTHENTICATION_ERROR_CODES["INVALID_EMAIL"], + error_message="INVALID_EMAIL", + payload={"email": email}, + ) + + # Sanitize email + email = str(email).lower().strip() + + # validate email + try: + validate_email(email) + except ValidationError: + raise AuthenticationException( + error_code=AUTHENTICATION_ERROR_CODES["INVALID_EMAIL"], + error_message="INVALID_EMAIL", + payload={"email": email}, + ) + # Return email + return email + + def validate_password(self, email): + """Validate password strength""" + results = zxcvbn(self.code) + if results["score"] < 3: + raise AuthenticationException( + error_code=AUTHENTICATION_ERROR_CODES["INVALID_PASSWORD"], + error_message="INVALID_PASSWORD", + payload={"email": email}, + ) + return + + def __check_signup(self, email): + """Check if sign up is enabled or not and raise exception if not enabled""" + + # Get configuration value + (ENABLE_SIGNUP,) = get_configuration_value( + [ + { + "key": "ENABLE_SIGNUP", + "default": os.environ.get("ENABLE_SIGNUP", "1"), + }, + ] + ) + + # Check if sign up is disabled and invite is present or not + if ( + ENABLE_SIGNUP == "0" + and not WorkspaceMemberInvite.objects.filter( + email=email, + ).exists() + ): + # Raise exception + raise AuthenticationException( + error_code=AUTHENTICATION_ERROR_CODES["SIGNUP_DISABLED"], + error_message="SIGNUP_DISABLED", + payload={"email": email}, + ) + + return True + + def save_user_data(self, user): + # Update user details + user.last_login_medium = self.provider + user.last_active = timezone.now() + user.last_login_time = timezone.now() + user.last_login_ip = self.request.META.get("REMOTE_ADDR") + user.last_login_uagent = self.request.META.get("HTTP_USER_AGENT") + user.token_updated_at = timezone.now() + user.save() + return user + def complete_login_or_signup(self): + # Get email email = self.user_data.get("email") + + # Sanitize email + email = self.sanitize_email(email) + + # Check if the user is present user = User.objects.filter(email=email).first() # Check if sign up case or login is_signup = bool(user) + # If user is not present, create a new user if not user: # New user - (ENABLE_SIGNUP,) = get_configuration_value( - [ - { - "key": "ENABLE_SIGNUP", - "default": os.environ.get("ENABLE_SIGNUP", "1"), - }, - ] - ) - if ( - ENABLE_SIGNUP == "0" - and not WorkspaceMemberInvite.objects.filter( - email=email, - ).exists() - ): - raise AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES["SIGNUP_DISABLED"], - error_message="SIGNUP_DISABLED", - payload={"email": email}, - ) + self.__check_signup(email) + + # Initialize user user = User(email=email, username=uuid.uuid4().hex) + # Check if password is autoset if self.user_data.get("user").get("is_password_autoset"): user.set_password(uuid.uuid4().hex) user.is_password_autoset = True user.is_email_verified = True + + # Validate password else: # Validate password - results = zxcvbn(self.code) - if results["score"] < 3: - raise AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES[ - "INVALID_PASSWORD" - ], - error_message="INVALID_PASSWORD", - payload={"email": email}, - ) - + self.validate_password(email) + # Set password user.set_password(self.code) user.is_password_autoset = False + # Set user details avatar = self.user_data.get("user", {}).get("avatar", "") first_name = self.user_data.get("user", {}).get("first_name", "") last_name = self.user_data.get("user", {}).get("last_name", "") @@ -100,6 +164,8 @@ def complete_login_or_signup(self): user.first_name = first_name if first_name else "" user.last_name = last_name if last_name else "" user.save() + + # Create profile Profile.objects.create(user=user) if not user.is_active: @@ -108,15 +174,10 @@ def complete_login_or_signup(self): error_message="USER_ACCOUNT_DEACTIVATED", ) - # Update user details - user.last_login_medium = self.provider - user.last_active = timezone.now() - user.last_login_time = timezone.now() - user.last_login_ip = self.request.META.get("REMOTE_ADDR") - user.last_login_uagent = self.request.META.get("HTTP_USER_AGENT") - user.token_updated_at = timezone.now() - user.save() + # Save user data + user = self.save_user_data(user=user) + # Call callback if present if self.callback: self.callback( user, @@ -124,7 +185,9 @@ def complete_login_or_signup(self): self.request, ) + # Create or update account if token data is present if self.token_data: self.create_update_account(user=user) + # Return user return user diff --git a/apiserver/plane/authentication/adapter/error.py b/apiserver/plane/authentication/adapter/error.py index 7b12db9456a..55ff1098820 100644 --- a/apiserver/plane/authentication/adapter/error.py +++ b/apiserver/plane/authentication/adapter/error.py @@ -58,6 +58,8 @@ "ADMIN_USER_DEACTIVATED": 5190, # Rate limit "RATE_LIMIT_EXCEEDED": 5900, + # Unknown + "AUTHENTICATION_FAILED": 5999, } diff --git a/apiserver/plane/authentication/adapter/oauth.py b/apiserver/plane/authentication/adapter/oauth.py index a917c002aef..b1a92e79e55 100644 --- a/apiserver/plane/authentication/adapter/oauth.py +++ b/apiserver/plane/authentication/adapter/oauth.py @@ -81,11 +81,11 @@ def get_user_response(self): response.raise_for_status() return response.json() except requests.RequestException: - code = ( - "GOOGLE_OAUTH_PROVIDER_ERROR" - if self.provider == "google" - else "GITHUB_OAUTH_PROVIDER_ERROR" - ) + if self.provider == "google": + code = "GOOGLE_OAUTH_PROVIDER_ERROR" + if self.provider == "github": + code = "GITHUB_OAUTH_PROVIDER_ERROR" + raise AuthenticationException( error_code=AUTHENTICATION_ERROR_CODES[code], error_message=str(code), From 911832d5465717f1c2a3386cbced0f24fc05d8ba Mon Sep 17 00:00:00 2001 From: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Date: Wed, 5 Jun 2024 15:17:43 +0530 Subject: [PATCH 016/307] fix: cache invalidation on new members invite (#4699) --- .../authentication/utils/workspace_project_join.py | 11 +++++++++++ apiserver/plane/utils/cache.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/apiserver/plane/authentication/utils/workspace_project_join.py b/apiserver/plane/authentication/utils/workspace_project_join.py index 8910ec63780..3b6f231edfc 100644 --- a/apiserver/plane/authentication/utils/workspace_project_join.py +++ b/apiserver/plane/authentication/utils/workspace_project_join.py @@ -4,6 +4,7 @@ WorkspaceMember, WorkspaceMemberInvite, ) +from plane.utils.cache import invalidate_cache_directly def process_workspace_project_invitations(user): @@ -26,6 +27,16 @@ def process_workspace_project_invitations(user): ignore_conflicts=True, ) + [ + invalidate_cache_directly( + path=f"/api/workspaces/{str(workspace_member_invite.workspace.slug)}/members/", + url_params=False, + user=False, + multiple=True, + ) + for workspace_member_invite in workspace_member_invites + ] + # Check if user has any project invites project_member_invites = ProjectMemberInvite.objects.filter( email=user.email, accepted=True diff --git a/apiserver/plane/utils/cache.py b/apiserver/plane/utils/cache.py index 07105112962..bda94289966 100644 --- a/apiserver/plane/utils/cache.py +++ b/apiserver/plane/utils/cache.py @@ -66,7 +66,7 @@ def invalidate_cache_directly( custom_path = path if path is not None else request.get_full_path() auth_header = ( None - if request.user.is_anonymous + if request and request.user.is_anonymous else str(request.user.id) if user else None ) key = generate_cache_key(custom_path, auth_header) From 272428b05efd20b83b22257207ac81b2c113d5a6 Mon Sep 17 00:00:00 2001 From: Henit Chobisa Date: Wed, 5 Jun 2024 15:56:36 +0530 Subject: [PATCH 017/307] fix: build test pull request running on non draft PRs (#4708) --- .github/workflows/build-test-pull-request.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-test-pull-request.yml b/.github/workflows/build-test-pull-request.yml index 5b94b215adc..2e6f9c642f9 100644 --- a/.github/workflows/build-test-pull-request.yml +++ b/.github/workflows/build-test-pull-request.yml @@ -3,10 +3,11 @@ name: Build and Lint on Pull Request on: workflow_dispatch: pull_request: - types: ["opened", "synchronize"] + types: ["opened", "synchronize", "ready_for_review"] jobs: get-changed-files: + if: github.event.pull_request.draft == false runs-on: ubuntu-latest outputs: apiserver_changed: ${{ steps.changed-files.outputs.apiserver_any_changed }} From 30fdc1015ceacd62f5038da30fb3c1c042870d1c Mon Sep 17 00:00:00 2001 From: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Date: Wed, 5 Jun 2024 15:17:43 +0530 Subject: [PATCH 018/307] fix: cache invalidation on new members invite (#4699) --- .../authentication/utils/workspace_project_join.py | 11 +++++++++++ apiserver/plane/utils/cache.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/apiserver/plane/authentication/utils/workspace_project_join.py b/apiserver/plane/authentication/utils/workspace_project_join.py index 8910ec63780..3b6f231edfc 100644 --- a/apiserver/plane/authentication/utils/workspace_project_join.py +++ b/apiserver/plane/authentication/utils/workspace_project_join.py @@ -4,6 +4,7 @@ WorkspaceMember, WorkspaceMemberInvite, ) +from plane.utils.cache import invalidate_cache_directly def process_workspace_project_invitations(user): @@ -26,6 +27,16 @@ def process_workspace_project_invitations(user): ignore_conflicts=True, ) + [ + invalidate_cache_directly( + path=f"/api/workspaces/{str(workspace_member_invite.workspace.slug)}/members/", + url_params=False, + user=False, + multiple=True, + ) + for workspace_member_invite in workspace_member_invites + ] + # Check if user has any project invites project_member_invites = ProjectMemberInvite.objects.filter( email=user.email, accepted=True diff --git a/apiserver/plane/utils/cache.py b/apiserver/plane/utils/cache.py index 07105112962..bda94289966 100644 --- a/apiserver/plane/utils/cache.py +++ b/apiserver/plane/utils/cache.py @@ -66,7 +66,7 @@ def invalidate_cache_directly( custom_path = path if path is not None else request.get_full_path() auth_header = ( None - if request.user.is_anonymous + if request and request.user.is_anonymous else str(request.user.id) if user else None ) key = generate_cache_key(custom_path, auth_header) From ca9f3f2f5a71de4482a974edf18c4749b0568831 Mon Sep 17 00:00:00 2001 From: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Date: Wed, 5 Jun 2024 20:13:28 +0530 Subject: [PATCH 019/307] fix: add version max length (#4713) --- ..._title_alter_changelog_version_and_more.py | 43 +++++++++++++++++++ apiserver/plane/license/models/instance.py | 12 +++--- 2 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 apiserver/plane/license/migrations/0003_alter_changelog_title_alter_changelog_version_and_more.py diff --git a/apiserver/plane/license/migrations/0003_alter_changelog_title_alter_changelog_version_and_more.py b/apiserver/plane/license/migrations/0003_alter_changelog_title_alter_changelog_version_and_more.py new file mode 100644 index 00000000000..8d7b9a40220 --- /dev/null +++ b/apiserver/plane/license/migrations/0003_alter_changelog_title_alter_changelog_version_and_more.py @@ -0,0 +1,43 @@ +# Generated by Django 4.2.11 on 2024-06-05 13:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("license", "0002_rename_version_instance_current_version_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="changelog", + name="title", + field=models.CharField(max_length=255), + ), + migrations.AlterField( + model_name="changelog", + name="version", + field=models.CharField(max_length=255), + ), + migrations.AlterField( + model_name="instance", + name="current_version", + field=models.CharField(max_length=255), + ), + migrations.AlterField( + model_name="instance", + name="latest_version", + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AlterField( + model_name="instance", + name="namespace", + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AlterField( + model_name="instance", + name="product", + field=models.CharField(default="plane-ce", max_length=255), + ), + ] diff --git a/apiserver/plane/license/models/instance.py b/apiserver/plane/license/models/instance.py index ea88ba9bb9b..0c0581c8b23 100644 --- a/apiserver/plane/license/models/instance.py +++ b/apiserver/plane/license/models/instance.py @@ -21,15 +21,15 @@ class Instance(BaseModel): whitelist_emails = models.TextField(blank=True, null=True) instance_id = models.CharField(max_length=255, unique=True) license_key = models.CharField(max_length=256, null=True, blank=True) - current_version = models.CharField(max_length=10) - latest_version = models.CharField(max_length=10, null=True, blank=True) + current_version = models.CharField(max_length=255) + latest_version = models.CharField(max_length=255, null=True, blank=True) product = models.CharField( - max_length=50, default=ProductTypes.PLANE_CE.value + max_length=255, default=ProductTypes.PLANE_CE.value ) domain = models.TextField(blank=True) # Instance specifics last_checked_at = models.DateTimeField() - namespace = models.CharField(max_length=50, blank=True, null=True) + namespace = models.CharField(max_length=255, blank=True, null=True) # telemetry and support is_telemetry_enabled = models.BooleanField(default=True) is_support_required = models.BooleanField(default=True) @@ -86,9 +86,9 @@ class Meta: class ChangeLog(BaseModel): """Change Log model to store the release changelogs made in the application.""" - title = models.CharField(max_length=100) + title = models.CharField(max_length=255) description = models.TextField(blank=True) - version = models.CharField(max_length=100) + version = models.CharField(max_length=255) tags = models.JSONField(default=list) release_date = models.DateTimeField(null=True) is_release_candidate = models.BooleanField(default=False) From b24e530816fe21881797068a68b739bbde82454f Mon Sep 17 00:00:00 2001 From: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Date: Thu, 6 Jun 2024 11:03:56 +0530 Subject: [PATCH 020/307] [WEB-1533] chore: fix alignment issues in List and Spreadsheet view (#4714) * fix alignment issues in List and Spreadsheet view * fix spreadsheet indentation --- .../issues/issue-layouts/list/block-root.tsx | 2 +- .../issues/issue-layouts/list/block.tsx | 12 ++++-- .../issue-layouts/spreadsheet/issue-row.tsx | 37 +++++++++++-------- .../spreadsheet/spreadsheet-header.tsx | 3 +- 4 files changed, 33 insertions(+), 21 deletions(-) diff --git a/web/components/issues/issue-layouts/list/block-root.tsx b/web/components/issues/issue-layouts/list/block-root.tsx index 4be71acaf3a..7825a5b1834 100644 --- a/web/components/issues/issue-layouts/list/block-root.tsx +++ b/web/components/issues/issue-layouts/list/block-root.tsx @@ -154,7 +154,7 @@ export const IssueBlockRoot: FC = observer((props) => { canEditProperties={canEditProperties} displayProperties={displayProperties} nestingLevel={nestingLevel + 1} - spacingLeft={spacingLeft + (displayProperties?.key ? 12 : 0)} + spacingLeft={spacingLeft + 12} containerRef={containerRef} selectionHelpers={selectionHelpers} groupId={groupId} diff --git a/web/components/issues/issue-layouts/list/block.tsx b/web/components/issues/issue-layouts/list/block.tsx index 358c7218953..32b656745b0 100644 --- a/web/components/issues/issue-layouts/list/block.tsx +++ b/web/components/issues/issue-layouts/list/block.tsx @@ -119,6 +119,9 @@ export const IssueBlock = observer((props: IssueBlockProps) => { } }; + //TODO: add better logic. This is to have a min width for ID/Key based on the length of project identifier + const keyMinWidth = (projectIdentifier.length + 5) * 7; + return (
{ }} >
-
-
+
+
{/* select checkbox */} {projectId && canEditIssueProperties && ( { )} {displayProperties && displayProperties?.key && ( -
+
{projectIdentifier}-{issue.sequence_id}
)} diff --git a/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx b/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx index 771871bda50..1e5a2574efe 100644 --- a/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/issue-row.tsx @@ -108,7 +108,7 @@ export const SpreadsheetIssueRow = observer((props: Props) => { quickActions={quickActions} canEditProperties={canEditProperties} nestingLevel={nestingLevel + 1} - spacingLeft={spacingLeft + (displayProperties.key ? 12 : 28)} + spacingLeft={spacingLeft + 12} isEstimateEnabled={isEstimateEnabled} updateIssue={updateIssue} portalElement={portalElement} @@ -187,7 +187,7 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => { const issueDetail = issue.getIssueById(issueId); - const marginLeft = `${spacingLeft}px`; + const subIssueIndentation = `${spacingLeft}px`; useOutsideClickDetector(menuActionRef, () => setIsMenuActive(false)); @@ -222,6 +222,9 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => { const subIssuesCount = issueDetail?.sub_issues_count ?? 0; const isIssueSelected = selectionHelpers.getIsEntitySelected(issueDetail.id); + //TODO: add better logic. This is to have a min width for ID/Key based on the length of project identifier + const keyMinWidth = (getProjectIdentifierById(issueDetail.project_id)?.length + 5) * 7; + return ( <> { )} disabled={!!issueDetail?.tempId} > -
+
{/* select checkbox */} {projectId && !disableUserActions && ( { } disabled={issueDetail.project_id === projectId} > -
+
{
)} + + {/* sub issues indentation */} +
+ + +
+

+ {getProjectIdentifierById(issueDetail.project_id)}-{issueDetail.sequence_id} +

+
+
+ {/* sub-issues chevron */} -
+
{subIssuesCount > 0 && ( )}
- - -
-

- {getProjectIdentifierById(issueDetail.project_id)}-{issueDetail.sequence_id} -

-
-
-
+
{issueDetail.name} diff --git a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-header.tsx b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-header.tsx index 9de9a68e7fa..74f65db3179 100644 --- a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-header.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-header.tsx @@ -48,7 +48,7 @@ export const SpreadsheetHeader = observer((props: Props) => { tabIndex={-1} > {canSelectIssues && ( -
+
{ />
)} -
Issues From 282597bf835286eb7bece14d999ba098014fb4fc Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Thu, 6 Jun 2024 17:02:47 +0530 Subject: [PATCH 021/307] chore: handle undefined identifier case --- .../issues/issue-layouts/list/block.tsx | 2 +- .../issue-layouts/spreadsheet/issue-row.tsx | 2 +- web/store/project/project.store.ts | 4 +- yarn.lock | 40 +++---------------- 4 files changed, 9 insertions(+), 39 deletions(-) diff --git a/web/components/issues/issue-layouts/list/block.tsx b/web/components/issues/issue-layouts/list/block.tsx index 32b656745b0..42f7afff686 100644 --- a/web/components/issues/issue-layouts/list/block.tsx +++ b/web/components/issues/issue-layouts/list/block.tsx @@ -120,7 +120,7 @@ export const IssueBlock = observer((props: IssueBlockProps) => { }; //TODO: add better logic. This is to have a min width for ID/Key based on the length of project identifier - const keyMinWidth = (projectIdentifier.length + 5) * 7; + const keyMinWidth = ((projectIdentifier?.length ?? 0) + 5) * 7; return (
{ const isIssueSelected = selectionHelpers.getIsEntitySelected(issueDetail.id); //TODO: add better logic. This is to have a min width for ID/Key based on the length of project identifier - const keyMinWidth = (getProjectIdentifierById(issueDetail.project_id)?.length + 5) * 7; + const keyMinWidth = (getProjectIdentifierById(issueDetail.project_id)?.length ?? 0 + 5) * 7; return ( <> diff --git a/web/store/project/project.store.ts b/web/store/project/project.store.ts index 9916faca4b2..8fb576e1c81 100644 --- a/web/store/project/project.store.ts +++ b/web/store/project/project.store.ts @@ -28,7 +28,7 @@ export interface IProjectStore { currentProjectDetails: IProject | undefined; // actions getProjectById: (projectId: string) => IProject | null; - getProjectIdentifierById: (projectId: string) => string; + getProjectIdentifierById: (projectId: string) => string | undefined; // fetch actions fetchProjects: (workspaceSlug: string) => Promise; fetchProjectDetails: (workspaceSlug: string, projectId: string) => Promise; @@ -261,7 +261,7 @@ export class ProjectStore implements IProjectStore { * @param projectId * @returns string */ - getProjectIdentifierById = computedFn((projectId: string) => { + getProjectIdentifierById = computedFn((projectId: string): string | undefined => { const projectInfo = this.projectMap?.[projectId]; return projectInfo?.identifier; }); diff --git a/yarn.lock b/yarn.lock index 05aec74bf30..c37f094a910 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4432,7 +4432,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^16.8.0 || ^17.0.0 || ^18.0.0", "@types/react@^18.2.42", "@types/react@^18.2.48": +"@types/react@*", "@types/react@18.2.48", "@types/react@^16.8.0 || ^17.0.0 || ^18.0.0", "@types/react@^18.2.42", "@types/react@^18.2.48": version "18.2.48" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.48.tgz#11df5664642d0bd879c1f58bc1d37205b064e8f1" integrity sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w== @@ -10681,7 +10681,7 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -"prettier-fallback@npm:prettier@^3": +"prettier-fallback@npm:prettier@^3", prettier@^3.1.1, prettier@^3.2.5, prettier@latest: version "3.3.0" resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.0.tgz#d173ea0524a691d4c0b1181752f2b46724328cdf" integrity sha512-J9odKxERhCQ10OC2yb93583f6UnYutOeiV5i0zEDS7UGTdUt0u+y8erxl3lBKvwo/JHyyoEdXjwp4dke9oyZ/g== @@ -10708,11 +10708,6 @@ prettier@^2.8.8: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== -prettier@^3.1.1, prettier@^3.2.5, prettier@latest: - version "3.3.0" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.0.tgz#d173ea0524a691d4c0b1181752f2b46724328cdf" - integrity sha512-J9odKxERhCQ10OC2yb93583f6UnYutOeiV5i0zEDS7UGTdUt0u+y8erxl3lBKvwo/JHyyoEdXjwp4dke9oyZ/g== - pretty-bytes@^5.3.0, pretty-bytes@^5.4.1: version "5.6.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" @@ -12112,16 +12107,7 @@ string-argv@~0.3.2: resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -12217,14 +12203,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -13648,16 +13627,7 @@ workbox-window@6.6.1, workbox-window@^6.5.4: "@types/trusted-types" "^2.0.2" workbox-core "6.6.1" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From 9af9268be67ad9c7f8aa21affb6b6ee26e23a6a6 Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Thu, 6 Jun 2024 17:46:49 +0530 Subject: [PATCH 022/307] fix: Overflowing loader in issue edit modal (#4720) --- web/components/issues/issue-modal/form.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/components/issues/issue-modal/form.tsx b/web/components/issues/issue-modal/form.tsx index 42ce948adcd..013eb498bca 100644 --- a/web/components/issues/issue-modal/form.tsx +++ b/web/components/issues/issue-modal/form.tsx @@ -413,9 +413,9 @@ export const IssueFormRoot: FC = observer((props) => { /> {errors?.name?.message}
-
+
{data?.description_html === undefined ? ( - +
@@ -429,7 +429,7 @@ export const IssueFormRoot: FC = observer((props) => {
-
+
From d31aaee32c79fafd6ad529f5833bb7c8d0a48af6 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Fri, 7 Jun 2024 12:22:30 +0530 Subject: [PATCH 023/307] [WEB-1529] chore: workspace sidebar updates. (#4710) --- web/components/workspace/sidebar-dropdown.tsx | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/web/components/workspace/sidebar-dropdown.tsx b/web/components/workspace/sidebar-dropdown.tsx index 9c7ce78cf88..12b58eeca7f 100644 --- a/web/components/workspace/sidebar-dropdown.tsx +++ b/web/components/workspace/sidebar-dropdown.tsx @@ -16,22 +16,16 @@ import { GOD_MODE_URL } from "@/helpers/common.helper"; import { useAppTheme, useUser, useUserProfile, useWorkspace } from "@/hooks/store"; import { WorkspaceLogo } from "./logo"; // Static Data -const userLinks = (workspaceSlug: string, userId: string) => [ +const userLinks = (workspaceSlug: string) => [ { key: "workspace_invites", name: "Workspace invites", href: "/invitations", icon: Mails, }, - { - key: "my_activity", - name: "My activity", - href: `/${workspaceSlug}/profile/${userId}`, - icon: Activity, - }, { key: "settings", - name: "Settings", + name: "Workspace settings", href: `/${workspaceSlug}/settings`, icon: Settings, }, @@ -211,7 +205,7 @@ export const WorkspaceSidebarDropdown = observer(() => { Create workspace - {userLinks(workspaceSlug?.toString() ?? "", currentUser?.id ?? "").map((link, index) => ( + {userLinks(workspaceSlug?.toString() ?? "").map((link, index) => ( Date: Fri, 7 Jun 2024 12:22:55 +0530 Subject: [PATCH 024/307] fix: temporary fix exiting lines with slashes (#4725) --- packages/editor/extensions/src/extensions/slash-commands.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/editor/extensions/src/extensions/slash-commands.tsx b/packages/editor/extensions/src/extensions/slash-commands.tsx index c1b1ef9c041..7844700bf74 100644 --- a/packages/editor/extensions/src/extensions/slash-commands.tsx +++ b/packages/editor/extensions/src/extensions/slash-commands.tsx @@ -69,7 +69,6 @@ const Command = Extension.create({ return true; }, - allowSpaces: true, }, }; }, From f5656111ee55207f4b6bfce8b3e7ca71fc7e5ce6 Mon Sep 17 00:00:00 2001 From: "M. Palanikannan" <73993394+Palanikannan1437@users.noreply.github.com> Date: Fri, 7 Jun 2024 12:34:57 +0530 Subject: [PATCH 025/307] [WEB-1537] fix: inline code block size fixed for headers, etc (#4709) * fix: inline code block size fixed for headers, etc * feat: persisting focus accurately post converting the code block into text * fix: typo in error handling --- .../editor/core/src/lib/editor-commands.ts | 68 ++-------- .../src/ui/extensions/code-inline/index.tsx | 2 +- .../utils/replace-code-block-with-text.ts | 124 ++++++++++++++++++ .../custom-list-keymap/list-keymap.ts | 4 +- 4 files changed, 135 insertions(+), 63 deletions(-) create mode 100644 packages/editor/core/src/ui/extensions/code/utils/replace-code-block-with-text.ts diff --git a/packages/editor/core/src/lib/editor-commands.ts b/packages/editor/core/src/lib/editor-commands.ts index 911347e7fca..6e05ff13df7 100644 --- a/packages/editor/core/src/lib/editor-commands.ts +++ b/packages/editor/core/src/lib/editor-commands.ts @@ -3,6 +3,7 @@ import { startImageUpload } from "src/ui/plugins/image/image-upload-handler"; import { findTableAncestor } from "src/lib/utils"; import { Selection } from "@tiptap/pm/state"; import { UploadImage } from "src/types/upload-image"; +import { replaceCodeWithText } from "src/ui/extensions/code/utils/replace-code-block-with-text"; export const setText = (editor: Editor, range?: Range) => { if (range) editor.chain().focus().deleteRange(range).clearNodes().run(); @@ -54,69 +55,11 @@ export const toggleUnderline = (editor: Editor, range?: Range) => { else editor.chain().focus().toggleUnderline().run(); }; -const replaceCodeBlockWithContent = (editor: Editor) => { - try { - const { schema } = editor.state; - const { paragraph } = schema.nodes; - let replaced = false; - - const replaceCodeBlock = (from: number, to: number, textContent: string) => { - const docSize = editor.state.doc.content.size; - - if (from < 0 || to > docSize || from > to) { - console.error("Invalid range for replacement: ", from, to, "in a document of size", docSize); - return; - } - - // split the textContent by new lines to handle each line as a separate paragraph - const lines = textContent.split(/\r?\n/); - - const tr = editor.state.tr; - - // Calculate the position for inserting the first paragraph - let insertPos = from; - - // Remove the code block first - tr.delete(from, to); - - // For each line, create a paragraph node and insert it - lines.forEach((line) => { - const paragraphNode = paragraph.create({}, schema.text(line)); - tr.insert(insertPos, paragraphNode); - // Update insertPos for the next insertion - insertPos += paragraphNode.nodeSize; - }); - - // Dispatch the transaction - editor.view.dispatch(tr); - replaced = true; - }; - - editor.state.doc.nodesBetween(editor.state.selection.from, editor.state.selection.to, (node, pos) => { - if (node.type === schema.nodes.codeBlock) { - const startPos = pos; - const endPos = pos + node.nodeSize; - const textContent = node.textContent; - if (textContent.length === 0) { - editor.chain().focus().toggleCodeBlock().run(); - } - replaceCodeBlock(startPos, endPos, textContent); - return false; - } - }); - - if (!replaced) { - console.log("No code block to replace."); - } - } catch (error) { - console.error("An error occurred while replacing code block content:", error); - } -}; - export const toggleCodeBlock = (editor: Editor, range?: Range) => { try { + // if it's a code block, replace it with the code with paragraphs if (editor.isActive("codeBlock")) { - replaceCodeBlockWithContent(editor); + replaceCodeWithText(editor); return; } @@ -124,11 +67,16 @@ export const toggleCodeBlock = (editor: Editor, range?: Range) => { const text = editor.state.doc.textBetween(from, to, "\n"); const isMultiline = text.includes("\n"); + // if the selection is not a range i.e. empty, then simply convert it into a code block if (editor.state.selection.empty) { editor.chain().focus().toggleCodeBlock().run(); } else if (isMultiline) { + // if the selection is multiline, then also replace the text content with + // a code block editor.chain().focus().deleteRange({ from, to }).insertContentAt(from, `\`\`\`\n${text}\n\`\`\``).run(); } else { + // if the selection is single line, then simply convert it into inline + // code editor.chain().focus().toggleCode().run(); } } catch (error) { diff --git a/packages/editor/core/src/ui/extensions/code-inline/index.tsx b/packages/editor/core/src/ui/extensions/code-inline/index.tsx index bc629160a8d..60a12364ef9 100644 --- a/packages/editor/core/src/ui/extensions/code-inline/index.tsx +++ b/packages/editor/core/src/ui/extensions/code-inline/index.tsx @@ -33,7 +33,7 @@ export const CustomCodeInlineExtension = Mark.create({ return { HTMLAttributes: { class: - "rounded bg-custom-background-80 px-1 py-[2px] font-mono font-medium text-orange-500 border-[0.5px] border-custom-border-200 text-sm", + "rounded bg-custom-background-80 px-1 py-[2px] font-mono font-medium text-orange-500 border-[0.5px] border-custom-border-200", spellcheck: "false", }, }; diff --git a/packages/editor/core/src/ui/extensions/code/utils/replace-code-block-with-text.ts b/packages/editor/core/src/ui/extensions/code/utils/replace-code-block-with-text.ts new file mode 100644 index 00000000000..daf2c5f05d6 --- /dev/null +++ b/packages/editor/core/src/ui/extensions/code/utils/replace-code-block-with-text.ts @@ -0,0 +1,124 @@ +import { Editor, findParentNode } from "@tiptap/core"; + +type ReplaceCodeBlockParams = { + editor: Editor; + from: number; + to: number; + textContent: string; + cursorPosInsideCodeblock: number; +}; + +export function replaceCodeWithText(editor: Editor): void { + try { + const { from, to } = editor.state.selection; + const cursorPosInsideCodeblock = from; + let replaced = false; + + editor.state.doc.nodesBetween(from, to, (node, pos) => { + if (node.type === editor.state.schema.nodes.codeBlock) { + const startPos = pos; + const endPos = pos + node.nodeSize; + const textContent = node.textContent; + + if (textContent.length === 0) { + editor.chain().focus().toggleCodeBlock().run(); + } else { + transformCodeBlockToParagraphs({ + editor, + from: startPos, + to: endPos, + textContent, + cursorPosInsideCodeblock, + }); + } + + replaced = true; + return false; + } + }); + + if (!replaced) { + console.log("No code block to replace."); + } + } catch (error) { + console.error("An error occurred while replacing code block content:", error); + } +} + +function transformCodeBlockToParagraphs({ + editor, + from, + to, + textContent, + cursorPosInsideCodeblock, +}: ReplaceCodeBlockParams): void { + const { schema } = editor.state; + const { paragraph } = schema.nodes; + const docSize = editor.state.doc.content.size; + + if (from < 0 || to > docSize || from > to) { + console.error("Invalid range for replacement: ", from, to, "in a document of size", docSize); + return; + } + + // Split the textContent by new lines to handle each line as a separate paragraph for Windows (\r\n) and Unix (\n) + const lines = textContent.split(/\r?\n/); + const tr = editor.state.tr; + let insertPos = from; + + // Remove the code block first + tr.delete(from, to); + + // For each line, create a paragraph node and insert it + lines.forEach((line) => { + // if the line is empty, create a paragraph node with no content + const paragraphNode = line.length === 0 ? paragraph.create({}) : paragraph.create({}, schema.text(line)); + tr.insert(insertPos, paragraphNode); + insertPos += paragraphNode.nodeSize; + }); + + // Now persist the focus to the converted paragraph + const parentNodeOffset = findParentNode((node) => node.type === schema.nodes.codeBlock)(editor.state.selection)?.pos; + + if (parentNodeOffset === undefined) throw new Error("Invalid code block offset"); + + const lineNumber = getLineNumber(textContent, cursorPosInsideCodeblock, parentNodeOffset); + const cursorPosOutsideCodeblock = cursorPosInsideCodeblock + (lineNumber - 1); + + editor.view.dispatch(tr); + editor.chain().focus(cursorPosOutsideCodeblock).run(); +} + +/** + * Calculates the line number where the cursor is located inside the code block. + * Assumes the indexing of the content inside the code block is like ProseMirror's indexing. + * + * @param {string} textContent - The content of the code block. + * @param {number} cursorPosition - The absolute cursor position in the document. + * @param {number} codeBlockNodePos - The starting position of the code block node in the document. + * @returns {number} The 1-based line number where the cursor is located. + */ +function getLineNumber(textContent: string, cursorPosition: number, codeBlockNodePos: number): number { + // Split the text content into lines, handling both Unix and Windows newlines + const lines = textContent.split(/\r?\n/); + const cursorPosInsideCodeblockRelative = cursorPosition - codeBlockNodePos; + + let startPosition = 0; + let lineNumber = 0; + + for (let i = 0; i < lines.length; i++) { + // Calculate the end position of the current line + const endPosition = startPosition + lines[i].length + 1; // +1 for the newline character + + // Check if the cursor position is within the current line + if (cursorPosInsideCodeblockRelative >= startPosition && cursorPosInsideCodeblockRelative <= endPosition) { + lineNumber = i + 1; // Line numbers are 1-based + break; + } + + // Update the start position for the next line + startPosition = endPosition; + } + + return lineNumber; +} diff --git a/packages/editor/core/src/ui/extensions/custom-list-keymap/list-keymap.ts b/packages/editor/core/src/ui/extensions/custom-list-keymap/list-keymap.ts index db1264f5726..f2b6dd99957 100644 --- a/packages/editor/core/src/ui/extensions/custom-list-keymap/list-keymap.ts +++ b/packages/editor/core/src/ui/extensions/custom-list-keymap/list-keymap.ts @@ -69,7 +69,7 @@ export const ListKeymap = ({ tabIndex }: { tabIndex?: number }) => return handled; } catch (e) { - console.log("error in handling Backspac:", e); + console.log("Error in handling Delete:", e); return false; } }, @@ -104,7 +104,7 @@ export const ListKeymap = ({ tabIndex }: { tabIndex?: number }) => return handled; } catch (e) { - console.log("error in handling Backspac:", e); + console.log("Error in handling Backspace:", e); return false; } }, From b1c7e6ae2092e930574dc00b5fe314bd4510a4db Mon Sep 17 00:00:00 2001 From: "M. Palanikannan" <73993394+Palanikannan1437@users.noreply.github.com> Date: Fri, 7 Jun 2024 12:36:19 +0530 Subject: [PATCH 026/307] [WEB-1526] feat: add auto merge behaviour to task lists and fix infinite backspace case (#4703) * feat: add auto merge behaviour to task lists * fix: unhandled cases for taskItem and taskList * fix: css task list such that toggling task list doesn't shift things * fix: task list jumps around while trying create/delete things in between two task lists * fix: remove filtering for generic transactions i.e. transactions with some meta data while tying to join things --- packages/editor/core/src/styles/editor.css | 9 ++++++ .../custom-list-keymap/list-helpers.ts | 16 +++++++--- .../editor/core/src/ui/extensions/keymap.tsx | 30 ++++++------------- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/packages/editor/core/src/styles/editor.css b/packages/editor/core/src/styles/editor.css index 00312cb00f2..f76b6b1bfac 100644 --- a/packages/editor/core/src/styles/editor.css +++ b/packages/editor/core/src/styles/editor.css @@ -110,6 +110,11 @@ ul[data-type="taskList"] li > label input[type="checkbox"]:checked:hover { } } +/* the p tag just after the ul tag */ +ul[data-type="taskList"] + p { + margin-top: 0.4rem !important; +} + ul[data-type="taskList"] li > label input[type="checkbox"] { position: relative; -webkit-appearance: none; @@ -152,6 +157,10 @@ ul[data-type="taskList"] li > label input[type="checkbox"] { } } +ul[data-type="taskList"] li > div > p { + margin-top: 10px; +} + ul[data-type="taskList"] li[data-checked="true"] > div > p { color: rgb(var(--color-text-400)); text-decoration: line-through; diff --git a/packages/editor/core/src/ui/extensions/custom-list-keymap/list-helpers.ts b/packages/editor/core/src/ui/extensions/custom-list-keymap/list-helpers.ts index 330ebbc12c9..d6ddb8ce062 100644 --- a/packages/editor/core/src/ui/extensions/custom-list-keymap/list-helpers.ts +++ b/packages/editor/core/src/ui/extensions/custom-list-keymap/list-helpers.ts @@ -72,7 +72,7 @@ const getPrevListDepth = (typeOrName: string, state: EditorState) => { // Traverse up the document structure from the adjusted position for (let d = resolvedPos.depth; d > 0; d--) { const node = resolvedPos.node(d); - if (node.type.name === "bulletList" || node.type.name === "orderedList") { + if (node.type.name === "bulletList" || node.type.name === "orderedList" || node.type.name === "taskList") { // Increment depth for each list ancestor found depth++; } @@ -146,6 +146,8 @@ export const handleBackspace = (editor: Editor, name: string, parentListTypes: s if (!isAtStartOfNode(editor.state)) { return false; } + + // is the paragraph node inside of the current list item (maybe with a hard break) const isParaSibling = isCurrentParagraphASibling(editor.state); const isCurrentListItemSublist = prevListIsHigher(name, editor.state); const listItemPos = findListItemPos(name, editor.state); @@ -306,7 +308,10 @@ const isCurrentParagraphASibling = (state: EditorState): boolean => { const currentParagraphNode = $from.parent; // Get the current node where the selection is. // Ensure we're in a paragraph and the parent is a list item. - if (currentParagraphNode.type.name === "paragraph" && listItemNode.type.name === "listItem") { + if ( + currentParagraphNode.type.name === "paragraph" && + (listItemNode.type.name === "listItem" || listItemNode.type.name === "taskItem") + ) { let paragraphNodesCount = 0; listItemNode.forEach((child) => { if (child.type.name === "paragraph") { @@ -327,16 +332,19 @@ export function isCursorInSubList(editor: Editor) { // Check if the current node is a list item const listItem = editor.schema.nodes.listItem; + const taskItem = editor.schema.nodes.taskItem; // Traverse up the document tree from the current position for (let depth = $from.depth; depth > 0; depth--) { const node = $from.node(depth); - if (node.type === listItem) { + if (node.type === listItem || node.type === taskItem) { // If the parent of the list item is also a list, it's a sub-list const parent = $from.node(depth - 1); if ( parent && - (parent.type === editor.schema.nodes.bulletList || parent.type === editor.schema.nodes.orderedList) + (parent.type === editor.schema.nodes.bulletList || + parent.type === editor.schema.nodes.orderedList || + parent.type === editor.schema.nodes.taskList) ) { return true; } diff --git a/packages/editor/core/src/ui/extensions/keymap.tsx b/packages/editor/core/src/ui/extensions/keymap.tsx index 2e0bdd1fe94..43b4e343537 100644 --- a/packages/editor/core/src/ui/extensions/keymap.tsx +++ b/packages/editor/core/src/ui/extensions/keymap.tsx @@ -15,9 +15,7 @@ declare module "@tiptap/core" { } } -function autoJoin(tr: Transaction, newTr: Transaction, nodeType: NodeType) { - if (!tr.isGeneric) return false; - +function autoJoin(tr: Transaction, newTr: Transaction, nodeTypes: NodeType[]) { // Find all ranges where we might want to join. const ranges: Array = []; for (let i = 0; i < tr.mapping.maps.length; i++) { @@ -28,7 +26,7 @@ function autoJoin(tr: Transaction, newTr: Transaction, nodeType: NodeType) { // Figure out which joinable points exist inside those ranges, // by checking all node boundaries in their parent nodes. - const joinable = []; + const joinable: number[] = []; for (let i = 0; i < ranges.length; i += 2) { const from = ranges[i], to = ranges[i + 1]; @@ -40,7 +38,7 @@ function autoJoin(tr: Transaction, newTr: Transaction, nodeType: NodeType) { if (!after) break; if (index && joinable.indexOf(pos) == -1) { const before = parent.child(index - 1); - if (before.type == after.type && before.type === nodeType) joinable.push(pos); + if (before.type == after.type && nodeTypes.includes(before.type)) joinable.push(pos); } pos += after.nodeSize; } @@ -88,25 +86,15 @@ export const CustomKeymap = Extension.create({ // Create a new transaction. const newTr = newState.tr; - let joined = false; - for (const transaction of transactions) { - const anotherJoin = autoJoin(transaction, newTr, newState.schema.nodes["orderedList"]); - joined = anotherJoin || joined; - } - if (joined) { - return newTr; - } - }, - }), - new Plugin({ - key: new PluginKey("unordered-list-merging"), - appendTransaction(transactions, oldState, newState) { - // Create a new transaction. - const newTr = newState.tr; + const joinableNodes = [ + newState.schema.nodes["orderedList"], + newState.schema.nodes["taskList"], + newState.schema.nodes["bulletList"], + ]; let joined = false; for (const transaction of transactions) { - const anotherJoin = autoJoin(transaction, newTr, newState.schema.nodes["bulletList"]); + const anotherJoin = autoJoin(transaction, newTr, joinableNodes); joined = anotherJoin || joined; } if (joined) { From cdb932ab67165d79460c9522215628a79623b005 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Fri, 7 Jun 2024 13:59:31 +0530 Subject: [PATCH 027/307] [WEB-1201] dev: dropdowns (#4721) * chore: lodash package added * chore: dropdown key down hook added * dev: dropdown component * chore: build error and code refactor * chore: readme file updated --- packages/ui/src/dropdown/Readme.md | 44 +++++ packages/ui/src/dropdown/common/button.tsx | 38 ++++ packages/ui/src/dropdown/common/index.ts | 4 + .../ui/src/dropdown/common/input-search.tsx | 58 ++++++ packages/ui/src/dropdown/common/loader.tsx | 9 + packages/ui/src/dropdown/common/options.tsx | 88 +++++++++ packages/ui/src/dropdown/dropdown.d.ts | 94 ++++++++++ packages/ui/src/dropdown/index.ts | 3 + packages/ui/src/dropdown/multi-select.tsx | 167 ++++++++++++++++++ packages/ui/src/dropdown/single-select.tsx | 166 +++++++++++++++++ .../ui/src/hooks/use-dropdown-key-pressed.ts | 36 ++++ packages/ui/src/index.ts | 1 + 12 files changed, 708 insertions(+) create mode 100644 packages/ui/src/dropdown/Readme.md create mode 100644 packages/ui/src/dropdown/common/button.tsx create mode 100644 packages/ui/src/dropdown/common/index.ts create mode 100644 packages/ui/src/dropdown/common/input-search.tsx create mode 100644 packages/ui/src/dropdown/common/loader.tsx create mode 100644 packages/ui/src/dropdown/common/options.tsx create mode 100644 packages/ui/src/dropdown/dropdown.d.ts create mode 100644 packages/ui/src/dropdown/index.ts create mode 100644 packages/ui/src/dropdown/multi-select.tsx create mode 100644 packages/ui/src/dropdown/single-select.tsx create mode 100644 packages/ui/src/hooks/use-dropdown-key-pressed.ts diff --git a/packages/ui/src/dropdown/Readme.md b/packages/ui/src/dropdown/Readme.md new file mode 100644 index 00000000000..314347b1e6a --- /dev/null +++ b/packages/ui/src/dropdown/Readme.md @@ -0,0 +1,44 @@ +Below is a detailed list of the props included: + +### Root Props +- value: string | string[]; - Current selected value. +- onChange: (value: string | string []) => void; - Callback function for handling value changes. +- options: TDropdownOption[] | undefined; - Array of options. +- onOpen?: () => void; - Callback function triggered when the dropdown opens. +- onClose?: () => void; - Callback function triggered when the dropdown closes. +- containerClassName?: (isOpen: boolean) => string; - Function to return the class name for the container based on the open state. +- tabIndex?: number; - Sets the tab index for the dropdown. +- placement?: Placement; - Determines the placement of the dropdown (e.g., top, bottom, left, right). +- disabled?: boolean; - Disables the dropdown if set to true. + +--- + +### Button Props +- buttonContent?: (isOpen: boolean) => React.ReactNode; - Function to render the content of the button based on the open state. +- buttonContainerClassName?: string; - Class name for the button container. +- buttonClassName?: string; - Class name for the button itself. + +--- + +### Input Props +- disableSearch?: boolean; - Disables the search input if set to true. +- inputPlaceholder?: string; - Placeholder text for the search input. +- inputClassName?: string; - Class name for the search input. +- inputIcon?: React.ReactNode; - Icon to be displayed in the search input. +- inputContainerClassName?: string; - Class name for the search input container. + +--- + +### Options Props +- keyExtractor: (option: TDropdownOption) => string; - Function to extract the key from each option. +- optionsContainerClassName?: string; - Class name for the options container. +- queryArray: string[]; - Array of strings to be used for querying the options. +- sortByKey: string; - Key to sort the options by. +- firstItem?: (optionValue: string) => boolean; - Function to determine if an option should be the first item. +- renderItem?: ({ value, selected }: { value: string; selected: boolean }) => React.ReactNode; - Function to render each option. +- loader?: React.ReactNode; - Loader element to be displayed while options are being loaded. +- disableSorting?: boolean; - Disables sorting of the options if set to true. + +--- + +These properties offer extensive control over the dropdown's behavior and presentation, making it a highly versatile component suitable for various scenarios. \ No newline at end of file diff --git a/packages/ui/src/dropdown/common/button.tsx b/packages/ui/src/dropdown/common/button.tsx new file mode 100644 index 00000000000..c0a4627e9f5 --- /dev/null +++ b/packages/ui/src/dropdown/common/button.tsx @@ -0,0 +1,38 @@ +import React, { Fragment } from "react"; +// headless ui +import { Combobox } from "@headlessui/react"; +// helper +import { cn } from "../../../helpers"; +import { IMultiSelectDropdownButton, ISingleSelectDropdownButton } from "../dropdown"; + +export const DropdownButton: React.FC = (props) => { + const { + isOpen, + buttonContent, + buttonClassName, + buttonContainerClassName, + handleOnClick, + value, + setReferenceElement, + disabled, + } = props; + return ( + + + + ); +}; diff --git a/packages/ui/src/dropdown/common/index.ts b/packages/ui/src/dropdown/common/index.ts new file mode 100644 index 00000000000..f9a6d738851 --- /dev/null +++ b/packages/ui/src/dropdown/common/index.ts @@ -0,0 +1,4 @@ +export * from "./input-search"; +export * from "./button"; +export * from "./options"; +export * from "./loader"; diff --git a/packages/ui/src/dropdown/common/input-search.tsx b/packages/ui/src/dropdown/common/input-search.tsx new file mode 100644 index 00000000000..10fc258e14a --- /dev/null +++ b/packages/ui/src/dropdown/common/input-search.tsx @@ -0,0 +1,58 @@ +import React, { FC, useEffect, useRef } from "react"; +// headless ui +import { Combobox } from "@headlessui/react"; +// icons +import { Search } from "lucide-react"; +// helpers +import { cn } from "../../../helpers"; + +interface IInputSearch { + isOpen: boolean; + query: string; + updateQuery: (query: string) => void; + inputIcon?: React.ReactNode; + inputContainerClassName?: string; + inputClassName?: string; + inputPlaceholder?: string; +} + +export const InputSearch: FC = (props) => { + const { isOpen, query, updateQuery, inputIcon, inputContainerClassName, inputClassName, inputPlaceholder } = props; + + const inputRef = useRef(null); + + const searchInputKeyDown = (e: React.KeyboardEvent) => { + if (query !== "" && e.key === "Escape") { + e.stopPropagation(); + updateQuery(""); + } + }; + + useEffect(() => { + if (isOpen) { + inputRef.current && inputRef.current.focus(); + } + }, [isOpen]); + return ( +
+ {inputIcon ? <>{inputIcon} :
+ ); +}; diff --git a/packages/ui/src/dropdown/common/loader.tsx b/packages/ui/src/dropdown/common/loader.tsx new file mode 100644 index 00000000000..0ec1f053b68 --- /dev/null +++ b/packages/ui/src/dropdown/common/loader.tsx @@ -0,0 +1,9 @@ +import React from "react"; + +export const DropdownOptionsLoader = () => ( +
+ {Array.from({ length: 6 }, (_, i) => ( +
+ ))} +
+); diff --git a/packages/ui/src/dropdown/common/options.tsx b/packages/ui/src/dropdown/common/options.tsx new file mode 100644 index 00000000000..f17a431a14d --- /dev/null +++ b/packages/ui/src/dropdown/common/options.tsx @@ -0,0 +1,88 @@ +import React from "react"; +// headless ui +import { Combobox } from "@headlessui/react"; +// icons +import { Check } from "lucide-react"; +// components +import { DropdownOptionsLoader, InputSearch } from "."; +// helpers +import { cn } from "../../../helpers"; +// types +import { IMultiSelectDropdownOptions, ISingleSelectDropdownOptions } from "../dropdown"; + +export const DropdownOptions: React.FC = (props) => { + const { + isOpen, + query, + setQuery, + inputIcon, + inputPlaceholder, + inputClassName, + inputContainerClassName, + disableSearch, + keyExtractor, + options, + value, + renderItem, + loader, + } = props; + return ( + <> + {!disableSearch && ( + setQuery(query)} + inputIcon={inputIcon} + inputPlaceholder={inputPlaceholder} + inputClassName={inputClassName} + inputContainerClassName={inputContainerClassName} + /> + )} +
+ <> + {options ? ( + options.length > 0 ? ( + options?.map((option) => ( + + cn( + "flex w-full cursor-pointer select-none items-center justify-between gap-2 truncate rounded px-1 py-1.5", + { + "bg-custom-background-80": active, + "text-custom-text-100": selected, + "text-custom-text-200": !selected, + }, + option.className && option.className({ active, selected }) + ) + } + > + {({ selected }) => ( + <> + {renderItem ? ( + <>{renderItem({ value: option.data[option.value], selected })} + ) : ( + <> + {value} + {selected && } + + )} + + )} + + )) + ) : ( +

No matching results

+ ) + ) : loader ? ( + <> {loader} + ) : ( + + )} + +
+ + ); +}; diff --git a/packages/ui/src/dropdown/dropdown.d.ts b/packages/ui/src/dropdown/dropdown.d.ts new file mode 100644 index 00000000000..1f109add6f0 --- /dev/null +++ b/packages/ui/src/dropdown/dropdown.d.ts @@ -0,0 +1,94 @@ +import { Placement } from "@popperjs/core"; + +export interface IDropdown { + // root props + onOpen?: () => void; + onClose?: () => void; + containerClassName?: (isOpen: boolean) => string; + tabIndex?: number; + placement?: Placement; + disabled?: boolean; + + // button props + buttonContent?: (isOpen: boolean) => React.ReactNode; + buttonContainerClassName?: string; + buttonClassName?: string; + + // input props + disableSearch?: boolean; + inputPlaceholder?: string; + inputClassName?: string; + inputIcon?: React.ReactNode; + inputContainerClassName?: string; + + // options props + keyExtractor: (option: TDropdownOption) => string; + optionsContainerClassName?: string; + queryArray: string[]; + sortByKey: string; + firstItem?: (optionValue: string) => boolean; + renderItem?: ({ value, selected }: { value: string; selected: boolean }) => React.ReactNode; + loader?: React.ReactNode; + disableSorting?: boolean; +} + +export interface TDropdownOption { + data: any; + value: string; + className?: ({ active, selected }: { active: boolean; selected: boolean }) => string; +} + +export interface IMultiSelectDropdown extends IDropdown { + value: string[]; + onChange: (value: string[]) => void; + options: TDropdownOption[] | undefined; +} + +export interface ISingleSelectDropdown extends IDropdown { + value: string; + onChange: (value: string) => void; + options: TDropdownOption[] | undefined; +} + +export interface IDropdownButton { + isOpen: boolean; + buttonContent?: (isOpen: boolean) => React.ReactNode; + buttonClassName?: string; + buttonContainerClassName?: string; + handleOnClick: (e: React.MouseEvent) => void; + setReferenceElement: (element: HTMLButtonElement | null) => void; + disabled?: boolean; +} + +export interface IMultiSelectDropdownButton extends IDropdownButton { + value: string[]; +} + +export interface ISingleSelectDropdownButton extends IDropdownButton { + value: string; +} + +export interface IDropdownOptions { + isOpen: boolean; + query: string; + setQuery: (query: string) => void; + + inputPlaceholder?: string; + inputClassName?: string; + inputIcon?: React.ReactNode; + inputContainerClassName?: string; + disableSearch?: boolean; + + keyExtractor: (option: TDropdownOption) => string; + renderItem: (({ value, selected }: { value: string; selected: boolean }) => React.ReactNode) | undefined; + options: TDropdownOption[] | undefined; + loader?: React.ReactNode; +} + +export interface IMultiSelectDropdownOptions extends IDropdownOptions { + value: string[]; +} + +export interface ISingleSelectDropdownOptions extends IDropdownOptions { + value: string; +} diff --git a/packages/ui/src/dropdown/index.ts b/packages/ui/src/dropdown/index.ts new file mode 100644 index 00000000000..a15df9567fc --- /dev/null +++ b/packages/ui/src/dropdown/index.ts @@ -0,0 +1,3 @@ +export * from "./common"; +export * from "./multi-select"; +export * from "./single-select"; diff --git a/packages/ui/src/dropdown/multi-select.tsx b/packages/ui/src/dropdown/multi-select.tsx new file mode 100644 index 00000000000..08bc5863869 --- /dev/null +++ b/packages/ui/src/dropdown/multi-select.tsx @@ -0,0 +1,167 @@ +import React, { FC, useMemo, useRef, useState } from "react"; +import sortBy from "lodash/sortBy"; +// headless ui +import { Combobox } from "@headlessui/react"; +// popper-js +import { usePopper } from "react-popper"; +// components +import { DropdownButton } from "./common"; +import { DropdownOptions } from "./common/options"; +// hooks +import { useDropdownKeyPressed } from "../hooks/use-dropdown-key-pressed"; +import useOutsideClickDetector from "../hooks/use-outside-click-detector"; +// helper +import { cn } from "../../helpers"; +// types +import { IMultiSelectDropdown } from "./dropdown"; + +export const MultiSelectDropdown: FC = (props) => { + const { + value, + onChange, + options, + onOpen, + onClose, + containerClassName, + tabIndex, + placement, + disabled, + buttonContent, + buttonContainerClassName, + buttonClassName, + disableSearch, + inputPlaceholder, + inputClassName, + inputIcon, + inputContainerClassName, + keyExtractor, + optionsContainerClassName, + queryArray, + sortByKey, + firstItem, + renderItem, + loader = false, + disableSorting, + } = props; + + // states + const [isOpen, setIsOpen] = useState(false); + const [query, setQuery] = useState(""); + const [popperElement, setPopperElement] = useState(null); + // refs + const dropdownRef = useRef(null); + // popper-js refs + const [referenceElement, setReferenceElement] = useState(null); + + // popper-js init + const { styles, attributes } = usePopper(referenceElement, popperElement, { + placement: placement ?? "bottom-start", + modifiers: [ + { + name: "preventOverflow", + options: { + padding: 12, + }, + }, + ], + }); + + // handlers + const toggleDropdown = () => { + if (!isOpen) onOpen?.(); + setIsOpen((prevIsOpen) => !prevIsOpen); + if (isOpen) onClose?.(); + }; + + const handleOnClick = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + toggleDropdown(); + }; + + const handleClose = () => { + if (!isOpen) return; + setIsOpen(false); + onClose?.(); + setQuery?.(""); + }; + + // options + const sortedOptions = useMemo(() => { + if (!options) return undefined; + + const filteredOptions = (options || []).filter((options) => { + const queryString = queryArray.map((query) => options.data[query]).join(" "); + return queryString.toLowerCase().includes(query.toLowerCase()); + }); + + if (disableSorting) return filteredOptions; + + return sortBy(filteredOptions, [ + (option) => firstItem && firstItem(option.data[option.value]), + (option) => !(value ?? []).includes(option.data[option.value]), + () => sortByKey && sortByKey.toLowerCase(), + ]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [query, options]); + + // hooks + const handleKeyDown = useDropdownKeyPressed(toggleDropdown, handleClose); + + useOutsideClickDetector(dropdownRef, handleClose); + + return ( + + + + {isOpen && ( + +
+ +
+
+ )} +
+ ); +}; diff --git a/packages/ui/src/dropdown/single-select.tsx b/packages/ui/src/dropdown/single-select.tsx new file mode 100644 index 00000000000..045296873f2 --- /dev/null +++ b/packages/ui/src/dropdown/single-select.tsx @@ -0,0 +1,166 @@ +import React, { FC, useMemo, useRef, useState } from "react"; +import sortBy from "lodash/sortBy"; +// headless ui +import { Combobox } from "@headlessui/react"; +// popper-js +import { usePopper } from "react-popper"; +// components +import { DropdownButton } from "./common"; +import { DropdownOptions } from "./common/options"; +// hooks +import { useDropdownKeyPressed } from "../hooks/use-dropdown-key-pressed"; +import useOutsideClickDetector from "../hooks/use-outside-click-detector"; +// helper +import { cn } from "../../helpers"; +// types +import { ISingleSelectDropdown } from "./dropdown"; + +export const Dropdown: FC = (props) => { + const { + value, + onChange, + options, + onOpen, + onClose, + containerClassName, + tabIndex, + placement, + disabled, + buttonContent, + buttonContainerClassName, + buttonClassName, + disableSearch, + inputPlaceholder, + inputClassName, + inputIcon, + inputContainerClassName, + keyExtractor, + optionsContainerClassName, + queryArray, + sortByKey, + firstItem, + renderItem, + loader = false, + disableSorting, + } = props; + + // states + const [isOpen, setIsOpen] = useState(false); + const [query, setQuery] = useState(""); + const [popperElement, setPopperElement] = useState(null); + // refs + const dropdownRef = useRef(null); + // popper-js refs + const [referenceElement, setReferenceElement] = useState(null); + + // popper-js init + const { styles, attributes } = usePopper(referenceElement, popperElement, { + placement: placement ?? "bottom-start", + modifiers: [ + { + name: "preventOverflow", + options: { + padding: 12, + }, + }, + ], + }); + + // handlers + const toggleDropdown = () => { + if (!isOpen) onOpen?.(); + setIsOpen((prevIsOpen) => !prevIsOpen); + if (isOpen) onClose?.(); + }; + + const handleOnClick = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + toggleDropdown(); + }; + + const handleClose = () => { + if (!isOpen) return; + setIsOpen(false); + onClose?.(); + setQuery?.(""); + }; + + // options + const sortedOptions = useMemo(() => { + if (!options) return undefined; + + const filteredOptions = (options || []).filter((options) => { + const queryString = queryArray.map((query) => options.data[query]).join(" "); + return queryString.toLowerCase().includes(query.toLowerCase()); + }); + + if (disableSorting) return filteredOptions; + + return sortBy(filteredOptions, [ + (option) => firstItem && firstItem(option.data[option.value]), + (option) => !(value ?? []).includes(option.data[option.value]), + () => sortByKey && sortByKey.toLowerCase(), + ]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [query, options]); + + // hooks + const handleKeyDown = useDropdownKeyPressed(toggleDropdown, handleClose); + + useOutsideClickDetector(dropdownRef, handleClose); + + return ( + + + + {isOpen && ( + +
+ +
+
+ )} +
+ ); +}; diff --git a/packages/ui/src/hooks/use-dropdown-key-pressed.ts b/packages/ui/src/hooks/use-dropdown-key-pressed.ts new file mode 100644 index 00000000000..15552d34d80 --- /dev/null +++ b/packages/ui/src/hooks/use-dropdown-key-pressed.ts @@ -0,0 +1,36 @@ +import { useCallback } from "react"; + +type TUseDropdownKeyPressed = { + ( + onEnterKeyDown: () => void, + onEscKeyDown: () => void, + stopPropagation?: boolean + ): (event: React.KeyboardEvent) => void; +}; + +export const useDropdownKeyPressed: TUseDropdownKeyPressed = (onEnterKeyDown, onEscKeyDown, stopPropagation = true) => { + const stopEventPropagation = useCallback( + (event: React.KeyboardEvent) => { + if (stopPropagation) { + event.stopPropagation(); + event.preventDefault(); + } + }, + [stopPropagation] + ); + + const handleKeyDown = useCallback( + (event: React.KeyboardEvent) => { + if (event.key === "Enter") { + stopEventPropagation(event); + onEnterKeyDown(); + } else if (event.key === "Escape") { + stopEventPropagation(event); + onEscKeyDown(); + } else if (event.key === "Tab") onEscKeyDown(); + }, + [onEnterKeyDown, onEscKeyDown, stopEventPropagation] + ); + + return handleKeyDown; +}; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 23fc7ed62fd..c3c0cd4f106 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -4,6 +4,7 @@ export * from "./badge"; export * from "./button"; export * from "./emoji"; export * from "./dropdowns"; +export * from "./dropdown"; export * from "./form-fields"; export * from "./icons"; export * from "./progress"; From 1c849103f98c0ecabb7e1328eadfdc3f8277fcd8 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 7 Jun 2024 13:59:57 +0530 Subject: [PATCH 028/307] chore: added disabled prop to multiple select components (#4724) * chore: added disabled prop to mutliple select group hoc * style: fix empty space --- .../multiple-select/entity-select-action.tsx | 2 + .../multiple-select/group-select-action.tsx | 2 + .../core/multiple-select/select-group.tsx | 4 +- .../gantt-chart/chart/main-content.tsx | 1 + .../issues/bulk-operations/root.tsx | 4 +- .../issue-layouts/gantt/base-gantt-root.tsx | 2 +- .../issues/issue-layouts/list/block.tsx | 3 +- .../issues/issue-layouts/list/default.tsx | 2 +- .../list/headers/group-by-card.tsx | 4 +- .../issue-layouts/spreadsheet/issue-row.tsx | 4 +- .../spreadsheet/spreadsheet-header.tsx | 2 +- .../spreadsheet/spreadsheet-view.tsx | 1 + web/hooks/use-multiple-select.ts | 49 ++++++++++++++++--- 13 files changed, 62 insertions(+), 18 deletions(-) diff --git a/web/components/core/multiple-select/entity-select-action.tsx b/web/components/core/multiple-select/entity-select-action.tsx index 7b9ca94096b..7672928cd29 100644 --- a/web/components/core/multiple-select/entity-select-action.tsx +++ b/web/components/core/multiple-select/entity-select-action.tsx @@ -18,6 +18,8 @@ export const MultipleSelectEntityAction: React.FC = (props) => { // derived values const isSelected = selectionHelpers.getIsEntitySelected(id); + if (selectionHelpers.isSelectionDisabled) return null; + return ( = (props) => { // derived values const groupSelectionStatus = selectionHelpers.isGroupSelected(groupID); + if (selectionHelpers.isSelectionDisabled) return null; + return ( React.ReactNode; containerRef: React.MutableRefObject; + disabled?: boolean; entities: Record; // { groupID: entityIds[] } }; export const MultipleSelectGroup: React.FC = observer((props) => { - const { children, containerRef, entities } = props; + const { children, containerRef, disabled = false, entities } = props; const helpers = useMultipleSelect({ containerRef, + disabled, entities, }); diff --git a/web/components/gantt-chart/chart/main-content.tsx b/web/components/gantt-chart/chart/main-content.tsx index e5bd7afbfb8..fc62f1bbcee 100644 --- a/web/components/gantt-chart/chart/main-content.tsx +++ b/web/components/gantt-chart/chart/main-content.tsx @@ -118,6 +118,7 @@ export const GanttChartMainContent: React.FC = observer((props) => { entities={{ [GANTT_SELECT_GROUP]: chartBlocks?.map((block) => block.id) ?? [], }} + disabled > {(helpers) => ( <> diff --git a/web/components/issues/bulk-operations/root.tsx b/web/components/issues/bulk-operations/root.tsx index f92e0227965..741a341bec6 100644 --- a/web/components/issues/bulk-operations/root.tsx +++ b/web/components/issues/bulk-operations/root.tsx @@ -11,11 +11,11 @@ type Props = { }; export const IssueBulkOperationsRoot: React.FC = observer((props) => { - const { className } = props; + const { className, selectionHelpers } = props; // store hooks const { isSelectionActive } = useMultipleSelectStore(); - if (!isSelectionActive) return null; + if (!isSelectionActive || selectionHelpers.isSelectionDisabled) return null; return ; }); diff --git a/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx b/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx index 17df47163f2..2f257c6cd7c 100644 --- a/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx +++ b/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx @@ -72,7 +72,7 @@ export const BaseGanttRoot: React.FC = observer((props: IBaseGan enableBlockMove={isAllowed} enableReorder={appliedDisplayFilters?.order_by === "sort_order" && isAllowed} enableAddBlock={isAllowed} - enableSelection={isAllowed} + enableSelection={false} quickAdd={ enableIssueCreation && isAllowed ? ( diff --git a/web/components/issues/issue-layouts/list/block.tsx b/web/components/issues/issue-layouts/list/block.tsx index 42f7afff686..dabcf3a240b 100644 --- a/web/components/issues/issue-layouts/list/block.tsx +++ b/web/components/issues/issue-layouts/list/block.tsx @@ -102,6 +102,7 @@ export const IssueBlock = observer((props: IssueBlockProps) => { const isIssueSelected = selectionHelpers.getIsEntitySelected(issue.id); const isIssueActive = selectionHelpers.getIsEntityActive(issue.id); const isSubIssue = nestingLevel !== 0; + const canSelectIssues = canEditIssueProperties && !selectionHelpers.isSelectionDisabled; const marginLeft = `${spacingLeft}px`; @@ -149,7 +150,7 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
{/* select checkbox */} - {projectId && canEditIssueProperties && ( + {projectId && canSelectIssues && ( diff --git a/web/components/issues/issue-layouts/list/default.tsx b/web/components/issues/issue-layouts/list/default.tsx index daddc572f33..321f0640f8f 100644 --- a/web/components/issues/issue-layouts/list/default.tsx +++ b/web/components/issues/issue-layouts/list/default.tsx @@ -134,7 +134,7 @@ const GroupByList: React.FC = observer((props) => { return (
{groups && ( - + {(helpers) => ( <>
{ const existingIssuesListModalPayload = moduleId ? { module: moduleId.toString() } : { cycle: true }; const isGroupSelectionEmpty = selectionHelpers.isGroupSelected(groupID) === "empty"; // auth - const canSelectIssues = canEditProperties(projectId?.toString()); + const canSelectIssues = canEditProperties(projectId?.toString()) && !selectionHelpers.isSelectionDisabled; const handleAddIssuesToView = async (data: ISearchIssueResponse[]) => { if (!workspaceSlug || !projectId) return; @@ -83,7 +83,7 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => { return ( <> -
+
{canSelectIssues && (
{ const subIssuesCount = issueDetail?.sub_issues_count ?? 0; const isIssueSelected = selectionHelpers.getIsEntitySelected(issueDetail.id); + const canSelectIssues = !disableUserActions && !selectionHelpers.isSelectionDisabled; + //TODO: add better logic. This is to have a min width for ID/Key based on the length of project identifier const keyMinWidth = (getProjectIdentifierById(issueDetail.project_id)?.length ?? 0 + 5) * 7; @@ -250,7 +252,7 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => { >
{/* select checkbox */} - {projectId && !disableUserActions && ( + {projectId && canSelectIssues && ( diff --git a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-header.tsx b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-header.tsx index 74f65db3179..6052454d1b5 100644 --- a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-header.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-header.tsx @@ -38,7 +38,7 @@ export const SpreadsheetHeader = observer((props: Props) => { // derived values const isGroupSelectionEmpty = selectionHelpers.isGroupSelected(SPREADSHEET_SELECT_GROUP) === "empty"; // auth - const canSelectIssues = canEditProperties(projectId?.toString()); + const canSelectIssues = canEditProperties(projectId?.toString()) && !selectionHelpers.isSelectionDisabled; return ( diff --git a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx index 8287ea74600..169d4f94cf2 100644 --- a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx @@ -81,6 +81,7 @@ export const SpreadsheetView: React.FC = observer((props) => { entities={{ [SPREADSHEET_SELECT_GROUP]: issueIds, }} + disabled > {(helpers) => ( <> diff --git a/web/hooks/use-multiple-select.ts b/web/hooks/use-multiple-select.ts index 47f67362423..fdb3c0b1c17 100644 --- a/web/hooks/use-multiple-select.ts +++ b/web/hooks/use-multiple-select.ts @@ -10,6 +10,7 @@ export type TEntityDetails = { type Props = { containerRef: React.MutableRefObject; + disabled: boolean; entities: Record; // { groupID: entityIds[] } }; @@ -25,10 +26,11 @@ export type TSelectionHelper = { getIsEntityActive: (entityID: string) => boolean; handleGroupClick: (groupID: string) => void; isGroupSelected: (groupID: string) => "empty" | "partial" | "complete"; + isSelectionDisabled: boolean; }; export const useMultipleSelect = (props: Props) => { - const { containerRef, entities } = props; + const { containerRef, disabled, entities } = props; // router const router = useRouter(); // store hooks @@ -97,6 +99,8 @@ export const useMultipleSelect = (props: Props) => { const handleActiveEntityChange = useCallback( (entityDetails: TEntityDetails | null, shouldScroll: boolean = true) => { + if (disabled) return; + if (!entityDetails) { updateActiveEntityDetails(null); updatePreviousActiveEntity(null); @@ -134,6 +138,7 @@ export const useMultipleSelect = (props: Props) => { }, [ containerRef, + disabled, getPreviousAndNextEntities, updateActiveEntityDetails, updateNextActiveEntity, @@ -147,6 +152,8 @@ export const useMultipleSelect = (props: Props) => { shouldScroll: boolean = true, forceAction: "force-add" | "force-remove" | null = null ) => { + if (disabled) return; + if (Array.isArray(entityDetails)) { bulkUpdateSelectedEntityDetails(entityDetails, forceAction === "force-add" ? "add" : "remove"); if (forceAction === "force-add" && entityDetails.length > 0) { @@ -176,7 +183,13 @@ export const useMultipleSelect = (props: Props) => { handleActiveEntityChange(entityDetails, shouldScroll); } }, - [bulkUpdateSelectedEntityDetails, getIsEntitySelected, handleActiveEntityChange, updateSelectedEntityDetails] + [ + bulkUpdateSelectedEntityDetails, + disabled, + getIsEntitySelected, + handleActiveEntityChange, + updateSelectedEntityDetails, + ] ); /** @@ -187,6 +200,7 @@ export const useMultipleSelect = (props: Props) => { */ const handleEntityClick = useCallback( (e: React.MouseEvent, entityID: string, groupID: string) => { + if (disabled) return; const lastSelectedEntityDetails = getLastSelectedEntityDetails(); if (e.shiftKey && lastSelectedEntityDetails) { const currentEntityIndex = entitiesList.findIndex((entity) => entity?.entityID === entityID); @@ -223,7 +237,7 @@ export const useMultipleSelect = (props: Props) => { handleEntitySelection({ entityID, groupID }, false); }, - [entitiesList, handleEntitySelection, getLastSelectedEntityDetails] + [disabled, entitiesList, handleEntitySelection, getLastSelectedEntityDetails] ); /** @@ -248,15 +262,19 @@ export const useMultipleSelect = (props: Props) => { */ const handleGroupClick = useCallback( (groupID: string) => { + if (disabled) return; + const groupEntities = entitiesList.filter((entity) => entity.groupID === groupID); const groupSelectionStatus = isGroupSelected(groupID); handleEntitySelection(groupEntities, false, groupSelectionStatus === "empty" ? "force-add" : "force-remove"); }, - [entitiesList, handleEntitySelection, isGroupSelected] + [disabled, entitiesList, handleEntitySelection, isGroupSelected] ); // clear selection on escape key press useEffect(() => { + if (disabled) return; + const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") clearSelection(); }; @@ -265,10 +283,12 @@ export const useMultipleSelect = (props: Props) => { return () => { window.removeEventListener("keydown", handleKeyDown); }; - }, [clearSelection]); + }, [clearSelection, disabled]); // select entities on shift + arrow up/down key press useEffect(() => { + if (disabled) return; + const handleKeyDown = (e: KeyboardEvent) => { if (!e.shiftKey) return; @@ -291,6 +311,7 @@ export const useMultipleSelect = (props: Props) => { window.removeEventListener("keydown", handleKeyDown); }; }, [ + disabled, getActiveEntityDetails, handleEntitySelection, getLastSelectedEntityDetails, @@ -299,6 +320,8 @@ export const useMultipleSelect = (props: Props) => { ]); useEffect(() => { + if (disabled) return; + const handleKeyDown = (e: KeyboardEvent) => { if (e.shiftKey) return; const activeEntityDetails = getActiveEntityDetails(); @@ -331,7 +354,7 @@ export const useMultipleSelect = (props: Props) => { return () => { window.removeEventListener("keydown", handleKeyDown); }; - }, [getActiveEntityDetails, entitiesList, groups, getPreviousAndNextEntities, handleActiveEntityChange]); + }, [disabled, getActiveEntityDetails, entitiesList, groups, getPreviousAndNextEntities, handleActiveEntityChange]); // clear selection on route change useEffect(() => { @@ -346,6 +369,7 @@ export const useMultipleSelect = (props: Props) => { // when entities list change, remove entityIds from the selected entities array, which are not present in the new list useEffect(() => { + if (disabled) return; selectedEntityIds.map((entityID) => { const isEntityPresent = entitiesList.find((en) => en.entityID === entityID); if (!isEntityPresent) { @@ -355,7 +379,7 @@ export const useMultipleSelect = (props: Props) => { } } }); - }, [entitiesList, getEntityDetailsFromEntityID, handleEntitySelection, selectedEntityIds]); + }, [disabled, entitiesList, getEntityDetailsFromEntityID, handleEntitySelection, selectedEntityIds]); /** * @description helper functions for selection @@ -368,8 +392,17 @@ export const useMultipleSelect = (props: Props) => { getIsEntityActive, handleGroupClick, isGroupSelected, + isSelectionDisabled: disabled, }), - [clearSelection, getIsEntityActive, getIsEntitySelected, handleEntityClick, handleGroupClick, isGroupSelected] + [ + clearSelection, + disabled, + getIsEntityActive, + getIsEntitySelected, + handleEntityClick, + handleGroupClick, + isGroupSelected, + ] ); return helpers; From 8b6a48f05cbfd269ec0be98fb1dc389dd6df1612 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:08:21 +0530 Subject: [PATCH 029/307] fix: don't add as a sub-issue if parent has been removed (#4731) --- web/components/issues/sub-issues/root.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/components/issues/sub-issues/root.tsx b/web/components/issues/sub-issues/root.tsx index b697ff20a81..45d6119c1e2 100644 --- a/web/components/issues/sub-issues/root.tsx +++ b/web/components/issues/sub-issues/root.tsx @@ -462,7 +462,9 @@ export const SubIssuesRoot: FC = observer((props) => { toggleCreateIssueModal(false); }} onSubmit={async (_issue: TIssue) => { - await subIssueOperations.addSubIssue(workspaceSlug, projectId, parentIssueId, [_issue.id]); + if (_issue.parent_id) { + await subIssueOperations.addSubIssue(workspaceSlug, projectId, parentIssueId, [_issue.id]); + } }} /> )} From 15918f2d9ffc54e47947239de11ebcafc0bf3714 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:08:56 +0530 Subject: [PATCH 030/307] fix: member list item custom menu placement (#4729) --- web/components/workspace/settings/members-list-item.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/components/workspace/settings/members-list-item.tsx b/web/components/workspace/settings/members-list-item.tsx index 12ba998f364..00a36b54ae3 100644 --- a/web/components/workspace/settings/members-list-item.tsx +++ b/web/components/workspace/settings/members-list-item.tsx @@ -124,7 +124,7 @@ export const WorkspaceMembersListItem: FC = observer((props) => { )} -
+
From dee57326a515d1f1d53a30a8de9d4c287a4af9f5 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:09:27 +0530 Subject: [PATCH 031/307] [WEB-1535] chore: project logo picker improvement (#4718) * chore: emoji icon picker improvement * chore: emoji icon picker improvement --- web/components/project/form.tsx | 66 ++++++++++++++++----------------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/web/components/project/form.tsx b/web/components/project/form.tsx index 547aaa61c8d..e988d395cec 100644 --- a/web/components/project/form.tsx +++ b/web/components/project/form.tsx @@ -144,42 +144,40 @@ export const ProjectDetailsForm: FC = (props) => { {watch("cover_image")!}
-
- ( - setIsOpen(val)} - className="flex items-center justify-center" - buttonClassName="flex items-center justify-center" - label={} - onChange={(val) => { - let logoValue = {}; + ( + setIsOpen(val)} + className="flex items-center justify-center" + buttonClassName="flex h-[52px] w-[52px] flex-shrink-0 items-center justify-center rounded-lg bg-custom-background-90" + label={} + onChange={(val) => { + let logoValue = {}; - if (val?.type === "emoji") - logoValue = { - value: convertHexEmojiToDecimal(val.value.unified), - url: val.value.imageUrl, - }; - else if (val?.type === "icon") logoValue = val.value; + if (val?.type === "emoji") + logoValue = { + value: convertHexEmojiToDecimal(val.value.unified), + url: val.value.imageUrl, + }; + else if (val?.type === "icon") logoValue = val.value; - onChange({ - in_use: val?.type, - [val?.type]: logoValue, - }); - setIsOpen(false); - }} - defaultIconColor={value?.in_use && value.in_use === "icon" ? value?.icon?.color : undefined} - defaultOpen={ - value.in_use && value.in_use === "emoji" ? EmojiIconPickerTypes.EMOJI : EmojiIconPickerTypes.ICON - } - disabled={!isAdmin} - /> - )} - /> -
+ onChange({ + in_use: val?.type, + [val?.type]: logoValue, + }); + setIsOpen(false); + }} + defaultIconColor={value?.in_use && value.in_use === "icon" ? value?.icon?.color : undefined} + defaultOpen={ + value.in_use && value.in_use === "emoji" ? EmojiIconPickerTypes.EMOJI : EmojiIconPickerTypes.ICON + } + disabled={!isAdmin} + /> + )} + />
{watch("name")} From a23c528396c1f74f7fe9e18b8b4e99fc7d00a566 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:09:56 +0530 Subject: [PATCH 032/307] fix: resolved border flicker on issue title (#4727) --- web/components/issues/title-input.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/components/issues/title-input.tsx b/web/components/issues/title-input.tsx index 6499f53308b..f12008ab6ec 100644 --- a/web/components/issues/title-input.tsx +++ b/web/components/issues/title-input.tsx @@ -108,7 +108,7 @@ export const IssueTitleInput: FC = observer((props) => { className={cn( "block w-full resize-none overflow-hidden rounded border-none bg-transparent px-3 py-0 text-2xl font-medium outline-none ring-0", { - "ring-1 ring-red-400 mx-3": title.length === 0, + "ring-1 ring-red-400 mx-3": title.length && title.length === 0, }, className )} From 2331404d46a9cae40c28d1a351f022e2c19db917 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:26:52 +0530 Subject: [PATCH 033/307] chore: profile activity empty state added (#4732) --- packages/types/src/users.d.ts | 1 + .../profile/activity/profile-activity-list.tsx | 8 ++++++-- web/constants/empty-state.ts | 8 ++++++++ web/pages/profile/activity.tsx | 11 +++++++++++ .../empty-state/profile/activity-dark.webp | Bin 0 -> 65728 bytes .../empty-state/profile/activity-light.webp | Bin 0 -> 66542 bytes 6 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 web/public/empty-state/profile/activity-dark.webp create mode 100644 web/public/empty-state/profile/activity-light.webp diff --git a/packages/types/src/users.d.ts b/packages/types/src/users.d.ts index c191cac89cd..3adec55d118 100644 --- a/packages/types/src/users.d.ts +++ b/packages/types/src/users.d.ts @@ -128,6 +128,7 @@ export interface IUserActivityResponse { prev_page_results: boolean; results: IIssueActivity[]; total_pages: number; + total_results: number; } export type UserAuth = { diff --git a/web/components/profile/activity/profile-activity-list.tsx b/web/components/profile/activity/profile-activity-list.tsx index 108589ff6cd..c381690e7f7 100644 --- a/web/components/profile/activity/profile-activity-list.tsx +++ b/web/components/profile/activity/profile-activity-list.tsx @@ -24,10 +24,11 @@ type Props = { perPage: number; updateResultsCount: (count: number) => void; updateTotalPages: (count: number) => void; + updateEmptyState: (state: boolean) => void; }; export const ProfileActivityListPage: React.FC = observer((props) => { - const { cursor, perPage, updateResultsCount, updateTotalPages } = props; + const { cursor, perPage, updateResultsCount, updateTotalPages, updateEmptyState } = props; // store hooks const { data: currentUser } = useUser(); @@ -45,9 +46,12 @@ export const ProfileActivityListPage: React.FC = observer((props) => { useEffect(() => { if (!userProfileActivity) return; + // if no results found then show empty state + if (userProfileActivity.total_results === 0) updateEmptyState(true); + updateTotalPages(userProfileActivity.total_pages); updateResultsCount(userProfileActivity.results.length); - }, [updateResultsCount, updateTotalPages, userProfileActivity]); + }, [updateResultsCount, updateTotalPages, userProfileActivity, updateEmptyState]); // TODO: refactor this component return ( diff --git a/web/constants/empty-state.ts b/web/constants/empty-state.ts index 084e618a253..02dea7b6364 100644 --- a/web/constants/empty-state.ts +++ b/web/constants/empty-state.ts @@ -40,6 +40,7 @@ export enum EmptyStateType { WORKSPACE_SETTINGS_WEBHOOKS = "workspace-settings-webhooks", WORKSPACE_SETTINGS_EXPORT = "workspace-settings-export", WORKSPACE_SETTINGS_IMPORT = "workspace-settings-import", + PROFILE_ACTIVITY = "profile-activity", PROFILE_ASSIGNED = "profile-assigned", PROFILE_CREATED = "profile-created", PROFILE_SUBSCRIBED = "profile-subscribed", @@ -241,6 +242,13 @@ const emptyStateDetails = { path: "/empty-state/workspace-settings/imports", }, // profile + [EmptyStateType.PROFILE_ACTIVITY]: { + key: EmptyStateType.PROFILE_ASSIGNED, + title: "No activities yet", + description: + "Get started by creating a new issue! Add details and properties to it. Explore more in Plane to see your activity.", + path: "/empty-state/profile/activity", + }, [EmptyStateType.PROFILE_ASSIGNED]: { key: EmptyStateType.PROFILE_ASSIGNED, title: "No issues are assigned to you", diff --git a/web/pages/profile/activity.tsx b/web/pages/profile/activity.tsx index 414969445b6..1f724b4ffb4 100644 --- a/web/pages/profile/activity.tsx +++ b/web/pages/profile/activity.tsx @@ -5,7 +5,10 @@ import { Button } from "@plane/ui"; // components import { PageHead } from "@/components/core"; import { SidebarHamburgerToggle } from "@/components/core/sidebar"; +import { EmptyState } from "@/components/empty-state"; import { ProfileActivityListPage } from "@/components/profile"; +// constants +import { EmptyStateType } from "@/constants/empty-state"; //hooks import { useAppTheme } from "@/hooks/store"; // layouts @@ -20,6 +23,7 @@ const ProfileActivityPage: NextPageWithLayout = observer(() => { const [pageCount, setPageCount] = useState(1); const [totalPages, setTotalPages] = useState(0); const [resultsCount, setResultsCount] = useState(0); + const [isEmpty, setIsEmpty] = useState(false); // store hooks const { toggleSidebar } = useAppTheme(); @@ -27,6 +31,8 @@ const ProfileActivityPage: NextPageWithLayout = observer(() => { const updateResultsCount = (count: number) => setResultsCount(count); + const updateEmptyState = (isEmpty: boolean) => setIsEmpty(isEmpty); + const handleLoadMore = () => setPageCount((prev) => prev + 1); const activityPages: JSX.Element[] = []; @@ -38,11 +44,16 @@ const ProfileActivityPage: NextPageWithLayout = observer(() => { perPage={PER_PAGE} updateResultsCount={updateResultsCount} updateTotalPages={updateTotalPages} + updateEmptyState={updateEmptyState} /> ); const isLoadMoreVisible = pageCount < totalPages && resultsCount !== 0; + if (isEmpty) { + return ; + } + return ( <> diff --git a/web/public/empty-state/profile/activity-dark.webp b/web/public/empty-state/profile/activity-dark.webp new file mode 100644 index 0000000000000000000000000000000000000000..d44e6a1de2f3d0dc342b17018f3773a4fcc348aa GIT binary patch literal 65728 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 M{au58Zs4EwKk;|e{{R30 literal 0 HcmV?d00001 diff --git a/web/public/empty-state/profile/activity-light.webp b/web/public/empty-state/profile/activity-light.webp new file mode 100644 index 0000000000000000000000000000000000000000..206685a4c048f4967c8833a459ea9edbaf278f51 GIT binary patch literal 66542 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 Date: Fri, 7 Jun 2024 17:02:30 +0530 Subject: [PATCH 034/307] [WEB-1481] fix: inbox issue list update after changing issue status. (#4715) --- .../inbox/content/inbox-issue-header.tsx | 24 ++++++------- web/components/inbox/sidebar/root.tsx | 6 ++-- web/store/inbox/project-inbox.store.ts | 36 +++++++------------ 3 files changed, 27 insertions(+), 39 deletions(-) diff --git a/web/components/inbox/content/inbox-issue-header.tsx b/web/components/inbox/content/inbox-issue-header.tsx index 8a3401569bc..d94fabeba1f 100644 --- a/web/components/inbox/content/inbox-issue-header.tsx +++ b/web/components/inbox/content/inbox-issue-header.tsx @@ -52,7 +52,7 @@ export const InboxIssueActionsHeader: FC = observer((p const [declineIssueModal, setDeclineIssueModal] = useState(false); const [deleteIssueModal, setDeleteIssueModal] = useState(false); // store - const { currentTab, deleteInboxIssue, inboxIssueIds } = useProjectInbox(); + const { currentTab, deleteInboxIssue, filteredInboxIssueIds } = useProjectInbox(); const { data: currentUser } = useUser(); const { membership: { currentProjectRole }, @@ -76,11 +76,11 @@ export const InboxIssueActionsHeader: FC = observer((p const redirectIssue = (): string | undefined => { let nextOrPreviousIssueId: string | undefined = undefined; - const currentIssueIndex = inboxIssueIds.findIndex((id) => id === currentInboxIssueId); - if (inboxIssueIds[currentIssueIndex + 1]) - nextOrPreviousIssueId = inboxIssueIds[currentIssueIndex + 1]; - else if (inboxIssueIds[currentIssueIndex - 1]) - nextOrPreviousIssueId = inboxIssueIds[currentIssueIndex - 1]; + const currentIssueIndex = filteredInboxIssueIds.findIndex((id) => id === currentInboxIssueId); + if (filteredInboxIssueIds[currentIssueIndex + 1]) + nextOrPreviousIssueId = filteredInboxIssueIds[currentIssueIndex + 1]; + else if (filteredInboxIssueIds[currentIssueIndex - 1]) + nextOrPreviousIssueId = filteredInboxIssueIds[currentIssueIndex - 1]; else nextOrPreviousIssueId = undefined; return nextOrPreviousIssueId; }; @@ -134,22 +134,22 @@ export const InboxIssueActionsHeader: FC = observer((p }) ); - const currentIssueIndex = inboxIssueIds.findIndex((issueId) => issueId === currentInboxIssueId) ?? 0; + const currentIssueIndex = filteredInboxIssueIds.findIndex((issueId) => issueId === currentInboxIssueId) ?? 0; const handleInboxIssueNavigation = useCallback( (direction: "next" | "prev") => { - if (!inboxIssueIds || !currentInboxIssueId) return; + if (!filteredInboxIssueIds || !currentInboxIssueId) return; const activeElement = document.activeElement as HTMLElement; if (activeElement && (activeElement.classList.contains("tiptap") || activeElement.id === "title-input")) return; const nextIssueIndex = direction === "next" - ? (currentIssueIndex + 1) % inboxIssueIds.length - : (currentIssueIndex - 1 + inboxIssueIds.length) % inboxIssueIds.length; - const nextIssueId = inboxIssueIds[nextIssueIndex]; + ? (currentIssueIndex + 1) % filteredInboxIssueIds.length + : (currentIssueIndex - 1 + filteredInboxIssueIds.length) % filteredInboxIssueIds.length; + const nextIssueId = filteredInboxIssueIds[nextIssueIndex]; if (!nextIssueId) return; router.push(`/${workspaceSlug}/projects/${projectId}/inbox?inboxIssueId=${nextIssueId}`); }, - [currentInboxIssueId, currentIssueIndex, inboxIssueIds, projectId, router, workspaceSlug] + [currentInboxIssueId, currentIssueIndex, filteredInboxIssueIds, projectId, router, workspaceSlug] ); const onKeyDown = useCallback( diff --git a/web/components/inbox/sidebar/root.tsx b/web/components/inbox/sidebar/root.tsx index f0cd9657b59..9b7ad2581e3 100644 --- a/web/components/inbox/sidebar/root.tsx +++ b/web/components/inbox/sidebar/root.tsx @@ -44,7 +44,7 @@ export const InboxSidebar: FC = observer((props) => { currentTab, handleCurrentTab, loader, - inboxIssueIds, + filteredInboxIssueIds, inboxIssuePaginationInfo, fetchInboxPaginationIssues, getAppliedFiltersCount, @@ -106,13 +106,13 @@ export const InboxSidebar: FC = observer((props) => { className="w-full h-full overflow-hidden overflow-y-auto vertical-scrollbar scrollbar-md" ref={containerRef} > - {inboxIssueIds.length > 0 ? ( + {filteredInboxIssueIds.length > 0 ? ( ) : (

diff --git a/web/store/inbox/project-inbox.store.ts b/web/store/inbox/project-inbox.store.ts index 2557f79797b..0e80143d597 100644 --- a/web/store/inbox/project-inbox.store.ts +++ b/web/store/inbox/project-inbox.store.ts @@ -1,7 +1,6 @@ import { uniq, update } from "lodash"; import isEmpty from "lodash/isEmpty"; import omit from "lodash/omit"; -import orderBy from "lodash/orderBy"; import set from "lodash/set"; import { action, computed, makeObservable, observable, runInAction } from "mobx"; import { computedFn } from "mobx-utils"; @@ -42,11 +41,11 @@ export interface IProjectInboxStore { inboxIssueIds: string[]; // computed getAppliedFiltersCount: number; + filteredInboxIssueIds: string[]; // computed functions getIssueInboxByIssueId: (issueId: string) => IInboxIssueStore; getIsIssueAvailable: (inboxIssueId: string) => boolean; // helper actions - inboxIssueSorting: (issues: IInboxIssueStore[]) => IInboxIssueStore[]; inboxIssueQueryParams: ( inboxFilters: Partial, inboxSorting: Partial, @@ -103,6 +102,7 @@ export class ProjectInboxStore implements IProjectInboxStore { inboxIssueIds: observable, // computed getAppliedFiltersCount: computed, + filteredInboxIssueIds: computed, // actions handleInboxIssueFilters: action, handleInboxIssueSorting: action, @@ -127,6 +127,16 @@ export class ProjectInboxStore implements IProjectInboxStore { return count; } + get filteredInboxIssueIds() { + let appliedFilters = + this.currentTab === EInboxIssueCurrentTab.OPEN + ? [EInboxIssueStatus.PENDING, EInboxIssueStatus.SNOOZED] + : [EInboxIssueStatus.ACCEPTED, EInboxIssueStatus.DECLINED, EInboxIssueStatus.DUPLICATE]; + appliedFilters = appliedFilters.filter((filter) => this.inboxFilters?.status?.includes(filter)); + + return this.inboxIssueIds.filter((id) => appliedFilters.includes(this.inboxIssues[id].status)); + } + getIssueInboxByIssueId = computedFn((issueId: string) => this.inboxIssues?.[issueId]); getIsIssueAvailable = computedFn((inboxIssueId: string) => { @@ -134,28 +144,6 @@ export class ProjectInboxStore implements IProjectInboxStore { return this.inboxIssueIds.includes(inboxIssueId); }); - // helpers - inboxIssueSorting = (issues: IInboxIssueStore[]) => { - let inboxIssues: IInboxIssueStore[] = issues; - inboxIssues = orderBy(inboxIssues, "issue.sequence_id", "desc"); - if (this.inboxSorting?.order_by && this.inboxSorting?.sort_by) { - switch (this.inboxSorting.order_by) { - case "issue__created_at": - if (this.inboxSorting.sort_by === "desc") inboxIssues = orderBy(inboxIssues, "issue.created_at", "desc"); - else inboxIssues = orderBy(inboxIssues, "issue.created_at", "asc"); - case "issue__updated_at": - if (this.inboxSorting.sort_by === "desc") inboxIssues = orderBy(inboxIssues, "issue.updated_at", "desc"); - else inboxIssues = orderBy(inboxIssues, "issue.updated_at", "asc"); - case "issue__sequence_id": - if (this.inboxSorting.sort_by === "desc") inboxIssues = orderBy(inboxIssues, "issue.sequence_id", "desc"); - else inboxIssues = orderBy(inboxIssues, "issue.sequence_id", "asc"); - default: - inboxIssues = inboxIssues; - } - } - return inboxIssues; - }; - inboxIssueQueryParams = ( inboxFilters: Partial, inboxSorting: Partial, From 1561b710ca389e01587644bc59c8110760e545f9 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Fri, 7 Jun 2024 18:01:42 +0530 Subject: [PATCH 035/307] style: fix ux copy style on project feature preview page. (#4734) --- web/components/project/project-feature-update.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/components/project/project-feature-update.tsx b/web/components/project/project-feature-update.tsx index 24c7091eded..797c00023d7 100644 --- a/web/components/project/project-feature-update.tsx +++ b/web/components/project/project-feature-update.tsx @@ -34,7 +34,7 @@ export const ProjectFeatureUpdate: FC = observer((props) => {
-
+
Congrats! Project {" "}

{currentProjectDetails.name}

created.
From 17ce1bceb6d8434c1711e7414108553279ee8f03 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 7 Jun 2024 18:18:47 +0530 Subject: [PATCH 036/307] chore: remove clear seleciton logic on escape key press (#4735) --- web/hooks/use-multiple-select.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/web/hooks/use-multiple-select.ts b/web/hooks/use-multiple-select.ts index fdb3c0b1c17..0a0013eac9f 100644 --- a/web/hooks/use-multiple-select.ts +++ b/web/hooks/use-multiple-select.ts @@ -271,20 +271,6 @@ export const useMultipleSelect = (props: Props) => { [disabled, entitiesList, handleEntitySelection, isGroupSelected] ); - // clear selection on escape key press - useEffect(() => { - if (disabled) return; - - const handleKeyDown = (e: KeyboardEvent) => { - if (e.key === "Escape") clearSelection(); - }; - - window.addEventListener("keydown", handleKeyDown); - return () => { - window.removeEventListener("keydown", handleKeyDown); - }; - }, [clearSelection, disabled]); - // select entities on shift + arrow up/down key press useEffect(() => { if (disabled) return; From de8da176d365e0b8ff1ccdbf2e90b8a8f6954541 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Fri, 7 Jun 2024 19:33:13 +0530 Subject: [PATCH 037/307] chore: issue and properties filter dropdown improvement (#4733) --- web/components/cycles/cycle-mobile-header.tsx | 2 ++ web/components/headers/cycle-issues.tsx | 2 ++ web/components/headers/global-issues.tsx | 2 ++ web/components/headers/module-issues.tsx | 2 ++ .../headers/project-draft-issues.tsx | 2 ++ web/components/headers/project-issues.tsx | 2 ++ .../headers/project-view-issues.tsx | 2 ++ .../issues/archived-issues-header.tsx | 2 ++ .../display-filters-selection.tsx | 15 ------------ .../header/filters/filters-selection.tsx | 24 +++++++++++++++++-- .../issues/issues-mobile-header.tsx | 2 ++ .../modules/module-mobile-header.tsx | 2 ++ .../profile/profile-issues-filter.tsx | 2 ++ .../profile/profile-issues-mobile-header.tsx | 2 ++ 14 files changed, 46 insertions(+), 17 deletions(-) diff --git a/web/components/cycles/cycle-mobile-header.tsx b/web/components/cycles/cycle-mobile-header.tsx index 3e37b586f79..ce1b45a9bf4 100644 --- a/web/components/cycles/cycle-mobile-header.tsx +++ b/web/components/cycles/cycle-mobile-header.tsx @@ -156,6 +156,8 @@ export const CycleMobileHeader = () => { layoutDisplayFiltersOptions={ activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined } + displayFilters={issueFilters?.displayFilters ?? {}} + handleDisplayFiltersUpdate={handleDisplayFilters} labels={projectLabels} memberIds={projectMemberIds ?? undefined} states={projectStates} diff --git a/web/components/headers/cycle-issues.tsx b/web/components/headers/cycle-issues.tsx index c26be96064c..4e9c37c9d04 100644 --- a/web/components/headers/cycle-issues.tsx +++ b/web/components/headers/cycle-issues.tsx @@ -241,6 +241,8 @@ export const CycleIssuesHeader: React.FC = observer(() => { layoutDisplayFiltersOptions={ activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined } + displayFilters={issueFilters?.displayFilters ?? {}} + handleDisplayFiltersUpdate={handleDisplayFilters} labels={projectLabels} memberIds={projectMemberIds ?? undefined} states={projectStates} diff --git a/web/components/headers/global-issues.tsx b/web/components/headers/global-issues.tsx index 1ec2a5d2cbd..a6a35d16f6f 100644 --- a/web/components/headers/global-issues.tsx +++ b/web/components/headers/global-issues.tsx @@ -117,6 +117,8 @@ export const GlobalIssuesHeader: React.FC = observer(() => { layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_LAYOUT.my_issues.spreadsheet} filters={issueFilters?.filters ?? {}} handleFiltersUpdate={handleFiltersUpdate} + displayFilters={issueFilters?.displayFilters ?? {}} + handleDisplayFiltersUpdate={handleDisplayFilters} labels={workspaceLabels ?? undefined} memberIds={workspaceMemberIds ?? undefined} /> diff --git a/web/components/headers/module-issues.tsx b/web/components/headers/module-issues.tsx index 119cb9a9447..a433bead50f 100644 --- a/web/components/headers/module-issues.tsx +++ b/web/components/headers/module-issues.tsx @@ -239,6 +239,8 @@ export const ModuleIssuesHeader: React.FC = observer(() => { { { { { = observer((props) => {
)} - {/* issue type */} - {isDisplayFilterEnabled("type") && ( -
- - handleDisplayFiltersUpdate({ - type: val, - }) - } - /> -
- )} - {/* Options */} {layoutDisplayFiltersOptions?.extra_options.access && (
diff --git a/web/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx b/web/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx index 3f45946e082..61ed05a2414 100644 --- a/web/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx +++ b/web/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { observer } from "mobx-react"; import { Search, X } from "lucide-react"; -import { IIssueFilterOptions, IIssueLabel, IState } from "@plane/types"; +import { IIssueDisplayFilterOptions, IIssueFilterOptions, IIssueLabel, IState } from "@plane/types"; // hooks import { FilterAssignees, @@ -16,6 +16,7 @@ import { FilterTargetDate, FilterCycle, FilterModule, + FilterIssueType, } from "@/components/issues"; import { ILayoutDisplayFiltersOptions } from "@/constants/issue"; import { useAppRouter } from "@/hooks/store"; @@ -25,6 +26,8 @@ import { useAppRouter } from "@/hooks/store"; type Props = { filters: IIssueFilterOptions; + displayFilters?: IIssueDisplayFilterOptions | undefined; + handleDisplayFiltersUpdate?: (updatedDisplayFilter: Partial) => void; handleFiltersUpdate: (key: keyof IIssueFilterOptions, value: string | string[]) => void; layoutDisplayFiltersOptions: ILayoutDisplayFiltersOptions | undefined; labels?: IIssueLabel[] | undefined; @@ -37,6 +40,8 @@ type Props = { export const FilterSelection: React.FC = observer((props) => { const { filters, + displayFilters, + handleDisplayFiltersUpdate, handleFiltersUpdate, layoutDisplayFiltersOptions, labels, @@ -52,6 +57,9 @@ export const FilterSelection: React.FC = observer((props) => { const isFilterEnabled = (filter: keyof IIssueFilterOptions) => layoutDisplayFiltersOptions?.filters.includes(filter); + const isDisplayFilterEnabled = (displayFilter: keyof IIssueDisplayFilterOptions) => + Object.keys(layoutDisplayFiltersOptions?.display_filters ?? {}).includes(displayFilter); + return (
@@ -187,7 +195,19 @@ export const FilterSelection: React.FC = observer((props) => { />
)} - + {/* issue type */} + {isDisplayFilterEnabled("type") && displayFilters && handleDisplayFiltersUpdate && ( +
+ + handleDisplayFiltersUpdate({ + type: val, + }) + } + /> +
+ )} {/* start_date */} {isFilterEnabled("start_date") && (
diff --git a/web/components/issues/issues-mobile-header.tsx b/web/components/issues/issues-mobile-header.tsx index 72df560a9bb..956aa0d295d 100644 --- a/web/components/issues/issues-mobile-header.tsx +++ b/web/components/issues/issues-mobile-header.tsx @@ -132,6 +132,8 @@ export const IssuesMobileHeader = observer(() => { { { } filters={issueFilters?.filters ?? {}} handleFiltersUpdate={handleFiltersUpdate} + displayFilters={issueFilters?.displayFilters ?? {}} + handleDisplayFiltersUpdate={handleDisplayFilters} states={states} labels={workspaceLabels} memberIds={members} diff --git a/web/components/profile/profile-issues-mobile-header.tsx b/web/components/profile/profile-issues-mobile-header.tsx index 657aab0a0c7..6088d0194f2 100644 --- a/web/components/profile/profile-issues-mobile-header.tsx +++ b/web/components/profile/profile-issues-mobile-header.tsx @@ -147,6 +147,8 @@ const ProfileIssuesMobileHeader = observer(() => { } filters={issueFilters?.filters ?? {}} handleFiltersUpdate={handleFiltersUpdate} + displayFilters={issueFilters?.displayFilters ?? {}} + handleDisplayFiltersUpdate={handleDisplayFilters} states={states} labels={workspaceLabels} memberIds={members} From fb2b4ae3035e6156bcf230c0322d10083bb8eaf7 Mon Sep 17 00:00:00 2001 From: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Date: Fri, 7 Jun 2024 19:43:27 +0530 Subject: [PATCH 038/307] save all filters and properties for views (#4728) --- .../filters/applied-filters/filters-list.tsx | 7 +++---- .../applied-filters/roots/archived-issue.tsx | 4 +--- .../applied-filters/roots/cycle-root.tsx | 8 ++++++-- .../applied-filters/roots/draft-issue.tsx | 4 +--- .../applied-filters/roots/module-root.tsx | 8 ++++++-- .../applied-filters/roots/project-root.tsx | 10 +++++++++- .../roots/project-view-root.tsx | 10 ++++++++-- .../issues/issue-layouts/save-filter-view.tsx | 9 +++++++-- web/components/issues/issue-layouts/utils.tsx | 20 +++++++++++++++++++ web/components/views/form.tsx | 2 ++ 10 files changed, 63 insertions(+), 19 deletions(-) diff --git a/web/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx b/web/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx index ac3535a83f8..1d53921bb54 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx @@ -19,7 +19,7 @@ import { EUserProjectRoles } from "@/constants/project"; // helpers import { replaceUnderscoreIfSnakeCase } from "@/helpers/string.helper"; // hooks -import { useAppRouter, useUser } from "@/hooks/store"; +import { useUser } from "@/hooks/store"; type Props = { appliedFilters: IIssueFilterOptions; @@ -36,7 +36,6 @@ const dateFilters = ["start_date", "target_date"]; export const AppliedFiltersList: React.FC = observer((props) => { const { appliedFilters, handleClearAllFilters, handleRemoveFilter, labels, states, alwaysAllowEditing } = props; // store hooks - const { moduleId, cycleId } = useAppRouter(); const { membership: { currentProjectRole }, } = useUser(); @@ -108,14 +107,14 @@ export const AppliedFiltersList: React.FC = observer((props) => { values={value} /> )} - {filterKey === "cycle" && !cycleId && ( + {filterKey === "cycle" && ( handleRemoveFilter("cycle", val)} values={value} /> )} - {filterKey === "module" && !moduleId && ( + {filterKey === "module" && ( handleRemoveFilter("module", val)} diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx index fa89757216c..d5c6bc701fd 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/archived-issue.tsx @@ -2,7 +2,7 @@ import { observer } from "mobx-react"; import { useRouter } from "next/router"; import { IIssueFilterOptions } from "@plane/types"; // hooks -import { AppliedFiltersList, SaveFilterView } from "@/components/issues"; +import { AppliedFiltersList } from "@/components/issues"; import { EIssueFilterType, EIssuesStoreType } from "@/constants/issue"; import { useIssues, useLabel, useProjectState } from "@/hooks/store"; // components @@ -76,8 +76,6 @@ export const ArchivedIssueAppliedFiltersRoot: React.FC = observer(() => { labels={projectLabels ?? []} states={projectStates} /> - -
); }); diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx index ae96de484ec..b555c1cfecd 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx @@ -74,7 +74,7 @@ export const CycleAppliedFiltersRoot: React.FC = observer(() => { }; // return if no filters are applied - if (Object.keys(appliedFilters).length === 0 || !workspaceSlug || !projectId) return null; + if (Object.keys(appliedFilters).length === 0 || !workspaceSlug || !projectId || !cycleId) return null; return (
@@ -89,7 +89,11 @@ export const CycleAppliedFiltersRoot: React.FC = observer(() => {
); diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx index 50589317525..f45e5688840 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/draft-issue.tsx @@ -2,7 +2,7 @@ import { observer } from "mobx-react"; import { useRouter } from "next/router"; import { IIssueFilterOptions } from "@plane/types"; // hooks -import { AppliedFiltersList, SaveFilterView } from "@/components/issues"; +import { AppliedFiltersList } from "@/components/issues"; import { EIssueFilterType, EIssuesStoreType } from "@/constants/issue"; import { useIssues, useLabel, useProjectState } from "@/hooks/store"; // components @@ -71,8 +71,6 @@ export const DraftIssueAppliedFiltersRoot: React.FC = observer(() => { labels={projectLabels ?? []} states={projectStates} /> - -
); }); diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx index 220e6e9dce2..a6841ab065d 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx @@ -73,7 +73,7 @@ export const ModuleAppliedFiltersRoot: React.FC = observer(() => { }; // return if no filters are applied - if (!workspaceSlug || !projectId || Object.keys(appliedFilters).length === 0) return null; + if (!workspaceSlug || !projectId || !moduleId || Object.keys(appliedFilters).length === 0) return null; return (
@@ -88,7 +88,11 @@ export const ModuleAppliedFiltersRoot: React.FC = observer(() => {
); diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx index 0b8b8758a24..ac0d1dd721c 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx @@ -77,7 +77,15 @@ export const ProjectAppliedFiltersRoot: React.FC = observer(() => { states={projectStates} /> {isEditingAllowed && ( - + )}
); diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx index 049867f84a8..1423fc39381 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx @@ -1,5 +1,4 @@ import isEmpty from "lodash/isEmpty"; -import isEqual from "lodash/isEqual"; import { observer } from "mobx-react"; import { useRouter } from "next/router"; import { IIssueFilterOptions } from "@plane/types"; @@ -8,6 +7,7 @@ import { Button } from "@plane/ui"; import { AppliedFiltersList } from "@/components/issues"; import { EIssueFilterType, EIssuesStoreType } from "@/constants/issue"; import { useIssues, useLabel, useProjectState, useProjectView } from "@/hooks/store"; +import { getAreFiltersEqual } from "../../../utils"; // components // ui // types @@ -79,7 +79,7 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => { ); }; - const areFiltersEqual = isEqual(appliedFilters ?? {}, viewDetails?.filters ?? {}); + const areFiltersEqual = getAreFiltersEqual(appliedFilters, issueFilters, viewDetails); // return if no filters are applied if (isEmpty(appliedFilters) && areFiltersEqual) return null; @@ -91,6 +91,12 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => { filters: { ...(appliedFilters ?? {}), }, + display_filters: { + ...issueFilters?.displayFilters, + }, + display_properties: { + ...issueFilters?.displayProperties, + }, }); }; diff --git a/web/components/issues/issue-layouts/save-filter-view.tsx b/web/components/issues/issue-layouts/save-filter-view.tsx index 036d537d82d..98ef5a41457 100644 --- a/web/components/issues/issue-layouts/save-filter-view.tsx +++ b/web/components/issues/issue-layouts/save-filter-view.tsx @@ -1,4 +1,5 @@ import { FC, useState } from "react"; +import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; import { Button } from "@plane/ui"; // components import { CreateUpdateProjectViewModal } from "@/components/views"; @@ -6,7 +7,11 @@ import { CreateUpdateProjectViewModal } from "@/components/views"; interface ISaveFilterView { workspaceSlug: string; projectId: string; - filterParams: any; + filterParams: { + filters: IIssueFilterOptions; + display_filters?: IIssueDisplayFilterOptions; + display_properties?: IIssueDisplayProperties; + }; } export const SaveFilterView: FC = (props) => { @@ -19,7 +24,7 @@ export const SaveFilterView: FC = (props) => { setViewModal(false)} /> diff --git a/web/components/issues/issue-layouts/utils.tsx b/web/components/issues/issue-layouts/utils.tsx index 78048b4b4ba..c95ea748e39 100644 --- a/web/components/issues/issue-layouts/utils.tsx +++ b/web/components/issues/issue-layouts/utils.tsx @@ -1,6 +1,7 @@ import { extractInstruction } from "@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item"; import clone from "lodash/clone"; import concat from "lodash/concat"; +import isEqual from "lodash/isEqual"; import pull from "lodash/pull"; import uniq from "lodash/uniq"; import scrollIntoView from "smooth-scroll-into-view-if-needed"; @@ -13,6 +14,9 @@ import { IPragmaticDropPayload, TIssue, TIssueGroupByOptions, + IIssueFilterOptions, + IIssueFilters, + IProjectView, } from "@plane/types"; // ui import { Avatar, CycleGroupIcon, DiceIcon, PriorityIcon, StateGroupIcon } from "@plane/ui"; @@ -535,3 +539,19 @@ export const handleGroupDragDrop = async ( return await updateIssueOnDrop(sourceIssue.project_id, sourceIssue.id, updatedIssue, issueUpdates); } }; + +/** + * This Method compares filters and returns a boolean based on which and updateView button is shown + * @param appliedFilters + * @param issueFilters + * @param viewDetails + * @returns + */ +export const getAreFiltersEqual = ( + appliedFilters: IIssueFilterOptions | undefined, + issueFilters: IIssueFilters | undefined, + viewDetails: IProjectView | null +) => + isEqual(appliedFilters ?? {}, viewDetails?.filters ?? {}) && + isEqual(issueFilters?.displayFilters ?? {}, viewDetails?.display_filters ?? {}) && + isEqual(issueFilters?.displayProperties ?? {}, viewDetails?.display_properties ?? {}); diff --git a/web/components/views/form.tsx b/web/components/views/form.tsx index b0786ec1b5f..295354c6b6c 100644 --- a/web/components/views/form.tsx +++ b/web/components/views/form.tsx @@ -94,6 +94,8 @@ export const ProjectViewForm: React.FC = observer((props) => { description: formData.description, logo_props: formData.logo_props, filters: formData.filters, + display_filters: formData.display_filters, + display_properties: formData.display_properties, } as IProjectView); reset({ From 59fdd611e4b840993e93fc53c89a75a3787f248d Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Mon, 10 Jun 2024 12:16:23 +0530 Subject: [PATCH 039/307] feat: estimates revamp and space app refactor (#4742) * Move code from EE to CE repo * chore: folder structure updates * Move sortabla and radio input to packages/ui * chore: updated empty and loading screens * chore: delete an estimate point * chore: estimate point response change * chore: updated create estimate and handled the build error * chore: migration fixes * chore: updated create estimate * chore: create estimate workflow update * chore: editing and deleting the existing estimate updates * chore: updating the new estinates in update modal * chore: ui changed * chore: response changes of get and post * chore: new field added in estimates * chore: individual endpoint for estimate points * chore: typo changes * chore: create estimate point * chore: integrated new endpoints * chore: update key value pair * chore: update sorting in the estimates * Add custom option in the estimate templates * chore: handled current project active estimate * chore: handle estimate update worklfow * chore: handled estimates switch * chore: handled estimate edit * chore: handled close button in estimate edit * chore: updated ceate estimare workflow * chore: updated switch estimate * chore: UI and typos * chore: resolved build error * chore: updated delete dropdown and handled the repeated values while creating and updating the estimate point * chore: handled inline errors in the estimate switch * chore: handled active and availability vadilation * chore: handled create and update components in projecr estimates * chore: added migration * Add category specific values for custom template * chore: estimate dropdown handled in issues * chore: estimate alerts * chore: updated alerts * Extract the list row actions * fix: updated and handled the estimate points * fix: upgrader ee banner * Fix issues with sortable * Fix sortable spacing issue in create estimate modal * fix: updated the issue create sorting * chore: removed radio button from ui and updated in the estimates * chore: resolved import error in packaged ui * chore: handled props in create modal * chore: removed ee files * chore: changed default analytics * chore: removed the migration file * chore: estimate point value in graph * chore: estimate point key change * chore: squashed migration (#4634) * chore: squashed migration * chore: removed instance migraion * chore: key changes * chore: issue activity back migration * dev: replaced estimate key with estimate id and replaced estimate type from number to string in issue * chore: estimate point value field * chore: estimate point activity * chore: removed the unused function * chore: resolved merge conflicts * chore: deploy board keys changed * chore: yarn lock file change * chore: resolved frontend build --------- Co-authored-by: guru_sainath * [WEB-1516] refactor: space app routing and layouts (#4705) * dev: change layout * chore: replace workspace slug and project id with anchor * chore: migration fixes * chore: update filtering logic * chore: endpoint changes * chore: update endpoint * chore: changed url pratterns * chore: use client side for layout and page * chore: issue vote changes * chore: project deploy board response change * refactor: publish project store and components * fix: update layout options after fetching settings * chore: remove unnecessary types * style: peek overview * refactor: components folder structure * fix: redirect from old path * chore: make the whole issue block clickable * chore: removed the migration file * chore: add server side redirection for old routes * chore: is enabled key change * chore: update types * chore: removed the migration file --------- Co-authored-by: NarayanBavisetti * Merge develop into revamp-estimates-ce * chore: removed migration file and updated the estimate system order and removed ee banner * chore: initial radio select in create estimate * chore: space key changes * Fix sortable component as the sort order was broken. * [WEB-1516] refactor: publish project modal and types (#4716) * refacotr: project publish * chore: rename service names * chore: is_deployed changed to anchor * chore: update is_deployed key --------- Co-authored-by: NarayanBavisetti * [WEB-412] chore: estimates analytics (#4730) * chore: estimate points in modules and cycle * chore: burn down chart analytics * chore: module serializer change * dev: handled y-axis estimates in analytics, implemented estimate points on modules * chore: burn down analytics * chore: state estimate point analytics * chore: updated the burn down values * Remove check mark from estimate point edit field in create estimate flow --------- Co-authored-by: guru_sainath Co-authored-by: Satish Gandham --------- Co-authored-by: Satish Gandham Co-authored-by: guru_sainath Co-authored-by: NarayanBavisetti Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Co-authored-by: pushya22 <130810100+pushya22@users.noreply.github.com> --- apiserver/plane/api/views/cycle.py | 2 + apiserver/plane/api/views/project.py | 4 +- apiserver/plane/app/serializers/__init__.py | 2 +- apiserver/plane/app/serializers/estimate.py | 8 - apiserver/plane/app/serializers/module.py | 4 + apiserver/plane/app/serializers/project.py | 10 +- apiserver/plane/app/urls/estimate.py | 20 + apiserver/plane/app/urls/project.py | 6 +- apiserver/plane/app/views/__init__.py | 3 +- apiserver/plane/app/views/analytic/base.py | 6 +- apiserver/plane/app/views/cycle/archive.py | 2 + apiserver/plane/app/views/cycle/base.py | 138 +- apiserver/plane/app/views/estimate/base.py | 172 +- apiserver/plane/app/views/module/archive.py | 2 + apiserver/plane/app/views/module/base.py | 51 +- apiserver/plane/app/views/project/base.py | 61 +- .../plane/bgtasks/issue_activites_task.py | 25 +- .../db/migrations/0067_issue_estimate.py | 260 +++ apiserver/plane/db/models/__init__.py | 3 +- apiserver/plane/db/models/deploy_board.py | 53 + apiserver/plane/db/models/estimate.py | 5 +- apiserver/plane/db/models/issue.py | 9 +- apiserver/plane/db/models/project.py | 2 + apiserver/plane/space/urls/inbox.py | 6 +- apiserver/plane/space/urls/issue.py | 14 +- apiserver/plane/space/urls/project.py | 10 +- apiserver/plane/space/views/__init__.py | 1 + apiserver/plane/space/views/inbox.py | 68 +- apiserver/plane/space/views/issue.py | 207 ++- apiserver/plane/space/views/project.py | 36 +- apiserver/plane/utils/analytics_plot.py | 154 +- packages/types/src/analytics.d.ts | 2 +- packages/types/src/enums.ts | 13 + packages/types/src/estimate.d.ts | 95 +- packages/types/src/index.d.ts | 2 +- packages/types/src/issues/issue.d.ts | 2 +- packages/types/src/module/modules.d.ts | 2 + packages/types/src/project/projects.d.ts | 2 +- packages/types/src/publish.d.ts | 41 + packages/ui/package.json | 4 +- packages/ui/src/index.ts | 1 + packages/ui/src/sortable/sortable.stories.tsx | 3 +- packages/ui/src/sortable/sortable.tsx | 4 +- space/app/[workspaceSlug]/[projectId]/page.ts | 42 + .../[workspace_slug]/[project_id]/page.tsx | 16 - space/app/error.tsx | 53 +- .../[anchor]}/layout.tsx | 42 +- space/app/issues/[anchor]/page.tsx | 30 + space/app/not-found.tsx | 26 +- space/components/account/user-logged-in.tsx | 30 +- space/components/common/index.ts | 1 - .../common/latest-feature-block.tsx | 40 - space/components/instance/index.ts | 1 - space/components/instance/not-ready-view.tsx | 62 - .../issues/board-views/block-downvotes.tsx | 10 - .../issues/board-views/block-due-date.tsx | 59 - .../issues/board-views/block-labels.tsx | 19 - .../issues/board-views/block-state.tsx | 18 - .../issues/board-views/block-upvotes.tsx | 8 - .../issues/board-views/calendar/index.tsx | 1 - .../issues/board-views/gantt/index.tsx | 1 - .../issues/board-views/kanban/block.tsx | 82 - .../issues/board-views/kanban/header.tsx | 28 - .../issues/board-views/kanban/index.tsx | 58 - .../issues/board-views/list/index.tsx | 42 - .../issues/board-views/spreadsheet/index.tsx | 1 - .../filters/applied-filters/filters-list.tsx | 6 +- .../issues/filters/applied-filters/root.tsx | 32 +- .../issues/filters/applied-filters/state.tsx | 7 +- space/components/issues/filters/root.tsx | 21 +- space/components/issues/filters/selection.tsx | 5 +- space/components/issues/filters/state.tsx | 7 +- space/components/issues/index.ts | 2 + .../components/issues/issue-layouts/index.ts | 4 + .../issues/issue-layouts/kanban/block.tsx | 78 + .../issues/issue-layouts/kanban/header.tsx | 25 + .../issues/issue-layouts/kanban/index.ts | 3 + .../issues/issue-layouts/kanban/root.tsx | 50 + .../list/block.tsx | 44 +- .../list/header.tsx | 20 +- .../issues/issue-layouts/list/index.ts | 3 + .../issues/issue-layouts/list/root.tsx | 40 + .../issue-layouts/properties/due-date.tsx | 32 + .../issues/issue-layouts/properties/index.ts | 4 + .../issue-layouts/properties/labels.tsx | 17 + .../properties/priority.tsx} | 6 +- .../issues/issue-layouts/properties/state.tsx | 11 + .../issue-layouts/root.tsx} | 67 +- space/components/issues/navbar/controls.tsx | 48 +- space/components/issues/navbar/index.ts | 5 + .../issues/navbar/issue-board-view.tsx | 72 - .../issues/navbar/layout-selection.tsx | 67 + .../issues/navbar/{index.tsx => root.tsx} | 29 +- .../peek-overview/comment/add-comment.tsx | 20 +- .../comment/comment-detail-card.tsx | 24 +- .../comment/comment-reactions.tsx | 13 +- .../peek-overview/full-screen-peek-view.tsx | 13 +- .../issues/peek-overview/header.tsx | 46 +- .../issues/peek-overview/issue-activity.tsx | 69 +- .../issues/peek-overview/issue-details.tsx | 47 +- .../peek-overview/issue-emoji-reactions.tsx | 25 +- .../issues/peek-overview/issue-properties.tsx | 70 +- .../issues/peek-overview/issue-reaction.tsx | 32 +- .../peek-overview/issue-vote-reactions.tsx | 33 +- .../issues/peek-overview/layout.tsx | 38 +- .../issues/peek-overview/side-peek-view.tsx | 23 +- space/components/ui/dropdown.tsx | 142 -- space/components/ui/index.ts | 1 - space/components/views/index.ts | 1 - space/constants/issue.ts | 92 +- space/constants/state.ts | 37 + space/constants/workspace.ts | 12 - space/helpers/date-time.helper.ts | 52 +- space/helpers/emoji.helper.tsx | 20 - space/helpers/issue.helper.ts | 30 + space/helpers/string.helper.ts | 4 +- space/hooks/store/index.ts | 2 +- space/hooks/store/publish/index.ts | 2 + space/hooks/store/publish/use-publish-list.ts | 11 + space/hooks/store/publish/use-publish.ts | 11 + space/hooks/store/use-project.ts | 11 - space/hooks/use-mention.tsx | 4 +- space/lib/user-provider.tsx | 12 - space/package.json | 1 + space/services/file.service.ts | 58 - space/services/issue.service.ts | 96 +- space/services/project-member.service.ts | 8 +- space/services/project.service.ts | 19 - space/services/publish.service.ts | 30 + space/store/issue-detail.store.ts | 257 ++- space/store/issue-filters.store.ts | 77 +- space/store/issue.store.ts | 112 +- space/store/project.store.ts | 96 - space/store/publish/publish.store.ts | 111 ++ space/store/publish/publish_list.store.ts | 55 + space/store/root.store.ts | 8 +- space/styles/globals.css | 17 + space/types/app.d.ts | 14 - space/types/issue.d.ts | 80 +- space/types/project.d.ts | 42 - space/types/theme.d.ts | 4 - space/types/user.d.ts | 30 - .../custom-analytics/select/y-axis.tsx | 62 +- web/components/core/activity.tsx | 20 +- .../core/modals/gpt-assistant-popover.tsx | 4 +- web/components/dropdowns/estimate.tsx | 76 +- .../empty-state/comic-box-button.tsx | 4 +- .../create-update-estimate-modal.tsx | 292 --- web/components/estimates/create/index.ts | 2 + web/components/estimates/create/modal.tsx | 134 ++ web/components/estimates/create/stage-one.tsx | 101 + .../estimates/delete-estimate-modal.tsx | 79 - web/components/estimates/delete/index.ts | 1 + web/components/estimates/delete/modal.tsx | 81 + web/components/estimates/empty-screen.tsx | 42 + .../estimates/estimate-disable-switch.tsx | 50 + .../estimates/estimate-list-item-buttons.tsx | 34 + .../estimates/estimate-list-item.tsx | 138 +- web/components/estimates/estimate-list.tsx | 35 + web/components/estimates/estimate-search.tsx | 9 + web/components/estimates/estimates-list.tsx | 121 -- web/components/estimates/index.ts | 27 +- web/components/estimates/loader-screen.tsx | 11 + .../estimates/points/create-root.tsx | 139 ++ web/components/estimates/points/create.tsx | 173 ++ web/components/estimates/points/delete.tsx | 118 ++ web/components/estimates/points/index.ts | 7 + web/components/estimates/points/preview.tsx | 103 ++ .../estimates/points/select-dropdown.tsx | 127 ++ web/components/estimates/points/update.tsx | 184 ++ web/components/estimates/radio-select.tsx | 85 + web/components/estimates/root.tsx | 118 ++ web/components/estimates/update/index.ts | 2 + web/components/estimates/update/modal.tsx | 42 + web/components/estimates/update/stage-one.tsx | 42 + web/components/headers/project-issues.tsx | 8 +- .../create-edit-modal/issue-properties.tsx | 8 +- .../activity/actions/estimate.tsx | 16 +- .../issue-detail/issue-activity/root.tsx | 8 +- .../issues/issue-detail/sidebar.tsx | 12 +- .../properties/all-properties.tsx | 16 +- .../spreadsheet/columns/estimate-column.tsx | 2 +- web/components/issues/issue-layouts/utils.tsx | 4 +- web/components/issues/issue-modal/form.tsx | 15 +- .../issues/peek-overview/properties.tsx | 4 +- web/components/modules/module-card-item.tsx | 45 +- web/components/modules/module-list-item.tsx | 22 +- .../project/publish-project/index.ts | 1 + .../project/publish-project/index.tsx | 2 - .../project/publish-project/modal.tsx | 603 +++--- .../project/publish-project/popover.tsx | 47 - web/components/project/sidebar-list-item.tsx | 2 +- web/components/workspace/sidebar-dropdown.tsx | 6 +- web/constants/analytics.ts | 2 +- web/constants/estimates.ts | 144 ++ web/helpers/estimates.ts | 33 + web/hooks/store/estimates/index.ts | 3 + .../store/estimates/use-estimate-point.ts | 16 + web/hooks/store/estimates/use-estimate.ts | 13 + .../store/estimates/use-project-estimate.ts | 12 + web/hooks/store/index.ts | 2 +- web/hooks/store/use-estimate.ts | 11 - web/hooks/use-project-issue-properties.ts | 6 +- web/hooks/use-workspace-issue-properties.ts | 6 +- web/layouts/auth-layout/project-wrapper.tsx | 6 +- web/next.config.js | 20 +- web/package.json | 8 +- .../[projectId]/settings/estimates.tsx | 17 +- web/public/empty-state/estimates/dark.svg | 19 + web/public/empty-state/estimates/light.svg | 19 + web/services/project/estimate.service.ts | 138 ++ web/services/project/index.ts | 2 +- .../project/project-estimate.service.ts | 72 - .../project/project-publish.service.ts | 41 +- web/store/estimate.store.ts | 222 --- web/store/estimates/estimate-point.ts | 148 ++ web/store/estimates/estimate.ts | 292 +++ web/store/estimates/project-estimate.store.ts | 292 +++ .../issue/issue-details/activity.store.ts | 16 +- .../issue/issue-details/sub_issues.store.ts | 5 +- web/store/project/project-publish.store.ts | 185 +- web/store/root.store.ts | 8 +- yarn.lock | 1632 +++++++++-------- 223 files changed, 6896 insertions(+), 4680 deletions(-) create mode 100644 apiserver/plane/db/migrations/0067_issue_estimate.py create mode 100644 apiserver/plane/db/models/deploy_board.py create mode 100644 packages/types/src/publish.d.ts create mode 100644 space/app/[workspaceSlug]/[projectId]/page.ts delete mode 100644 space/app/[workspace_slug]/[project_id]/page.tsx rename space/app/{[workspace_slug]/[project_id] => issues/[anchor]}/layout.tsx (54%) create mode 100644 space/app/issues/[anchor]/page.tsx delete mode 100644 space/components/common/latest-feature-block.tsx delete mode 100644 space/components/instance/not-ready-view.tsx delete mode 100644 space/components/issues/board-views/block-downvotes.tsx delete mode 100644 space/components/issues/board-views/block-due-date.tsx delete mode 100644 space/components/issues/board-views/block-labels.tsx delete mode 100644 space/components/issues/board-views/block-state.tsx delete mode 100644 space/components/issues/board-views/block-upvotes.tsx delete mode 100644 space/components/issues/board-views/calendar/index.tsx delete mode 100644 space/components/issues/board-views/gantt/index.tsx delete mode 100644 space/components/issues/board-views/kanban/block.tsx delete mode 100644 space/components/issues/board-views/kanban/header.tsx delete mode 100644 space/components/issues/board-views/kanban/index.tsx delete mode 100644 space/components/issues/board-views/list/index.tsx delete mode 100644 space/components/issues/board-views/spreadsheet/index.tsx create mode 100644 space/components/issues/index.ts create mode 100644 space/components/issues/issue-layouts/index.ts create mode 100644 space/components/issues/issue-layouts/kanban/block.tsx create mode 100644 space/components/issues/issue-layouts/kanban/header.tsx create mode 100644 space/components/issues/issue-layouts/kanban/index.ts create mode 100644 space/components/issues/issue-layouts/kanban/root.tsx rename space/components/issues/{board-views => issue-layouts}/list/block.tsx (64%) rename space/components/issues/{board-views => issue-layouts}/list/header.tsx (54%) create mode 100644 space/components/issues/issue-layouts/list/index.ts create mode 100644 space/components/issues/issue-layouts/list/root.tsx create mode 100644 space/components/issues/issue-layouts/properties/due-date.tsx create mode 100644 space/components/issues/issue-layouts/properties/index.ts create mode 100644 space/components/issues/issue-layouts/properties/labels.tsx rename space/components/issues/{board-views/block-priority.tsx => issue-layouts/properties/priority.tsx} (85%) create mode 100644 space/components/issues/issue-layouts/properties/state.tsx rename space/components/{views/project-details.tsx => issues/issue-layouts/root.tsx} (52%) create mode 100644 space/components/issues/navbar/index.ts delete mode 100644 space/components/issues/navbar/issue-board-view.tsx create mode 100644 space/components/issues/navbar/layout-selection.tsx rename space/components/issues/navbar/{index.tsx => root.tsx} (58%) delete mode 100644 space/components/ui/dropdown.tsx create mode 100644 space/constants/state.ts delete mode 100644 space/constants/workspace.ts create mode 100644 space/helpers/issue.helper.ts create mode 100644 space/hooks/store/publish/index.ts create mode 100644 space/hooks/store/publish/use-publish-list.ts create mode 100644 space/hooks/store/publish/use-publish.ts delete mode 100644 space/hooks/store/use-project.ts delete mode 100644 space/lib/user-provider.tsx delete mode 100644 space/services/project.service.ts create mode 100644 space/services/publish.service.ts delete mode 100644 space/store/project.store.ts create mode 100644 space/store/publish/publish.store.ts create mode 100644 space/store/publish/publish_list.store.ts delete mode 100644 space/types/app.d.ts delete mode 100644 space/types/project.d.ts delete mode 100644 space/types/theme.d.ts delete mode 100644 space/types/user.d.ts delete mode 100644 web/components/estimates/create-update-estimate-modal.tsx create mode 100644 web/components/estimates/create/index.ts create mode 100644 web/components/estimates/create/modal.tsx create mode 100644 web/components/estimates/create/stage-one.tsx delete mode 100644 web/components/estimates/delete-estimate-modal.tsx create mode 100644 web/components/estimates/delete/index.ts create mode 100644 web/components/estimates/delete/modal.tsx create mode 100644 web/components/estimates/empty-screen.tsx create mode 100644 web/components/estimates/estimate-disable-switch.tsx create mode 100644 web/components/estimates/estimate-list-item-buttons.tsx create mode 100644 web/components/estimates/estimate-list.tsx create mode 100644 web/components/estimates/estimate-search.tsx delete mode 100644 web/components/estimates/estimates-list.tsx create mode 100644 web/components/estimates/loader-screen.tsx create mode 100644 web/components/estimates/points/create-root.tsx create mode 100644 web/components/estimates/points/create.tsx create mode 100644 web/components/estimates/points/delete.tsx create mode 100644 web/components/estimates/points/index.ts create mode 100644 web/components/estimates/points/preview.tsx create mode 100644 web/components/estimates/points/select-dropdown.tsx create mode 100644 web/components/estimates/points/update.tsx create mode 100644 web/components/estimates/radio-select.tsx create mode 100644 web/components/estimates/root.tsx create mode 100644 web/components/estimates/update/index.ts create mode 100644 web/components/estimates/update/modal.tsx create mode 100644 web/components/estimates/update/stage-one.tsx create mode 100644 web/components/project/publish-project/index.ts delete mode 100644 web/components/project/publish-project/index.tsx delete mode 100644 web/components/project/publish-project/popover.tsx create mode 100644 web/constants/estimates.ts create mode 100644 web/helpers/estimates.ts create mode 100644 web/hooks/store/estimates/index.ts create mode 100644 web/hooks/store/estimates/use-estimate-point.ts create mode 100644 web/hooks/store/estimates/use-estimate.ts create mode 100644 web/hooks/store/estimates/use-project-estimate.ts delete mode 100644 web/hooks/store/use-estimate.ts create mode 100644 web/public/empty-state/estimates/dark.svg create mode 100644 web/public/empty-state/estimates/light.svg create mode 100644 web/services/project/estimate.service.ts delete mode 100644 web/services/project/project-estimate.service.ts delete mode 100644 web/store/estimate.store.ts create mode 100644 web/store/estimates/estimate-point.ts create mode 100644 web/store/estimates/estimate.ts create mode 100644 web/store/estimates/project-estimate.store.ts diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index 6e1e5e057f9..d1e1917a4c4 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -784,6 +784,7 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView): def post(self, request, slug, project_id, cycle_id): new_cycle_id = request.data.get("new_cycle_id", False) + plot_type = request.GET.get("plot_type", "issues") if not new_cycle_id: return Response( @@ -865,6 +866,7 @@ def post(self, request, slug, project_id, cycle_id): queryset=old_cycle.first(), slug=slug, project_id=project_id, + plot_type=plot_type, cycle_id=cycle_id, ) diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index 019ab704eca..408e14fed05 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -22,7 +22,7 @@ IssueProperty, Module, Project, - ProjectDeployBoard, + DeployBoard, ProjectMember, State, Workspace, @@ -99,7 +99,7 @@ def get_queryset(self): ) .annotate( is_deployed=Exists( - ProjectDeployBoard.objects.filter( + DeployBoard.objects.filter( project_id=OuterRef("pk"), workspace__slug=self.kwargs.get("slug"), ) diff --git a/apiserver/plane/app/serializers/__init__.py b/apiserver/plane/app/serializers/__init__.py index bdcdf6c0de6..d8364f93148 100644 --- a/apiserver/plane/app/serializers/__init__.py +++ b/apiserver/plane/app/serializers/__init__.py @@ -30,7 +30,7 @@ ProjectIdentifierSerializer, ProjectLiteSerializer, ProjectMemberLiteSerializer, - ProjectDeployBoardSerializer, + DeployBoardSerializer, ProjectMemberAdminSerializer, ProjectPublicMemberSerializer, ProjectMemberRoleSerializer, diff --git a/apiserver/plane/app/serializers/estimate.py b/apiserver/plane/app/serializers/estimate.py index d28f38c75ab..e73b5ceefaf 100644 --- a/apiserver/plane/app/serializers/estimate.py +++ b/apiserver/plane/app/serializers/estimate.py @@ -11,10 +11,6 @@ class EstimateSerializer(BaseSerializer): - workspace_detail = WorkspaceLiteSerializer( - read_only=True, source="workspace" - ) - project_detail = ProjectLiteSerializer(read_only=True, source="project") class Meta: model = Estimate @@ -48,10 +44,6 @@ class Meta: class EstimateReadSerializer(BaseSerializer): points = EstimatePointSerializer(read_only=True, many=True) - workspace_detail = WorkspaceLiteSerializer( - read_only=True, source="workspace" - ) - project_detail = ProjectLiteSerializer(read_only=True, source="project") class Meta: model = Estimate diff --git a/apiserver/plane/app/serializers/module.py b/apiserver/plane/app/serializers/module.py index 28d28d7dbc7..7762d3b800f 100644 --- a/apiserver/plane/app/serializers/module.py +++ b/apiserver/plane/app/serializers/module.py @@ -177,6 +177,8 @@ class ModuleSerializer(DynamicBaseSerializer): started_issues = serializers.IntegerField(read_only=True) unstarted_issues = serializers.IntegerField(read_only=True) backlog_issues = serializers.IntegerField(read_only=True) + total_estimate_points = serializers.IntegerField(read_only=True) + completed_estimate_points = serializers.IntegerField(read_only=True) class Meta: model = Module @@ -201,6 +203,8 @@ class Meta: "external_id", "logo_props", # computed fields + "total_estimate_points", + "completed_estimate_points", "is_favorite", "total_issues", "cancelled_issues", diff --git a/apiserver/plane/app/serializers/project.py b/apiserver/plane/app/serializers/project.py index 96d92f3404a..1bbc580c119 100644 --- a/apiserver/plane/app/serializers/project.py +++ b/apiserver/plane/app/serializers/project.py @@ -13,7 +13,7 @@ ProjectMember, ProjectMemberInvite, ProjectIdentifier, - ProjectDeployBoard, + DeployBoard, ProjectPublicMember, ) @@ -114,7 +114,7 @@ class ProjectListSerializer(DynamicBaseSerializer): is_member = serializers.BooleanField(read_only=True) sort_order = serializers.FloatField(read_only=True) member_role = serializers.IntegerField(read_only=True) - is_deployed = serializers.BooleanField(read_only=True) + anchor = serializers.CharField(read_only=True) members = serializers.SerializerMethodField() def get_members(self, obj): @@ -148,7 +148,7 @@ class ProjectDetailSerializer(BaseSerializer): is_member = serializers.BooleanField(read_only=True) sort_order = serializers.FloatField(read_only=True) member_role = serializers.IntegerField(read_only=True) - is_deployed = serializers.BooleanField(read_only=True) + anchor = serializers.CharField(read_only=True) class Meta: model = Project @@ -206,14 +206,14 @@ class Meta: read_only_fields = fields -class ProjectDeployBoardSerializer(BaseSerializer): +class DeployBoardSerializer(BaseSerializer): project_details = ProjectLiteSerializer(read_only=True, source="project") workspace_detail = WorkspaceLiteSerializer( read_only=True, source="workspace" ) class Meta: - model = ProjectDeployBoard + model = DeployBoard fields = "__all__" read_only_fields = [ "workspace", diff --git a/apiserver/plane/app/urls/estimate.py b/apiserver/plane/app/urls/estimate.py index d8571ff0c98..7db94aa46ce 100644 --- a/apiserver/plane/app/urls/estimate.py +++ b/apiserver/plane/app/urls/estimate.py @@ -4,6 +4,7 @@ from plane.app.views import ( ProjectEstimatePointEndpoint, BulkEstimatePointEndpoint, + EstimatePointEndpoint, ) @@ -34,4 +35,23 @@ ), name="bulk-create-estimate-points", ), + path( + "workspaces//projects//estimates//estimate-points/", + EstimatePointEndpoint.as_view( + { + "post": "create", + } + ), + name="estimate-points", + ), + path( + "workspaces//projects//estimates//estimate-points//", + EstimatePointEndpoint.as_view( + { + "patch": "partial_update", + "delete": "destroy", + } + ), + name="estimate-points", + ), ] diff --git a/apiserver/plane/app/urls/project.py b/apiserver/plane/app/urls/project.py index 7ea636df8e9..0807c7616a2 100644 --- a/apiserver/plane/app/urls/project.py +++ b/apiserver/plane/app/urls/project.py @@ -2,6 +2,7 @@ from plane.app.views import ( ProjectViewSet, + DeployBoardViewSet, ProjectInvitationsViewset, ProjectMemberViewSet, ProjectMemberUserEndpoint, @@ -12,7 +13,6 @@ ProjectFavoritesViewSet, UserProjectInvitationsViewset, ProjectPublicCoverImagesEndpoint, - ProjectDeployBoardViewSet, UserProjectRolesEndpoint, ProjectArchiveUnarchiveEndpoint, ) @@ -157,7 +157,7 @@ ), path( "workspaces//projects//project-deploy-boards/", - ProjectDeployBoardViewSet.as_view( + DeployBoardViewSet.as_view( { "get": "list", "post": "create", @@ -167,7 +167,7 @@ ), path( "workspaces//projects//project-deploy-boards//", - ProjectDeployBoardViewSet.as_view( + DeployBoardViewSet.as_view( { "get": "retrieve", "patch": "partial_update", diff --git a/apiserver/plane/app/views/__init__.py b/apiserver/plane/app/views/__init__.py index 0c489593d63..8da0268b9a9 100644 --- a/apiserver/plane/app/views/__init__.py +++ b/apiserver/plane/app/views/__init__.py @@ -4,7 +4,7 @@ ProjectUserViewsEndpoint, ProjectFavoritesViewSet, ProjectPublicCoverImagesEndpoint, - ProjectDeployBoardViewSet, + DeployBoardViewSet, ProjectArchiveUnarchiveEndpoint, ) @@ -190,6 +190,7 @@ from .estimate.base import ( ProjectEstimatePointEndpoint, BulkEstimatePointEndpoint, + EstimatePointEndpoint, ) from .inbox.base import InboxViewSet, InboxIssueViewSet diff --git a/apiserver/plane/app/views/analytic/base.py b/apiserver/plane/app/views/analytic/base.py index 256d3cae5bf..3d27641e3c4 100644 --- a/apiserver/plane/app/views/analytic/base.py +++ b/apiserver/plane/app/views/analytic/base.py @@ -33,7 +33,7 @@ def get(self, request, slug): "state__group", "labels__id", "assignees__id", - "estimate_point", + "estimate_point__value", "issue_cycle__cycle_id", "issue_module__module_id", "priority", @@ -381,9 +381,9 @@ def get(self, request, slug): ) open_estimate_sum = open_issues_queryset.aggregate( - sum=Sum("estimate_point") + sum=Sum("point") )["sum"] - total_estimate_sum = base_issues.aggregate(sum=Sum("estimate_point"))[ + total_estimate_sum = base_issues.aggregate(sum=Sum("point"))[ "sum" ] diff --git a/apiserver/plane/app/views/cycle/archive.py b/apiserver/plane/app/views/cycle/archive.py index 5e1241b08f6..f99c2ec9720 100644 --- a/apiserver/plane/app/views/cycle/archive.py +++ b/apiserver/plane/app/views/cycle/archive.py @@ -177,6 +177,7 @@ def get_queryset(self): ) def get(self, request, slug, project_id, pk=None): + plot_type = request.GET.get("plot_type", "issues") if pk is None: queryset = ( self.get_queryset() @@ -375,6 +376,7 @@ def get(self, request, slug, project_id, pk=None): queryset=queryset, slug=slug, project_id=project_id, + plot_type=plot_type, cycle_id=pk, ) diff --git a/apiserver/plane/app/views/cycle/base.py b/apiserver/plane/app/views/cycle/base.py index 5982daf7f97..91012748fe8 100644 --- a/apiserver/plane/app/views/cycle/base.py +++ b/apiserver/plane/app/views/cycle/base.py @@ -17,8 +17,11 @@ UUIDField, Value, When, + Subquery, + Sum, + IntegerField, ) -from django.db.models.functions import Coalesce +from django.db.models.functions import Coalesce, Cast from django.utils import timezone from django.core.serializers.json import DjangoJSONEncoder @@ -73,6 +76,89 @@ def get_queryset(self): project_id=self.kwargs.get("project_id"), workspace__slug=self.kwargs.get("slug"), ) + backlog_estimate_point = ( + Issue.issue_objects.filter( + estimate_point__estimate__type="points", + state__group="backlog", + issue_cycle__cycle_id=OuterRef("pk"), + ) + .values("issue_cycle__cycle_id") + .annotate( + backlog_estimate_point=Sum( + Cast("estimate_point__value", IntegerField()) + ) + ) + .values("backlog_estimate_point")[:1] + ) + unstarted_estimate_point = ( + Issue.issue_objects.filter( + estimate_point__estimate__type="points", + state__group="unstarted", + issue_cycle__cycle_id=OuterRef("pk"), + ) + .values("issue_cycle__cycle_id") + .annotate( + unstarted_estimate_point=Sum( + Cast("estimate_point__value", IntegerField()) + ) + ) + .values("unstarted_estimate_point")[:1] + ) + started_estimate_point = ( + Issue.issue_objects.filter( + estimate_point__estimate__type="points", + state__group="started", + issue_cycle__cycle_id=OuterRef("pk"), + ) + .values("issue_cycle__cycle_id") + .annotate( + started_estimate_point=Sum( + Cast("estimate_point__value", IntegerField()) + ) + ) + .values("started_estimate_point")[:1] + ) + cancelled_estimate_point = ( + Issue.issue_objects.filter( + estimate_point__estimate__type="points", + state__group="cancelled", + issue_cycle__cycle_id=OuterRef("pk"), + ) + .values("issue_cycle__cycle_id") + .annotate( + cancelled_estimate_point=Sum( + Cast("estimate_point__value", IntegerField()) + ) + ) + .values("cancelled_estimate_point")[:1] + ) + completed_estimate_point = ( + Issue.issue_objects.filter( + estimate_point__estimate__type="points", + state__group="completed", + issue_cycle__cycle_id=OuterRef("pk"), + ) + .values("issue_cycle__cycle_id") + .annotate( + completed_estimate_points=Sum( + Cast("estimate_point__value", IntegerField()) + ) + ) + .values("completed_estimate_points")[:1] + ) + total_estimate_point = ( + Issue.issue_objects.filter( + estimate_point__estimate__type="points", + issue_cycle__cycle_id=OuterRef("pk"), + ) + .values("issue_cycle__cycle_id") + .annotate( + total_estimate_points=Sum( + Cast("estimate_point__value", IntegerField()) + ) + ) + .values("total_estimate_points")[:1] + ) return self.filter_queryset( super() .get_queryset() @@ -197,12 +283,49 @@ def get_queryset(self): Value([], output_field=ArrayField(UUIDField())), ) ) + .annotate( + backlog_estimate_points=Coalesce( + Subquery(backlog_estimate_point), + Value(0, output_field=IntegerField()), + ), + ) + .annotate( + unstarted_estimate_points=Coalesce( + Subquery(unstarted_estimate_point), + Value(0, output_field=IntegerField()), + ), + ) + .annotate( + started_estimate_points=Coalesce( + Subquery(started_estimate_point), + Value(0, output_field=IntegerField()), + ), + ) + .annotate( + cancelled_estimate_points=Coalesce( + Subquery(cancelled_estimate_point), + Value(0, output_field=IntegerField()), + ), + ) + .annotate( + completed_estimate_points=Coalesce( + Subquery(completed_estimate_point), + Value(0, output_field=IntegerField()), + ), + ) + .annotate( + total_estimate_points=Coalesce( + Subquery(total_estimate_point), + Value(0, output_field=IntegerField()), + ), + ) .order_by("-is_favorite", "name") .distinct() ) def list(self, request, slug, project_id): queryset = self.get_queryset().filter(archived_at__isnull=True) + plot_type = request.GET.get("plot_type", "issues") cycle_view = request.GET.get("cycle_view", "all") # Update the order by @@ -233,6 +356,12 @@ def list(self, request, slug, project_id): "progress_snapshot", "logo_props", # meta fields + "backlog_estimate_points", + "unstarted_estimate_points", + "started_estimate_points", + "cancelled_estimate_points", + "completed_estimate_points", + "total_estimate_points", "is_favorite", "total_issues", "cancelled_issues", @@ -335,6 +464,7 @@ def list(self, request, slug, project_id): queryset=queryset.first(), slug=slug, project_id=project_id, + plot_type=plot_type, cycle_id=data[0]["id"], ) ) @@ -359,6 +489,8 @@ def list(self, request, slug, project_id): "progress_snapshot", "logo_props", # meta fields + "completed_estimate_points", + "total_estimate_points", "is_favorite", "total_issues", "cancelled_issues", @@ -527,6 +659,7 @@ def partial_update(self, request, slug, project_id, pk): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def retrieve(self, request, slug, project_id, pk): + plot_type = request.GET.get("plot_type", "issues") queryset = ( self.get_queryset().filter(archived_at__isnull=True).filter(pk=pk) ) @@ -682,6 +815,7 @@ def retrieve(self, request, slug, project_id, pk): queryset=queryset, slug=slug, project_id=project_id, + plot_type=plot_type, cycle_id=pk, ) @@ -798,6 +932,7 @@ class TransferCycleIssueEndpoint(BaseAPIView): def post(self, request, slug, project_id, cycle_id): new_cycle_id = request.data.get("new_cycle_id", False) + plot_type = request.GET.get("plot_type", "issues") if not new_cycle_id: return Response( @@ -879,6 +1014,7 @@ def post(self, request, slug, project_id, cycle_id): queryset=old_cycle.first(), slug=slug, project_id=project_id, + plot_type=plot_type, cycle_id=cycle_id, ) diff --git a/apiserver/plane/app/views/estimate/base.py b/apiserver/plane/app/views/estimate/base.py index 7ac3035a956..2bd9e3dfe97 100644 --- a/apiserver/plane/app/views/estimate/base.py +++ b/apiserver/plane/app/views/estimate/base.py @@ -1,3 +1,6 @@ +import random +import string + # Third party imports from rest_framework.response import Response from rest_framework import status @@ -5,7 +8,7 @@ # Module imports from ..base import BaseViewSet, BaseAPIView from plane.app.permissions import ProjectEntityPermission -from plane.db.models import Project, Estimate, EstimatePoint +from plane.db.models import Project, Estimate, EstimatePoint, Issue from plane.app.serializers import ( EstimateSerializer, EstimatePointSerializer, @@ -13,6 +16,12 @@ ) from plane.utils.cache import invalidate_cache + +def generate_random_name(length=10): + letters = string.ascii_lowercase + return "".join(random.choice(letters) for i in range(length)) + + class ProjectEstimatePointEndpoint(BaseAPIView): permission_classes = [ ProjectEntityPermission, @@ -49,13 +58,17 @@ def list(self, request, slug, project_id): serializer = EstimateReadSerializer(estimates, many=True) return Response(serializer.data, status=status.HTTP_200_OK) - @invalidate_cache(path="/api/workspaces/:slug/estimates/", url_params=True, user=False) + @invalidate_cache( + path="/api/workspaces/:slug/estimates/", url_params=True, user=False + ) def create(self, request, slug, project_id): - if not request.data.get("estimate", False): - return Response( - {"error": "Estimate is required"}, - status=status.HTTP_400_BAD_REQUEST, - ) + estimate = request.data.get('estimate') + estimate_name = estimate.get("name", generate_random_name()) + estimate_type = estimate.get("type", 'categories') + last_used = estimate.get("last_used", False) + estimate = Estimate.objects.create( + name=estimate_name, project_id=project_id, last_used=last_used, type=estimate_type + ) estimate_points = request.data.get("estimate_points", []) @@ -67,14 +80,6 @@ def create(self, request, slug, project_id): serializer.errors, status=status.HTTP_400_BAD_REQUEST ) - estimate_serializer = EstimateSerializer( - data=request.data.get("estimate") - ) - if not estimate_serializer.is_valid(): - return Response( - estimate_serializer.errors, status=status.HTTP_400_BAD_REQUEST - ) - estimate = estimate_serializer.save(project_id=project_id) estimate_points = EstimatePoint.objects.bulk_create( [ EstimatePoint( @@ -93,17 +98,8 @@ def create(self, request, slug, project_id): ignore_conflicts=True, ) - estimate_point_serializer = EstimatePointSerializer( - estimate_points, many=True - ) - - return Response( - { - "estimate": estimate_serializer.data, - "estimate_points": estimate_point_serializer.data, - }, - status=status.HTTP_200_OK, - ) + serializer = EstimateReadSerializer(estimate) + return Response(serializer.data, status=status.HTTP_200_OK) def retrieve(self, request, slug, project_id, estimate_id): estimate = Estimate.objects.get( @@ -115,13 +111,10 @@ def retrieve(self, request, slug, project_id, estimate_id): status=status.HTTP_200_OK, ) - @invalidate_cache(path="/api/workspaces/:slug/estimates/", url_params=True, user=False) + @invalidate_cache( + path="/api/workspaces/:slug/estimates/", url_params=True, user=False + ) def partial_update(self, request, slug, project_id, estimate_id): - if not request.data.get("estimate", False): - return Response( - {"error": "Estimate is required"}, - status=status.HTTP_400_BAD_REQUEST, - ) if not len(request.data.get("estimate_points", [])): return Response( @@ -131,15 +124,10 @@ def partial_update(self, request, slug, project_id, estimate_id): estimate = Estimate.objects.get(pk=estimate_id) - estimate_serializer = EstimateSerializer( - estimate, data=request.data.get("estimate"), partial=True - ) - if not estimate_serializer.is_valid(): - return Response( - estimate_serializer.errors, status=status.HTTP_400_BAD_REQUEST - ) - - estimate = estimate_serializer.save() + if request.data.get("estimate"): + estimate.name = request.data.get("estimate").get("name", estimate.name) + estimate.type = request.data.get("estimate").get("type", estimate.type) + estimate.save() estimate_points_data = request.data.get("estimate_points", []) @@ -165,29 +153,113 @@ def partial_update(self, request, slug, project_id, estimate_id): estimate_point.value = estimate_point_data[0].get( "value", estimate_point.value ) + estimate_point.key = estimate_point_data[0].get( + "key", estimate_point.key + ) updated_estimate_points.append(estimate_point) EstimatePoint.objects.bulk_update( updated_estimate_points, - ["value"], + ["key", "value"], batch_size=10, ) - estimate_point_serializer = EstimatePointSerializer( - estimate_points, many=True - ) + estimate_serializer = EstimateReadSerializer(estimate) return Response( - { - "estimate": estimate_serializer.data, - "estimate_points": estimate_point_serializer.data, - }, + estimate_serializer.data, status=status.HTTP_200_OK, ) - @invalidate_cache(path="/api/workspaces/:slug/estimates/", url_params=True, user=False) + @invalidate_cache( + path="/api/workspaces/:slug/estimates/", url_params=True, user=False + ) def destroy(self, request, slug, project_id, estimate_id): estimate = Estimate.objects.get( pk=estimate_id, workspace__slug=slug, project_id=project_id ) estimate.delete() return Response(status=status.HTTP_204_NO_CONTENT) + + +class EstimatePointEndpoint(BaseViewSet): + permission_classes = [ + ProjectEntityPermission, + ] + + def create(self, request, slug, project_id, estimate_id): + # TODO: add a key validation if the same key already exists + if not request.data.get("key") or not request.data.get("value"): + return Response( + {"error": "Key and value are required"}, + status=status.HTTP_400_BAD_REQUEST, + ) + key = request.data.get("key", 0) + value = request.data.get("value", "") + estimate_point = EstimatePoint.objects.create( + estimate_id=estimate_id, + project_id=project_id, + key=key, + value=value, + ) + serializer = EstimatePointSerializer(estimate_point).data + return Response(serializer, status=status.HTTP_200_OK) + + def partial_update(self, request, slug, project_id, estimate_id, estimate_point_id): + # TODO: add a key validation if the same key already exists + estimate_point = EstimatePoint.objects.get( + pk=estimate_point_id, + estimate_id=estimate_id, + project_id=project_id, + workspace__slug=slug, + ) + serializer = EstimatePointSerializer( + estimate_point, data=request.data, partial=True + ) + if not serializer.is_valid(): + return Response( + serializer.errors, status=status.HTTP_400_BAD_REQUEST + ) + serializer.save() + return Response(serializer.data, status=status.HTTP_200_OK) + + def destroy( + self, request, slug, project_id, estimate_id, estimate_point_id + ): + new_estimate_id = request.GET.get("new_estimate_id", None) + estimate_points = EstimatePoint.objects.filter( + estimate_id=estimate_id, + project_id=project_id, + workspace__slug=slug, + ) + # update all the issues with the new estimate + if new_estimate_id: + _ = Issue.objects.filter( + project_id=project_id, + workspace__slug=slug, + estimate_id=estimate_point_id, + ).update(estimate_id=new_estimate_id) + + # delete the estimate point + old_estimate_point = EstimatePoint.objects.filter( + pk=estimate_point_id + ).first() + + # rearrange the estimate points + updated_estimate_points = [] + for estimate_point in estimate_points: + if estimate_point.key > old_estimate_point.key: + estimate_point.key -= 1 + updated_estimate_points.append(estimate_point) + + EstimatePoint.objects.bulk_update( + updated_estimate_points, + ["key"], + batch_size=10, + ) + + old_estimate_point.delete() + + return Response( + EstimatePointSerializer(updated_estimate_points, many=True).data, + status=status.HTTP_200_OK, + ) diff --git a/apiserver/plane/app/views/module/archive.py b/apiserver/plane/app/views/module/archive.py index 2cac5f36692..d474f26c651 100644 --- a/apiserver/plane/app/views/module/archive.py +++ b/apiserver/plane/app/views/module/archive.py @@ -165,6 +165,7 @@ def get_queryset(self): ) def get(self, request, slug, project_id, pk=None): + plot_type = request.GET.get("plot_type", "issues") if pk is None: queryset = self.get_queryset() modules = queryset.values( # Required fields @@ -323,6 +324,7 @@ def get(self, request, slug, project_id, pk=None): queryset=modules, slug=slug, project_id=project_id, + plot_type=plot_type, module_id=pk, ) diff --git a/apiserver/plane/app/views/module/base.py b/apiserver/plane/app/views/module/base.py index 56267554d71..5b631be463f 100644 --- a/apiserver/plane/app/views/module/base.py +++ b/apiserver/plane/app/views/module/base.py @@ -16,8 +16,9 @@ Subquery, UUIDField, Value, + Sum, ) -from django.db.models.functions import Coalesce +from django.db.models.functions import Coalesce, Cast from django.core.serializers.json import DjangoJSONEncoder from django.utils import timezone @@ -128,6 +129,34 @@ def get_queryset(self): .annotate(cnt=Count("pk")) .values("cnt") ) + completed_estimate_point = ( + Issue.issue_objects.filter( + estimate_point__estimate__type="points", + state__group="completed", + issue_module__module_id=OuterRef("pk"), + ) + .values("issue_module__module_id") + .annotate( + completed_estimate_points=Sum( + Cast("estimate_point__value", IntegerField()) + ) + ) + .values("completed_estimate_points")[:1] + ) + + total_estimate_point = ( + Issue.issue_objects.filter( + estimate_point__estimate__type="points", + issue_module__module_id=OuterRef("pk"), + ) + .values("issue_module__module_id") + .annotate( + total_estimate_points=Sum( + Cast("estimate_point__value", IntegerField()) + ) + ) + .values("total_estimate_points")[:1] + ) return ( super() .get_queryset() @@ -182,6 +211,18 @@ def get_queryset(self): Value(0, output_field=IntegerField()), ) ) + .annotate( + completed_estimate_points=Coalesce( + Subquery(completed_estimate_point), + Value(0, output_field=IntegerField()), + ), + ) + .annotate( + total_estimate_points=Coalesce( + Subquery(total_estimate_point), + Value(0, output_field=IntegerField()), + ), + ) .annotate( member_ids=Coalesce( ArrayAgg( @@ -233,6 +274,8 @@ def create(self, request, slug, project_id): "total_issues", "started_issues", "unstarted_issues", + "completed_estimate_points", + "total_estimate_points", "backlog_issues", "created_at", "updated_at", @@ -284,6 +327,8 @@ def list(self, request, slug, project_id): "external_id", "logo_props", # computed fields + "completed_estimate_points", + "total_estimate_points", "total_issues", "is_favorite", "cancelled_issues", @@ -301,6 +346,7 @@ def list(self, request, slug, project_id): return Response(modules, status=status.HTTP_200_OK) def retrieve(self, request, slug, project_id, pk): + plot_type = request.GET.get("plot_type", "burndown") queryset = ( self.get_queryset() .filter(archived_at__isnull=True) @@ -423,6 +469,7 @@ def retrieve(self, request, slug, project_id, pk): queryset=modules, slug=slug, project_id=project_id, + plot_type=plot_type, module_id=pk, ) @@ -469,6 +516,8 @@ def partial_update(self, request, slug, project_id, pk): "external_id", "logo_props", # computed fields + "completed_estimate_points", + "total_estimate_points", "is_favorite", "cancelled_issues", "completed_issues", diff --git a/apiserver/plane/app/views/project/base.py b/apiserver/plane/app/views/project/base.py index 39db11871d8..7e3326e02ce 100644 --- a/apiserver/plane/app/views/project/base.py +++ b/apiserver/plane/app/views/project/base.py @@ -28,7 +28,7 @@ from plane.app.serializers import ( ProjectSerializer, ProjectListSerializer, - ProjectDeployBoardSerializer, + DeployBoardSerializer, ) from plane.app.permissions import ( @@ -46,7 +46,7 @@ Module, Cycle, Inbox, - ProjectDeployBoard, + DeployBoard, IssueProperty, Issue, ) @@ -137,12 +137,11 @@ def get_queryset(self): ).values("role") ) .annotate( - is_deployed=Exists( - ProjectDeployBoard.objects.filter( - project_id=OuterRef("pk"), - workspace__slug=self.kwargs.get("slug"), - ) - ) + anchor=DeployBoard.objects.filter( + entity_name="project", + entity_identifier=OuterRef("pk"), + workspace__slug=self.kwargs.get("slug"), + ).values("anchor") ) .annotate(sort_order=Subquery(sort_order)) .prefetch_related( @@ -639,29 +638,28 @@ def get(self, request): return Response(files, status=status.HTTP_200_OK) -class ProjectDeployBoardViewSet(BaseViewSet): +class DeployBoardViewSet(BaseViewSet): permission_classes = [ ProjectMemberPermission, ] - serializer_class = ProjectDeployBoardSerializer - model = ProjectDeployBoard + serializer_class = DeployBoardSerializer + model = DeployBoard - def get_queryset(self): - return ( - super() - .get_queryset() - .filter( - workspace__slug=self.kwargs.get("slug"), - project_id=self.kwargs.get("project_id"), - ) - .select_related("project") - ) + def list(self, request, slug, project_id): + project_deploy_board = DeployBoard.objects.filter( + entity_name="project", + entity_identifier=project_id, + workspace__slug=slug, + ).first() + + serializer = DeployBoardSerializer(project_deploy_board) + return Response(serializer.data, status=status.HTTP_200_OK) def create(self, request, slug, project_id): - comments = request.data.get("comments", False) - reactions = request.data.get("reactions", False) + comments = request.data.get("is_comments_enabled", False) + reactions = request.data.get("is_reactions_enabled", False) inbox = request.data.get("inbox", None) - votes = request.data.get("votes", False) + votes = request.data.get("is_votes_enabled", False) views = request.data.get( "views", { @@ -673,17 +671,18 @@ def create(self, request, slug, project_id): }, ) - project_deploy_board, _ = ProjectDeployBoard.objects.get_or_create( - anchor=f"{slug}/{project_id}", + project_deploy_board, _ = DeployBoard.objects.get_or_create( + entity_name="project", + entity_identifier=project_id, project_id=project_id, ) - project_deploy_board.comments = comments - project_deploy_board.reactions = reactions project_deploy_board.inbox = inbox - project_deploy_board.votes = votes - project_deploy_board.views = views + project_deploy_board.view_props = views + project_deploy_board.is_votes_enabled = votes + project_deploy_board.is_comments_enabled = comments + project_deploy_board.is_reactions_enabled = reactions project_deploy_board.save() - serializer = ProjectDeployBoardSerializer(project_deploy_board) + serializer = DeployBoardSerializer(project_deploy_board) return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/apiserver/plane/bgtasks/issue_activites_task.py b/apiserver/plane/bgtasks/issue_activites_task.py index 007b3e48c96..67cda14af62 100644 --- a/apiserver/plane/bgtasks/issue_activites_task.py +++ b/apiserver/plane/bgtasks/issue_activites_task.py @@ -28,6 +28,7 @@ Project, State, User, + EstimatePoint, ) from plane.settings.redis import redis_instance from plane.utils.exception_logger import log_exception @@ -448,21 +449,37 @@ def track_estimate_points( if current_instance.get("estimate_point") != requested_data.get( "estimate_point" ): + old_estimate = ( + EstimatePoint.objects.filter( + pk=current_instance.get("estimate_point") + ).first() + if current_instance.get("estimate_point") is not None + else None + ) + new_estimate = ( + EstimatePoint.objects.filter( + pk=requested_data.get("estimate_point") + ).first() + if requested_data.get("estimate_point") is not None + else None + ) issue_activities.append( IssueActivity( issue_id=issue_id, actor_id=actor_id, verb="updated", - old_value=( + old_identifier=( current_instance.get("estimate_point") if current_instance.get("estimate_point") is not None - else "" + else None ), - new_value=( + new_identifier=( requested_data.get("estimate_point") if requested_data.get("estimate_point") is not None - else "" + else None ), + old_value=old_estimate.value if old_estimate else None, + new_value=new_estimate.value if new_estimate else None, field="estimate_point", project_id=project_id, workspace_id=workspace_id, diff --git a/apiserver/plane/db/migrations/0067_issue_estimate.py b/apiserver/plane/db/migrations/0067_issue_estimate.py new file mode 100644 index 00000000000..b341f986409 --- /dev/null +++ b/apiserver/plane/db/migrations/0067_issue_estimate.py @@ -0,0 +1,260 @@ +# # Generated by Django 4.2.7 on 2024-05-24 09:47 +# Python imports +import uuid +from uuid import uuid4 +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models +import plane.db.models.deploy_board + + +def issue_estimate_point(apps, schema_editor): + Issue = apps.get_model("db", "Issue") + Project = apps.get_model("db", "Project") + EstimatePoint = apps.get_model("db", "EstimatePoint") + IssueActivity = apps.get_model("db", "IssueActivity") + updated_estimate_point = [] + updated_issue_activity = [] + + # loop through all the projects + for project in Project.objects.filter(estimate__isnull=False): + estimate_points = EstimatePoint.objects.filter( + estimate=project.estimate, project=project + ) + + for issue_activity in IssueActivity.objects.filter( + field="estimate_point", project=project + ): + if issue_activity.new_value: + new_identifier = estimate_points.filter( + key=issue_activity.new_value + ).first().id + issue_activity.new_identifier = new_identifier + new_value = estimate_points.filter( + key=issue_activity.new_value + ).first().value + issue_activity.new_value = new_value + + if issue_activity.old_value: + old_identifier = estimate_points.filter( + key=issue_activity.old_value + ).first().id + issue_activity.old_identifier = old_identifier + old_value = estimate_points.filter( + key=issue_activity.old_value + ).first().value + issue_activity.old_value = old_value + updated_issue_activity.append(issue_activity) + + for issue in Issue.objects.filter( + point__isnull=False, project=project + ): + # get the estimate id for the corresponding estimate point in the issue + estimate = estimate_points.filter(key=issue.point).first() + issue.estimate_point = estimate + updated_estimate_point.append(issue) + + Issue.objects.bulk_update( + updated_estimate_point, ["estimate_point"], batch_size=1000 + ) + IssueActivity.objects.bulk_update( + updated_issue_activity, + ["new_value", "old_value", "new_identifier", "old_identifier"], + batch_size=1000, + ) + + +def last_used_estimate(apps, schema_editor): + Project = apps.get_model("db", "Project") + Estimate = apps.get_model("db", "Estimate") + + # Get all estimate ids used in projects + estimate_ids = Project.objects.filter(estimate__isnull=False).values_list( + "estimate", flat=True + ) + + # Update all matching estimates + Estimate.objects.filter(id__in=estimate_ids).update(last_used=True) + + +def populate_deploy_board(apps, schema_editor): + DeployBoard = apps.get_model("db", "DeployBoard") + ProjectDeployBoard = apps.get_model("db", "ProjectDeployBoard") + + DeployBoard.objects.bulk_create( + [ + DeployBoard( + entity_identifier=deploy_board.project_id, + project_id=deploy_board.project_id, + entity_name="project", + anchor=uuid4().hex, + is_comments_enabled=deploy_board.comments, + is_reactions_enabled=deploy_board.reactions, + inbox=deploy_board.inbox, + is_votes_enabled=deploy_board.votes, + view_props=deploy_board.views, + workspace_id=deploy_board.workspace_id, + created_at=deploy_board.created_at, + updated_at=deploy_board.updated_at, + created_by_id=deploy_board.created_by_id, + updated_by_id=deploy_board.updated_by_id, + ) + for deploy_board in ProjectDeployBoard.objects.all() + ], + batch_size=100, + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ("db", "0066_account_id_token_cycle_logo_props_module_logo_props"), + ] + + operations = [ + migrations.CreateModel( + name="DeployBoard", + fields=[ + ( + "created_at", + models.DateTimeField( + auto_now_add=True, verbose_name="Created At" + ), + ), + ( + "updated_at", + models.DateTimeField( + auto_now=True, verbose_name="Last Modified At" + ), + ), + ( + "id", + models.UUIDField( + db_index=True, + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + unique=True, + ), + ), + ("entity_identifier", models.UUIDField(null=True)), + ( + "entity_name", + models.CharField( + choices=[ + ("project", "Project"), + ("issue", "Issue"), + ("module", "Module"), + ("cycle", "Task"), + ("page", "Page"), + ("view", "View"), + ], + max_length=30, + ), + ), + ( + "anchor", + models.CharField( + db_index=True, + default=plane.db.models.deploy_board.get_anchor, + max_length=255, + unique=True, + ), + ), + ("is_comments_enabled", models.BooleanField(default=False)), + ("is_reactions_enabled", models.BooleanField(default=False)), + ("is_votes_enabled", models.BooleanField(default=False)), + ("view_props", models.JSONField(default=dict)), + ( + "created_by", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(class)s_created_by", + to=settings.AUTH_USER_MODEL, + verbose_name="Created By", + ), + ), + ( + "inbox", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="board_inbox", + to="db.inbox", + ), + ), + ( + "project", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="project_%(class)s", + to="db.project", + ), + ), + ( + "updated_by", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(class)s_updated_by", + to=settings.AUTH_USER_MODEL, + verbose_name="Last Modified By", + ), + ), + ( + "workspace", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="workspace_%(class)s", + to="db.workspace", + ), + ), + ], + options={ + "verbose_name": "Deploy Board", + "verbose_name_plural": "Deploy Boards", + "db_table": "deploy_boards", + "ordering": ("-created_at",), + "unique_together": {("entity_name", "entity_identifier")}, + }, + ), + migrations.AddField( + model_name="estimate", + name="last_used", + field=models.BooleanField(default=False), + ), + # Rename the existing field + migrations.RenameField( + model_name="issue", + old_name="estimate_point", + new_name="point", + ), + # Add a new field with the original name as a foreign key + migrations.AddField( + model_name="issue", + name="estimate_point", + field=models.ForeignKey( + on_delete=django.db.models.deletion.SET_NULL, + related_name="issue_estimates", + to="db.EstimatePoint", + blank=True, + null=True, + ), + ), + migrations.AlterField( + model_name="estimate", + name="type", + field=models.CharField(default="categories", max_length=255), + ), + migrations.AlterField( + model_name="estimatepoint", + name="value", + field=models.CharField(max_length=255), + ), + migrations.RunPython(issue_estimate_point), + migrations.RunPython(last_used_estimate), + migrations.RunPython(populate_deploy_board), + ] diff --git a/apiserver/plane/db/models/__init__.py b/apiserver/plane/db/models/__init__.py index b11ce7aa354..51b0e70e5a2 100644 --- a/apiserver/plane/db/models/__init__.py +++ b/apiserver/plane/db/models/__init__.py @@ -4,6 +4,7 @@ from .base import BaseModel from .cycle import Cycle, CycleFavorite, CycleIssue, CycleUserProperties from .dashboard import Dashboard, DashboardWidget, Widget +from .deploy_board import DeployBoard from .estimate import Estimate, EstimatePoint from .exporter import ExporterHistory from .importer import Importer @@ -53,13 +54,13 @@ from .project import ( Project, ProjectBaseModel, - ProjectDeployBoard, ProjectFavorite, ProjectIdentifier, ProjectMember, ProjectMemberInvite, ProjectPublicMember, ) +from .deploy_board import DeployBoard from .session import Session from .social_connection import SocialLoginConnection from .state import State diff --git a/apiserver/plane/db/models/deploy_board.py b/apiserver/plane/db/models/deploy_board.py new file mode 100644 index 00000000000..41ffbc7c17e --- /dev/null +++ b/apiserver/plane/db/models/deploy_board.py @@ -0,0 +1,53 @@ +# Python imports +from uuid import uuid4 + +# Django imports +from django.db import models + +# Module imports +from .workspace import WorkspaceBaseModel + + +def get_anchor(): + return uuid4().hex + + +class DeployBoard(WorkspaceBaseModel): + TYPE_CHOICES = ( + ("project", "Project"), + ("issue", "Issue"), + ("module", "Module"), + ("cycle", "Task"), + ("page", "Page"), + ("view", "View"), + ) + + entity_identifier = models.UUIDField(null=True) + entity_name = models.CharField( + max_length=30, + choices=TYPE_CHOICES, + ) + anchor = models.CharField( + max_length=255, default=get_anchor, unique=True, db_index=True + ) + is_comments_enabled = models.BooleanField(default=False) + is_reactions_enabled = models.BooleanField(default=False) + inbox = models.ForeignKey( + "db.Inbox", + related_name="board_inbox", + on_delete=models.SET_NULL, + null=True, + ) + is_votes_enabled = models.BooleanField(default=False) + view_props = models.JSONField(default=dict) + + def __str__(self): + """Return name of the deploy board""" + return f"{self.entity_identifier} <{self.entity_name}>" + + class Meta: + unique_together = ["entity_name", "entity_identifier"] + verbose_name = "Deploy Board" + verbose_name_plural = "Deploy Boards" + db_table = "deploy_boards" + ordering = ("-created_at",) diff --git a/apiserver/plane/db/models/estimate.py b/apiserver/plane/db/models/estimate.py index 6ff1186c3d1..0713d774fa4 100644 --- a/apiserver/plane/db/models/estimate.py +++ b/apiserver/plane/db/models/estimate.py @@ -11,7 +11,8 @@ class Estimate(ProjectBaseModel): description = models.TextField( verbose_name="Estimate Description", blank=True ) - type = models.CharField(max_length=255, default="Categories") + type = models.CharField(max_length=255, default="categories") + last_used = models.BooleanField(default=False) def __str__(self): """Return name of the estimate""" @@ -35,7 +36,7 @@ class EstimatePoint(ProjectBaseModel): default=0, validators=[MinValueValidator(0), MaxValueValidator(12)] ) description = models.TextField(blank=True) - value = models.CharField(max_length=20) + value = models.CharField(max_length=255) def __str__(self): """Return name of the estimate""" diff --git a/apiserver/plane/db/models/issue.py b/apiserver/plane/db/models/issue.py index 527597ddc3b..2b07bd77b66 100644 --- a/apiserver/plane/db/models/issue.py +++ b/apiserver/plane/db/models/issue.py @@ -119,11 +119,18 @@ class Issue(ProjectBaseModel): blank=True, related_name="state_issue", ) - estimate_point = models.IntegerField( + point = models.IntegerField( validators=[MinValueValidator(0), MaxValueValidator(12)], null=True, blank=True, ) + estimate_point = models.ForeignKey( + "db.EstimatePoint", + on_delete=models.SET_NULL, + related_name="issue_estimates", + null=True, + blank=True, + ) name = models.CharField(max_length=255, verbose_name="Issue Name") description = models.JSONField(blank=True, default=dict) description_html = models.TextField(blank=True, default="

") diff --git a/apiserver/plane/db/models/project.py b/apiserver/plane/db/models/project.py index 49fca1323e8..ba8dbf580f8 100644 --- a/apiserver/plane/db/models/project.py +++ b/apiserver/plane/db/models/project.py @@ -260,6 +260,8 @@ def get_default_views(): } +# DEPRECATED TODO: +# used to get the old anchors for the project deploy boards class ProjectDeployBoard(ProjectBaseModel): anchor = models.CharField( max_length=255, default=get_anchor, unique=True, db_index=True diff --git a/apiserver/plane/space/urls/inbox.py b/apiserver/plane/space/urls/inbox.py index 60de040e241..20ebb343768 100644 --- a/apiserver/plane/space/urls/inbox.py +++ b/apiserver/plane/space/urls/inbox.py @@ -10,7 +10,7 @@ urlpatterns = [ path( - "workspaces//project-boards//inboxes//inbox-issues/", + "anchor//inboxes//inbox-issues/", InboxIssuePublicViewSet.as_view( { "get": "list", @@ -20,7 +20,7 @@ name="inbox-issue", ), path( - "workspaces//project-boards//inboxes//inbox-issues//", + "anchor//inboxes//inbox-issues//", InboxIssuePublicViewSet.as_view( { "get": "retrieve", @@ -31,7 +31,7 @@ name="inbox-issue", ), path( - "workspaces//project-boards//issues//votes/", + "anchor//issues//votes/", IssueVotePublicViewSet.as_view( { "get": "list", diff --git a/apiserver/plane/space/urls/issue.py b/apiserver/plane/space/urls/issue.py index 099eace5d2d..61c19ba0168 100644 --- a/apiserver/plane/space/urls/issue.py +++ b/apiserver/plane/space/urls/issue.py @@ -10,12 +10,12 @@ urlpatterns = [ path( - "workspaces//project-boards//issues//", + "anchor//issues//", IssueRetrievePublicEndpoint.as_view(), name="workspace-project-boards", ), path( - "workspaces//project-boards//issues//comments/", + "anchor//issues//comments/", IssueCommentPublicViewSet.as_view( { "get": "list", @@ -25,7 +25,7 @@ name="issue-comments-project-board", ), path( - "workspaces//project-boards//issues//comments//", + "anchor//issues//comments//", IssueCommentPublicViewSet.as_view( { "get": "retrieve", @@ -36,7 +36,7 @@ name="issue-comments-project-board", ), path( - "workspaces//project-boards//issues//reactions/", + "anchor//issues//reactions/", IssueReactionPublicViewSet.as_view( { "get": "list", @@ -46,7 +46,7 @@ name="issue-reactions-project-board", ), path( - "workspaces//project-boards//issues//reactions//", + "anchor//issues//reactions//", IssueReactionPublicViewSet.as_view( { "delete": "destroy", @@ -55,7 +55,7 @@ name="issue-reactions-project-board", ), path( - "workspaces//project-boards//comments//reactions/", + "anchor//comments//reactions/", CommentReactionPublicViewSet.as_view( { "get": "list", @@ -65,7 +65,7 @@ name="comment-reactions-project-board", ), path( - "workspaces//project-boards//comments//reactions//", + "anchor//comments//reactions//", CommentReactionPublicViewSet.as_view( { "delete": "destroy", diff --git a/apiserver/plane/space/urls/project.py b/apiserver/plane/space/urls/project.py index dc97b43a792..3294b01f642 100644 --- a/apiserver/plane/space/urls/project.py +++ b/apiserver/plane/space/urls/project.py @@ -4,17 +4,23 @@ from plane.space.views import ( ProjectDeployBoardPublicSettingsEndpoint, ProjectIssuesPublicEndpoint, + WorkspaceProjectAnchorEndpoint, ) urlpatterns = [ path( - "workspaces//project-boards//settings/", + "anchor//settings/", ProjectDeployBoardPublicSettingsEndpoint.as_view(), name="project-deploy-board-settings", ), path( - "workspaces//project-boards//issues/", + "anchor//issues/", ProjectIssuesPublicEndpoint.as_view(), name="project-deploy-board", ), + path( + "workspaces//projects//anchor/", + WorkspaceProjectAnchorEndpoint.as_view(), + name="project-deploy-board", + ), ] diff --git a/apiserver/plane/space/views/__init__.py b/apiserver/plane/space/views/__init__.py index 5130e04d5e4..eced7d1b46a 100644 --- a/apiserver/plane/space/views/__init__.py +++ b/apiserver/plane/space/views/__init__.py @@ -1,6 +1,7 @@ from .project import ( ProjectDeployBoardPublicSettingsEndpoint, WorkspaceProjectDeployBoardEndpoint, + WorkspaceProjectAnchorEndpoint, ) from .issue import ( diff --git a/apiserver/plane/space/views/inbox.py b/apiserver/plane/space/views/inbox.py index 9f681c16098..b89c77672c4 100644 --- a/apiserver/plane/space/views/inbox.py +++ b/apiserver/plane/space/views/inbox.py @@ -18,7 +18,7 @@ State, IssueLink, IssueAttachment, - ProjectDeployBoard, + DeployBoard, ) from plane.app.serializers import ( IssueSerializer, @@ -39,7 +39,7 @@ class InboxIssuePublicViewSet(BaseViewSet): ] def get_queryset(self): - project_deploy_board = ProjectDeployBoard.objects.get( + project_deploy_board = DeployBoard.objects.get( workspace__slug=self.kwargs.get("slug"), project_id=self.kwargs.get("project_id"), ) @@ -58,9 +58,9 @@ def get_queryset(self): ) return InboxIssue.objects.none() - def list(self, request, slug, project_id, inbox_id): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id + def list(self, request, anchor, inbox_id): + project_deploy_board = DeployBoard.objects.get( + anchor=anchor, entity_name="project" ) if project_deploy_board.inbox is None: return Response( @@ -72,8 +72,8 @@ def list(self, request, slug, project_id, inbox_id): issues = ( Issue.objects.filter( issue_inbox__inbox_id=inbox_id, - workspace__slug=slug, - project_id=project_id, + workspace_id=project_deploy_board.workspace_id, + project_id=project_deploy_board.project_id, ) .filter(**filters) .annotate(bridge_id=F("issue_inbox__id")) @@ -117,9 +117,9 @@ def list(self, request, slug, project_id, inbox_id): status=status.HTTP_200_OK, ) - def create(self, request, slug, project_id, inbox_id): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id + def create(self, request, anchor, inbox_id): + project_deploy_board = DeployBoard.objects.get( + anchor=anchor, entity_name="project" ) if project_deploy_board.inbox is None: return Response( @@ -151,7 +151,7 @@ def create(self, request, slug, project_id, inbox_id): name="Triage", group="backlog", description="Default state for managing all Inbox Issues", - project_id=project_id, + project_id=project_deploy_board.project_id, color="#ff7700", ) @@ -163,7 +163,7 @@ def create(self, request, slug, project_id, inbox_id): "description_html", "

" ), priority=request.data.get("issue", {}).get("priority", "low"), - project_id=project_id, + project_id=project_deploy_board.project_id, state=state, ) @@ -173,14 +173,14 @@ def create(self, request, slug, project_id, inbox_id): requested_data=json.dumps(request.data, cls=DjangoJSONEncoder), actor_id=str(request.user.id), issue_id=str(issue.id), - project_id=str(project_id), + project_id=str(project_deploy_board.project_id), current_instance=None, epoch=int(timezone.now().timestamp()), ) # create an inbox issue InboxIssue.objects.create( inbox_id=inbox_id, - project_id=project_id, + project_id=project_deploy_board.project_id, issue=issue, source=request.data.get("source", "in-app"), ) @@ -188,9 +188,9 @@ def create(self, request, slug, project_id, inbox_id): serializer = IssueStateInboxSerializer(issue) return Response(serializer.data, status=status.HTTP_200_OK) - def partial_update(self, request, slug, project_id, inbox_id, pk): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id + def partial_update(self, request, anchor, inbox_id, pk): + project_deploy_board = DeployBoard.objects.get( + anchor=anchor, entity_name="project" ) if project_deploy_board.inbox is None: return Response( @@ -200,8 +200,8 @@ def partial_update(self, request, slug, project_id, inbox_id, pk): inbox_issue = InboxIssue.objects.get( pk=pk, - workspace__slug=slug, - project_id=project_id, + workspace_id=project_deploy_board.workspace_id, + project_id=project_deploy_board.project_id, inbox_id=inbox_id, ) # Get the project member @@ -216,8 +216,8 @@ def partial_update(self, request, slug, project_id, inbox_id, pk): issue = Issue.objects.get( pk=inbox_issue.issue_id, - workspace__slug=slug, - project_id=project_id, + workspace_id=project_deploy_board.workspace_id, + project_id=project_deploy_board.project_id, ) # viewers and guests since only viewers and guests issue_data = { @@ -242,7 +242,7 @@ def partial_update(self, request, slug, project_id, inbox_id, pk): requested_data=requested_data, actor_id=str(request.user.id), issue_id=str(issue.id), - project_id=str(project_id), + project_id=str(project_deploy_board.project_id), current_instance=json.dumps( IssueSerializer(current_instance).data, cls=DjangoJSONEncoder, @@ -255,9 +255,9 @@ def partial_update(self, request, slug, project_id, inbox_id, pk): issue_serializer.errors, status=status.HTTP_400_BAD_REQUEST ) - def retrieve(self, request, slug, project_id, inbox_id, pk): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id + def retrieve(self, request, anchor, inbox_id, pk): + project_deploy_board = DeployBoard.objects.get( + anchor=anchor, entity_name="project" ) if project_deploy_board.inbox is None: return Response( @@ -267,21 +267,21 @@ def retrieve(self, request, slug, project_id, inbox_id, pk): inbox_issue = InboxIssue.objects.get( pk=pk, - workspace__slug=slug, - project_id=project_id, + workspace_id=project_deploy_board.workspace_id, + project_id=project_deploy_board.project_id, inbox_id=inbox_id, ) issue = Issue.objects.get( pk=inbox_issue.issue_id, - workspace__slug=slug, - project_id=project_id, + workspace_id=project_deploy_board.workspace_id, + project_id=project_deploy_board.project_id, ) serializer = IssueStateInboxSerializer(issue) return Response(serializer.data, status=status.HTTP_200_OK) - def destroy(self, request, slug, project_id, inbox_id, pk): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id + def destroy(self, request, anchor, inbox_id, pk): + project_deploy_board = DeployBoard.objects.get( + anchor=anchor, entity_name="project" ) if project_deploy_board.inbox is None: return Response( @@ -291,8 +291,8 @@ def destroy(self, request, slug, project_id, inbox_id, pk): inbox_issue = InboxIssue.objects.get( pk=pk, - workspace__slug=slug, - project_id=project_id, + workspace_id=project_deploy_board.workspace_id, + project_id=project_deploy_board.project_id, inbox_id=inbox_id, ) diff --git a/apiserver/plane/space/views/issue.py b/apiserver/plane/space/views/issue.py index 8c4d6e1501f..71ba0f6a709 100644 --- a/apiserver/plane/space/views/issue.py +++ b/apiserver/plane/space/views/issue.py @@ -44,7 +44,7 @@ ProjectMember, IssueReaction, CommentReaction, - ProjectDeployBoard, + DeployBoard, IssueVote, ProjectPublicMember, ) @@ -76,15 +76,15 @@ def get_permissions(self): def get_queryset(self): try: - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=self.kwargs.get("slug"), - project_id=self.kwargs.get("project_id"), + project_deploy_board = DeployBoard.objects.get( + anchor=self.kwargs.get("anchor"), + entity_name="project", ) - if project_deploy_board.comments: + if project_deploy_board.is_comments_enabled: return self.filter_queryset( super() .get_queryset() - .filter(workspace__slug=self.kwargs.get("slug")) + .filter(workspace_id=project_deploy_board.workspace_id) .filter(issue_id=self.kwargs.get("issue_id")) .filter(access="EXTERNAL") .select_related("project") @@ -93,8 +93,8 @@ def get_queryset(self): .annotate( is_member=Exists( ProjectMember.objects.filter( - workspace__slug=self.kwargs.get("slug"), - project_id=self.kwargs.get("project_id"), + workspace_id=project_deploy_board.workspace_id, + project_id=project_deploy_board.project_id, member_id=self.request.user.id, is_active=True, ) @@ -103,15 +103,15 @@ def get_queryset(self): .distinct() ).order_by("created_at") return IssueComment.objects.none() - except ProjectDeployBoard.DoesNotExist: + except DeployBoard.DoesNotExist: return IssueComment.objects.none() - def create(self, request, slug, project_id, issue_id): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id + def create(self, request, anchor, issue_id): + project_deploy_board = DeployBoard.objects.get( + anchor=anchor, entity_name="project" ) - if not project_deploy_board.comments: + if not project_deploy_board.is_comments_enabled: return Response( {"error": "Comments are not enabled for this project"}, status=status.HTTP_400_BAD_REQUEST, @@ -120,7 +120,7 @@ def create(self, request, slug, project_id, issue_id): serializer = IssueCommentSerializer(data=request.data) if serializer.is_valid(): serializer.save( - project_id=project_id, + project_id=project_deploy_board.project_id, issue_id=issue_id, actor=request.user, access="EXTERNAL", @@ -132,37 +132,35 @@ def create(self, request, slug, project_id, issue_id): ), actor_id=str(request.user.id), issue_id=str(issue_id), - project_id=str(project_id), + project_id=str(project_deploy_board.project_id), current_instance=None, epoch=int(timezone.now().timestamp()), ) if not ProjectMember.objects.filter( - project_id=project_id, + project_id=project_deploy_board.project_id, member=request.user, is_active=True, ).exists(): # Add the user for workspace tracking _ = ProjectPublicMember.objects.get_or_create( - project_id=project_id, + project_id=project_deploy_board.project_id, member=request.user, ) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - def partial_update(self, request, slug, project_id, issue_id, pk): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id + def partial_update(self, request, anchor, issue_id, pk): + project_deploy_board = DeployBoard.objects.get( + anchor=anchor, entity_name="project" ) - if not project_deploy_board.comments: + if not project_deploy_board.is_comments_enabled: return Response( {"error": "Comments are not enabled for this project"}, status=status.HTTP_400_BAD_REQUEST, ) - comment = IssueComment.objects.get( - workspace__slug=slug, pk=pk, actor=request.user - ) + comment = IssueComment.objects.get(pk=pk, actor=request.user) serializer = IssueCommentSerializer( comment, data=request.data, partial=True ) @@ -173,7 +171,7 @@ def partial_update(self, request, slug, project_id, issue_id, pk): requested_data=json.dumps(request.data, cls=DjangoJSONEncoder), actor_id=str(request.user.id), issue_id=str(issue_id), - project_id=str(project_id), + project_id=str(project_deploy_board.project_id), current_instance=json.dumps( IssueCommentSerializer(comment).data, cls=DjangoJSONEncoder, @@ -183,20 +181,18 @@ def partial_update(self, request, slug, project_id, issue_id, pk): return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - def destroy(self, request, slug, project_id, issue_id, pk): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id + def destroy(self, request, anchor, issue_id, pk): + project_deploy_board = DeployBoard.objects.get( + anchor=anchor, entity_name="project" ) - if not project_deploy_board.comments: + if not project_deploy_board.is_comments_enabled: return Response( {"error": "Comments are not enabled for this project"}, status=status.HTTP_400_BAD_REQUEST, ) comment = IssueComment.objects.get( - workspace__slug=slug, pk=pk, - project_id=project_id, actor=request.user, ) issue_activity.delay( @@ -204,7 +200,7 @@ def destroy(self, request, slug, project_id, issue_id, pk): requested_data=json.dumps({"comment_id": str(pk)}), actor_id=str(request.user.id), issue_id=str(issue_id), - project_id=str(project_id), + project_id=str(project_deploy_board.project_id), current_instance=json.dumps( IssueCommentSerializer(comment).data, cls=DjangoJSONEncoder, @@ -221,11 +217,11 @@ class IssueReactionPublicViewSet(BaseViewSet): def get_queryset(self): try: - project_deploy_board = ProjectDeployBoard.objects.get( + project_deploy_board = DeployBoard.objects.get( workspace__slug=self.kwargs.get("slug"), project_id=self.kwargs.get("project_id"), ) - if project_deploy_board.reactions: + if project_deploy_board.is_reactions_enabled: return ( super() .get_queryset() @@ -236,15 +232,15 @@ def get_queryset(self): .distinct() ) return IssueReaction.objects.none() - except ProjectDeployBoard.DoesNotExist: + except DeployBoard.DoesNotExist: return IssueReaction.objects.none() - def create(self, request, slug, project_id, issue_id): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id + def create(self, request, anchor, issue_id): + project_deploy_board = DeployBoard.objects.get( + anchor=anchor, entity_name="project" ) - if not project_deploy_board.reactions: + if not project_deploy_board.is_reactions_enabled: return Response( {"error": "Reactions are not enabled for this project board"}, status=status.HTTP_400_BAD_REQUEST, @@ -253,16 +249,18 @@ def create(self, request, slug, project_id, issue_id): serializer = IssueReactionSerializer(data=request.data) if serializer.is_valid(): serializer.save( - project_id=project_id, issue_id=issue_id, actor=request.user + project_id=project_deploy_board.project_id, + issue_id=issue_id, + actor=request.user, ) if not ProjectMember.objects.filter( - project_id=project_id, + project_id=project_deploy_board.project_id, member=request.user, is_active=True, ).exists(): # Add the user for workspace tracking _ = ProjectPublicMember.objects.get_or_create( - project_id=project_id, + project_id=project_deploy_board.project_id, member=request.user, ) issue_activity.delay( @@ -272,25 +270,25 @@ def create(self, request, slug, project_id, issue_id): ), actor_id=str(self.request.user.id), issue_id=str(self.kwargs.get("issue_id", None)), - project_id=str(self.kwargs.get("project_id", None)), + project_id=str(project_deploy_board.project_id), current_instance=None, epoch=int(timezone.now().timestamp()), ) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - def destroy(self, request, slug, project_id, issue_id, reaction_code): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id + def destroy(self, request, anchor, issue_id, reaction_code): + project_deploy_board = DeployBoard.objects.get( + anchor=anchor, entity_name="project" ) - if not project_deploy_board.reactions: + if not project_deploy_board.is_reactions_enabled: return Response( {"error": "Reactions are not enabled for this project board"}, status=status.HTTP_400_BAD_REQUEST, ) issue_reaction = IssueReaction.objects.get( - workspace__slug=slug, + workspace_id=project_deploy_board.workspace_id, issue_id=issue_id, reaction=reaction_code, actor=request.user, @@ -300,7 +298,7 @@ def destroy(self, request, slug, project_id, issue_id, reaction_code): requested_data=None, actor_id=str(self.request.user.id), issue_id=str(self.kwargs.get("issue_id", None)), - project_id=str(self.kwargs.get("project_id", None)), + project_id=str(project_deploy_board.project_id), current_instance=json.dumps( { "reaction": str(reaction_code), @@ -319,30 +317,29 @@ class CommentReactionPublicViewSet(BaseViewSet): def get_queryset(self): try: - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=self.kwargs.get("slug"), - project_id=self.kwargs.get("project_id"), + project_deploy_board = DeployBoard.objects.get( + anchor=self.kwargs.get("anchor"), entity_name="project" ) - if project_deploy_board.reactions: + if project_deploy_board.is_reactions_enabled: return ( super() .get_queryset() - .filter(workspace__slug=self.kwargs.get("slug")) - .filter(project_id=self.kwargs.get("project_id")) + .filter(workspace_id=project_deploy_board.workspace_id) + .filter(project_id=project_deploy_board.project_id) .filter(comment_id=self.kwargs.get("comment_id")) .order_by("-created_at") .distinct() ) return CommentReaction.objects.none() - except ProjectDeployBoard.DoesNotExist: + except DeployBoard.DoesNotExist: return CommentReaction.objects.none() - def create(self, request, slug, project_id, comment_id): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id + def create(self, request, anchor, comment_id): + project_deploy_board = DeployBoard.objects.get( + anchor=anchor, entity_name="project" ) - if not project_deploy_board.reactions: + if not project_deploy_board.is_reactions_enabled: return Response( {"error": "Reactions are not enabled for this board"}, status=status.HTTP_400_BAD_REQUEST, @@ -351,18 +348,18 @@ def create(self, request, slug, project_id, comment_id): serializer = CommentReactionSerializer(data=request.data) if serializer.is_valid(): serializer.save( - project_id=project_id, + project_id=project_deploy_board.project_id, comment_id=comment_id, actor=request.user, ) if not ProjectMember.objects.filter( - project_id=project_id, + project_id=project_deploy_board.project_id, member=request.user, is_active=True, ).exists(): # Add the user for workspace tracking _ = ProjectPublicMember.objects.get_or_create( - project_id=project_id, + project_id=project_deploy_board.project_id, member=request.user, ) issue_activity.delay( @@ -379,19 +376,19 @@ def create(self, request, slug, project_id, comment_id): return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - def destroy(self, request, slug, project_id, comment_id, reaction_code): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id + def destroy(self, request, anchor, comment_id, reaction_code): + project_deploy_board = DeployBoard.objects.get( + anchor=anchor, entity_name="project" ) - if not project_deploy_board.reactions: + if not project_deploy_board.is_reactions_enabled: return Response( {"error": "Reactions are not enabled for this board"}, status=status.HTTP_400_BAD_REQUEST, ) comment_reaction = CommentReaction.objects.get( - project_id=project_id, - workspace__slug=slug, + project_id=project_deploy_board.project_id, + workspace_id=project_deploy_board.workspace_id, comment_id=comment_id, reaction=reaction_code, actor=request.user, @@ -401,7 +398,7 @@ def destroy(self, request, slug, project_id, comment_id, reaction_code): requested_data=None, actor_id=str(self.request.user.id), issue_id=None, - project_id=str(self.kwargs.get("project_id", None)), + project_id=str(project_deploy_board.project_id), current_instance=json.dumps( { "reaction": str(reaction_code), @@ -421,36 +418,42 @@ class IssueVotePublicViewSet(BaseViewSet): def get_queryset(self): try: - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=self.kwargs.get("slug"), - project_id=self.kwargs.get("project_id"), + project_deploy_board = DeployBoard.objects.get( + workspace__slug=self.kwargs.get("anchor"), + entity_name="project", ) - if project_deploy_board.votes: + if project_deploy_board.is_votes_enabled: return ( super() .get_queryset() .filter(issue_id=self.kwargs.get("issue_id")) - .filter(workspace__slug=self.kwargs.get("slug")) - .filter(project_id=self.kwargs.get("project_id")) + .filter(workspace_id=project_deploy_board.workspace_id) + .filter(project_id=project_deploy_board.project_id) ) return IssueVote.objects.none() - except ProjectDeployBoard.DoesNotExist: + except DeployBoard.DoesNotExist: return IssueVote.objects.none() - def create(self, request, slug, project_id, issue_id): + def create(self, request, anchor, issue_id): + print("hite") + project_deploy_board = DeployBoard.objects.get( + anchor=anchor, entity_name="project" + ) + print("awer") issue_vote, _ = IssueVote.objects.get_or_create( actor_id=request.user.id, - project_id=project_id, + project_id=project_deploy_board.project_id, issue_id=issue_id, ) + print("AWer") # Add the user for workspace tracking if not ProjectMember.objects.filter( - project_id=project_id, + project_id=project_deploy_board.project_id, member=request.user, is_active=True, ).exists(): _ = ProjectPublicMember.objects.get_or_create( - project_id=project_id, + project_id=project_deploy_board.project_id, member=request.user, ) issue_vote.vote = request.data.get("vote", 1) @@ -462,26 +465,29 @@ def create(self, request, slug, project_id, issue_id): ), actor_id=str(self.request.user.id), issue_id=str(self.kwargs.get("issue_id", None)), - project_id=str(self.kwargs.get("project_id", None)), + project_id=str(project_deploy_board.project_id), current_instance=None, epoch=int(timezone.now().timestamp()), ) serializer = IssueVoteSerializer(issue_vote) return Response(serializer.data, status=status.HTTP_201_CREATED) - def destroy(self, request, slug, project_id, issue_id): + def destroy(self, request, anchor, issue_id): + project_deploy_board = DeployBoard.objects.get( + anchor=anchor, entity_name="project" + ) issue_vote = IssueVote.objects.get( - workspace__slug=slug, - project_id=project_id, issue_id=issue_id, actor_id=request.user.id, + project_id=project_deploy_board.project_id, + workspace_id=project_deploy_board.workspace_id, ) issue_activity.delay( type="issue_vote.activity.deleted", requested_data=None, actor_id=str(self.request.user.id), issue_id=str(self.kwargs.get("issue_id", None)), - project_id=str(self.kwargs.get("project_id", None)), + project_id=str(project_deploy_board.project_id), current_instance=json.dumps( { "vote": str(issue_vote.vote), @@ -499,9 +505,14 @@ class IssueRetrievePublicEndpoint(BaseAPIView): AllowAny, ] - def get(self, request, slug, project_id, issue_id): + def get(self, request, anchor, issue_id): + project_deploy_board = DeployBoard.objects.get( + anchor=anchor, entity_name="project" + ) issue = Issue.objects.get( - workspace__slug=slug, project_id=project_id, pk=issue_id + workspace_id=project_deploy_board.workspace_id, + project_id=project_deploy_board.project_id, + pk=issue_id, ) serializer = IssuePublicSerializer(issue) return Response(serializer.data, status=status.HTTP_200_OK) @@ -512,14 +523,17 @@ class ProjectIssuesPublicEndpoint(BaseAPIView): AllowAny, ] - def get(self, request, slug, project_id): - if not ProjectDeployBoard.objects.filter( - workspace__slug=slug, project_id=project_id + def get(self, request, anchor): + if not DeployBoard.objects.filter( + anchor=anchor, entity_name="project" ).exists(): return Response( {"error": "Project is not published"}, status=status.HTTP_404_NOT_FOUND, ) + project_deploy_board = DeployBoard.objects.get( + anchor=anchor, entity_name="project" + ) filters = issue_filters(request.query_params, "GET") @@ -544,8 +558,8 @@ def get(self, request, slug, project_id): .annotate(count=Func(F("id"), function="Count")) .values("count") ) - .filter(project_id=project_id) - .filter(workspace__slug=slug) + .filter(project_id=project_deploy_board.project_id) + .filter(workspace_id=project_deploy_board.workspace_id) .select_related("project", "workspace", "state", "parent") .prefetch_related("assignees", "labels") .prefetch_related( @@ -652,8 +666,8 @@ def get(self, request, slug, project_id): states = ( State.objects.filter( ~Q(name="Triage"), - workspace__slug=slug, - project_id=project_id, + workspace_id=project_deploy_board.workspace_id, + project_id=project_deploy_board.project_id, ) .annotate( custom_order=Case( @@ -670,7 +684,8 @@ def get(self, request, slug, project_id): ) labels = Label.objects.filter( - workspace__slug=slug, project_id=project_id + workspace_id=project_deploy_board.workspace_id, + project_id=project_deploy_board.project_id, ).values("id", "name", "color", "parent") ## Grouping the results diff --git a/apiserver/plane/space/views/project.py b/apiserver/plane/space/views/project.py index 10a3c387910..76f1600eeed 100644 --- a/apiserver/plane/space/views/project.py +++ b/apiserver/plane/space/views/project.py @@ -11,10 +11,10 @@ # Module imports from .base import BaseAPIView -from plane.app.serializers import ProjectDeployBoardSerializer +from plane.app.serializers import DeployBoardSerializer from plane.db.models import ( Project, - ProjectDeployBoard, + DeployBoard, ) @@ -23,11 +23,11 @@ class ProjectDeployBoardPublicSettingsEndpoint(BaseAPIView): AllowAny, ] - def get(self, request, slug, project_id): - project_deploy_board = ProjectDeployBoard.objects.get( - workspace__slug=slug, project_id=project_id + def get(self, request, anchor): + project_deploy_board = DeployBoard.objects.get( + anchor=anchor, entity_name="project" ) - serializer = ProjectDeployBoardSerializer(project_deploy_board) + serializer = DeployBoardSerializer(project_deploy_board) return Response(serializer.data, status=status.HTTP_200_OK) @@ -36,13 +36,16 @@ class WorkspaceProjectDeployBoardEndpoint(BaseAPIView): AllowAny, ] - def get(self, request, slug): + def get(self, request, anchor): + deploy_board = DeployBoard.objects.filter(anchor=anchor, entity_name="project").values_list projects = ( - Project.objects.filter(workspace__slug=slug) + Project.objects.filter(workspace=deploy_board.workspace) .annotate( is_public=Exists( - ProjectDeployBoard.objects.filter( - workspace__slug=slug, project_id=OuterRef("pk") + DeployBoard.objects.filter( + anchor=anchor, + project_id=OuterRef("pk"), + entity_name="project", ) ) ) @@ -58,3 +61,16 @@ def get(self, request, slug): ) return Response(projects, status=status.HTTP_200_OK) + + +class WorkspaceProjectAnchorEndpoint(BaseAPIView): + permission_classes = [ + AllowAny, + ] + + def get(self, request, slug, project_id): + project_deploy_board = DeployBoard.objects.get( + workspace__slug=slug, project_id=project_id + ) + serializer = DeployBoardSerializer(project_deploy_board) + return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/apiserver/plane/utils/analytics_plot.py b/apiserver/plane/utils/analytics_plot.py index cd57690c605..59ddbd9334a 100644 --- a/apiserver/plane/utils/analytics_plot.py +++ b/apiserver/plane/utils/analytics_plot.py @@ -4,18 +4,28 @@ # Django import from django.db import models -from django.db.models import Case, CharField, Count, F, Sum, Value, When +from django.db.models import ( + Case, + CharField, + Count, + F, + Sum, + Value, + When, + IntegerField, +) from django.db.models.functions import ( Coalesce, Concat, ExtractMonth, ExtractYear, TruncDate, + Cast, ) from django.utils import timezone # Module imports -from plane.db.models import Issue +from plane.db.models import Issue, Project def annotate_with_monthly_dimension(queryset, field_name, attribute): @@ -87,9 +97,9 @@ def build_graph_plot(queryset, x_axis, y_axis, segment=None): # Estimate else: - queryset = queryset.annotate(estimate=Sum("estimate_point")).order_by( - x_axis - ) + queryset = queryset.annotate( + estimate=Sum(Cast("estimate_point__value", IntegerField())) + ).order_by(x_axis) queryset = ( queryset.annotate(segment=F(segment)) if segment else queryset ) @@ -110,9 +120,33 @@ def build_graph_plot(queryset, x_axis, y_axis, segment=None): return sort_data(grouped_data, temp_axis) -def burndown_plot(queryset, slug, project_id, cycle_id=None, module_id=None): +def burndown_plot( + queryset, + slug, + project_id, + plot_type, + cycle_id=None, + module_id=None, +): # Total Issues in Cycle or Module total_issues = queryset.total_issues + # check whether the estimate is a point or not + estimate_type = Project.objects.filter( + workspace__slug=slug, + pk=project_id, + estimate__isnull=False, + estimate__type="points", + ).exists() + if estimate_type and plot_type == "points": + issue_estimates = Issue.objects.filter( + workspace__slug=slug, + project_id=project_id, + issue_cycle__cycle_id=cycle_id, + estimate_point__isnull=False, + ).values_list("estimate_point__value", flat=True) + + issue_estimates = [int(value) for value in issue_estimates] + total_estimate_points = sum(issue_estimates) if cycle_id: if queryset.end_date and queryset.start_date: @@ -128,18 +162,32 @@ def burndown_plot(queryset, slug, project_id, cycle_id=None, module_id=None): chart_data = {str(date): 0 for date in date_range} - completed_issues_distribution = ( - Issue.issue_objects.filter( - workspace__slug=slug, - project_id=project_id, - issue_cycle__cycle_id=cycle_id, + if plot_type == "points": + completed_issues_estimate_point_distribution = ( + Issue.issue_objects.filter( + workspace__slug=slug, + project_id=project_id, + issue_cycle__cycle_id=cycle_id, + estimate_point__isnull=False, + ) + .annotate(date=TruncDate("completed_at")) + .values("date") + .values("date", "estimate_point__value") + .order_by("date") + ) + else: + completed_issues_distribution = ( + Issue.issue_objects.filter( + workspace__slug=slug, + project_id=project_id, + issue_cycle__cycle_id=cycle_id, + ) + .annotate(date=TruncDate("completed_at")) + .values("date") + .annotate(total_completed=Count("id")) + .values("date", "total_completed") + .order_by("date") ) - .annotate(date=TruncDate("completed_at")) - .values("date") - .annotate(total_completed=Count("id")) - .values("date", "total_completed") - .order_by("date") - ) if module_id: # Get all dates between the two dates @@ -152,31 +200,59 @@ def burndown_plot(queryset, slug, project_id, cycle_id=None, module_id=None): chart_data = {str(date): 0 for date in date_range} - completed_issues_distribution = ( - Issue.issue_objects.filter( - workspace__slug=slug, - project_id=project_id, - issue_module__module_id=module_id, + if plot_type == "points": + completed_issues_estimate_point_distribution = ( + Issue.issue_objects.filter( + workspace__slug=slug, + project_id=project_id, + issue_module__module_id=module_id, + estimate_point__isnull=False, + ) + .annotate(date=TruncDate("completed_at")) + .values("date") + .values("date", "estimate_point__value") + .order_by("date") + ) + else: + completed_issues_distribution = ( + Issue.issue_objects.filter( + workspace__slug=slug, + project_id=project_id, + issue_module__module_id=module_id, + ) + .annotate(date=TruncDate("completed_at")) + .values("date") + .annotate(total_completed=Count("id")) + .values("date", "total_completed") + .order_by("date") ) - .annotate(date=TruncDate("completed_at")) - .values("date") - .annotate(total_completed=Count("id")) - .values("date", "total_completed") - .order_by("date") - ) for date in date_range: - cumulative_pending_issues = total_issues - total_completed = 0 - total_completed = sum( - item["total_completed"] - for item in completed_issues_distribution - if item["date"] is not None and item["date"] <= date - ) - cumulative_pending_issues -= total_completed - if date > timezone.now().date(): - chart_data[str(date)] = None + if plot_type == "points": + cumulative_pending_issues = total_estimate_points + total_completed = 0 + total_completed = sum( + int(item["estimate_point__value"]) + for item in completed_issues_estimate_point_distribution + if item["date"] is not None and item["date"] <= date + ) + cumulative_pending_issues -= total_completed + if date > timezone.now().date(): + chart_data[str(date)] = None + else: + chart_data[str(date)] = cumulative_pending_issues else: - chart_data[str(date)] = cumulative_pending_issues + cumulative_pending_issues = total_issues + total_completed = 0 + total_completed = sum( + item["total_completed"] + for item in completed_issues_distribution + if item["date"] is not None and item["date"] <= date + ) + cumulative_pending_issues -= total_completed + if date > timezone.now().date(): + chart_data[str(date)] = None + else: + chart_data[str(date)] = cumulative_pending_issues return chart_data diff --git a/packages/types/src/analytics.d.ts b/packages/types/src/analytics.d.ts index 35da4b723d9..2fb7ad51a72 100644 --- a/packages/types/src/analytics.d.ts +++ b/packages/types/src/analytics.d.ts @@ -54,7 +54,7 @@ export type TXAxisValues = | "state__group" | "labels__id" | "assignees__id" - | "estimate_point" + | "estimate_point__value" | "issue_cycle__cycle_id" | "issue_module__module_id" | "priority" diff --git a/packages/types/src/enums.ts b/packages/types/src/enums.ts index a4d098506ea..cc85753740a 100644 --- a/packages/types/src/enums.ts +++ b/packages/types/src/enums.ts @@ -24,3 +24,16 @@ export enum EIssueCommentAccessSpecifier { EXTERNAL = "EXTERNAL", INTERNAL = "INTERNAL", } + +// estimates +export enum EEstimateSystem { + POINTS = "points", + CATEGORIES = "categories", + TIME = "time", +} + +export enum EEstimateUpdateStages { + CREATE = "create", + EDIT = "edit", + SWITCH = "switch", +} diff --git a/packages/types/src/estimate.d.ts b/packages/types/src/estimate.d.ts index 96b584ce1fc..9bad7e260ff 100644 --- a/packages/types/src/estimate.d.ts +++ b/packages/types/src/estimate.d.ts @@ -1,40 +1,77 @@ -export interface IEstimate { - created_at: Date; - created_by: string; - description: string; - id: string; - name: string; - project: string; - project_detail: IProject; - updated_at: Date; - updated_by: string; - points: IEstimatePoint[]; - workspace: string; - workspace_detail: IWorkspace; -} +import { EEstimateSystem, EEstimateUpdateStages } from "./enums"; export interface IEstimatePoint { - created_at: string; - created_by: string; - description: string; - estimate: string; - id: string; - key: number; - project: string; - updated_at: string; - updated_by: string; - value: string; - workspace: string; + id: string | undefined; + key: number | undefined; + value: string | undefined; + description: string | undefined; + workspace: string | undefined; + project: string | undefined; + estimate: string | undefined; + created_at: Date | undefined; + updated_at: Date | undefined; + created_by: string | undefined; + updated_by: string | undefined; +} + +export type TEstimateSystemKeys = + | EEstimateSystem.POINTS + | EEstimateSystem.CATEGORIES + | EEstimateSystem.TIME; + +export interface IEstimate { + id: string | undefined; + name: string | undefined; + description: string | undefined; + type: TEstimateSystemKeys | undefined; // categories, points, time + points: IEstimatePoint[] | undefined; + workspace: string | undefined; + project: string | undefined; + last_used: boolean | undefined; + created_at: Date | undefined; + updated_at: Date | undefined; + created_by: string | undefined; + updated_by: string | undefined; } export interface IEstimateFormData { - estimate: { - name: string; - description: string; + estimate?: { + name?: string; + type?: string; + last_used?: boolean; }; estimate_points: { - id?: string; + id?: string | undefined; key: number; value: string; }[]; } + +export type TEstimatePointsObject = { + id?: string | undefined; + key: number; + value: string; +}; + +export type TTemplateValues = { + title: string; + values: TEstimatePointsObject[]; + hide?: boolean; +}; + +export type TEstimateSystem = { + name: string; + templates: Record; + is_available: boolean; + is_ee: boolean; +}; + +export type TEstimateSystems = { + [K in TEstimateSystemKeys]: TEstimateSystem; +}; + +// update estimates +export type TEstimateUpdateStageKeys = + | EEstimateUpdateStages.CREATE + | EEstimateUpdateStages.EDIT + | EEstimateUpdateStages.SWITCH; diff --git a/packages/types/src/index.d.ts b/packages/types/src/index.d.ts index b8dd2d3c153..25c2b255b38 100644 --- a/packages/types/src/index.d.ts +++ b/packages/types/src/index.d.ts @@ -15,7 +15,6 @@ export * from "./importer"; export * from "./inbox"; export * from "./analytics"; export * from "./api_token"; -export * from "./app"; export * from "./auth"; export * from "./calendar"; export * from "./instance"; @@ -28,3 +27,4 @@ export * from "./webhook"; export * from "./workspace-views"; export * from "./common"; export * from "./pragmatic"; +export * from "./publish"; diff --git a/packages/types/src/issues/issue.d.ts b/packages/types/src/issues/issue.d.ts index 42c95dc4e30..990b308e7d8 100644 --- a/packages/types/src/issues/issue.d.ts +++ b/packages/types/src/issues/issue.d.ts @@ -15,7 +15,7 @@ export type TIssue = { priority: TIssuePriorities; label_ids: string[]; assignee_ids: string[]; - estimate_point: number | null; + estimate_point: string | null; sub_issues_count: number; attachment_count: number; diff --git a/packages/types/src/module/modules.d.ts b/packages/types/src/module/modules.d.ts index 0019781bad6..53be1434792 100644 --- a/packages/types/src/module/modules.d.ts +++ b/packages/types/src/module/modules.d.ts @@ -44,6 +44,8 @@ export interface IModule { target_date: string | null; total_issues: number; unstarted_issues: number; + total_estimate_points?: number; + completed_estimate_points?: number; updated_at: string; updated_by?: string; archived_at: string | null; diff --git a/packages/types/src/project/projects.d.ts b/packages/types/src/project/projects.d.ts index ee974fd6392..59ccf73b6e2 100644 --- a/packages/types/src/project/projects.d.ts +++ b/packages/types/src/project/projects.d.ts @@ -32,7 +32,7 @@ export interface IProject { estimate: string | null; id: string; identifier: string; - is_deployed: boolean; + anchor: string | null; is_favorite: boolean; is_member: boolean; logo_props: TLogoProps; diff --git a/packages/types/src/publish.d.ts b/packages/types/src/publish.d.ts new file mode 100644 index 00000000000..883ef8dd6d0 --- /dev/null +++ b/packages/types/src/publish.d.ts @@ -0,0 +1,41 @@ +import { IProject, IProjectLite, IWorkspaceLite } from "@plane/types"; + +export type TPublishEntityType = "project"; + +export type TProjectPublishLayouts = + | "calendar" + | "gantt" + | "kanban" + | "list" + | "spreadsheet"; + +export type TPublishViewProps = { + calendar?: boolean; + gantt?: boolean; + kanban?: boolean; + list?: boolean; + spreadsheet?: boolean; +}; + +export type TProjectDetails = IProjectLite & + Pick; + +export type TPublishSettings = { + anchor: string | undefined; + is_comments_enabled: boolean; + created_at: string | undefined; + created_by: string | undefined; + entity_identifier: string | undefined; + entity_name: TPublishEntityType | undefined; + id: string | undefined; + inbox: unknown; + project: string | undefined; + project_details: TProjectDetails | undefined; + is_reactions_enabled: boolean; + updated_at: string | undefined; + updated_by: string | undefined; + view_props: TViewProps | undefined; + is_votes_enabled: boolean; + workspace: string | undefined; + workspace_detail: IWorkspaceLite | undefined; +}; diff --git a/packages/ui/package.json b/packages/ui/package.json index 84a655bf747..ed09323397f 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -24,7 +24,7 @@ "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3", "@blueprintjs/core": "^4.16.3", "@blueprintjs/popover2": "^1.13.3", - "@headlessui/react": "^1.7.17", + "@headlessui/react": "^2.0.3", "@popperjs/core": "^2.11.8", "clsx": "^2.0.0", "emoji-picker-react": "^4.5.16", @@ -33,7 +33,7 @@ "react-color": "^2.19.3", "react-dom": "^18.2.0", "react-popper": "^2.3.0", - "sonner": "^1.4.2", + "sonner": "^1.4.41", "tailwind-merge": "^2.0.0" }, "devDependencies": { diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index c3c0cd4f106..05adddfb0f9 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -14,5 +14,6 @@ export * from "./loader"; export * from "./control-link"; export * from "./toast"; export * from "./drag-handle"; +export * from "./typography"; export * from "./drop-indicator"; export * from "./sortable"; diff --git a/packages/ui/src/sortable/sortable.stories.tsx b/packages/ui/src/sortable/sortable.stories.tsx index 6d40ddc2ee1..2d469b767fd 100644 --- a/packages/ui/src/sortable/sortable.stories.tsx +++ b/packages/ui/src/sortable/sortable.stories.tsx @@ -1,6 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react"; import React from "react"; -import { Draggable } from "./draggable"; import { Sortable } from "./sortable"; const meta: Meta = { @@ -13,7 +12,7 @@ type Story = StoryObj; const data = [ { id: "1", name: "John Doe" }, - { id: "2", name: "Jane Doe 2" }, + { id: "2", name: "Satish" }, { id: "3", name: "Alice" }, { id: "4", name: "Bob" }, { id: "5", name: "Charlie" }, diff --git a/packages/ui/src/sortable/sortable.tsx b/packages/ui/src/sortable/sortable.tsx index 9d79a8f5900..b495d535ec4 100644 --- a/packages/ui/src/sortable/sortable.tsx +++ b/packages/ui/src/sortable/sortable.tsx @@ -8,7 +8,7 @@ type Props = { onChange: (data: T[]) => void; keyExtractor: (item: T, index: number) => string; containerClassName?: string; - id: string; + id?: string; }; const moveItem = ( @@ -17,7 +17,7 @@ const moveItem = ( destination: T & Record, keyExtractor: (item: T, index: number) => string ) => { - const sourceIndex = data.indexOf(source); + const sourceIndex = data.findIndex((item, index) => keyExtractor(item, index) === keyExtractor(source, 0)); if (sourceIndex === -1) return data; const destinationIndex = data.findIndex((item, index) => keyExtractor(item, index) === keyExtractor(destination, 0)); diff --git a/space/app/[workspaceSlug]/[projectId]/page.ts b/space/app/[workspaceSlug]/[projectId]/page.ts new file mode 100644 index 00000000000..4f18e8bd5c0 --- /dev/null +++ b/space/app/[workspaceSlug]/[projectId]/page.ts @@ -0,0 +1,42 @@ +import { notFound, redirect } from "next/navigation"; +// types +import { TPublishSettings } from "@plane/types"; +// services +import PublishService from "@/services/publish.service"; + +const publishService = new PublishService(); + +type Props = { + params: { + workspaceSlug: string; + projectId: string; + }; + searchParams: any; +}; + +export default async function IssuesPage(props: Props) { + const { params, searchParams } = props; + // query params + const { workspaceSlug, projectId } = params; + const { board, peekId } = searchParams; + + let response: TPublishSettings | undefined = undefined; + try { + response = await publishService.fetchAnchorFromProjectDetails(workspaceSlug, projectId); + } catch (error) { + // redirect to 404 page on error + notFound(); + } + + let url = ""; + if (response?.entity_name === "project") { + url = `/issues/${response?.anchor}`; + const params = new URLSearchParams(); + if (board) params.append("board", board); + if (peekId) params.append("peekId", peekId); + if (params.toString()) url += `?${params.toString()}`; + redirect(url); + } else { + notFound(); + } +} diff --git a/space/app/[workspace_slug]/[project_id]/page.tsx b/space/app/[workspace_slug]/[project_id]/page.tsx deleted file mode 100644 index 0d08ae7eb14..00000000000 --- a/space/app/[workspace_slug]/[project_id]/page.tsx +++ /dev/null @@ -1,16 +0,0 @@ -"use client"; - -import { useSearchParams } from "next/navigation"; -// components -import { ProjectDetailsView } from "@/components/views"; - -export default function WorkspaceProjectPage({ params }: { params: { workspace_slug: any; project_id: any } }) { - const { workspace_slug, project_id } = params; - - const searchParams = useSearchParams(); - const peekId = searchParams.get("peekId") || undefined; - - if (!workspace_slug || !project_id) return <>; - - return ; -} diff --git a/space/app/error.tsx b/space/app/error.tsx index 2d6f22e907d..e47a1af1d59 100644 --- a/space/app/error.tsx +++ b/space/app/error.tsx @@ -1,38 +1,47 @@ "use client"; -import Image from "next/image"; -import { useTheme } from "next-themes"; +// ui import { Button } from "@plane/ui"; -// assets -import InstanceFailureDarkImage from "@/public/instance/instance-failure-dark.svg"; -import InstanceFailureImage from "@/public/instance/instance-failure.svg"; - -export default function InstanceError() { - const { resolvedTheme } = useTheme(); - - const instanceImage = resolvedTheme === "dark" ? InstanceFailureDarkImage : InstanceFailureImage; +const ErrorPage = () => { const handleRetry = () => { window.location.reload(); }; return ( -
-
-
- Plane instance failure image -

Unable to fetch instance details.

-

- We were unable to fetch the details of the instance.
- Fret not, it might just be a connectivity issue. +

+
+
+

Exception Detected!

+

+ We{"'"}re Sorry! An exception has been detected, and our engineering team has been notified. We apologize + for any inconvenience this may have caused. Please reach out to our engineering team at{" "} + + support@plane.so + {" "} + or on our{" "} + + Discord + {" "} + server for further assistance.

-
- + {/* */}
); -} +}; + +export default ErrorPage; diff --git a/space/app/[workspace_slug]/[project_id]/layout.tsx b/space/app/issues/[anchor]/layout.tsx similarity index 54% rename from space/app/[workspace_slug]/[project_id]/layout.tsx rename to space/app/issues/[anchor]/layout.tsx index b1e134ea673..91291e481cd 100644 --- a/space/app/[workspace_slug]/[project_id]/layout.tsx +++ b/space/app/issues/[anchor]/layout.tsx @@ -1,25 +1,39 @@ +"use client"; + +import { observer } from "mobx-react-lite"; import Image from "next/image"; -import { notFound } from "next/navigation"; +import useSWR from "swr"; // components -import IssueNavbar from "@/components/issues/navbar"; +import { LogoSpinner } from "@/components/common"; +import { IssuesNavbarRoot } from "@/components/issues"; +// hooks +import { usePublish, usePublishList } from "@/hooks/store"; // assets -import planeLogo from "public/plane-logo.svg"; +import planeLogo from "@/public/plane-logo.svg"; -export default async function ProjectLayout({ - children, - params, -}: { +type Props = { children: React.ReactNode; - params: { workspace_slug: string; project_id: string }; -}) { - const { workspace_slug, project_id } = params; + params: { + anchor: string; + }; +}; + +const IssuesLayout = observer((props: Props) => { + const { children, params } = props; + // params + const { anchor } = params; + // store hooks + const { fetchPublishSettings } = usePublishList(); + const publishSettings = usePublish(anchor); + // fetch publish settings + useSWR(anchor ? `PUBLISH_SETTINGS_${anchor}` : null, anchor ? () => fetchPublishSettings(anchor) : null); - if (!workspace_slug || !project_id) notFound(); + if (!publishSettings) return ; return (
- +
{children}
); -} +}); + +export default IssuesLayout; diff --git a/space/app/issues/[anchor]/page.tsx b/space/app/issues/[anchor]/page.tsx new file mode 100644 index 00000000000..b3c9353e625 --- /dev/null +++ b/space/app/issues/[anchor]/page.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { observer } from "mobx-react-lite"; +import { useSearchParams } from "next/navigation"; +// components +import { IssuesLayoutsRoot } from "@/components/issues"; +// hooks +import { usePublish } from "@/hooks/store"; + +type Props = { + params: { + anchor: string; + }; +}; + +const IssuesPage = observer((props: Props) => { + const { params } = props; + const { anchor } = params; + // params + const searchParams = useSearchParams(); + const peekId = searchParams.get("peekId") || undefined; + + const publishSettings = usePublish(anchor); + + if (!publishSettings) return null; + + return ; +}); + +export default IssuesPage; diff --git a/space/app/not-found.tsx b/space/app/not-found.tsx index cae576319b1..c5320b2dcfc 100644 --- a/space/app/not-found.tsx +++ b/space/app/not-found.tsx @@ -4,20 +4,18 @@ import Image from "next/image"; // assets import UserLoggedInImage from "public/user-logged-in.svg"; -export default function NotFound() { - return ( -
-
-
-
-
- User already logged in -
-
-

Not Found

-

Please enter the appropriate project URL to view the issue board.

+const NotFound = () => ( +
+
+
+
+ User already logged in
+

Not Found

+

Please enter the appropriate project URL to view the issue board.

- ); -} +
+); + +export default NotFound; diff --git a/space/components/account/user-logged-in.tsx b/space/components/account/user-logged-in.tsx index 33be330fa5f..5975d73b6c7 100644 --- a/space/components/account/user-logged-in.tsx +++ b/space/components/account/user-logged-in.tsx @@ -1,36 +1,44 @@ "use client"; +import { observer } from "mobx-react-lite"; import Image from "next/image"; +import { useTheme } from "next-themes"; // components -import { UserAvatar } from "@/components/issues/navbar/user-avatar"; +import { UserAvatar } from "@/components/issues"; // hooks import { useUser } from "@/hooks/store"; // assets -import PlaneLogo from "@/public/plane-logos/black-horizontal-with-blue-logo.png"; +import PlaneBlackLogo from "@/public/plane-logos/black-horizontal-with-blue-logo.png"; +import PlaneWhiteLogo from "@/public/plane-logos/white-horizontal-with-blue-logo.png"; import UserLoggedInImage from "@/public/user-logged-in.svg"; -export const UserLoggedIn = () => { +export const UserLoggedIn = observer(() => { + // store hooks const { data: user } = useUser(); + // next-themes + const { resolvedTheme } = useTheme(); + + const logo = resolvedTheme === "dark" ? PlaneWhiteLogo : PlaneBlackLogo; if (!user) return null; return ( -
+
-
- User already logged in +
+ Plane logo
-
+
-
-
+
+
User already logged in
-

Logged in Successfully!

+

Logged in successfully!

You{"'"}ve successfully logged in. Please enter the appropriate project URL to view the issue board.

@@ -38,4 +46,4 @@ export const UserLoggedIn = () => {
); -}; +}); diff --git a/space/components/common/index.ts b/space/components/common/index.ts index c4ea97f3c3e..1949c069bef 100644 --- a/space/components/common/index.ts +++ b/space/components/common/index.ts @@ -1,3 +1,2 @@ -export * from "./latest-feature-block"; export * from "./project-logo"; export * from "./logo-spinner"; diff --git a/space/components/common/latest-feature-block.tsx b/space/components/common/latest-feature-block.tsx deleted file mode 100644 index c1b5db954de..00000000000 --- a/space/components/common/latest-feature-block.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import Image from "next/image"; -import Link from "next/link"; -import { useTheme } from "next-themes"; -// icons -import { Lightbulb } from "lucide-react"; -// images -import latestFeatures from "public/onboarding/onboarding-pages.svg"; - -export const LatestFeatureBlock = () => { - const { resolvedTheme } = useTheme(); - - return ( - <> -
- -

- Pages gets a facelift! Write anything and use Galileo to help you start.{" "} - - Learn more - -

-
-
-
- Plane Issues -
-
- - ); -}; diff --git a/space/components/instance/index.ts b/space/components/instance/index.ts index 6568894f005..be80bc6694f 100644 --- a/space/components/instance/index.ts +++ b/space/components/instance/index.ts @@ -1,2 +1 @@ -export * from "./not-ready-view"; export * from "./instance-failure-view"; diff --git a/space/components/instance/not-ready-view.tsx b/space/components/instance/not-ready-view.tsx deleted file mode 100644 index be46a9473f1..00000000000 --- a/space/components/instance/not-ready-view.tsx +++ /dev/null @@ -1,62 +0,0 @@ -"use client"; - -import { FC } from "react"; -import Image from "next/image"; -import Link from "next/link"; -import { useTheme } from "next-themes"; -// ui -import { Button } from "@plane/ui"; -// helper -import { GOD_MODE_URL, SPACE_BASE_PATH } from "@/helpers/common.helper"; -// images -import PlaneTakeOffImage from "@/public/instance/plane-takeoff.png"; -import PlaneBackgroundPatternDark from "public/auth/background-pattern-dark.svg"; -import PlaneBackgroundPattern from "public/auth/background-pattern.svg"; -import BlackHorizontalLogo from "public/plane-logos/black-horizontal-with-blue-logo.png"; -import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-logo.png"; - -export const InstanceNotReady: FC = () => { - const { resolvedTheme } = useTheme(); - const patternBackground = resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern; - - const logo = resolvedTheme === "light" ? BlackHorizontalLogo : WhiteHorizontalLogo; - - return ( -
-
-
-
- - Plane logo - -
-
- -
- Plane background pattern -
- -
-
-
-
-

Welcome aboard Plane!

- Plane Logo -

- Get started by setting up your instance and workspace -

-
- -
-
-
-
-
- ); -}; diff --git a/space/components/issues/board-views/block-downvotes.tsx b/space/components/issues/board-views/block-downvotes.tsx deleted file mode 100644 index 4326a882303..00000000000 --- a/space/components/issues/board-views/block-downvotes.tsx +++ /dev/null @@ -1,10 +0,0 @@ -"use client"; - -export const IssueBlockDownVotes = ({ number }: { number: number }) => ( -
- - arrow_upward_alt - - {number} -
-); diff --git a/space/components/issues/board-views/block-due-date.tsx b/space/components/issues/board-views/block-due-date.tsx deleted file mode 100644 index ecf2295621a..00000000000 --- a/space/components/issues/board-views/block-due-date.tsx +++ /dev/null @@ -1,59 +0,0 @@ -"use client"; - -// helpers -import { renderFullDate } from "@/helpers/date-time.helper"; - -export const dueDateIconDetails = ( - date: string, - stateGroup: string -): { - iconName: string; - className: string; -} => { - let iconName = "calendar_today"; - let className = ""; - - if (!date || ["completed", "cancelled"].includes(stateGroup)) { - iconName = "calendar_today"; - className = ""; - } else { - const today = new Date(); - today.setHours(0, 0, 0, 0); - const targetDate = new Date(date); - targetDate.setHours(0, 0, 0, 0); - - const timeDifference = targetDate.getTime() - today.getTime(); - - if (timeDifference < 0) { - iconName = "event_busy"; - className = "text-red-500"; - } else if (timeDifference === 0) { - iconName = "today"; - className = "text-red-500"; - } else if (timeDifference === 24 * 60 * 60 * 1000) { - iconName = "event"; - className = "text-yellow-500"; - } else { - iconName = "calendar_today"; - className = ""; - } - } - - return { - iconName, - className, - }; -}; - -export const IssueBlockDueDate = ({ due_date, group }: { due_date: string; group: string }) => { - const iconDetails = dueDateIconDetails(due_date, group); - - return ( -
- - {iconDetails.iconName} - - {renderFullDate(due_date)} -
- ); -}; diff --git a/space/components/issues/board-views/block-labels.tsx b/space/components/issues/board-views/block-labels.tsx deleted file mode 100644 index 05f6a039f8f..00000000000 --- a/space/components/issues/board-views/block-labels.tsx +++ /dev/null @@ -1,19 +0,0 @@ -"use client"; - -export const IssueBlockLabels = ({ labels }: any) => ( -
- {labels && - labels.length > 0 && - labels.map((_label: any) => ( -
-
-
-
{_label?.name}
-
-
- ))} -
-); diff --git a/space/components/issues/board-views/block-state.tsx b/space/components/issues/board-views/block-state.tsx deleted file mode 100644 index 39b10ceb039..00000000000 --- a/space/components/issues/board-views/block-state.tsx +++ /dev/null @@ -1,18 +0,0 @@ -// ui -import { StateGroupIcon } from "@plane/ui"; -// constants -import { issueGroupFilter } from "@/constants/issue"; - -export const IssueBlockState = ({ state }: any) => { - const stateGroup = issueGroupFilter(state.group); - - if (stateGroup === null) return <>; - return ( -
-
- -
{state?.name}
-
-
- ); -}; diff --git a/space/components/issues/board-views/block-upvotes.tsx b/space/components/issues/board-views/block-upvotes.tsx deleted file mode 100644 index 3927acac493..00000000000 --- a/space/components/issues/board-views/block-upvotes.tsx +++ /dev/null @@ -1,8 +0,0 @@ -"use client"; - -export const IssueBlockUpVotes = ({ number }: { number: number }) => ( -
- arrow_upward_alt - {number} -
-); diff --git a/space/components/issues/board-views/calendar/index.tsx b/space/components/issues/board-views/calendar/index.tsx deleted file mode 100644 index 0edeca96c4c..00000000000 --- a/space/components/issues/board-views/calendar/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export const IssueCalendarView = () =>
; diff --git a/space/components/issues/board-views/gantt/index.tsx b/space/components/issues/board-views/gantt/index.tsx deleted file mode 100644 index 5da924b2c82..00000000000 --- a/space/components/issues/board-views/gantt/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export const IssueGanttView = () =>
; diff --git a/space/components/issues/board-views/kanban/block.tsx b/space/components/issues/board-views/kanban/block.tsx deleted file mode 100644 index e34222dd471..00000000000 --- a/space/components/issues/board-views/kanban/block.tsx +++ /dev/null @@ -1,82 +0,0 @@ -"use client"; - -import { FC } from "react"; -import { observer } from "mobx-react-lite"; -import { useRouter, useSearchParams } from "next/navigation"; -// components -import { IssueBlockDueDate } from "@/components/issues/board-views/block-due-date"; -import { IssueBlockPriority } from "@/components/issues/board-views/block-priority"; -import { IssueBlockState } from "@/components/issues/board-views/block-state"; -// helpers -import { queryParamGenerator } from "@/helpers/query-param-generator"; -// hooks -import { useIssueDetails, useProject } from "@/hooks/store"; -// interfaces -import { IIssue } from "@/types/issue"; - -type IssueKanBanBlockProps = { - issue: IIssue; - workspaceSlug: string; - projectId: string; - params: any; -}; - -export const IssueKanBanBlock: FC = observer((props) => { - const router = useRouter(); - const searchParams = useSearchParams(); - // query params - const board = searchParams.get("board") || undefined; - const state = searchParams.get("state") || undefined; - const priority = searchParams.get("priority") || undefined; - const labels = searchParams.get("labels") || undefined; - // props - const { workspaceSlug, projectId, issue } = props; - // hooks - const { project } = useProject(); - const { setPeekId } = useIssueDetails(); - - const handleBlockClick = () => { - setPeekId(issue.id); - const { queryParam } = queryParamGenerator({ board, peekId: issue.id, priority, state, labels }); - router.push(`/${workspaceSlug}/${projectId}?${queryParam}`); - }; - - return ( -
- {/* id */} -
- {project?.identifier}-{issue?.sequence_id} -
- - {/* name */} -
- {issue.name} -
- -
- {/* priority */} - {issue?.priority && ( -
- -
- )} - {/* state */} - {issue?.state_detail && ( -
- -
- )} - {/* due date */} - {issue?.target_date && ( -
- -
- )} -
-
- ); -}); diff --git a/space/components/issues/board-views/kanban/header.tsx b/space/components/issues/board-views/kanban/header.tsx deleted file mode 100644 index baf5612b3d1..00000000000 --- a/space/components/issues/board-views/kanban/header.tsx +++ /dev/null @@ -1,28 +0,0 @@ -"use client"; -// mobx react lite -import { observer } from "mobx-react-lite"; -// ui -import { StateGroupIcon } from "@plane/ui"; -// constants -import { issueGroupFilter } from "@/constants/issue"; -// mobx hook -// import { useIssue } from "@/hooks/store"; -// interfaces -import { IIssueState } from "@/types/issue"; - -export const IssueKanBanHeader = observer(({ state }: { state: IIssueState }) => { - // const { getCountOfIssuesByState } = useIssue(); - const stateGroup = issueGroupFilter(state.group); - - if (stateGroup === null) return <>; - - return ( -
-
- -
-
{state?.name}
- {/* {getCountOfIssuesByState(state.id)} */} -
- ); -}); diff --git a/space/components/issues/board-views/kanban/index.tsx b/space/components/issues/board-views/kanban/index.tsx deleted file mode 100644 index e2e4e9900c5..00000000000 --- a/space/components/issues/board-views/kanban/index.tsx +++ /dev/null @@ -1,58 +0,0 @@ -"use client"; - -import { FC } from "react"; -import { observer } from "mobx-react-lite"; -// components -import { IssueKanBanBlock } from "@/components/issues/board-views/kanban/block"; -import { IssueKanBanHeader } from "@/components/issues/board-views/kanban/header"; -// ui -import { Icon } from "@/components/ui"; -// mobx hook -import { useIssue } from "@/hooks/store"; -// interfaces -import { IIssueState, IIssue } from "@/types/issue"; - -type IssueKanbanViewProps = { - workspaceSlug: string; - projectId: string; -}; - -export const IssueKanbanView: FC = observer((props) => { - const { workspaceSlug, projectId } = props; - // store hooks - const { states, getFilteredIssuesByState } = useIssue(); - - return ( -
- {states && - states.length > 0 && - states.map((_state: IIssueState) => ( -
-
- -
-
- {getFilteredIssuesByState(_state.id) && getFilteredIssuesByState(_state.id).length > 0 ? ( -
- {getFilteredIssuesByState(_state.id).map((_issue: IIssue) => ( - - ))} -
- ) : ( -
- - No issues in this state -
- )} -
-
- ))} -
- ); -}); diff --git a/space/components/issues/board-views/list/index.tsx b/space/components/issues/board-views/list/index.tsx deleted file mode 100644 index 2a2b958be7d..00000000000 --- a/space/components/issues/board-views/list/index.tsx +++ /dev/null @@ -1,42 +0,0 @@ -"use client"; -import { FC } from "react"; -import { observer } from "mobx-react-lite"; -// components -import { IssueListBlock } from "@/components/issues/board-views/list/block"; -import { IssueListHeader } from "@/components/issues/board-views/list/header"; -// mobx hook -import { useIssue } from "@/hooks/store"; -// types -import { IIssueState, IIssue } from "@/types/issue"; - -type IssueListViewProps = { - workspaceSlug: string; - projectId: string; -}; - -export const IssueListView: FC = observer((props) => { - const { workspaceSlug, projectId } = props; - // store hooks - const { states, getFilteredIssuesByState } = useIssue(); - - return ( - <> - {states && - states.length > 0 && - states.map((_state: IIssueState) => ( -
- - {getFilteredIssuesByState(_state.id) && getFilteredIssuesByState(_state.id).length > 0 ? ( -
- {getFilteredIssuesByState(_state.id).map((_issue: IIssue) => ( - - ))} -
- ) : ( -
No issues.
- )} -
- ))} - - ); -}); diff --git a/space/components/issues/board-views/spreadsheet/index.tsx b/space/components/issues/board-views/spreadsheet/index.tsx deleted file mode 100644 index 45ebf27928c..00000000000 --- a/space/components/issues/board-views/spreadsheet/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export const IssueSpreadsheetView = () =>
; diff --git a/space/components/issues/filters/applied-filters/filters-list.tsx b/space/components/issues/filters/applied-filters/filters-list.tsx index 83d651f5d23..87089c500af 100644 --- a/space/components/issues/filters/applied-filters/filters-list.tsx +++ b/space/components/issues/filters/applied-filters/filters-list.tsx @@ -1,10 +1,10 @@ "use client"; -// icons import { observer } from "mobx-react-lite"; import { X } from "lucide-react"; // types -import { IIssueLabel, IIssueState, TFilters } from "@/types/issue"; +import { IStateLite } from "@plane/types"; +import { IIssueLabel, TFilters } from "@/types/issue"; // components import { AppliedPriorityFilters } from "./priority"; import { AppliedStateFilters } from "./state"; @@ -14,7 +14,7 @@ type Props = { handleRemoveAllFilters: () => void; handleRemoveFilter: (key: keyof TFilters, value: string | null) => void; labels?: IIssueLabel[] | undefined; - states?: IIssueState[] | undefined; + states?: IStateLite[] | undefined; }; export const replaceUnderscoreIfSnakeCase = (str: string) => str.replace(/_/g, " "); diff --git a/space/components/issues/filters/applied-filters/root.tsx b/space/components/issues/filters/applied-filters/root.tsx index 9dd1eb01350..9b6625d752f 100644 --- a/space/components/issues/filters/applied-filters/root.tsx +++ b/space/components/issues/filters/applied-filters/root.tsx @@ -12,18 +12,18 @@ import { TIssueQueryFilters } from "@/types/issue"; import { AppliedFiltersList } from "./filters-list"; type TIssueAppliedFilters = { - workspaceSlug: string; - projectId: string; + anchor: string; }; export const IssueAppliedFilters: FC = observer((props) => { + const { anchor } = props; + // router const router = useRouter(); - // props - const { workspaceSlug, projectId } = props; - // hooks - const { issueFilters, initIssueFilters, updateIssueFilters } = useIssueFilter(); + // store hooks + const { getIssueFilters, initIssueFilters, updateIssueFilters } = useIssueFilter(); const { states, labels } = useIssue(); - + // derived values + const issueFilters = getIssueFilters(anchor); const activeLayout = issueFilters?.display_filters?.layout || undefined; const userFilters = issueFilters?.filters || {}; @@ -46,30 +46,26 @@ export const IssueAppliedFilters: FC = observer((props) => if (labels.length > 0) params = { ...params, labels: labels.join(",") }; params = new URLSearchParams(params).toString(); - router.push(`/${workspaceSlug}/${projectId}?${params}`); + router.push(`/issues/${anchor}?${params}`); }, - [workspaceSlug, projectId, activeLayout, issueFilters, router] + [activeLayout, anchor, issueFilters, router] ); const handleFilters = useCallback( (key: keyof TIssueQueryFilters, value: string | null) => { - if (!projectId) return; - let newValues = cloneDeep(issueFilters?.filters?.[key]) ?? []; if (value === null) newValues = []; else if (newValues.includes(value)) newValues.splice(newValues.indexOf(value), 1); - updateIssueFilters(projectId, "filters", key, newValues); + updateIssueFilters(anchor, "filters", key, newValues); updateRouteParams(key, newValues); }, - [projectId, issueFilters, updateIssueFilters, updateRouteParams] + [anchor, issueFilters, updateIssueFilters, updateRouteParams] ); const handleRemoveAllFilters = () => { - if (!projectId) return; - - initIssueFilters(projectId, { + initIssueFilters(anchor, { display_filters: { layout: activeLayout || "list" }, filters: { state: [], @@ -78,13 +74,13 @@ export const IssueAppliedFilters: FC = observer((props) => }, }); - router.push(`/${workspaceSlug}/${projectId}?${`board=${activeLayout || "list"}`}`); + router.push(`/issues/${anchor}?${`board=${activeLayout || "list"}`}`); }; if (Object.keys(appliedFilters).length === 0) return null; return ( -
+
void; - states: IIssueState[]; + states: IStateLite[]; values: string[]; }; diff --git a/space/components/issues/filters/root.tsx b/space/components/issues/filters/root.tsx index de972ea8aa2..dba13f9fbda 100644 --- a/space/components/issues/filters/root.tsx +++ b/space/components/issues/filters/root.tsx @@ -17,17 +17,18 @@ import { useIssue, useIssueFilter } from "@/hooks/store"; import { TIssueQueryFilters } from "@/types/issue"; type IssueFiltersDropdownProps = { - workspaceSlug: string; - projectId: string; + anchor: string; }; export const IssueFiltersDropdown: FC = observer((props) => { + const { anchor } = props; + // router const router = useRouter(); - const { workspaceSlug, projectId } = props; // hooks - const { issueFilters, updateIssueFilters } = useIssueFilter(); + const { getIssueFilters, updateIssueFilters } = useIssueFilter(); const { states, labels } = useIssue(); - + // derived values + const issueFilters = getIssueFilters(anchor); const activeLayout = issueFilters?.display_filters?.layout || undefined; const updateRouteParams = useCallback( @@ -37,24 +38,24 @@ export const IssueFiltersDropdown: FC = observer((pro const labels = key === "labels" ? value : issueFilters?.filters?.labels ?? []; const { queryParam } = queryParamGenerator({ board: activeLayout, priority, state, labels }); - router.push(`/${workspaceSlug}/${projectId}?${queryParam}`); + router.push(`/issues/${anchor}?${queryParam}`); }, - [workspaceSlug, projectId, activeLayout, issueFilters, router] + [anchor, activeLayout, issueFilters, router] ); const handleFilters = useCallback( (key: keyof TIssueQueryFilters, value: string) => { - if (!projectId || !value) return; + if (!value) return; const newValues = cloneDeep(issueFilters?.filters?.[key]) ?? []; if (newValues.includes(value)) newValues.splice(newValues.indexOf(value), 1); else newValues.push(value); - updateIssueFilters(projectId, "filters", key, newValues); + updateIssueFilters(anchor, "filters", key, newValues); updateRouteParams(key, newValues); }, - [projectId, issueFilters, updateIssueFilters, updateRouteParams] + [anchor, issueFilters, updateIssueFilters, updateRouteParams] ); return ( diff --git a/space/components/issues/filters/selection.tsx b/space/components/issues/filters/selection.tsx index a1180b0ee68..926fbf5b083 100644 --- a/space/components/issues/filters/selection.tsx +++ b/space/components/issues/filters/selection.tsx @@ -4,7 +4,8 @@ import React, { useState } from "react"; import { observer } from "mobx-react-lite"; import { Search, X } from "lucide-react"; // types -import { IIssueState, IIssueLabel, IIssueFilterOptions, TIssueFilterKeys } from "@/types/issue"; +import { IStateLite } from "@plane/types"; +import { IIssueLabel, IIssueFilterOptions, TIssueFilterKeys } from "@/types/issue"; // components import { FilterPriority, FilterState } from "./"; @@ -13,7 +14,7 @@ type Props = { handleFilters: (key: keyof IIssueFilterOptions, value: string | string[]) => void; layoutDisplayFiltersOptions: TIssueFilterKeys[]; labels?: IIssueLabel[] | undefined; - states?: IIssueState[] | undefined; + states?: IStateLite[] | undefined; }; export const FilterSelection: React.FC = observer((props) => { diff --git a/space/components/issues/filters/state.tsx b/space/components/issues/filters/state.tsx index 24b6bb5c862..f61237eef2b 100644 --- a/space/components/issues/filters/state.tsx +++ b/space/components/issues/filters/state.tsx @@ -1,17 +1,18 @@ "use client"; import React, { useState } from "react"; +// types +import { IStateLite } from "@plane/types"; +// ui import { Loader, StateGroupIcon } from "@plane/ui"; // components import { FilterHeader, FilterOption } from "@/components/issues/filters/helpers"; -// types -import { IIssueState } from "@/types/issue"; type Props = { appliedFilters: string[] | null; handleUpdate: (val: string) => void; searchQuery: string; - states: IIssueState[] | undefined; + states: IStateLite[] | undefined; }; export const FilterState: React.FC = (props) => { diff --git a/space/components/issues/index.ts b/space/components/issues/index.ts new file mode 100644 index 00000000000..6aee62097ee --- /dev/null +++ b/space/components/issues/index.ts @@ -0,0 +1,2 @@ +export * from "./issue-layouts"; +export * from "./navbar"; diff --git a/space/components/issues/issue-layouts/index.ts b/space/components/issues/issue-layouts/index.ts new file mode 100644 index 00000000000..5ab6813cdfe --- /dev/null +++ b/space/components/issues/issue-layouts/index.ts @@ -0,0 +1,4 @@ +export * from "./kanban"; +export * from "./list"; +export * from "./properties"; +export * from "./root"; diff --git a/space/components/issues/issue-layouts/kanban/block.tsx b/space/components/issues/issue-layouts/kanban/block.tsx new file mode 100644 index 00000000000..ac03823b4c4 --- /dev/null +++ b/space/components/issues/issue-layouts/kanban/block.tsx @@ -0,0 +1,78 @@ +"use client"; + +import { FC } from "react"; +import { observer } from "mobx-react-lite"; +import Link from "next/link"; +import { useSearchParams } from "next/navigation"; +// components +import { IssueBlockDueDate, IssueBlockPriority, IssueBlockState } from "@/components/issues"; +// helpers +import { queryParamGenerator } from "@/helpers/query-param-generator"; +// hooks +import { useIssueDetails, usePublish } from "@/hooks/store"; +// interfaces +import { IIssue } from "@/types/issue"; + +type Props = { + anchor: string; + issue: IIssue; + params: any; +}; + +export const IssueKanBanBlock: FC = observer((props) => { + const { anchor, issue } = props; + const searchParams = useSearchParams(); + // query params + const board = searchParams.get("board"); + const state = searchParams.get("state"); + const priority = searchParams.get("priority"); + const labels = searchParams.get("labels"); + // store hooks + const { project_details } = usePublish(anchor); + const { setPeekId } = useIssueDetails(); + + const { queryParam } = queryParamGenerator({ board, peekId: issue.id, priority, state, labels }); + + const handleBlockClick = () => { + setPeekId(issue.id); + }; + + return ( + + {/* id */} +
+ {project_details?.identifier}-{issue?.sequence_id} +
+ + {/* name */} +
+ {issue.name} +
+ +
+ {/* priority */} + {issue?.priority && ( +
+ +
+ )} + {/* state */} + {issue?.state_detail && ( +
+ +
+ )} + {/* due date */} + {issue?.target_date && ( +
+ +
+ )} +
+ + ); +}); diff --git a/space/components/issues/issue-layouts/kanban/header.tsx b/space/components/issues/issue-layouts/kanban/header.tsx new file mode 100644 index 00000000000..ee5433d68f8 --- /dev/null +++ b/space/components/issues/issue-layouts/kanban/header.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { observer } from "mobx-react-lite"; +// types +import { IStateLite } from "@plane/types"; +// ui +import { StateGroupIcon } from "@plane/ui"; + +type Props = { + state: IStateLite; +}; + +export const IssueKanBanHeader: React.FC = observer((props) => { + const { state } = props; + + return ( +
+
+ +
+
{state?.name}
+ {/* {getCountOfIssuesByState(state.id)} */} +
+ ); +}); diff --git a/space/components/issues/issue-layouts/kanban/index.ts b/space/components/issues/issue-layouts/kanban/index.ts new file mode 100644 index 00000000000..62874fbda4e --- /dev/null +++ b/space/components/issues/issue-layouts/kanban/index.ts @@ -0,0 +1,3 @@ +export * from "./block"; +export * from "./header"; +export * from "./root"; diff --git a/space/components/issues/issue-layouts/kanban/root.tsx b/space/components/issues/issue-layouts/kanban/root.tsx new file mode 100644 index 00000000000..e0a5593e9f9 --- /dev/null +++ b/space/components/issues/issue-layouts/kanban/root.tsx @@ -0,0 +1,50 @@ +"use client"; + +import { FC } from "react"; +import { observer } from "mobx-react-lite"; +// components +import { IssueKanBanBlock, IssueKanBanHeader } from "@/components/issues"; +// ui +import { Icon } from "@/components/ui"; +// mobx hook +import { useIssue } from "@/hooks/store"; + +type Props = { + anchor: string; +}; + +export const IssueKanbanLayoutRoot: FC = observer((props) => { + const { anchor } = props; + // store hooks + const { states, getFilteredIssuesByState } = useIssue(); + + return ( +
+ {states?.map((state) => { + const issues = getFilteredIssuesByState(state.id); + + return ( +
+
+ +
+
+ {issues && issues.length > 0 ? ( +
+ {issues.map((issue) => ( + + ))} +
+ ) : ( +
+ + No issues in this state +
+ )} +
+
+ ); + })} +
+ ); +}); diff --git a/space/components/issues/board-views/list/block.tsx b/space/components/issues/issue-layouts/list/block.tsx similarity index 64% rename from space/components/issues/board-views/list/block.tsx rename to space/components/issues/issue-layouts/list/block.tsx index 6b6231fcf54..8c241753def 100644 --- a/space/components/issues/board-views/list/block.tsx +++ b/space/components/issues/issue-layouts/list/block.tsx @@ -1,56 +1,52 @@ "use client"; import { FC } from "react"; import { observer } from "mobx-react-lite"; -import { useRouter, useSearchParams } from "next/navigation"; +import Link from "next/link"; +import { useSearchParams } from "next/navigation"; // components -import { IssueBlockDueDate } from "@/components/issues/board-views/block-due-date"; -import { IssueBlockLabels } from "@/components/issues/board-views/block-labels"; -import { IssueBlockPriority } from "@/components/issues/board-views/block-priority"; -import { IssueBlockState } from "@/components/issues/board-views/block-state"; +import { IssueBlockDueDate, IssueBlockLabels, IssueBlockPriority, IssueBlockState } from "@/components/issues"; // helpers import { queryParamGenerator } from "@/helpers/query-param-generator"; // hook -import { useIssueDetails, useProject } from "@/hooks/store"; -// interfaces +import { useIssueDetails, usePublish } from "@/hooks/store"; +// types import { IIssue } from "@/types/issue"; -// store type IssueListBlockProps = { + anchor: string; issue: IIssue; - workspaceSlug: string; - projectId: string; }; -export const IssueListBlock: FC = observer((props) => { - const { workspaceSlug, projectId, issue } = props; - const searchParams = useSearchParams(); +export const IssueListLayoutBlock: FC = observer((props) => { + const { anchor, issue } = props; // query params + const searchParams = useSearchParams(); const board = searchParams.get("board") || undefined; const state = searchParams.get("state") || undefined; const priority = searchParams.get("priority") || undefined; const labels = searchParams.get("labels") || undefined; - // store - const { project } = useProject(); + // store hooks const { setPeekId } = useIssueDetails(); - // router - const router = useRouter(); + const { project_details } = usePublish(anchor); + const { queryParam } = queryParamGenerator({ board, peekId: issue.id, priority, state, labels }); const handleBlockClick = () => { setPeekId(issue.id); - - const { queryParam } = queryParamGenerator({ board, peekId: issue.id, priority, state, labels }); - router.push(`/${workspaceSlug}/${projectId}?${queryParam}`); }; return ( -
+
{/* id */}
- {project?.identifier}-{issue?.sequence_id} + {project_details?.identifier}-{issue?.sequence_id}
{/* name */} -
+
{issue.name}
@@ -84,6 +80,6 @@ export const IssueListBlock: FC = observer((props) => {
)}
-
+ ); }); diff --git a/space/components/issues/board-views/list/header.tsx b/space/components/issues/issue-layouts/list/header.tsx similarity index 54% rename from space/components/issues/board-views/list/header.tsx rename to space/components/issues/issue-layouts/list/header.tsx index 2f8f6c018e6..a038050a9a9 100644 --- a/space/components/issues/board-views/list/header.tsx +++ b/space/components/issues/issue-layouts/list/header.tsx @@ -1,20 +1,18 @@ "use client"; + +import React from "react"; import { observer } from "mobx-react-lite"; +// types +import { IStateLite } from "@plane/types"; // ui import { StateGroupIcon } from "@plane/ui"; -// constants -import { issueGroupFilter } from "@/constants/issue"; -// mobx hook -// import { useIssue } from "@/hooks/store"; -// types -import { IIssueState } from "@/types/issue"; -export const IssueListHeader = observer(({ state }: { state: IIssueState }) => { - // const { getCountOfIssuesByState } = useIssue(); - const stateGroup = issueGroupFilter(state.group); - // const count = getCountOfIssuesByState(state.id); +type Props = { + state: IStateLite; +}; - if (stateGroup === null) return <>; +export const IssueListLayoutHeader: React.FC = observer((props) => { + const { state } = props; return (
diff --git a/space/components/issues/issue-layouts/list/index.ts b/space/components/issues/issue-layouts/list/index.ts new file mode 100644 index 00000000000..62874fbda4e --- /dev/null +++ b/space/components/issues/issue-layouts/list/index.ts @@ -0,0 +1,3 @@ +export * from "./block"; +export * from "./header"; +export * from "./root"; diff --git a/space/components/issues/issue-layouts/list/root.tsx b/space/components/issues/issue-layouts/list/root.tsx new file mode 100644 index 00000000000..02cd25b40b8 --- /dev/null +++ b/space/components/issues/issue-layouts/list/root.tsx @@ -0,0 +1,40 @@ +"use client"; +import { FC } from "react"; +import { observer } from "mobx-react-lite"; +// components +import { IssueListLayoutBlock, IssueListLayoutHeader } from "@/components/issues"; +// mobx hook +import { useIssue } from "@/hooks/store"; + +type Props = { + anchor: string; +}; + +export const IssuesListLayoutRoot: FC = observer((props) => { + const { anchor } = props; + // store hooks + const { states, getFilteredIssuesByState } = useIssue(); + + return ( + <> + {states?.map((state) => { + const issues = getFilteredIssuesByState(state.id); + + return ( +
+ + {issues && issues.length > 0 ? ( +
+ {issues.map((issue) => ( + + ))} +
+ ) : ( +
No issues.
+ )} +
+ ); + })} + + ); +}); diff --git a/space/components/issues/issue-layouts/properties/due-date.tsx b/space/components/issues/issue-layouts/properties/due-date.tsx new file mode 100644 index 00000000000..3b73973e72f --- /dev/null +++ b/space/components/issues/issue-layouts/properties/due-date.tsx @@ -0,0 +1,32 @@ +"use client"; + +import { CalendarCheck2 } from "lucide-react"; +// types +import { TStateGroups } from "@plane/types"; +// helpers +import { cn } from "@/helpers/common.helper"; +import { renderFormattedDate } from "@/helpers/date-time.helper"; +import { shouldHighlightIssueDueDate } from "@/helpers/issue.helper"; + +type Props = { + due_date: string; + group: TStateGroups; +}; + +export const IssueBlockDueDate = (props: Props) => { + const { due_date, group } = props; + + return ( +
+ + {renderFormattedDate(due_date)} +
+ ); +}; diff --git a/space/components/issues/issue-layouts/properties/index.ts b/space/components/issues/issue-layouts/properties/index.ts new file mode 100644 index 00000000000..de78f996697 --- /dev/null +++ b/space/components/issues/issue-layouts/properties/index.ts @@ -0,0 +1,4 @@ +export * from "./due-date"; +export * from "./labels"; +export * from "./priority"; +export * from "./state"; diff --git a/space/components/issues/issue-layouts/properties/labels.tsx b/space/components/issues/issue-layouts/properties/labels.tsx new file mode 100644 index 00000000000..75c32c4a035 --- /dev/null +++ b/space/components/issues/issue-layouts/properties/labels.tsx @@ -0,0 +1,17 @@ +"use client"; + +export const IssueBlockLabels = ({ labels }: any) => ( +
+ {labels?.map((_label: any) => ( +
+
+
+
{_label?.name}
+
+
+ ))} +
+); diff --git a/space/components/issues/board-views/block-priority.tsx b/space/components/issues/issue-layouts/properties/priority.tsx similarity index 85% rename from space/components/issues/board-views/block-priority.tsx rename to space/components/issues/issue-layouts/properties/priority.tsx index 3110930ec0a..b91d56bb87b 100644 --- a/space/components/issues/board-views/block-priority.tsx +++ b/space/components/issues/issue-layouts/properties/priority.tsx @@ -1,11 +1,11 @@ "use client"; // types -import { issuePriorityFilter } from "@/constants/issue"; -import { TIssueFilterPriority } from "@/types/issue"; +import { TIssuePriorities } from "@plane/types"; // constants +import { issuePriorityFilter } from "@/constants/issue"; -export const IssueBlockPriority = ({ priority }: { priority: TIssueFilterPriority | null }) => { +export const IssueBlockPriority = ({ priority }: { priority: TIssuePriorities | null }) => { const priority_detail = priority != null ? issuePriorityFilter(priority) : null; if (priority_detail === null) return <>; diff --git a/space/components/issues/issue-layouts/properties/state.tsx b/space/components/issues/issue-layouts/properties/state.tsx new file mode 100644 index 00000000000..b80f1f3df12 --- /dev/null +++ b/space/components/issues/issue-layouts/properties/state.tsx @@ -0,0 +1,11 @@ +// ui +import { StateGroupIcon } from "@plane/ui"; + +export const IssueBlockState = ({ state }: any) => ( +
+
+ +
{state?.name}
+
+
+); diff --git a/space/components/views/project-details.tsx b/space/components/issues/issue-layouts/root.tsx similarity index 52% rename from space/components/views/project-details.tsx rename to space/components/issues/issue-layouts/root.tsx index 462c656f0c8..e53986c8558 100644 --- a/space/components/views/project-details.tsx +++ b/space/components/issues/issue-layouts/root.tsx @@ -6,69 +6,55 @@ import Image from "next/image"; import { useSearchParams } from "next/navigation"; import useSWR from "swr"; // components -import { IssueCalendarView } from "@/components/issues/board-views/calendar"; -import { IssueGanttView } from "@/components/issues/board-views/gantt"; -import { IssueKanbanView } from "@/components/issues/board-views/kanban"; -import { IssueListView } from "@/components/issues/board-views/list"; -import { IssueSpreadsheetView } from "@/components/issues/board-views/spreadsheet"; +import { IssueKanbanLayoutRoot, IssuesListLayoutRoot } from "@/components/issues"; import { IssueAppliedFilters } from "@/components/issues/filters/applied-filters/root"; import { IssuePeekOverview } from "@/components/issues/peek-overview"; -// mobx store -import { useIssue, useUser, useIssueDetails, useIssueFilter, useProject } from "@/hooks/store"; +// hooks +import { useIssue, useIssueDetails, useIssueFilter } from "@/hooks/store"; +// store +import { PublishStore } from "@/store/publish/publish.store"; // assets import SomethingWentWrongImage from "public/something-went-wrong.svg"; -type ProjectDetailsViewProps = { - workspaceSlug: string; - projectId: string; +type Props = { peekId: string | undefined; + publishSettings: PublishStore; }; -export const ProjectDetailsView: FC = observer((props) => { - // router - const searchParams = useSearchParams(); +export const IssuesLayoutsRoot: FC = observer((props) => { + const { peekId, publishSettings } = props; // query params + const searchParams = useSearchParams(); const states = searchParams.get("states") || undefined; const priority = searchParams.get("priority") || undefined; const labels = searchParams.get("labels") || undefined; - - const { workspaceSlug, projectId, peekId } = props; - // hooks - const { fetchProjectSettings } = useProject(); - const { issueFilters } = useIssueFilter(); + // store hooks + const { getIssueFilters } = useIssueFilter(); const { loader, issues, error, fetchPublicIssues } = useIssue(); const issueDetailStore = useIssueDetails(); - const { data: currentUser, fetchCurrentUser } = useUser(); + // derived values + const { anchor } = publishSettings; + const issueFilters = anchor ? getIssueFilters(anchor) : undefined; useSWR( - workspaceSlug && projectId ? "WORKSPACE_PROJECT_SETTINGS" : null, - workspaceSlug && projectId ? () => fetchProjectSettings(workspaceSlug, projectId) : null - ); - useSWR( - (workspaceSlug && projectId) || states || priority || labels ? "WORKSPACE_PROJECT_PUBLIC_ISSUES" : null, - (workspaceSlug && projectId) || states || priority || labels - ? () => fetchPublicIssues(workspaceSlug, projectId, { states, priority, labels }) - : null - ); - useSWR( - workspaceSlug && projectId && !currentUser ? "WORKSPACE_PROJECT_CURRENT_USER" : null, - workspaceSlug && projectId && !currentUser ? () => fetchCurrentUser() : null + anchor ? `PUBLIC_ISSUES_${anchor}` : null, + anchor ? () => fetchPublicIssues(anchor, { states, priority, labels }) : null ); useEffect(() => { - if (peekId && workspaceSlug && projectId) { + if (peekId) { issueDetailStore.setPeekId(peekId.toString()); } - }, [peekId, issueDetailStore, projectId, workspaceSlug]); + }, [peekId, issueDetailStore]); // derived values const activeLayout = issueFilters?.display_filters?.layout || undefined; + if (!anchor) return null; + return (
- {workspaceSlug && projectId && peekId && ( - - )} + {peekId && } {loader && !issues ? (
Loading...
@@ -90,21 +76,18 @@ export const ProjectDetailsView: FC = observer((props) activeLayout && (
{/* applied filters */} - + {activeLayout === "list" && (
- +
)} {activeLayout === "kanban" && (
- +
)} - {activeLayout === "calendar" && } - {activeLayout === "spreadsheet" && } - {activeLayout === "gantt" && }
) )} diff --git a/space/components/issues/navbar/controls.tsx b/space/components/issues/navbar/controls.tsx index 20c0ca40847..25f2edfb0c8 100644 --- a/space/components/issues/navbar/controls.tsx +++ b/space/components/issues/navbar/controls.tsx @@ -4,26 +4,25 @@ import { useEffect, FC } from "react"; import { observer } from "mobx-react-lite"; import { useRouter, useSearchParams } from "next/navigation"; // components +import { IssuesLayoutSelection, NavbarTheme, UserAvatar } from "@/components/issues"; import { IssueFiltersDropdown } from "@/components/issues/filters"; -import { NavbarIssueBoardView } from "@/components/issues/navbar/issue-board-view"; -import { NavbarTheme } from "@/components/issues/navbar/theme"; -import { UserAvatar } from "@/components/issues/navbar/user-avatar"; // helpers import { queryParamGenerator } from "@/helpers/query-param-generator"; // hooks -import { useProject, useIssueFilter, useIssueDetails } from "@/hooks/store"; +import { useIssueFilter, useIssueDetails } from "@/hooks/store"; import useIsInIframe from "@/hooks/use-is-in-iframe"; +// store +import { PublishStore } from "@/store/publish/publish.store"; // types import { TIssueLayout } from "@/types/issue"; export type NavbarControlsProps = { - workspaceSlug: string; - projectId: string; + publishSettings: PublishStore; }; export const NavbarControls: FC = observer((props) => { // props - const { workspaceSlug, projectId } = props; + const { publishSettings } = props; // router const router = useRouter(); const searchParams = useSearchParams(); @@ -34,24 +33,25 @@ export const NavbarControls: FC = observer((props) => { const priority = searchParams.get("priority") || undefined; const peekId = searchParams.get("peekId") || undefined; // hooks - const { issueFilters, isIssueFiltersUpdated, initIssueFilters } = useIssueFilter(); - const { settings } = useProject(); + const { getIssueFilters, isIssueFiltersUpdated, initIssueFilters } = useIssueFilter(); const { setPeekId } = useIssueDetails(); // derived values + const { anchor, view_props, workspace_detail } = publishSettings; + const issueFilters = anchor ? getIssueFilters(anchor) : undefined; const activeLayout = issueFilters?.display_filters?.layout || undefined; const isInIframe = useIsInIframe(); useEffect(() => { - if (workspaceSlug && projectId && settings) { + if (anchor && workspace_detail) { const viewsAcceptable: string[] = []; let currentBoard: TIssueLayout | null = null; - if (settings?.views?.list) viewsAcceptable.push("list"); - if (settings?.views?.kanban) viewsAcceptable.push("kanban"); - if (settings?.views?.calendar) viewsAcceptable.push("calendar"); - if (settings?.views?.gantt) viewsAcceptable.push("gantt"); - if (settings?.views?.spreadsheet) viewsAcceptable.push("spreadsheet"); + if (view_props?.list) viewsAcceptable.push("list"); + if (view_props?.kanban) viewsAcceptable.push("kanban"); + if (view_props?.calendar) viewsAcceptable.push("calendar"); + if (view_props?.gantt) viewsAcceptable.push("gantt"); + if (view_props?.spreadsheet) viewsAcceptable.push("spreadsheet"); if (board) { if (viewsAcceptable.includes(board.toString())) currentBoard = board.toString() as TIssueLayout; @@ -74,39 +74,41 @@ export const NavbarControls: FC = observer((props) => { }, }; - if (!isIssueFiltersUpdated(params)) { - initIssueFilters(projectId, params); - router.push(`/${workspaceSlug}/${projectId}?${queryParam}`); + if (!isIssueFiltersUpdated(anchor, params)) { + initIssueFilters(anchor, params); + router.push(`/issues/${anchor}?${queryParam}`); } } } } }, [ - workspaceSlug, - projectId, + anchor, board, labels, state, priority, peekId, - settings, activeLayout, router, initIssueFilters, setPeekId, isIssueFiltersUpdated, + view_props, + workspace_detail, ]); + if (!anchor) return null; + return ( <> {/* issue views */}
- +
{/* issue filters */}
- +
{/* theming */} diff --git a/space/components/issues/navbar/index.ts b/space/components/issues/navbar/index.ts new file mode 100644 index 00000000000..e1bb02d91fa --- /dev/null +++ b/space/components/issues/navbar/index.ts @@ -0,0 +1,5 @@ +export * from "./controls"; +export * from "./layout-selection"; +export * from "./root"; +export * from "./theme"; +export * from "./user-avatar"; diff --git a/space/components/issues/navbar/issue-board-view.tsx b/space/components/issues/navbar/issue-board-view.tsx deleted file mode 100644 index 7112299612e..00000000000 --- a/space/components/issues/navbar/issue-board-view.tsx +++ /dev/null @@ -1,72 +0,0 @@ -"use client"; - -import { FC } from "react"; -import { observer } from "mobx-react-lite"; -import { useRouter, useSearchParams } from "next/navigation"; -// constants -import { issueLayoutViews } from "@/constants/issue"; -// helpers -import { queryParamGenerator } from "@/helpers/query-param-generator"; -// hooks -import { useIssueFilter } from "@/hooks/store"; -// mobx -import { TIssueLayout } from "@/types/issue"; - -type NavbarIssueBoardViewProps = { - workspaceSlug: string; - projectId: string; -}; - -export const NavbarIssueBoardView: FC = observer((props) => { - const router = useRouter(); - const searchParams = useSearchParams(); - // query params - const labels = searchParams.get("labels") || undefined; - const state = searchParams.get("state") || undefined; - const priority = searchParams.get("priority") || undefined; - const peekId = searchParams.get("peekId") || undefined; - // props - const { workspaceSlug, projectId } = props; - // hooks - const { layoutOptions, issueFilters, updateIssueFilters } = useIssueFilter(); - - // derived values - const activeLayout = issueFilters?.display_filters?.layout || undefined; - - const handleCurrentBoardView = (boardView: TIssueLayout) => { - updateIssueFilters(projectId, "display_filters", "layout", boardView); - const { queryParam } = queryParamGenerator({ board: boardView, peekId, priority, state, labels }); - router.push(`/${workspaceSlug}/${projectId}?${queryParam}`); - }; - - return ( - <> - {issueLayoutViews && - Object.keys(issueLayoutViews).map((key: string) => { - const layoutKey = key as TIssueLayout; - if (layoutOptions[layoutKey]) { - return ( -
handleCurrentBoardView(layoutKey)} - title={layoutKey} - > - - {issueLayoutViews[layoutKey]?.icon} - -
- ); - } - })} - - ); -}); diff --git a/space/components/issues/navbar/layout-selection.tsx b/space/components/issues/navbar/layout-selection.tsx new file mode 100644 index 00000000000..1989710b538 --- /dev/null +++ b/space/components/issues/navbar/layout-selection.tsx @@ -0,0 +1,67 @@ +"use client"; + +import { FC } from "react"; +import { observer } from "mobx-react-lite"; +import { useRouter, useSearchParams } from "next/navigation"; +// ui +import { Tooltip } from "@plane/ui"; +// constants +import { ISSUE_LAYOUTS } from "@/constants/issue"; +// helpers +import { queryParamGenerator } from "@/helpers/query-param-generator"; +// hooks +import { useIssueFilter } from "@/hooks/store"; +// mobx +import { TIssueLayout } from "@/types/issue"; + +type Props = { + anchor: string; +}; + +export const IssuesLayoutSelection: FC = observer((props) => { + const { anchor } = props; + // router + const router = useRouter(); + const searchParams = useSearchParams(); + // query params + const labels = searchParams.get("labels"); + const state = searchParams.get("state"); + const priority = searchParams.get("priority"); + const peekId = searchParams.get("peekId"); + // hooks + const { layoutOptions, getIssueFilters, updateIssueFilters } = useIssueFilter(); + // derived values + const issueFilters = getIssueFilters(anchor); + const activeLayout = issueFilters?.display_filters?.layout || undefined; + + const handleCurrentBoardView = (boardView: TIssueLayout) => { + updateIssueFilters(anchor, "display_filters", "layout", boardView); + const { queryParam } = queryParamGenerator({ board: boardView, peekId, priority, state, labels }); + router.push(`/issues/${anchor}?${queryParam}`); + }; + + return ( +
+ {ISSUE_LAYOUTS.map((layout) => { + if (!layoutOptions[layout.key]) return; + + return ( + + + + ); + })} +
+ ); +}); diff --git a/space/components/issues/navbar/index.tsx b/space/components/issues/navbar/root.tsx similarity index 58% rename from space/components/issues/navbar/index.tsx rename to space/components/issues/navbar/root.tsx index f5d60b8b057..1d1a294d94c 100644 --- a/space/components/issues/navbar/index.tsx +++ b/space/components/issues/navbar/root.tsx @@ -4,41 +4,40 @@ import { observer } from "mobx-react-lite"; import { Briefcase } from "lucide-react"; // components import { ProjectLogo } from "@/components/common"; -import { NavbarControls } from "@/components/issues/navbar/controls"; -// hooks -import { useProject } from "@/hooks/store"; +import { NavbarControls } from "@/components/issues"; +// store +import { PublishStore } from "@/store/publish/publish.store"; -type IssueNavbarProps = { - workspaceSlug: string; - projectId: string; +type Props = { + publishSettings: PublishStore; }; -const IssueNavbar: FC = observer((props) => { - const { workspaceSlug, projectId } = props; +export const IssuesNavbarRoot: FC = observer((props) => { + const { publishSettings } = props; // hooks - const { project } = useProject(); + const { project_details } = publishSettings; return (
{/* project detail */}
- {project ? ( + {project_details ? ( - + ) : ( )} -
{project?.name || `...`}
+
+ {project_details?.name || `...`} +
- +
); }); - -export default IssueNavbar; diff --git a/space/components/issues/peek-overview/comment/add-comment.tsx b/space/components/issues/peek-overview/comment/add-comment.tsx index a1647c9c541..57ed0b6f629 100644 --- a/space/components/issues/peek-overview/comment/add-comment.tsx +++ b/space/components/issues/peek-overview/comment/add-comment.tsx @@ -8,7 +8,7 @@ import { TOAST_TYPE, setToast } from "@plane/ui"; // editor components import { LiteTextEditor } from "@/components/editor/lite-text-editor"; // hooks -import { useIssueDetails, useProject, useUser } from "@/hooks/store"; +import { useIssueDetails, usePublish, useUser } from "@/hooks/store"; // types import { Comment } from "@/types/issue"; @@ -17,22 +17,18 @@ const defaultValues: Partial = { }; type Props = { + anchor: string; disabled?: boolean; - workspaceSlug: string; - projectId: string; }; export const AddComment: React.FC = observer((props) => { - // const { disabled = false } = props; - const { workspaceSlug, projectId } = props; + const { anchor } = props; // refs const editorRef = useRef(null); // store hooks - const { workspace } = useProject(); const { peekId: issueId, addIssueComment } = useIssueDetails(); const { data: currentUser } = useUser(); - // derived values - const workspaceId = workspace?.id; + const { workspaceSlug, workspace: workspaceID } = usePublish(anchor); // form info const { handleSubmit, @@ -43,9 +39,9 @@ export const AddComment: React.FC = observer((props) => { } = useForm({ defaultValues }); const onSubmit = async (formData: Comment) => { - if (!workspaceSlug || !projectId || !issueId || isSubmitting || !formData.comment_html) return; + if (!anchor || !issueId || isSubmitting || !formData.comment_html) return; - await addIssueComment(workspaceSlug, projectId, issueId, formData) + await addIssueComment(anchor, issueId, formData) .then(() => { reset(defaultValues); editorRef.current?.clearEditor(); @@ -71,8 +67,8 @@ export const AddComment: React.FC = observer((props) => { onEnterKeyPress={(e) => { if (currentUser) handleSubmit(onSubmit)(e); }} - workspaceId={workspaceId as string} - workspaceSlug={workspaceSlug} + workspaceId={workspaceID?.toString() ?? ""} + workspaceSlug={workspaceSlug?.toString() ?? ""} ref={editorRef} initialValue={ !value || value === "" || (typeof value === "object" && Object.keys(value).length === 0) diff --git a/space/components/issues/peek-overview/comment/comment-detail-card.tsx b/space/components/issues/peek-overview/comment/comment-detail-card.tsx index 3ede0333bd1..31e5f732401 100644 --- a/space/components/issues/peek-overview/comment/comment-detail-card.tsx +++ b/space/components/issues/peek-overview/comment/comment-detail-card.tsx @@ -10,25 +10,23 @@ import { CommentReactions } from "@/components/issues/peek-overview"; // helpers import { timeAgo } from "@/helpers/date-time.helper"; // hooks -import { useIssueDetails, useProject, useUser } from "@/hooks/store"; +import { useIssueDetails, usePublish, useUser } from "@/hooks/store"; import useIsInIframe from "@/hooks/use-is-in-iframe"; // types import { Comment } from "@/types/issue"; type Props = { - workspaceSlug: string; + anchor: string; comment: Comment; }; export const CommentCard: React.FC = observer((props) => { - const { comment, workspaceSlug } = props; + const { anchor, comment } = props; // store hooks - const { workspace } = useProject(); const { peekId, deleteIssueComment, updateIssueComment } = useIssueDetails(); const { data: currentUser } = useUser(); + const { workspaceSlug, workspace: workspaceID } = usePublish(anchor); const isInIframe = useIsInIframe(); - // derived values - const workspaceId = workspace?.id; // states const [isEditing, setIsEditing] = useState(false); @@ -45,13 +43,13 @@ export const CommentCard: React.FC = observer((props) => { }); const handleDelete = () => { - if (!workspaceSlug || !peekId) return; - deleteIssueComment(workspaceSlug, comment.project, peekId, comment.id); + if (!anchor || !peekId) return; + deleteIssueComment(anchor, peekId, comment.id); }; const handleCommentUpdate = async (formData: Comment) => { - if (!workspaceSlug || !peekId) return; - updateIssueComment(workspaceSlug, comment.project, peekId, comment.id, formData); + if (!anchor || !peekId) return; + updateIssueComment(anchor, peekId, comment.id, formData); setIsEditing(false); editorRef.current?.setEditorValue(formData.comment_html); showEditorRef.current?.setEditorValue(formData.comment_html); @@ -103,8 +101,8 @@ export const CommentCard: React.FC = observer((props) => { name="comment_html" render={({ field: { onChange, value } }) => ( = observer((props) => {
- +
diff --git a/space/components/issues/peek-overview/comment/comment-reactions.tsx b/space/components/issues/peek-overview/comment/comment-reactions.tsx index ed915eff432..1b39777940f 100644 --- a/space/components/issues/peek-overview/comment/comment-reactions.tsx +++ b/space/components/issues/peek-overview/comment/comment-reactions.tsx @@ -13,12 +13,12 @@ import { useIssueDetails, useUser } from "@/hooks/store"; import useIsInIframe from "@/hooks/use-is-in-iframe"; type Props = { + anchor: string; commentId: string; - projectId: string; - workspaceSlug: string; }; export const CommentReactions: React.FC = observer((props) => { + const { anchor, commentId } = props; const router = useRouter(); const pathName = usePathname(); const searchParams = useSearchParams(); @@ -28,7 +28,6 @@ export const CommentReactions: React.FC = observer((props) => { const priority = searchParams.get("priority") || undefined; const labels = searchParams.get("labels") || undefined; - const { commentId, projectId, workspaceSlug } = props; // hooks const { addCommentReaction, removeCommentReaction, details, peekId } = useIssueDetails(); const { data: user } = useUser(); @@ -40,13 +39,13 @@ export const CommentReactions: React.FC = observer((props) => { const userReactions = commentReactions?.filter((r) => r?.actor_detail?.id === user?.id); const handleAddReaction = (reactionHex: string) => { - if (!workspaceSlug || !projectId || !peekId) return; - addCommentReaction(workspaceSlug, projectId, peekId, commentId, reactionHex); + if (!anchor || !peekId) return; + addCommentReaction(anchor, peekId, commentId, reactionHex); }; const handleRemoveReaction = (reactionHex: string) => { - if (!workspaceSlug || !projectId || !peekId) return; - removeCommentReaction(workspaceSlug, projectId, peekId, commentId, reactionHex); + if (!anchor || !peekId) return; + removeCommentReaction(anchor, peekId, commentId, reactionHex); }; const handleReactionClick = (reactionHex: string) => { diff --git a/space/components/issues/peek-overview/full-screen-peek-view.tsx b/space/components/issues/peek-overview/full-screen-peek-view.tsx index f5918de43e2..4e9d5ed8cf5 100644 --- a/space/components/issues/peek-overview/full-screen-peek-view.tsx +++ b/space/components/issues/peek-overview/full-screen-peek-view.tsx @@ -11,14 +11,13 @@ import { import { IIssue } from "@/types/issue"; type Props = { + anchor: string; handleClose: () => void; issueDetails: IIssue | undefined; - workspaceSlug: string; - projectId: string; }; export const FullScreenPeekView: React.FC = observer((props) => { - const { handleClose, issueDetails, workspaceSlug, projectId } = props; + const { anchor, handleClose, issueDetails } = props; return (
@@ -30,17 +29,13 @@ export const FullScreenPeekView: React.FC = observer((props) => {
{/* issue title and description */}
- +
{/* divider */}
{/* issue activity/comments */}
- +
) : ( diff --git a/space/components/issues/peek-overview/header.tsx b/space/components/issues/peek-overview/header.tsx index 0e9b93ab9fa..3ad07e06b31 100644 --- a/space/components/issues/peek-overview/header.tsx +++ b/space/components/issues/peek-overview/header.tsx @@ -1,10 +1,9 @@ import React from "react"; import { observer } from "mobx-react-lite"; -import { MoveRight } from "lucide-react"; +import { Link2, MoveRight } from "lucide-react"; import { Listbox, Transition } from "@headlessui/react"; // ui -import { setToast, TOAST_TYPE } from "@plane/ui"; -import { Icon } from "@/components/ui"; +import { CenterPanelIcon, FullScreenPanelIcon, setToast, SidePanelIcon, TOAST_TYPE } from "@plane/ui"; // helpers import { copyTextToClipboard } from "@/helpers/string.helper"; // hooks @@ -18,21 +17,21 @@ type Props = { issueDetails: IIssue | undefined; }; -const peekModes: { +const PEEK_MODES: { key: IPeekMode; - icon: string; + icon: any; label: string; }[] = [ - { key: "side", icon: "side_navigation", label: "Side Peek" }, + { key: "side", icon: SidePanelIcon, label: "Side Peek" }, { key: "modal", - icon: "dialogs", - label: "Modal Peek", + icon: CenterPanelIcon, + label: "Modal", }, { key: "full", - icon: "nearby", - label: "Full Screen Peek", + icon: FullScreenPanelIcon, + label: "Full Screen", }, ]; @@ -47,20 +46,22 @@ export const PeekOverviewHeader: React.FC = observer((props) => { copyTextToClipboard(urlToCopy).then(() => { setToast({ - type: TOAST_TYPE.INFO, + type: TOAST_TYPE.SUCCESS, title: "Link copied!", - message: "Issue link copied to clipboard", + message: "Issue link copied to clipboard.", }); }); }; + const Icon = PEEK_MODES.find((m) => m.key === peekMode)?.icon ?? SidePanelIcon; + return ( <>
{peekMode === "side" && ( - )} = observer((props) => { onChange={(val) => setPeekMode(val)} className="relative flex-shrink-0 text-left" > - - m.key === peekMode)?.icon ?? ""} className="text-[1rem]" /> + + = observer((props) => { >
- {peekModes.map((mode) => ( + {PEEK_MODES.map((mode) => ( = observer((props) => {
{isClipboardWriteAllowed && (peekMode === "side" || peekMode === "modal") && (
-
)} diff --git a/space/components/issues/peek-overview/issue-activity.tsx b/space/components/issues/peek-overview/issue-activity.tsx index ec73bda7bd9..1ccb7fa8807 100644 --- a/space/components/issues/peek-overview/issue-activity.tsx +++ b/space/components/issues/peek-overview/issue-activity.tsx @@ -7,61 +7,58 @@ import { Button } from "@plane/ui"; import { CommentCard, AddComment } from "@/components/issues/peek-overview"; import { Icon } from "@/components/ui"; // hooks -import { useIssueDetails, useProject, useUser } from "@/hooks/store"; +import { useIssueDetails, usePublish, useUser } from "@/hooks/store"; import useIsInIframe from "@/hooks/use-is-in-iframe"; // types import { IIssue } from "@/types/issue"; type Props = { + anchor: string; issueDetails: IIssue; - workspaceSlug: string; - projectId: string; }; export const PeekOverviewIssueActivity: React.FC = observer((props) => { - const { workspaceSlug, projectId } = props; + const { anchor } = props; // router const pathname = usePathname(); - // store - const { canComment } = useProject(); + // store hooks const { details, peekId } = useIssueDetails(); const { data: currentUser } = useUser(); - const isInIframe = useIsInIframe(); - + const { canComment } = usePublish(anchor); + // derived values const comments = details[peekId || ""]?.comments || []; + const isInIframe = useIsInIframe(); return (

Comments

- {workspaceSlug && ( -
-
- {comments.map((comment: any) => ( - - ))} -
- {!isInIframe && - (currentUser ? ( - <> - {canComment && ( -
- -
- )} - - ) : ( -
-

- - Sign in to add your comment -

- - - -
- ))} +
+
+ {comments.map((comment) => ( + + ))}
- )} + {!isInIframe && + (currentUser ? ( + <> + {canComment && ( +
+ +
+ )} + + ) : ( +
+

+ + Sign in to add your comment +

+ + + +
+ ))} +
); }); diff --git a/space/components/issues/peek-overview/issue-details.tsx b/space/components/issues/peek-overview/issue-details.tsx index 5fe73f67a43..97a659554b4 100644 --- a/space/components/issues/peek-overview/issue-details.tsx +++ b/space/components/issues/peek-overview/issue-details.tsx @@ -5,26 +5,33 @@ import { IssueReactions } from "@/components/issues/peek-overview"; import { IIssue } from "@/types/issue"; type Props = { + anchor: string; issueDetails: IIssue; }; -export const PeekOverviewIssueDetails: React.FC = ({ issueDetails }) => ( -
-
- {issueDetails.project_detail.identifier}-{issueDetails.sequence_id} -
-

{issueDetails.name}

- {issueDetails.description_html !== "" && issueDetails.description_html !== "

" && ( -

" - : issueDetails.description_html - } - /> - )} - -
-); +export const PeekOverviewIssueDetails: React.FC = (props) => { + const { anchor, issueDetails } = props; + + const description = issueDetails.description_html; + + return ( +
+
+ {issueDetails.project_detail?.identifier}-{issueDetails?.sequence_id} +
+

{issueDetails.name}

+ {description !== "" && description !== "

" && ( +

" + : description + } + /> + )} + +
+ ); +}; diff --git a/space/components/issues/peek-overview/issue-emoji-reactions.tsx b/space/components/issues/peek-overview/issue-emoji-reactions.tsx index 4a0e615542a..ae960eab336 100644 --- a/space/components/issues/peek-overview/issue-emoji-reactions.tsx +++ b/space/components/issues/peek-overview/issue-emoji-reactions.tsx @@ -1,4 +1,3 @@ -import { useEffect } from "react"; import { observer } from "mobx-react-lite"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; // lib @@ -11,11 +10,12 @@ import { queryParamGenerator } from "@/helpers/query-param-generator"; import { useIssueDetails, useUser } from "@/hooks/store"; type IssueEmojiReactionsProps = { - workspaceSlug: string; - projectId: string; + anchor: string; }; export const IssueEmojiReactions: React.FC = observer((props) => { + const { anchor } = props; + // router const router = useRouter(); const pathName = usePathname(); const searchParams = useSearchParams(); @@ -25,11 +25,9 @@ export const IssueEmojiReactions: React.FC = observer( const state = searchParams.get("state") || undefined; const priority = searchParams.get("priority") || undefined; const labels = searchParams.get("labels") || undefined; - - const { workspaceSlug, projectId } = props; - // store + // store hooks const issueDetailsStore = useIssueDetails(); - const { data: user, fetchCurrentUser } = useUser(); + const { data: user } = useUser(); const issueId = issueDetailsStore.peekId; const reactions = issueId ? issueDetailsStore.details[issueId]?.reactions || [] : []; @@ -38,13 +36,13 @@ export const IssueEmojiReactions: React.FC = observer( const userReactions = reactions?.filter((r) => r.actor_detail.id === user?.id); const handleAddReaction = (reactionHex: string) => { - if (!workspaceSlug || !projectId || !issueId) return; - issueDetailsStore.addIssueReaction(workspaceSlug.toString(), projectId.toString(), issueId, reactionHex); + if (!issueId) return; + issueDetailsStore.addIssueReaction(anchor, issueId, reactionHex); }; const handleRemoveReaction = (reactionHex: string) => { - if (!workspaceSlug || !projectId || !issueId) return; - issueDetailsStore.removeIssueReaction(workspaceSlug.toString(), projectId.toString(), issueId, reactionHex); + if (!issueId) return; + issueDetailsStore.removeIssueReaction(anchor, issueId, reactionHex); }; const handleReactionClick = (reactionHex: string) => { @@ -53,11 +51,6 @@ export const IssueEmojiReactions: React.FC = observer( else handleAddReaction(reactionHex); }; - useEffect(() => { - if (user) return; - fetchCurrentUser(); - }, [user, fetchCurrentUser]); - // derived values const { queryParam } = queryParamGenerator({ peekId, board, state, priority, labels }); diff --git a/space/components/issues/peek-overview/issue-properties.tsx b/space/components/issues/peek-overview/issue-properties.tsx index 08d22b3124a..2bdfe21bb33 100644 --- a/space/components/issues/peek-overview/issue-properties.tsx +++ b/space/components/issues/peek-overview/issue-properties.tsx @@ -1,16 +1,17 @@ +import { CalendarCheck2, Signal } from "lucide-react"; // ui -import { StateGroupIcon, TOAST_TYPE, setToast } from "@plane/ui"; -// icons +import { DoubleCircleIcon, StateGroupIcon, TOAST_TYPE, setToast } from "@plane/ui"; +// components import { Icon } from "@/components/ui"; // constants -import { issueGroupFilter, issuePriorityFilter } from "@/constants/issue"; +import { issuePriorityFilter } from "@/constants/issue"; // helpers -import { renderFullDate } from "@/helpers/date-time.helper"; +import { cn } from "@/helpers/common.helper"; +import { renderFormattedDate } from "@/helpers/date-time.helper"; +import { shouldHighlightIssueDueDate } from "@/helpers/issue.helper"; import { copyTextToClipboard, addSpaceIfCamelCase } from "@/helpers/string.helper"; // types import { IIssue, IPeekMode } from "@/types/issue"; -// components -import { dueDateIconDetails } from "../board-views/block-due-date"; type Props = { issueDetails: IIssue; @@ -19,12 +20,9 @@ type Props = { export const PeekOverviewIssueProperties: React.FC = ({ issueDetails, mode }) => { const state = issueDetails.state_detail; - const stateGroup = issueGroupFilter(state.group); const priority = issueDetails.priority ? issuePriorityFilter(issueDetails.priority) : null; - const dueDateIcon = dueDateIconDetails(issueDetails.target_date, state.group); - const handleCopyLink = () => { const urlToCopy = window.location.href; @@ -51,28 +49,22 @@ export const PeekOverviewIssueProperties: React.FC = ({ issueDetails, mod
)} -
-
-
- - State +
+
+
+ + State
-
- {stateGroup && ( -
-
- - {addSpaceIfCamelCase(state?.name ?? "")} -
-
- )} +
+ + {addSpaceIfCamelCase(state?.name ?? "")}
-
-
- - Priority +
+
+ + Priority
= ({ issueDetails, mod
-
-
- - Due date + +
+
+ + Due date
{issueDetails.target_date ? ( -
- - {dueDateIcon.iconName} - - {renderFullDate(issueDetails.target_date)} +
+ + {renderFormattedDate(issueDetails.target_date)}
) : ( Empty diff --git a/space/components/issues/peek-overview/issue-reaction.tsx b/space/components/issues/peek-overview/issue-reaction.tsx index 87210f3776b..c3b580abc06 100644 --- a/space/components/issues/peek-overview/issue-reaction.tsx +++ b/space/components/issues/peek-overview/issue-reaction.tsx @@ -1,33 +1,31 @@ -import { useParams } from "next/navigation"; +import { observer } from "mobx-react-lite"; import { IssueEmojiReactions, IssueVotes } from "@/components/issues/peek-overview"; -import { useProject } from "@/hooks/store"; +// hooks +import { usePublish } from "@/hooks/store"; import useIsInIframe from "@/hooks/use-is-in-iframe"; -// type IssueReactionsProps = { -// workspaceSlug: string; -// projectId: string; -// }; - -export const IssueReactions: React.FC = () => { - const { workspace_slug: workspaceSlug, project_id: projectId } = useParams(); +type Props = { + anchor: string; +}; - const { canVote, canReact } = useProject(); +export const IssueReactions: React.FC = observer((props) => { + const { anchor } = props; + // store hooks + const { canVote, canReact } = usePublish(anchor); const isInIframe = useIsInIframe(); return (
{canVote && ( - <> -
- -
- +
+ +
)} {!isInIframe && canReact && (
- +
)}
); -}; +}); diff --git a/space/components/issues/peek-overview/issue-vote-reactions.tsx b/space/components/issues/peek-overview/issue-vote-reactions.tsx index 1e565e8626b..6b24e5a9f58 100644 --- a/space/components/issues/peek-overview/issue-vote-reactions.tsx +++ b/space/components/issues/peek-overview/issue-vote-reactions.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState } from "react"; import { observer } from "mobx-react-lite"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { Tooltip } from "@plane/ui"; @@ -12,11 +12,14 @@ import { useIssueDetails, useUser } from "@/hooks/store"; import useIsInIframe from "@/hooks/use-is-in-iframe"; type TIssueVotes = { - workspaceSlug: string; - projectId: string; + anchor: string; }; export const IssueVotes: React.FC = observer((props) => { + const { anchor } = props; + // states + const [isSubmitting, setIsSubmitting] = useState(false); + // router const router = useRouter(); const pathName = usePathname(); const searchParams = useSearchParams(); @@ -26,13 +29,9 @@ export const IssueVotes: React.FC = observer((props) => { const state = searchParams.get("state") || undefined; const priority = searchParams.get("priority") || undefined; const labels = searchParams.get("labels") || undefined; - - const { workspaceSlug, projectId } = props; - // states - const [isSubmitting, setIsSubmitting] = useState(false); - + // store hooks const issueDetailsStore = useIssueDetails(); - const { data: user, fetchCurrentUser } = useUser(); + const { data: user } = useUser(); const isInIframe = useIsInIframe(); @@ -47,28 +46,22 @@ export const IssueVotes: React.FC = observer((props) => { const isDownVotedByUser = allDownVotes?.some((vote) => vote.actor === user?.id); const handleVote = async (e: any, voteValue: 1 | -1) => { - if (!workspaceSlug || !projectId || !issueId) return; + if (!issueId) return; setIsSubmitting(true); const actionPerformed = votes?.find((vote) => vote.actor === user?.id && vote.vote === voteValue); - if (actionPerformed) - await issueDetailsStore.removeIssueVote(workspaceSlug.toString(), projectId.toString(), issueId); - else - await issueDetailsStore.addIssueVote(workspaceSlug.toString(), projectId.toString(), issueId, { + if (actionPerformed) await issueDetailsStore.removeIssueVote(anchor, issueId); + else { + await issueDetailsStore.addIssueVote(anchor, issueId, { vote: voteValue, }); + } setIsSubmitting(false); }; - useEffect(() => { - if (user) return; - - fetchCurrentUser(); - }, [user, fetchCurrentUser]); - const VOTES_LIMIT = 1000; // derived values diff --git a/space/components/issues/peek-overview/layout.tsx b/space/components/issues/peek-overview/layout.tsx index 453cc59f3c9..d1fe6f7aa0f 100644 --- a/space/components/issues/peek-overview/layout.tsx +++ b/space/components/issues/peek-overview/layout.tsx @@ -10,13 +10,12 @@ import { FullScreenPeekView, SidePeekView } from "@/components/issues/peek-overv import { useIssue, useIssueDetails } from "@/hooks/store"; type TIssuePeekOverview = { - workspaceSlug: string; - projectId: string; + anchor: string; peekId: string; }; export const IssuePeekOverview: FC = observer((props) => { - const { workspaceSlug, projectId, peekId } = props; + const { anchor, peekId } = props; const router = useRouter(); const searchParams = useSearchParams(); // query params @@ -34,21 +33,23 @@ export const IssuePeekOverview: FC = observer((props) => { const issueDetails = issueDetailStore.peekId && peekId ? issueDetailStore.details[peekId.toString()] : undefined; useEffect(() => { - if (workspaceSlug && projectId && peekId && issueStore.issues && issueStore.issues.length > 0) { + if (anchor && peekId && issueStore.issues && issueStore.issues.length > 0) { if (!issueDetails) { - issueDetailStore.fetchIssueDetails(workspaceSlug.toString(), projectId.toString(), peekId.toString()); + issueDetailStore.fetchIssueDetails(anchor, peekId.toString()); } } - }, [workspaceSlug, projectId, issueDetailStore, issueDetails, peekId, issueStore.issues]); + }, [anchor, issueDetailStore, issueDetails, peekId, issueStore.issues]); const handleClose = () => { issueDetailStore.setPeekId(null); - let queryParams: any = { board: board }; + let queryParams: any = { + board, + }; if (priority && priority.length > 0) queryParams = { ...queryParams, priority: priority }; if (state && state.length > 0) queryParams = { ...queryParams, state: state }; if (labels && labels.length > 0) queryParams = { ...queryParams, labels: labels }; queryParams = new URLSearchParams(queryParams).toString(); - router.push(`/${workspaceSlug}/${projectId}?${queryParams}`); + router.push(`/issues/${anchor}?${queryParams}`); }; useEffect(() => { @@ -80,12 +81,7 @@ export const IssuePeekOverview: FC = observer((props) => { leaveTo="translate-x-full" > - + @@ -119,20 +115,10 @@ export const IssuePeekOverview: FC = observer((props) => { }`} > {issueDetailStore.peekMode === "modal" && ( - + )} {issueDetailStore.peekMode === "full" && ( - + )}
diff --git a/space/components/issues/peek-overview/side-peek-view.tsx b/space/components/issues/peek-overview/side-peek-view.tsx index a0b544bddb3..89444141810 100644 --- a/space/components/issues/peek-overview/side-peek-view.tsx +++ b/space/components/issues/peek-overview/side-peek-view.tsx @@ -7,22 +7,21 @@ import { PeekOverviewIssueDetails, PeekOverviewIssueProperties, } from "@/components/issues/peek-overview"; -// hooks -import { useProject } from "@/hooks/store"; +// store hooks +import { usePublish } from "@/hooks/store"; // types import { IIssue } from "@/types/issue"; type Props = { + anchor: string; handleClose: () => void; issueDetails: IIssue | undefined; - workspaceSlug: string; - projectId: string; }; export const SidePeekView: React.FC = observer((props) => { - const { handleClose, issueDetails, workspaceSlug, projectId } = props; - - const { settings } = useProject(); + const { anchor, handleClose, issueDetails } = props; + // store hooks + const { canComment } = usePublish(anchor); return (
@@ -33,7 +32,7 @@ export const SidePeekView: React.FC = observer((props) => {
{/* issue title and description */}
- +
{/* issue properties */}
@@ -42,13 +41,9 @@ export const SidePeekView: React.FC = observer((props) => { {/* divider */}
{/* issue activity/comments */} - {settings?.comments && ( + {canComment && (
- +
)}
diff --git a/space/components/ui/dropdown.tsx b/space/components/ui/dropdown.tsx deleted file mode 100644 index 788627094b7..00000000000 --- a/space/components/ui/dropdown.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import { Fragment, useState, useRef } from "react"; -import Link from "next/link"; -import { Check, ChevronLeft } from "lucide-react"; -import { Popover, Transition } from "@headlessui/react"; -// hooks -import useOutSideClick from "hooks/use-outside-click"; - -type ItemOptionType = { - display: React.ReactNode; - as?: "button" | "link" | "div"; - href?: string; - isSelected?: boolean; - onClick?: () => void; - children?: ItemOptionType[] | null; -}; - -type DropdownItemProps = { - item: ItemOptionType; -}; - -type DropDownListProps = { - open: boolean; - handleClose?: () => void; - items: ItemOptionType[]; -}; - -type DropdownProps = { - button: React.ReactNode | (() => React.ReactNode); - items: ItemOptionType[]; -}; - -const DropdownList: React.FC = (props) => { - const { open, items, handleClose } = props; - - const ref = useRef(null); - - useOutSideClick(ref, () => { - if (handleClose) handleClose(); - }); - - return ( - - - -
- {items.map((item, index) => ( - - ))} -
-
-
-
- ); -}; - -const DropdownItem: React.FC = (props) => { - const { item } = props; - const { display, children, as: itemAs, href, onClick, isSelected } = item; - - const [open, setOpen] = useState(false); - - return ( -
- {(!itemAs || itemAs === "button" || itemAs === "div") && ( - - )} - - {itemAs === "link" && {display}} - - {children && setOpen(false)} items={children} />} -
- ); -}; - -const Dropdown: React.FC = (props) => { - const { button, items } = props; - - return ( - - {({ open }) => ( - <> - - {typeof button === "function" ? button() : button} - - - - -
- {items.map((item, index) => ( - - ))} -
-
-
- - )} -
- ); -}; - -export { Dropdown }; diff --git a/space/components/ui/index.ts b/space/components/ui/index.ts index 1e523d5ddf5..ccd2303c4a8 100644 --- a/space/components/ui/index.ts +++ b/space/components/ui/index.ts @@ -1,3 +1,2 @@ -export * from "./dropdown"; export * from "./icon"; export * from "./reaction-selector"; diff --git a/space/components/views/index.ts b/space/components/views/index.ts index 251de14e30d..97ccf76494a 100644 --- a/space/components/views/index.ts +++ b/space/components/views/index.ts @@ -1,2 +1 @@ export * from "./auth"; -export * from "./project-details"; diff --git a/space/constants/issue.ts b/space/constants/issue.ts index fb9c78fcd70..77297946f94 100644 --- a/space/constants/issue.ts +++ b/space/constants/issue.ts @@ -1,13 +1,7 @@ -// interfaces -import { - TIssueLayout, - TIssueLayoutViews, - TIssueFilterKeys, - TIssueFilterPriority, - TIssueFilterPriorityObject, - TIssueFilterState, - TIssueFilterStateObject, -} from "types/issue"; +import { Calendar, GanttChartSquare, Kanban, List, Sheet } from "lucide-react"; +// types +import { TIssuePriorities } from "@plane/types"; +import { TIssueLayout, TIssueFilterKeys, TIssueFilterPriorityObject } from "@/types/issue"; // issue filters export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { [key in TIssueLayout]: Record<"filters", TIssueFilterKeys[]> } = { @@ -28,20 +22,18 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { [key in TIssueLayout]: Record<"f }, }; -export const issueLayoutViews: Partial = { - list: { - title: "List View", - icon: "format_list_bulleted", - className: "", - }, - kanban: { - title: "Board View", - icon: "grid_view", - className: "", - }, -}; +export const ISSUE_LAYOUTS: { + key: TIssueLayout; + title: string; + icon: any; +}[] = [ + { key: "list", title: "List", icon: List }, + { key: "kanban", title: "Kanban", icon: Kanban }, + { key: "calendar", title: "Calendar", icon: Calendar }, + { key: "spreadsheet", title: "Spreadsheet", icon: Sheet }, + { key: "gantt", title: "Gantt chart", icon: GanttChartSquare }, +]; -// issue priority filters export const issuePriorityFilters: TIssueFilterPriorityObject[] = [ { key: "urgent", @@ -75,7 +67,7 @@ export const issuePriorityFilters: TIssueFilterPriorityObject[] = [ }, ]; -export const issuePriorityFilter = (priorityKey: TIssueFilterPriority): TIssueFilterPriorityObject | undefined => { +export const issuePriorityFilter = (priorityKey: TIssuePriorities): TIssueFilterPriorityObject | undefined => { const currentIssuePriority: TIssueFilterPriorityObject | undefined = issuePriorityFilters && issuePriorityFilters.length > 0 ? issuePriorityFilters.find((_priority) => _priority.key === priorityKey) @@ -84,55 +76,3 @@ export const issuePriorityFilter = (priorityKey: TIssueFilterPriority): TIssueFi if (currentIssuePriority) return currentIssuePriority; return undefined; }; - -// issue group filters -export const issueGroupColors: { - [key in TIssueFilterState]: string; -} = { - backlog: "#d9d9d9", - unstarted: "#3f76ff", - started: "#f59e0b", - completed: "#16a34a", - cancelled: "#dc2626", -}; - -export const issueGroups: TIssueFilterStateObject[] = [ - { - key: "backlog", - title: "Backlog", - color: "#d9d9d9", - className: `text-[#d9d9d9] bg-[#d9d9d9]/10`, - }, - { - key: "unstarted", - title: "Unstarted", - color: "#3f76ff", - className: `text-[#3f76ff] bg-[#3f76ff]/10`, - }, - { - key: "started", - title: "Started", - color: "#f59e0b", - className: `text-[#f59e0b] bg-[#f59e0b]/10`, - }, - { - key: "completed", - title: "Completed", - color: "#16a34a", - className: `text-[#16a34a] bg-[#16a34a]/10`, - }, - { - key: "cancelled", - title: "Cancelled", - color: "#dc2626", - className: `text-[#dc2626] bg-[#dc2626]/10`, - }, -]; - -export const issueGroupFilter = (issueKey: TIssueFilterState): TIssueFilterStateObject | undefined => { - const currentIssueStateGroup: TIssueFilterStateObject | undefined = - issueGroups && issueGroups.length > 0 ? issueGroups.find((group) => group.key === issueKey) : undefined; - - if (currentIssueStateGroup) return currentIssueStateGroup; - return undefined; -}; diff --git a/space/constants/state.ts b/space/constants/state.ts new file mode 100644 index 00000000000..b0fd622be06 --- /dev/null +++ b/space/constants/state.ts @@ -0,0 +1,37 @@ +import { TStateGroups } from "@plane/types"; + +export const STATE_GROUPS: { + [key in TStateGroups]: { + key: TStateGroups; + label: string; + color: string; + }; +} = { + backlog: { + key: "backlog", + label: "Backlog", + color: "#d9d9d9", + }, + unstarted: { + key: "unstarted", + label: "Unstarted", + color: "#3f76ff", + }, + started: { + key: "started", + label: "Started", + color: "#f59e0b", + }, + completed: { + key: "completed", + label: "Completed", + color: "#16a34a", + }, + cancelled: { + key: "cancelled", + label: "Canceled", + color: "#dc2626", + }, +}; + +export const ARCHIVABLE_STATE_GROUPS = [STATE_GROUPS.completed.key, STATE_GROUPS.cancelled.key]; diff --git a/space/constants/workspace.ts b/space/constants/workspace.ts deleted file mode 100644 index 5ae5a7cf4f6..00000000000 --- a/space/constants/workspace.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const USER_ROLES = [ - { value: "Product / Project Manager", label: "Product / Project Manager" }, - { value: "Development / Engineering", label: "Development / Engineering" }, - { value: "Founder / Executive", label: "Founder / Executive" }, - { value: "Freelancer / Consultant", label: "Freelancer / Consultant" }, - { value: "Marketing / Growth", label: "Marketing / Growth" }, - { value: "Sales / Business Development", label: "Sales / Business Development" }, - { value: "Support / Operations", label: "Support / Operations" }, - { value: "Student / Professor", label: "Student / Professor" }, - { value: "Human Resources", label: "Human Resources" }, - { value: "Other", label: "Other" }, -]; diff --git a/space/helpers/date-time.helper.ts b/space/helpers/date-time.helper.ts index f19a5358bf1..3930bcb8338 100644 --- a/space/helpers/date-time.helper.ts +++ b/space/helpers/date-time.helper.ts @@ -1,3 +1,6 @@ +import { format, isValid } from "date-fns"; +import isNumber from "lodash/isNumber"; + export const timeAgo = (time: any) => { switch (typeof time) { case "number": @@ -14,24 +17,43 @@ export const timeAgo = (time: any) => { }; /** - * @description Returns date and month, if date is of the current year - * @description Returns date, month adn year, if date is of a different year than current - * @param {string} date - * @example renderFullDate("2023-01-01") // 1 Jan - * @example renderFullDate("2021-01-01") // 1 Jan, 2021 + * This method returns a date from string of type yyyy-mm-dd + * This method is recommended to use instead of new Date() as this does not introduce any timezone offsets + * @param date + * @returns date or undefined */ +export const getDate = (date: string | Date | undefined | null): Date | undefined => { + try { + if (!date || date === "") return; -export const renderFullDate = (date: string): string => { - if (!date) return ""; - - const months: string[] = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + if (typeof date !== "string" && !(date instanceof String)) return date; - const currentDate: Date = new Date(); - const [year, month, day]: number[] = date.split("-").map(Number); + const [yearString, monthString, dayString] = date.substring(0, 10).split("-"); + const year = parseInt(yearString); + const month = parseInt(monthString); + const day = parseInt(dayString); + if (!isNumber(year) || !isNumber(month) || !isNumber(day)) return; - const formattedMonth: string = months[month - 1]; - const formattedDay: string = day < 10 ? `0${day}` : day.toString(); + return new Date(year, month - 1, day); + } catch (e) { + return undefined; + } +}; - if (currentDate.getFullYear() === year) return `${formattedDay} ${formattedMonth}`; - else return `${formattedDay} ${formattedMonth}, ${year}`; +/** + * @returns {string | null} formatted date in the format of MMM dd, yyyy + * @description Returns date in the formatted format + * @param {Date | string} date + * @example renderFormattedDate("2024-01-01") // Jan 01, 2024 + */ +export const renderFormattedDate = (date: string | Date | undefined | null): string | null => { + // Parse the date to check if it is valid + const parsedDate = getDate(date); + // return if undefined + if (!parsedDate) return null; + // Check if the parsed date is valid before formatting + if (!isValid(parsedDate)) return null; // Return null for invalid dates + // Format the date in format (MMM dd, yyyy) + const formattedDate = format(parsedDate, "MMM dd, yyyy"); + return formattedDate; }; diff --git a/space/helpers/emoji.helper.tsx b/space/helpers/emoji.helper.tsx index 7c9f3cfcb2d..d5f9d1b5a5b 100644 --- a/space/helpers/emoji.helper.tsx +++ b/space/helpers/emoji.helper.tsx @@ -1,23 +1,3 @@ -export const getRandomEmoji = () => { - const emojis = [ - "8986", - "9200", - "128204", - "127773", - "127891", - "127947", - "128076", - "128077", - "128187", - "128188", - "128512", - "128522", - "128578", - ]; - - return emojis[Math.floor(Math.random() * emojis.length)]; -}; - export const renderEmoji = ( emoji: | string diff --git a/space/helpers/issue.helper.ts b/space/helpers/issue.helper.ts new file mode 100644 index 00000000000..a5159edef27 --- /dev/null +++ b/space/helpers/issue.helper.ts @@ -0,0 +1,30 @@ +import { differenceInCalendarDays } from "date-fns"; +// types +import { TStateGroups } from "@plane/types"; +// constants +import { STATE_GROUPS } from "@/constants/state"; +// helpers +import { getDate } from "@/helpers/date-time.helper"; + +/** + * @description check if the issue due date should be highlighted + * @param date + * @param stateGroup + * @returns boolean + */ +export const shouldHighlightIssueDueDate = ( + date: string | Date | null, + stateGroup: TStateGroups | undefined +): boolean => { + if (!date || !stateGroup) return false; + // if the issue is completed or cancelled, don't highlight the due date + if ([STATE_GROUPS.completed.key, STATE_GROUPS.cancelled.key].includes(stateGroup)) return false; + + const parsedDate = getDate(date); + if (!parsedDate) return false; + + const targetDateDistance = differenceInCalendarDays(parsedDate, new Date()); + + // if the issue is overdue, highlight the due date + return targetDateDistance <= 0; +}; diff --git a/space/helpers/string.helper.ts b/space/helpers/string.helper.ts index 525a9fc99dd..f6319bc7507 100644 --- a/space/helpers/string.helper.ts +++ b/space/helpers/string.helper.ts @@ -3,7 +3,7 @@ import DOMPurify from "dompurify"; export const addSpaceIfCamelCase = (str: string) => str.replace(/([a-z])([A-Z])/g, "$1 $2"); const fallbackCopyTextToClipboard = (text: string) => { - var textArea = document.createElement("textarea"); + const textArea = document.createElement("textarea"); textArea.value = text; // Avoid scrolling to bottom @@ -18,7 +18,7 @@ const fallbackCopyTextToClipboard = (text: string) => { try { // FIXME: Even though we are using this as a fallback, execCommand is deprecated 👎. We should find a better way to do this. // https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand - var successful = document.execCommand("copy"); + document.execCommand("copy"); } catch (err) {} document.body.removeChild(textArea); diff --git a/space/hooks/store/index.ts b/space/hooks/store/index.ts index 76b6f931549..3f82613d567 100644 --- a/space/hooks/store/index.ts +++ b/space/hooks/store/index.ts @@ -1,5 +1,5 @@ +export * from "./publish"; export * from "./use-instance"; -export * from "./use-project"; export * from "./use-issue"; export * from "./use-user"; export * from "./use-user-profile"; diff --git a/space/hooks/store/publish/index.ts b/space/hooks/store/publish/index.ts new file mode 100644 index 00000000000..a7b42ad5bb5 --- /dev/null +++ b/space/hooks/store/publish/index.ts @@ -0,0 +1,2 @@ +export * from "./use-publish-list"; +export * from "./use-publish"; diff --git a/space/hooks/store/publish/use-publish-list.ts b/space/hooks/store/publish/use-publish-list.ts new file mode 100644 index 00000000000..aa50c295a31 --- /dev/null +++ b/space/hooks/store/publish/use-publish-list.ts @@ -0,0 +1,11 @@ +import { useContext } from "react"; +// lib +import { StoreContext } from "@/lib/store-provider"; +// store +import { IPublishListStore } from "@/store/publish/publish_list.store"; + +export const usePublishList = (): IPublishListStore => { + const context = useContext(StoreContext); + if (context === undefined) throw new Error("usePublishList must be used within StoreProvider"); + return context.publishList; +}; diff --git a/space/hooks/store/publish/use-publish.ts b/space/hooks/store/publish/use-publish.ts new file mode 100644 index 00000000000..3d920e8cb30 --- /dev/null +++ b/space/hooks/store/publish/use-publish.ts @@ -0,0 +1,11 @@ +import { useContext } from "react"; +// lib +import { StoreContext } from "@/lib/store-provider"; +// store +import { PublishStore } from "@/store/publish/publish.store"; + +export const usePublish = (anchor: string): PublishStore => { + const context = useContext(StoreContext); + if (context === undefined) throw new Error("usePublish must be used within StoreProvider"); + return context.publishList.publishMap?.[anchor] ?? {}; +}; diff --git a/space/hooks/store/use-project.ts b/space/hooks/store/use-project.ts deleted file mode 100644 index cd3e2895862..00000000000 --- a/space/hooks/store/use-project.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useContext } from "react"; -// lib -import { StoreContext } from "@/lib/store-provider"; -// store -import { IProjectStore } from "@/store/project.store"; - -export const useProject = (): IProjectStore => { - const context = useContext(StoreContext); - if (context === undefined) throw new Error("useUserProfile must be used within StoreProvider"); - return context.project; -}; diff --git a/space/hooks/use-mention.tsx b/space/hooks/use-mention.tsx index 8b2d6972032..9e33f7d904e 100644 --- a/space/hooks/use-mention.tsx +++ b/space/hooks/use-mention.tsx @@ -1,7 +1,9 @@ import { useRef, useEffect } from "react"; import useSWR from "swr"; +// types import { IUser } from "@plane/types"; -import { UserService } from "services/user.service"; +// services +import { UserService } from "@/services/user.service"; export const useMention = () => { const userService = new UserService(); diff --git a/space/lib/user-provider.tsx b/space/lib/user-provider.tsx deleted file mode 100644 index 1ac1c786c3b..00000000000 --- a/space/lib/user-provider.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { ReactNode } from "react"; -import { observer } from "mobx-react-lite"; -import useSWR from "swr"; -import { useUser } from "@/hooks/store"; - -export const UserProvider = observer(({ children }: { children: ReactNode }) => { - const { fetchCurrentUser } = useUser(); - - useSWR("CURRENT_USER", () => fetchCurrentUser()); - - return <>{children}; -}); diff --git a/space/package.json b/space/package.json index e3dadbff8e5..0932d7abfdc 100644 --- a/space/package.json +++ b/space/package.json @@ -26,6 +26,7 @@ "@sentry/nextjs": "^8", "axios": "^1.3.4", "clsx": "^2.0.0", + "date-fns": "^3.6.0", "dompurify": "^3.0.11", "dotenv": "^16.3.1", "js-cookie": "^3.0.1", diff --git a/space/services/file.service.ts b/space/services/file.service.ts index 0e277af1e2a..9fe06cd3644 100644 --- a/space/services/file.service.ts +++ b/space/services/file.service.ts @@ -4,30 +4,6 @@ import { API_BASE_URL } from "@/helpers/common.helper"; // services import { APIService } from "@/services/api.service"; -interface UnSplashImage { - id: string; - created_at: Date; - updated_at: Date; - promoted_at: Date; - width: number; - height: number; - color: string; - blur_hash: string; - description: null; - alt_description: string; - urls: UnSplashImageUrls; - [key: string]: any; -} - -interface UnSplashImageUrls { - raw: string; - full: string; - regular: string; - small: string; - thumb: string; - small_s3: string; -} - class FileService extends APIService { private cancelSource: any; @@ -123,40 +99,6 @@ class FileService extends APIService { throw error?.response?.data; }); } - - async deleteFile(workspaceId: string, assetUrl: string): Promise { - const lastIndex = assetUrl.lastIndexOf("/"); - const assetId = assetUrl.substring(lastIndex + 1); - - return this.delete(`/api/workspaces/file-assets/${workspaceId}/${assetId}/`) - .then((response) => response?.data) - .catch((error) => { - throw error?.response?.data; - }); - } - - async uploadUserFile(file: FormData): Promise { - return this.post(`/api/users/file-assets/`, file, { - headers: { - "Content-Type": "multipart/form-data", - }, - }) - .then((response) => response?.data) - .catch((error) => { - throw error?.response?.data; - }); - } - - async deleteUserFile(assetUrl: string): Promise { - const lastIndex = assetUrl.lastIndexOf("/"); - const assetId = assetUrl.substring(lastIndex + 1); - - return this.delete(`/api/users/file-assets/${assetId}`) - .then((response) => response?.data) - .catch((error) => { - throw error?.response?.data; - }); - } } const fileService = new FileService(); diff --git a/space/services/issue.service.ts b/space/services/issue.service.ts index 1913b678e0d..f864818126d 100644 --- a/space/services/issue.service.ts +++ b/space/services/issue.service.ts @@ -1,14 +1,16 @@ import { API_BASE_URL } from "@/helpers/common.helper"; // services import { APIService } from "@/services/api.service"; +// types +import { TIssuesResponse } from "@/types/issue"; class IssueService extends APIService { constructor() { super(API_BASE_URL); } - async getPublicIssues(workspace_slug: string, project_slug: string, params: any): Promise { - return this.get(`/api/public/workspaces/${workspace_slug}/project-boards/${project_slug}/issues/`, { + async fetchPublicIssues(anchor: string, params: any): Promise { + return this.get(`/api/public/anchor/${anchor}/issues/`, { params, }) .then((response) => response?.data) @@ -17,115 +19,88 @@ class IssueService extends APIService { }); } - async getIssueById(workspaceSlug: string, projectId: string, issueId: string): Promise { - return this.get(`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/`) + async getIssueById(anchor: string, issueID: string): Promise { + return this.get(`/api/public/anchor/${anchor}/issues/${issueID}/`) .then((response) => response?.data) .catch((error) => { throw error?.response; }); } - async getIssueVotes(workspaceSlug: string, projectId: string, issueId: string): Promise { - return this.get(`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/votes/`) + async getIssueVotes(anchor: string, issueID: string): Promise { + return this.get(`/api/public/anchor/${anchor}/issues/${issueID}/votes/`) .then((response) => response?.data) .catch((error) => { throw error?.response; }); } - async createIssueVote(workspaceSlug: string, projectId: string, issueId: string, data: any): Promise { - return this.post( - `/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/votes/`, - data - ) + async createIssueVote(anchor: string, issueID: string, data: any): Promise { + return this.post(`/api/public/anchor/${anchor}/issues/${issueID}/votes/`, data) .then((response) => response?.data) .catch((error) => { throw error?.response; }); } - async deleteIssueVote(workspaceSlug: string, projectId: string, issueId: string): Promise { - return this.delete(`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/votes/`) + async deleteIssueVote(anchor: string, issueID: string): Promise { + return this.delete(`/api/public/anchor/${anchor}/issues/${issueID}/votes/`) .then((response) => response?.data) .catch((error) => { throw error?.response; }); } - async getIssueReactions(workspaceSlug: string, projectId: string, issueId: string): Promise { - return this.get(`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/reactions/`) + async getIssueReactions(anchor: string, issueID: string): Promise { + return this.get(`/api/public/anchor/${anchor}/issues/${issueID}/reactions/`) .then((response) => response?.data) .catch((error) => { throw error?.response; }); } - async createIssueReaction(workspaceSlug: string, projectId: string, issueId: string, data: any): Promise { - return this.post( - `/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/reactions/`, - data - ) + async createIssueReaction(anchor: string, issueID: string, data: any): Promise { + return this.post(`/api/public/anchor/${anchor}/issues/${issueID}/reactions/`, data) .then((response) => response?.data) .catch((error) => { throw error?.response; }); } - async deleteIssueReaction( - workspaceSlug: string, - projectId: string, - issueId: string, - reactionId: string - ): Promise { - return this.delete( - `/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/reactions/${reactionId}/` - ) + async deleteIssueReaction(anchor: string, issueID: string, reactionId: string): Promise { + return this.delete(`/api/public/anchor/${anchor}/issues/${issueID}/reactions/${reactionId}/`) .then((response) => response?.data) .catch((error) => { throw error?.response; }); } - async getIssueComments(workspaceSlug: string, projectId: string, issueId: string): Promise { - return this.get(`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/comments/`) + async getIssueComments(anchor: string, issueID: string): Promise { + return this.get(`/api/public/anchor/${anchor}/issues/${issueID}/comments/`) .then((response) => response?.data) .catch((error) => { throw error?.response; }); } - async createIssueComment(workspaceSlug: string, projectId: string, issueId: string, data: any): Promise { - return this.post( - `/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/comments/`, - data - ) + async createIssueComment(anchor: string, issueID: string, data: any): Promise { + return this.post(`/api/public/anchor/${anchor}/issues/${issueID}/comments/`, data) .then((response) => response?.data) .catch((error) => { throw error?.response; }); } - async updateIssueComment( - workspaceSlug: string, - projectId: string, - issueId: string, - commentId: string, - data: any - ): Promise { - return this.patch( - `/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/comments/${commentId}/`, - data - ) + async updateIssueComment(anchor: string, issueID: string, commentId: string, data: any): Promise { + return this.patch(`/api/public/anchor/${anchor}/issues/${issueID}/comments/${commentId}/`, data) .then((response) => response?.data) .catch((error) => { throw error?.response; }); } - async deleteIssueComment(workspaceSlug: string, projectId: string, issueId: string, commentId: string): Promise { - return this.delete( - `/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/comments/${commentId}/` - ) + async deleteIssueComment(anchor: string, issueID: string, commentId: string): Promise { + return this.delete(`/api/public/anchor/${anchor}/issues/${issueID}/comments/${commentId}/`) .then((response) => response?.data) .catch((error) => { throw error?.response; @@ -133,32 +108,21 @@ class IssueService extends APIService { } async createCommentReaction( - workspaceSlug: string, - projectId: string, + anchor: string, commentId: string, data: { reaction: string; } ): Promise { - return this.post( - `/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/comments/${commentId}/reactions/`, - data - ) + return this.post(`/api/public/anchor/${anchor}/comments/${commentId}/reactions/`, data) .then((response) => response?.data) .catch((error) => { throw error?.response; }); } - async deleteCommentReaction( - workspaceSlug: string, - projectId: string, - commentId: string, - reactionHex: string - ): Promise { - return this.delete( - `/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/comments/${commentId}/reactions/${reactionHex}/` - ) + async deleteCommentReaction(anchor: string, commentId: string, reactionHex: string): Promise { + return this.delete(`/api/public/anchor/${anchor}/comments/${commentId}/reactions/${reactionHex}/`) .then((response) => response?.data) .catch((error) => { throw error?.response; diff --git a/space/services/project-member.service.ts b/space/services/project-member.service.ts index 264d5338695..722380efafe 100644 --- a/space/services/project-member.service.ts +++ b/space/services/project-member.service.ts @@ -9,16 +9,16 @@ export class ProjectMemberService extends APIService { super(API_BASE_URL); } - async fetchProjectMembers(workspaceSlug: string, projectId: string): Promise { - return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/`) + async fetchProjectMembers(anchor: string): Promise { + return this.get(`/api/anchor/${anchor}/members/`) .then((response) => response?.data) .catch((error) => { throw error?.response?.data; }); } - async getProjectMember(workspaceSlug: string, projectId: string, memberId: string): Promise { - return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/${memberId}/`) + async getProjectMember(anchor: string, memberID: string): Promise { + return this.get(`/api/anchor/${anchor}/members/${memberID}/`) .then((response) => response?.data) .catch((error) => { throw error?.response?.data; diff --git a/space/services/project.service.ts b/space/services/project.service.ts deleted file mode 100644 index 14ed7837b3f..00000000000 --- a/space/services/project.service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { API_BASE_URL } from "@/helpers/common.helper"; -// services -import { APIService } from "@/services/api.service"; - -class ProjectService extends APIService { - constructor() { - super(API_BASE_URL); - } - - async getProjectSettings(workspace_slug: string, project_slug: string): Promise { - return this.get(`/api/public/workspaces/${workspace_slug}/project-boards/${project_slug}/settings/`) - .then((response) => response?.data) - .catch((error) => { - throw error?.response; - }); - } -} - -export default ProjectService; diff --git a/space/services/publish.service.ts b/space/services/publish.service.ts new file mode 100644 index 00000000000..0275142c897 --- /dev/null +++ b/space/services/publish.service.ts @@ -0,0 +1,30 @@ +// types +import { TPublishSettings } from "@plane/types"; +// helpers +import { API_BASE_URL } from "@/helpers/common.helper"; +// services +import { APIService } from "@/services/api.service"; + +class PublishService extends APIService { + constructor() { + super(API_BASE_URL); + } + + async fetchPublishSettings(anchor: string): Promise { + return this.get(`/api/public/anchor/${anchor}/settings/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } + + async fetchAnchorFromProjectDetails(workspaceSlug: string, projectID: string): Promise { + return this.get(`/api/public/workspaces/${workspaceSlug}/projects/${projectID}/anchor/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } +} + +export default PublishService; diff --git a/space/store/issue-detail.store.ts b/space/store/issue-detail.store.ts index 03f611cc0ad..672fe29ad27 100644 --- a/space/store/issue-detail.store.ts +++ b/space/store/issue-detail.store.ts @@ -10,108 +10,102 @@ import { IIssue, IPeekMode, IVote } from "@/types/issue"; export interface IIssueDetailStore { loader: boolean; error: any; - // peek info + // observables peekId: string | null; peekMode: IPeekMode; details: { [key: string]: IIssue; }; - // peek actions - setPeekId: (issueId: string | null) => void; + // actions + setPeekId: (issueID: string | null) => void; setPeekMode: (mode: IPeekMode) => void; - // issue details - fetchIssueDetails: (workspaceId: string, projectId: string, issueId: string) => void; - // issue comments - addIssueComment: (workspaceId: string, projectId: string, issueId: string, data: any) => Promise; - updateIssueComment: ( - workspaceId: string, - projectId: string, - issueId: string, - comment_id: string, - data: any - ) => Promise; - deleteIssueComment: (workspaceId: string, projectId: string, issueId: string, comment_id: string) => void; - addCommentReaction: ( - workspaceId: string, - projectId: string, - issueId: string, - commentId: string, - reactionHex: string - ) => void; - removeCommentReaction: ( - workspaceId: string, - projectId: string, - issueId: string, - commentId: string, - reactionHex: string - ) => void; - // issue reactions - addIssueReaction: (workspaceId: string, projectId: string, issueId: string, reactionHex: string) => void; - removeIssueReaction: (workspaceId: string, projectId: string, issueId: string, reactionHex: string) => void; - // issue votes - addIssueVote: (workspaceId: string, projectId: string, issueId: string, data: { vote: 1 | -1 }) => Promise; - removeIssueVote: (workspaceId: string, projectId: string, issueId: string) => Promise; + // issue actions + fetchIssueDetails: (anchor: string, issueID: string) => void; + // comment actions + addIssueComment: (anchor: string, issueID: string, data: any) => Promise; + updateIssueComment: (anchor: string, issueID: string, commentID: string, data: any) => Promise; + deleteIssueComment: (anchor: string, issueID: string, commentID: string) => void; + addCommentReaction: (anchor: string, issueID: string, commentID: string, reactionHex: string) => void; + removeCommentReaction: (anchor: string, issueID: string, commentID: string, reactionHex: string) => void; + // reaction actions + addIssueReaction: (anchor: string, issueID: string, reactionHex: string) => void; + removeIssueReaction: (anchor: string, issueID: string, reactionHex: string) => void; + // vote actions + addIssueVote: (anchor: string, issueID: string, data: { vote: 1 | -1 }) => Promise; + removeIssueVote: (anchor: string, issueID: string) => Promise; } export class IssueDetailStore implements IIssueDetailStore { loader: boolean = false; error: any = null; + // observables peekId: string | null = null; peekMode: IPeekMode = "side"; details: { [key: string]: IIssue; } = {}; - issueService; + // root store rootStore: RootStore; + // services + issueService: IssueService; constructor(_rootStore: RootStore) { makeObservable(this, { loader: observable.ref, error: observable.ref, - // peek + // observables peekId: observable.ref, peekMode: observable.ref, - details: observable.ref, + details: observable, // actions setPeekId: action, setPeekMode: action, + // issue actions fetchIssueDetails: action, + // comment actions addIssueComment: action, updateIssueComment: action, deleteIssueComment: action, addCommentReaction: action, removeCommentReaction: action, + // reaction actions addIssueReaction: action, removeIssueReaction: action, + // vote actions addIssueVote: action, removeIssueVote: action, }); - this.issueService = new IssueService(); this.rootStore = _rootStore; + this.issueService = new IssueService(); } - setPeekId = (issueId: string | null) => { - this.peekId = issueId; + setPeekId = (issueID: string | null) => { + this.peekId = issueID; }; setPeekMode = (mode: IPeekMode) => { this.peekMode = mode; }; - fetchIssueDetails = async (workspaceSlug: string, projectId: string, issueId: string) => { + /** + * @description fetc + * @param {string} anchor + * @param {string} issueID + */ + fetchIssueDetails = async (anchor: string, issueID: string) => { try { this.loader = true; this.error = null; - const issueDetails = this.rootStore.issue.issues?.find((i) => i.id === issueId); - const commentsResponse = await this.issueService.getIssueComments(workspaceSlug, projectId, issueId); + const issueDetails = this.rootStore.issue.issues?.find((i) => i.id === issueID); + const commentsResponse = await this.issueService.getIssueComments(anchor, issueID); if (issueDetails) { runInAction(() => { this.details = { ...this.details, - [issueId]: { - ...(this.details[issueId] ?? issueDetails), + [issueID]: { + ...(this.details[issueID] ?? issueDetails), comments: commentsResponse, }, }; @@ -123,17 +117,17 @@ export class IssueDetailStore implements IIssueDetailStore { } }; - addIssueComment = async (workspaceSlug: string, projectId: string, issueId: string, data: any) => { + addIssueComment = async (anchor: string, issueID: string, data: any) => { try { - const issueDetails = this.rootStore.issue.issues?.find((i) => i.id === issueId); - const issueCommentResponse = await this.issueService.createIssueComment(workspaceSlug, projectId, issueId, data); + const issueDetails = this.rootStore.issue.issues?.find((i) => i.id === issueID); + const issueCommentResponse = await this.issueService.createIssueComment(anchor, issueID, data); if (issueDetails) { runInAction(() => { this.details = { ...this.details, - [issueId]: { + [issueID]: { ...issueDetails, - comments: [...this.details[issueId].comments, issueCommentResponse], + comments: [...this.details[issueID].comments, issueCommentResponse], }, }; }); @@ -145,36 +139,30 @@ export class IssueDetailStore implements IIssueDetailStore { } }; - updateIssueComment = async ( - workspaceSlug: string, - projectId: string, - issueId: string, - commentId: string, - data: any - ) => { + updateIssueComment = async (anchor: string, issueID: string, commentID: string, data: any) => { try { runInAction(() => { this.details = { ...this.details, - [issueId]: { - ...this.details[issueId], - comments: this.details[issueId].comments.map((c) => ({ + [issueID]: { + ...this.details[issueID], + comments: this.details[issueID].comments.map((c) => ({ ...c, - ...(c.id === commentId ? data : {}), + ...(c.id === commentID ? data : {}), })), }, }; }); - await this.issueService.updateIssueComment(workspaceSlug, projectId, issueId, commentId, data); + await this.issueService.updateIssueComment(anchor, issueID, commentID, data); } catch (error) { - const issueComments = await this.issueService.getIssueComments(workspaceSlug, projectId, issueId); + const issueComments = await this.issueService.getIssueComments(anchor, issueID); runInAction(() => { this.details = { ...this.details, - [issueId]: { - ...this.details[issueId], + [issueID]: { + ...this.details[issueID], comments: issueComments, }, }; @@ -182,15 +170,15 @@ export class IssueDetailStore implements IIssueDetailStore { } }; - deleteIssueComment = async (workspaceSlug: string, projectId: string, issueId: string, comment_id: string) => { + deleteIssueComment = async (anchor: string, issueID: string, commentID: string) => { try { - await this.issueService.deleteIssueComment(workspaceSlug, projectId, issueId, comment_id); - const remainingComments = this.details[issueId].comments.filter((c) => c.id != comment_id); + await this.issueService.deleteIssueComment(anchor, issueID, commentID); + const remainingComments = this.details[issueID].comments.filter((c) => c.id != commentID); runInAction(() => { this.details = { ...this.details, - [issueId]: { - ...this.details[issueId], + [issueID]: { + ...this.details[issueID], comments: remainingComments, }, }; @@ -200,47 +188,41 @@ export class IssueDetailStore implements IIssueDetailStore { } }; - addCommentReaction = async ( - workspaceSlug: string, - projectId: string, - issueId: string, - commentId: string, - reactionHex: string - ) => { + addCommentReaction = async (anchor: string, issueID: string, commentID: string, reactionHex: string) => { const newReaction = { id: uuidv4(), - comment: commentId, + comment: commentID, reaction: reactionHex, actor_detail: this.rootStore.user.currentActor, }; - const newComments = this.details[issueId].comments.map((comment) => ({ + const newComments = this.details[issueID].comments.map((comment) => ({ ...comment, comment_reactions: - comment.id === commentId ? [...comment.comment_reactions, newReaction] : comment.comment_reactions, + comment.id === commentID ? [...comment.comment_reactions, newReaction] : comment.comment_reactions, })); try { runInAction(() => { this.details = { ...this.details, - [issueId]: { - ...this.details[issueId], + [issueID]: { + ...this.details[issueID], comments: [...newComments], }, }; }); - await this.issueService.createCommentReaction(workspaceSlug, projectId, commentId, { + await this.issueService.createCommentReaction(anchor, commentID, { reaction: reactionHex, }); } catch (error) { - const issueComments = await this.issueService.getIssueComments(workspaceSlug, projectId, issueId); + const issueComments = await this.issueService.getIssueComments(anchor, issueID); runInAction(() => { this.details = { ...this.details, - [issueId]: { - ...this.details[issueId], + [issueID]: { + ...this.details[issueID], comments: issueComments, }, }; @@ -248,39 +230,33 @@ export class IssueDetailStore implements IIssueDetailStore { } }; - removeCommentReaction = async ( - workspaceSlug: string, - projectId: string, - issueId: string, - commentId: string, - reactionHex: string - ) => { + removeCommentReaction = async (anchor: string, issueID: string, commentID: string, reactionHex: string) => { try { - const comment = this.details[issueId].comments.find((c) => c.id === commentId); + const comment = this.details[issueID].comments.find((c) => c.id === commentID); const newCommentReactions = comment?.comment_reactions.filter((r) => r.reaction !== reactionHex) ?? []; runInAction(() => { this.details = { ...this.details, - [issueId]: { - ...this.details[issueId], - comments: this.details[issueId].comments.map((c) => ({ + [issueID]: { + ...this.details[issueID], + comments: this.details[issueID].comments.map((c) => ({ ...c, - comment_reactions: c.id === commentId ? newCommentReactions : c.comment_reactions, + comment_reactions: c.id === commentID ? newCommentReactions : c.comment_reactions, })), }, }; }); - await this.issueService.deleteCommentReaction(workspaceSlug, projectId, commentId, reactionHex); + await this.issueService.deleteCommentReaction(anchor, commentID, reactionHex); } catch (error) { - const issueComments = await this.issueService.getIssueComments(workspaceSlug, projectId, issueId); + const issueComments = await this.issueService.getIssueComments(anchor, issueID); runInAction(() => { this.details = { ...this.details, - [issueId]: { - ...this.details[issueId], + [issueID]: { + ...this.details[issueID], comments: issueComments, }, }; @@ -288,18 +264,18 @@ export class IssueDetailStore implements IIssueDetailStore { } }; - addIssueReaction = async (workspaceSlug: string, projectId: string, issueId: string, reactionHex: string) => { + addIssueReaction = async (anchor: string, issueID: string, reactionHex: string) => { try { runInAction(() => { this.details = { ...this.details, - [issueId]: { - ...this.details[issueId], + [issueID]: { + ...this.details[issueID], reactions: [ - ...this.details[issueId].reactions, + ...this.details[issueID].reactions, { id: uuidv4(), - issue: issueId, + issue: issueID, reaction: reactionHex, actor_detail: this.rootStore.user.currentActor, }, @@ -308,17 +284,17 @@ export class IssueDetailStore implements IIssueDetailStore { }; }); - await this.issueService.createIssueReaction(workspaceSlug, projectId, issueId, { + await this.issueService.createIssueReaction(anchor, issueID, { reaction: reactionHex, }); } catch (error) { console.log("Failed to add issue vote"); - const issueReactions = await this.issueService.getIssueReactions(workspaceSlug, projectId, issueId); + const issueReactions = await this.issueService.getIssueReactions(anchor, issueID); runInAction(() => { this.details = { ...this.details, - [issueId]: { - ...this.details[issueId], + [issueID]: { + ...this.details[issueID], reactions: issueReactions, }, }; @@ -326,31 +302,31 @@ export class IssueDetailStore implements IIssueDetailStore { } }; - removeIssueReaction = async (workspaceSlug: string, projectId: string, issueId: string, reactionHex: string) => { + removeIssueReaction = async (anchor: string, issueID: string, reactionHex: string) => { try { - const newReactions = this.details[issueId].reactions.filter( + const newReactions = this.details[issueID].reactions.filter( (_r) => !(_r.reaction === reactionHex && _r.actor_detail.id === this.rootStore.user.data?.id) ); runInAction(() => { this.details = { ...this.details, - [issueId]: { - ...this.details[issueId], + [issueID]: { + ...this.details[issueID], reactions: newReactions, }, }; }); - await this.issueService.deleteIssueReaction(workspaceSlug, projectId, issueId, reactionHex); + await this.issueService.deleteIssueReaction(anchor, issueID, reactionHex); } catch (error) { console.log("Failed to remove issue reaction"); - const reactions = await this.issueService.getIssueReactions(workspaceSlug, projectId, issueId); + const reactions = await this.issueService.getIssueReactions(anchor, issueID); runInAction(() => { this.details = { ...this.details, - [issueId]: { - ...this.details[issueId], + [issueID]: { + ...this.details[issueID], reactions: reactions, }, }; @@ -358,39 +334,44 @@ export class IssueDetailStore implements IIssueDetailStore { } }; - addIssueVote = async (workspaceSlug: string, projectId: string, issueId: string, data: { vote: 1 | -1 }) => { + addIssueVote = async (anchor: string, issueID: string, data: { vote: 1 | -1 }) => { + const publishSettings = this.rootStore.publishList?.publishMap?.[anchor]; + const projectID = publishSettings?.project; + const workspaceSlug = publishSettings?.workspace_detail?.slug; + if (!projectID || !workspaceSlug) throw new Error("Publish settings not found"); + const newVote: IVote = { actor: this.rootStore.user.data?.id ?? "", actor_detail: this.rootStore.user.currentActor, - issue: issueId, - project: projectId, + issue: issueID, + project: projectID, workspace: workspaceSlug, vote: data.vote, }; - const filteredVotes = this.details[issueId].votes.filter((v) => v.actor !== this.rootStore.user.data?.id); + const filteredVotes = this.details[issueID].votes.filter((v) => v.actor !== this.rootStore.user.data?.id); try { runInAction(() => { this.details = { ...this.details, - [issueId]: { - ...this.details[issueId], + [issueID]: { + ...this.details[issueID], votes: [...filteredVotes, newVote], }, }; }); - await this.issueService.createIssueVote(workspaceSlug, projectId, issueId, data); + await this.issueService.createIssueVote(anchor, issueID, data); } catch (error) { console.log("Failed to add issue vote"); - const issueVotes = await this.issueService.getIssueVotes(workspaceSlug, projectId, issueId); + const issueVotes = await this.issueService.getIssueVotes(anchor, issueID); runInAction(() => { this.details = { ...this.details, - [issueId]: { - ...this.details[issueId], + [issueID]: { + ...this.details[issueID], votes: issueVotes, }, }; @@ -398,30 +379,30 @@ export class IssueDetailStore implements IIssueDetailStore { } }; - removeIssueVote = async (workspaceSlug: string, projectId: string, issueId: string) => { - const newVotes = this.details[issueId].votes.filter((v) => v.actor !== this.rootStore.user.data?.id); + removeIssueVote = async (anchor: string, issueID: string) => { + const newVotes = this.details[issueID].votes.filter((v) => v.actor !== this.rootStore.user.data?.id); try { runInAction(() => { this.details = { ...this.details, - [issueId]: { - ...this.details[issueId], + [issueID]: { + ...this.details[issueID], votes: newVotes, }, }; }); - await this.issueService.deleteIssueVote(workspaceSlug, projectId, issueId); + await this.issueService.deleteIssueVote(anchor, issueID); } catch (error) { console.log("Failed to remove issue vote"); - const issueVotes = await this.issueService.getIssueVotes(workspaceSlug, projectId, issueId); + const issueVotes = await this.issueService.getIssueVotes(anchor, issueID); runInAction(() => { this.details = { ...this.details, - [issueId]: { - ...this.details[issueId], + [issueID]: { + ...this.details[issueID], votes: issueVotes, }, }; diff --git a/space/store/issue-filters.store.ts b/space/store/issue-filters.store.ts index b7b311af457..daf797f90ec 100644 --- a/space/store/issue-filters.store.ts +++ b/space/store/issue-filters.store.ts @@ -1,7 +1,7 @@ import cloneDeep from "lodash/cloneDeep"; import isEqual from "lodash/isEqual"; import set from "lodash/set"; -import { action, makeObservable, observable, runInAction, computed } from "mobx"; +import { action, makeObservable, observable, runInAction } from "mobx"; import { computedFn } from "mobx-utils"; // constants import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue"; @@ -19,16 +19,17 @@ import { export interface IIssueFilterStore { // observables layoutOptions: TIssueLayoutOptions; - filters: { [projectId: string]: TIssueFilters } | undefined; + filters: { [anchor: string]: TIssueFilters } | undefined; // computed - issueFilters: TIssueFilters | undefined; - appliedFilters: TIssueQueryFiltersParams | undefined; - isIssueFiltersUpdated: (filters: TIssueFilters) => boolean; + isIssueFiltersUpdated: (anchor: string, filters: TIssueFilters) => boolean; + // helpers + getIssueFilters: (anchor: string) => TIssueFilters | undefined; + getAppliedFilters: (anchor: string) => TIssueQueryFiltersParams | undefined; // actions updateLayoutOptions: (layout: TIssueLayoutOptions) => void; - initIssueFilters: (projectId: string, filters: TIssueFilters) => void; + initIssueFilters: (anchor: string, filters: TIssueFilters) => void; updateIssueFilters: ( - projectId: string, + anchor: string, filterKind: K, filterKey: keyof TIssueFilters[K], filters: TIssueFilters[K][typeof filterKey] @@ -44,16 +45,13 @@ export class IssueFilterStore implements IIssueFilterStore { gantt: false, spreadsheet: false, }; - filters: { [projectId: string]: TIssueFilters } | undefined = undefined; + filters: { [anchor: string]: TIssueFilters } | undefined = undefined; constructor(private store: RootStore) { makeObservable(this, { // observables layoutOptions: observable, filters: observable, - // computed - issueFilters: computed, - appliedFilters: computed, // actions updateLayoutOptions: action, initIssueFilters: action, @@ -82,79 +80,70 @@ export class IssueFilterStore implements IIssueFilterStore { }; // computed - get issueFilters() { - const projectId = this.store.project.project?.id; - if (!projectId) return undefined; - - const currentFilters = this.filters?.[projectId]; - if (!currentFilters) return undefined; - + getIssueFilters = computedFn((anchor: string) => { + const currentFilters = this.filters?.[anchor]; return currentFilters; - } + }); - get appliedFilters() { - const currentIssueFilters = this.issueFilters; - if (!currentIssueFilters) return undefined; + getAppliedFilters = computedFn((anchor: string) => { + const issueFilters = this.getIssueFilters(anchor); + if (!issueFilters) return undefined; - const currentLayout = currentIssueFilters?.display_filters?.layout; + const currentLayout = issueFilters?.display_filters?.layout; if (!currentLayout) return undefined; const currentFilters: TIssueQueryFilters = { - priority: currentIssueFilters?.filters?.priority || undefined, - state: currentIssueFilters?.filters?.state || undefined, - labels: currentIssueFilters?.filters?.labels || undefined, + priority: issueFilters?.filters?.priority || undefined, + state: issueFilters?.filters?.state || undefined, + labels: issueFilters?.filters?.labels || undefined, }; const filteredParams = ISSUE_DISPLAY_FILTERS_BY_LAYOUT?.[currentLayout]?.filters || []; const currentFilterQueryParams: TIssueQueryFiltersParams = this.computedFilter(currentFilters, filteredParams); return currentFilterQueryParams; - } + }); - isIssueFiltersUpdated = computedFn((userFilters: TIssueFilters) => { - if (!this.issueFilters) return false; + isIssueFiltersUpdated = computedFn((anchor: string, userFilters: TIssueFilters) => { + const issueFilters = this.getIssueFilters(anchor); + if (!issueFilters) return false; const currentUserFilters = cloneDeep(userFilters?.filters || {}); - const currentIssueFilters = cloneDeep(this.issueFilters?.filters || {}); + const currentIssueFilters = cloneDeep(issueFilters?.filters || {}); return isEqual(currentUserFilters, currentIssueFilters); }); // actions updateLayoutOptions = (options: TIssueLayoutOptions) => set(this, ["layoutOptions"], options); - initIssueFilters = async (projectId: string, initFilters: TIssueFilters) => { + initIssueFilters = async (anchor: string, initFilters: TIssueFilters) => { try { - if (!projectId) return; if (this.filters === undefined) runInAction(() => (this.filters = {})); - if (this.filters && initFilters) set(this.filters, [projectId], initFilters); + if (this.filters && initFilters) set(this.filters, [anchor], initFilters); - const workspaceSlug = this.store.project.workspace?.slug; - const currentAppliedFilters = this.appliedFilters; + const appliedFilters = this.getAppliedFilters(anchor); - if (!workspaceSlug) return; - await this.store.issue.fetchPublicIssues(workspaceSlug, projectId, currentAppliedFilters); + await this.store.issue.fetchPublicIssues(anchor, appliedFilters); } catch (error) { throw error; } }; updateIssueFilters = async ( - projectId: string, + anchor: string, filterKind: K, filterKey: keyof TIssueFilters[K], filterValue: TIssueFilters[K][typeof filterKey] ) => { try { - if (!projectId || !filterKind || !filterKey || !filterValue) return; + if (!filterKind || !filterKey || !filterValue) return; if (this.filters === undefined) runInAction(() => (this.filters = {})); runInAction(() => { - if (this.filters) set(this.filters, [projectId, filterKind, filterKey], filterValue); + if (this.filters) set(this.filters, [anchor, filterKind, filterKey], filterValue); }); - const workspaceSlug = this.store.project.workspace?.slug; - const currentAppliedFilters = this.appliedFilters; + const appliedFilters = this.getAppliedFilters(anchor); - if (!workspaceSlug) return; - await this.store.issue.fetchPublicIssues(workspaceSlug, projectId, currentAppliedFilters); + await this.store.issue.fetchPublicIssues(anchor, appliedFilters); } catch (error) { throw error; } diff --git a/space/store/issue.store.ts b/space/store/issue.store.ts index 7967aafb1c5..4f2d845b50e 100644 --- a/space/store/issue.store.ts +++ b/space/store/issue.store.ts @@ -1,87 +1,87 @@ import { observable, action, makeObservable, runInAction } from "mobx"; +import { computedFn } from "mobx-utils"; +// types +import { IStateLite } from "@plane/types"; // services import IssueService from "@/services/issue.service"; // types -import { IIssue, IIssueState, IIssueLabel } from "@/types/issue"; +import { IIssue, IIssueLabel } from "@/types/issue"; // store import { RootStore } from "./root.store"; -// import { IssueDetailType, TIssueBoardKeys } from "types/issue"; export interface IIssueStore { loader: boolean; error: any; - // issue options - issues: IIssue[] | null; - states: IIssueState[] | null; - labels: IIssueLabel[] | null; - // filtering + // observables + issues: IIssue[]; + states: IStateLite[]; + labels: IIssueLabel[]; + // filter observables filteredStates: string[]; filteredLabels: string[]; filteredPriorities: string[]; - // service - issueService: any; // actions - fetchPublicIssues: (workspace_slug: string, project_slug: string, params: any) => Promise; - getCountOfIssuesByState: (state: string) => number; - getFilteredIssuesByState: (state: string) => IIssue[]; + fetchPublicIssues: (anchor: string, params: any) => Promise; + // helpers + getCountOfIssuesByState: (stateID: string) => number; + getFilteredIssuesByState: (stateID: string) => IIssue[]; } export class IssueStore implements IIssueStore { loader: boolean = false; error: any | null = null; - - states: IIssueState[] | null = []; - labels: IIssueLabel[] | null = []; - + // observables + states: IStateLite[] = []; + labels: IIssueLabel[] = []; + issues: IIssue[] = []; + // filter observables filteredStates: string[] = []; filteredLabels: string[] = []; filteredPriorities: string[] = []; - - issues: IIssue[] | null = []; - issue_detail: any = {}; - + // root store rootStore: RootStore; - issueService: any; + // services + issueService: IssueService; - constructor(_rootStore: any) { + constructor(_rootStore: RootStore) { makeObservable(this, { - // observable - loader: observable, + loader: observable.ref, error: observable, - // issue options - states: observable.ref, - labels: observable.ref, - // filtering - filteredStates: observable.ref, - filteredLabels: observable.ref, - filteredPriorities: observable.ref, - // issues - issues: observable.ref, - issue_detail: observable.ref, + // observables + states: observable, + labels: observable, + issues: observable, + // filter observables + filteredStates: observable, + filteredLabels: observable, + filteredPriorities: observable, // actions fetchPublicIssues: action, - getFilteredIssuesByState: action, }); this.rootStore = _rootStore; this.issueService = new IssueService(); } - fetchPublicIssues = async (workspaceSlug: string, projectId: string, params: any) => { + /** + * @description fetch issues, states and labels + * @param {string} anchor + * @param params + */ + fetchPublicIssues = async (anchor: string, params: any) => { try { - this.loader = true; - this.error = null; + runInAction(() => { + this.loader = true; + this.error = null; + }); - const response = await this.issueService.getPublicIssues(workspaceSlug, projectId, params); + const response = await this.issueService.fetchPublicIssues(anchor, params); if (response) { - const states: IIssueState[] = [...response?.states]; - const labels: IIssueLabel[] = [...response?.labels]; - const issues: IIssue[] = [...response?.issues]; runInAction(() => { - this.states = states; - this.labels = labels; - this.issues = issues; + this.states = response.states; + this.labels = response.labels; + this.issues = response.issues; this.loader = false; }); } @@ -91,11 +91,21 @@ export class IssueStore implements IIssueStore { } }; - // computed - getCountOfIssuesByState(state_id: string): number { - return this.issues?.filter((issue) => issue.state == state_id).length || 0; - } + /** + * @description get total count of issues under a particular state + * @param {string} stateID + * @returns {number} + */ + getCountOfIssuesByState = computedFn( + (stateID: string) => this.issues?.filter((issue) => issue.state == stateID).length || 0 + ); - getFilteredIssuesByState = (state_id: string): IIssue[] | [] => - this.issues?.filter((issue) => issue.state == state_id) || []; + /** + * @description get array of issues under a particular state + * @param {string} stateID + * @returns {IIssue[]} + */ + getFilteredIssuesByState = computedFn( + (stateID: string) => this.issues?.filter((issue) => issue.state == stateID) || [] + ); } diff --git a/space/store/project.store.ts b/space/store/project.store.ts deleted file mode 100644 index 02f2503235f..00000000000 --- a/space/store/project.store.ts +++ /dev/null @@ -1,96 +0,0 @@ -// mobx -import { observable, action, makeObservable, runInAction, computed } from "mobx"; -// service -import ProjectService from "@/services/project.service"; -// store types -import { RootStore } from "@/store/root.store"; -// types -import { TWorkspaceDetails, TProjectDetails, TProjectSettings } from "@/types/project"; - -export interface IProjectStore { - // observables - loader: boolean; - error: any | undefined; - settings: TProjectSettings | undefined; - workspace: TWorkspaceDetails | undefined; - project: TProjectDetails | undefined; - canReact: boolean; - canComment: boolean; - canVote: boolean; - // actions - fetchProjectSettings: (workspace_slug: string, project_slug: string) => Promise; - hydrate: (projectSettings: any) => void; -} - -export class ProjectStore implements IProjectStore { - // observables - loader: boolean = false; - error: any | undefined = undefined; - settings: TProjectSettings | undefined = undefined; - workspace: TWorkspaceDetails | undefined = undefined; - project: TProjectDetails | undefined = undefined; - // service - projectService; - - constructor(private store: RootStore) { - makeObservable(this, { - // loaders and error observables - loader: observable, - error: observable.ref, - // observable - workspace: observable, - project: observable, - settings: observable, - // computed - canReact: computed, - canComment: computed, - canVote: computed, - // actions - fetchProjectSettings: action, - hydrate: action, - }); - // services - this.projectService = new ProjectService(); - } - - // computed - get canReact() { - return this.settings?.reactions ?? false; - } - get canComment() { - return this.settings?.comments ?? false; - } - get canVote() { - return this.settings?.votes ?? false; - } - - fetchProjectSettings = async (workspace_slug: string, project_slug: string) => { - try { - this.loader = true; - this.error = null; - - const response = await this.projectService.getProjectSettings(workspace_slug, project_slug); - - if (response) { - this.store.issueFilter.updateLayoutOptions(response?.views); - runInAction(() => { - this.project = response?.project_details; - this.workspace = response?.workspace_detail; - this.settings = response; - this.loader = false; - }); - } - return response; - } catch (error) { - this.loader = false; - this.error = error; - return error; - } - }; - - hydrate = (projectSettings: TProjectSettings) => { - const { workspace_detail, project_details } = projectSettings; - this.workspace = workspace_detail; - this.project = project_details; - }; -} diff --git a/space/store/publish/publish.store.ts b/space/store/publish/publish.store.ts new file mode 100644 index 00000000000..29cbc53ab5e --- /dev/null +++ b/space/store/publish/publish.store.ts @@ -0,0 +1,111 @@ +import { observable, makeObservable, computed } from "mobx"; +// types +import { IWorkspaceLite, TProjectDetails, TPublishEntityType, TPublishSettings, TPublishViewProps } from "@plane/types"; +// store types +import { RootStore } from "@/store/root.store"; + +export interface IPublishStore extends TPublishSettings { + // computed + workspaceSlug: string | undefined; + canComment: boolean; + canReact: boolean; + canVote: boolean; +} + +export class PublishStore implements IPublishStore { + // observables + anchor: string | undefined; + is_comments_enabled: boolean; + created_at: string | undefined; + created_by: string | undefined; + entity_identifier: string | undefined; + entity_name: TPublishEntityType | undefined; + id: string | undefined; + inbox: unknown; + project: string | undefined; + project_details: TProjectDetails | undefined; + is_reactions_enabled: boolean; + updated_at: string | undefined; + updated_by: string | undefined; + view_props: TPublishViewProps | undefined; + is_votes_enabled: boolean; + workspace: string | undefined; + workspace_detail: IWorkspaceLite | undefined; + + constructor( + private store: RootStore, + publishSettings: TPublishSettings + ) { + this.anchor = publishSettings.anchor; + this.is_comments_enabled = publishSettings.is_comments_enabled; + this.created_at = publishSettings.created_at; + this.created_by = publishSettings.created_by; + this.entity_identifier = publishSettings.entity_identifier; + this.entity_name = publishSettings.entity_name; + this.id = publishSettings.id; + this.inbox = publishSettings.inbox; + this.project = publishSettings.project; + this.project_details = publishSettings.project_details; + this.is_reactions_enabled = publishSettings.is_reactions_enabled; + this.updated_at = publishSettings.updated_at; + this.updated_by = publishSettings.updated_by; + this.view_props = publishSettings.view_props; + this.is_votes_enabled = publishSettings.is_votes_enabled; + this.workspace = publishSettings.workspace; + this.workspace_detail = publishSettings.workspace_detail; + + makeObservable(this, { + // observables + anchor: observable.ref, + is_comments_enabled: observable.ref, + created_at: observable.ref, + created_by: observable.ref, + entity_identifier: observable.ref, + entity_name: observable.ref, + id: observable.ref, + inbox: observable, + project: observable.ref, + project_details: observable, + is_reactions_enabled: observable.ref, + updated_at: observable.ref, + updated_by: observable.ref, + view_props: observable, + is_votes_enabled: observable.ref, + workspace: observable.ref, + workspace_detail: observable, + // computed + workspaceSlug: computed, + canComment: computed, + canReact: computed, + canVote: computed, + }); + } + + /** + * @description returns the workspace slug from the workspace details + */ + get workspaceSlug() { + return this?.workspace_detail?.slug ?? undefined; + } + + /** + * @description returns whether commenting is enabled or not + */ + get canComment() { + return !!this.is_comments_enabled; + } + + /** + * @description returns whether reacting is enabled or not + */ + get canReact() { + return !!this.is_reactions_enabled; + } + + /** + * @description returns whether voting is enabled or not + */ + get canVote() { + return !!this.is_votes_enabled; + } +} diff --git a/space/store/publish/publish_list.store.ts b/space/store/publish/publish_list.store.ts new file mode 100644 index 00000000000..b6722115d14 --- /dev/null +++ b/space/store/publish/publish_list.store.ts @@ -0,0 +1,55 @@ +import set from "lodash/set"; +import { makeObservable, observable, runInAction, action } from "mobx"; +// types +import { TPublishSettings } from "@plane/types"; +// services +import PublishService from "@/services/publish.service"; +// store +import { PublishStore } from "@/store/publish/publish.store"; +// store +import { RootStore } from "../root.store"; + +export interface IPublishListStore { + // observables + publishMap: Record; // anchor => PublishStore + // actions + fetchPublishSettings: (pageId: string) => Promise; +} + +export class PublishListStore implements IPublishListStore { + // observables + publishMap: Record = {}; // anchor => PublishStore + // service + publishService; + + constructor(private store: RootStore) { + makeObservable(this, { + // observables + publishMap: observable, + // actions + fetchPublishSettings: action, + }); + // services + this.publishService = new PublishService(); + } + + /** + * @description fetch publish settings + * @param {string} anchor + */ + fetchPublishSettings = async (anchor: string) => { + try { + const response = await this.publishService.fetchPublishSettings(anchor); + runInAction(() => { + if (response.anchor && response.view_props) { + this.store.issueFilter.updateLayoutOptions(response?.view_props); + set(this.publishMap, [response.anchor], new PublishStore(this.store, response)); + } + }); + + return response; + } catch (error) { + throw error; + } + }; +} diff --git a/space/store/root.store.ts b/space/store/root.store.ts index 4a31840db30..082220f5d65 100644 --- a/space/store/root.store.ts +++ b/space/store/root.store.ts @@ -3,30 +3,30 @@ import { enableStaticRendering } from "mobx-react-lite"; import { IInstanceStore, InstanceStore } from "@/store/instance.store"; import { IssueDetailStore, IIssueDetailStore } from "@/store/issue-detail.store"; import { IssueStore, IIssueStore } from "@/store/issue.store"; -import { IProjectStore, ProjectStore } from "@/store/project.store"; import { IUserStore, UserStore } from "@/store/user.store"; import { IssueFilterStore, IIssueFilterStore } from "./issue-filters.store"; import { IMentionsStore, MentionsStore } from "./mentions.store"; +import { IPublishListStore, PublishListStore } from "./publish/publish_list.store"; enableStaticRendering(typeof window === "undefined"); export class RootStore { instance: IInstanceStore; user: IUserStore; - project: IProjectStore; issue: IIssueStore; issueDetail: IIssueDetailStore; mentionStore: IMentionsStore; issueFilter: IIssueFilterStore; + publishList: IPublishListStore; constructor() { this.instance = new InstanceStore(this); this.user = new UserStore(this); - this.project = new ProjectStore(this); this.issue = new IssueStore(this); this.issueDetail = new IssueDetailStore(this); this.mentionStore = new MentionsStore(this); this.issueFilter = new IssueFilterStore(this); + this.publishList = new PublishListStore(this); } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -40,10 +40,10 @@ export class RootStore { localStorage.setItem("theme", "system"); this.instance = new InstanceStore(this); this.user = new UserStore(this); - this.project = new ProjectStore(this); this.issue = new IssueStore(this); this.issueDetail = new IssueDetailStore(this); this.mentionStore = new MentionsStore(this); this.issueFilter = new IssueFilterStore(this); + this.publishList = new PublishListStore(this); }; } diff --git a/space/styles/globals.css b/space/styles/globals.css index 47804b76863..0b41d84811b 100644 --- a/space/styles/globals.css +++ b/space/styles/globals.css @@ -302,6 +302,23 @@ } } +* { + margin: 0; + padding: 0; + box-sizing: border-box; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + font-variant-ligatures: none; + -webkit-font-variant-ligatures: none; + text-rendering: optimizeLegibility; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; +} + +body { + color: rgba(var(--color-text-100)); +} + ::-webkit-scrollbar { width: 5px; height: 5px; diff --git a/space/types/app.d.ts b/space/types/app.d.ts deleted file mode 100644 index bd4af3b0c71..00000000000 --- a/space/types/app.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -export interface IAppConfig { - email_password_login: boolean; - file_size_limit: number; - google_client_id: string | null; - github_app_name: string | null; - github_client_id: string | null; - magic_login: boolean; - slack_client_id: string | null; - posthog_api_key: string | null; - posthog_host: string | null; - has_openai_configured: boolean; - has_unsplash_configured: boolean; - is_self_managed: boolean; -} diff --git a/space/types/issue.d.ts b/space/types/issue.d.ts index f2625fb764b..5b729d1c09d 100644 --- a/space/types/issue.d.ts +++ b/space/types/issue.d.ts @@ -1,27 +1,17 @@ +import { IStateLite, IWorkspaceLite, TIssuePriorities, TStateGroups } from "@plane/types"; + export type TIssueLayout = "list" | "kanban" | "calendar" | "spreadsheet" | "gantt"; export type TIssueLayoutOptions = { [key in TIssueLayout]: boolean; }; -export type TIssueLayoutViews = { - [key in TIssueLayout]: { title: string; icon: string; className: string }; -}; -export type TIssueFilterPriority = "urgent" | "high" | "medium" | "low" | "none"; export type TIssueFilterPriorityObject = { - key: TIssueFilterPriority; + key: TIssuePriorities; title: string; className: string; icon: string; }; -export type TIssueFilterState = "backlog" | "unstarted" | "started" | "completed" | "cancelled"; -export type TIssueFilterStateObject = { - key: TIssueFilterState; - title: string; - color: string; - className: string; -}; - export type TIssueFilterKeys = "priority" | "state" | "labels"; export type TDisplayFilters = { @@ -29,8 +19,8 @@ export type TDisplayFilters = { }; export type TFilters = { - state: TIssueFilterState[]; - priority: TIssueFilterPriority[]; + state: TStateGroups[]; + priority: TIssuePriorities[]; labels: string[]; }; @@ -43,6 +33,12 @@ export type TIssueQueryFilters = Partial; export type TIssueQueryFiltersParams = Partial>; +export type TIssuesResponse = { + states: IStateLite[]; + labels: IIssueLabel[]; + issues: IIssue[]; +}; + export interface IIssue { id: string; comments: Comment[]; @@ -68,17 +64,11 @@ export interface IIssue { export type IPeekMode = "side" | "modal" | "full"; -export interface IIssueState { - id: string; - name: string; - group: TIssueGroupKey; - color: string; -} - export interface IIssueLabel { id: string; name: string; color: string; + parent: string | null; } export interface IVote { @@ -114,7 +104,7 @@ export interface Comment { updated_at: Date; updated_by: string; workspace: string; - workspace_detail: WorkspaceDetail; + workspace_detail: IWorkspaceLite; } export interface IIssueReaction { @@ -175,52 +165,8 @@ export interface ProjectDetail { description: string; } -export interface WorkspaceDetail { - name: string; - slug: string; - id: string; -} - -export interface IssueDetailType { - [issueId: string]: { - issue: IIssue; - comments: Comment[]; - reactions: any[]; - votes: any[]; - }; -} - -export type TIssueGroupByOptions = "state" | "priority" | "labels" | null; - -export type TIssueParams = "priority" | "state" | "labels"; - export interface IIssueFilterOptions { state?: string[] | null; labels?: string[] | null; priority?: string[] | null; } - -// issues -export interface IGroupedIssues { - [group_id: string]: string[]; -} - -export interface ISubGroupedIssues { - [sub_grouped_id: string]: { - [group_id: string]: string[]; - }; -} - -export type TUnGroupedIssues = string[]; - -export interface IIssueResponse { - [issue_id: string]: IIssue; -} - -export type TLoader = "init-loader" | "mutation" | undefined; - -export interface ViewFlags { - enableQuickAdd: boolean; - enableIssueCreation: boolean; - enableInlineEditing: boolean; -} diff --git a/space/types/project.d.ts b/space/types/project.d.ts deleted file mode 100644 index 90c89ed80c4..00000000000 --- a/space/types/project.d.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { TLogoProps } from "@plane/types"; - -export type TWorkspaceDetails = { - name: string; - slug: string; - id: string; -}; - -export type TViewDetails = { - list: boolean; - gantt: boolean; - kanban: boolean; - calendar: boolean; - spreadsheet: boolean; -}; - -export type TProjectDetails = { - id: string; - identifier: string; - name: string; - cover_image: string | undefined; - logo_props: TLogoProps; - description: string; -}; - -export type TProjectSettings = { - id: string; - anchor: string; - comments: boolean; - reactions: boolean; - votes: boolean; - inbox: unknown; - workspace: string; - workspace_detail: TWorkspaceDetails; - project: string; - project_details: TProjectDetails; - views: TViewDetails; - created_by: string; - updated_by: string; - created_at: string; - updated_at: string; -}; diff --git a/space/types/theme.d.ts b/space/types/theme.d.ts deleted file mode 100644 index ca306be51f9..00000000000 --- a/space/types/theme.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface IThemeStore { - theme: string; - setTheme: (theme: "light" | "dark" | string) => void; -} diff --git a/space/types/user.d.ts b/space/types/user.d.ts deleted file mode 100644 index d5882787634..00000000000 --- a/space/types/user.d.ts +++ /dev/null @@ -1,30 +0,0 @@ -export interface IUser { - avatar: string; - cover_image: string | null; - created_at: Date; - created_location: string; - date_joined: Date; - email: string; - display_name: string; - first_name: string; - id: string; - is_email_verified: boolean; - is_onboarded: boolean; - is_tour_completed: boolean; - last_location: string; - last_login: Date; - last_name: string; - mobile_number: string; - role: string; - is_password_autoset: boolean; - onboarding_step: { - workspace_join?: boolean; - profile_complete?: boolean; - workspace_create?: boolean; - workspace_invite?: boolean; - }; - token: string; - updated_at: Date; - username: string; - user_timezone: string; -} diff --git a/web/components/analytics/custom-analytics/select/y-axis.tsx b/web/components/analytics/custom-analytics/select/y-axis.tsx index a33feb96793..1baf010d2a3 100644 --- a/web/components/analytics/custom-analytics/select/y-axis.tsx +++ b/web/components/analytics/custom-analytics/select/y-axis.tsx @@ -1,26 +1,54 @@ -// ui +import { observer } from "mobx-react"; import { TYAxisValues } from "@plane/types"; import { CustomSelect } from "@plane/ui"; -// types -import { ANALYTICS_Y_AXIS_VALUES } from "@/constants/analytics"; // constants +import { ANALYTICS_Y_AXIS_VALUES } from "@/constants/analytics"; +import { EEstimateSystem } from "@/constants/estimates"; +// hooks +import { useAppRouter, useProjectEstimates } from "@/hooks/store"; type Props = { value: TYAxisValues; onChange: () => void; }; -export const SelectYAxis: React.FC = ({ value, onChange }) => ( - {ANALYTICS_Y_AXIS_VALUES.find((v) => v.value === value)?.label ?? "None"}} - onChange={onChange} - maxHeight="lg" - > - {ANALYTICS_Y_AXIS_VALUES.map((item) => ( - - {item.label} - - ))} - -); +export const SelectYAxis: React.FC = observer(({ value, onChange }) => { + // hooks + const { projectId } = useAppRouter(); + const { areEstimateEnabledByProjectId, currentActiveEstimateId, estimateById } = useProjectEstimates(); + + const isEstimateEnabled = (analyticsOption: string) => { + if (analyticsOption === "estimate") { + if ( + projectId && + currentActiveEstimateId && + areEstimateEnabledByProjectId(projectId) && + estimateById(currentActiveEstimateId)?.type === EEstimateSystem.POINTS + ) { + return true; + } else { + return false; + } + } + + return true; + }; + + return ( + {ANALYTICS_Y_AXIS_VALUES.find((v) => v.value === value)?.label ?? "None"}} + onChange={onChange} + maxHeight="lg" + > + {ANALYTICS_Y_AXIS_VALUES.map( + (item) => + isEstimateEnabled(item.value) && ( + + {item.label} + + ) + )} + + ); +}); diff --git a/web/components/core/activity.tsx b/web/components/core/activity.tsx index 28d84ffe47b..5def2d7a99a 100644 --- a/web/components/core/activity.tsx +++ b/web/components/core/activity.tsx @@ -24,7 +24,7 @@ import { Tooltip, BlockedIcon, BlockerIcon, RelatedIcon, LayersIcon, DiceIcon } // helpers import { renderFormattedDate } from "@/helpers/date-time.helper"; import { capitalizeFirstLetter } from "@/helpers/string.helper"; -import { useEstimate, useLabel } from "@/hooks/store"; +import { useLabel } from "@/hooks/store"; import { usePlatformOS } from "@/hooks/use-platform-os"; // types @@ -97,22 +97,6 @@ const LabelPill = observer(({ labelId, workspaceSlug }: { labelId: string; works ); }); -const EstimatePoint = observer((props: { point: string }) => { - const { point } = props; - const { areEstimatesEnabledForCurrentProject, getEstimatePointValue } = useEstimate(); - const currentPoint = Number(point) + 1; - - const estimateValue = getEstimatePointValue(Number(point), null); - - return ( - - {areEstimatesEnabledForCurrentProject - ? estimateValue - : `${currentPoint} ${currentPoint > 1 ? "points" : "point"}`} - - ); -}); - const inboxActivityMessage = { declined: { showIssue: "declined issue", @@ -267,7 +251,7 @@ const activityDetails: { else return ( <> - set the estimate point to + set the estimate point to {activity.new_value} {showIssue && ( <> {" "} diff --git a/web/components/core/modals/gpt-assistant-popover.tsx b/web/components/core/modals/gpt-assistant-popover.tsx index 099b90254e4..0c9c30b31a0 100644 --- a/web/components/core/modals/gpt-assistant-popover.tsx +++ b/web/components/core/modals/gpt-assistant-popover.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useRef, Fragment } from "react"; +import React, { useEffect, useState, useRef, Fragment, Ref } from "react"; import { Placement } from "@popperjs/core"; import { useRouter } from "next/router"; import { Controller, useForm } from "react-hook-form"; // services @@ -196,7 +196,7 @@ export const GptAssistantPopover: React.FC = (props) => { } style={styles.popper} {...attributes.popper} > diff --git a/web/components/dropdowns/estimate.tsx b/web/components/dropdowns/estimate.tsx index 58243cc2205..99e8106dece 100644 --- a/web/components/dropdowns/estimate.tsx +++ b/web/components/dropdowns/estimate.tsx @@ -1,5 +1,4 @@ import { Fragment, ReactNode, useRef, useState } from "react"; -import sortBy from "lodash/sortBy"; import { observer } from "mobx-react"; import { usePopper } from "react-popper"; import { Check, ChevronDown, Search, Triangle } from "lucide-react"; @@ -7,7 +6,12 @@ import { Combobox } from "@headlessui/react"; // helpers import { cn } from "@/helpers/common.helper"; // hooks -import { useAppRouter, useEstimate } from "@/hooks/store"; +import { + useAppRouter, + useEstimate, + useProjectEstimates, + // useEstimate +} from "@/hooks/store"; import { useDropdown } from "@/hooks/use-dropdown"; // components import { DropdownButton } from "./buttons"; @@ -19,15 +23,15 @@ type Props = TDropdownProps & { button?: ReactNode; dropdownArrow?: boolean; dropdownArrowClassName?: string; - onChange: (val: number | null) => void; + onChange: (val: string | undefined) => void; onClose?: () => void; projectId: string; - value: number | null; + value: string | undefined; }; type DropdownOptions = | { - value: number | null; + value: string | null; query: string; content: JSX.Element; }[] @@ -76,19 +80,29 @@ export const EstimateDropdown: React.FC = observer((props) => { }); // store hooks const { workspaceSlug } = useAppRouter(); - const { fetchProjectEstimates, getProjectActiveEstimateDetails, getEstimatePointValue } = useEstimate(); - const activeEstimate = getProjectActiveEstimateDetails(projectId); - const options: DropdownOptions = sortBy(activeEstimate?.points ?? [], "key")?.map((point) => ({ - value: point.key, - query: `${point?.value}`, - content: ( -
- - {point.value} -
- ), - })); + const { currentActiveEstimateId, getProjectEstimates } = useProjectEstimates(); + const { estimatePointIds, estimatePointById } = useEstimate( + currentActiveEstimateId ? currentActiveEstimateId : undefined + ); + + const options: DropdownOptions = (estimatePointIds ?? []) + ?.map((estimatePoint) => { + const currentEstimatePoint = estimatePointById(estimatePoint); + if (currentEstimatePoint) + return { + value: currentEstimatePoint.id, + query: `${currentEstimatePoint?.value}`, + content: ( +
+ + {currentEstimatePoint.value} +
+ ), + }; + else undefined; + }) + .filter((estimatePointDropdownOption) => estimatePointDropdownOption != undefined) as DropdownOptions; options?.unshift({ value: null, query: "No estimate", @@ -103,10 +117,10 @@ export const EstimateDropdown: React.FC = observer((props) => { const filteredOptions = query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase())); - const selectedEstimate = value !== null ? getEstimatePointValue(value, projectId) : null; + const selectedEstimate = value && estimatePointById ? estimatePointById(value) : undefined; const onOpen = async () => { - if (!activeEstimate && workspaceSlug) await fetchProjectEstimates(workspaceSlug, projectId); + if (!currentActiveEstimateId && workspaceSlug) await getProjectEstimates(workspaceSlug, projectId); }; const { handleClose, handleKeyDown, handleOnClick, searchInputKeyDown } = useDropdown({ @@ -120,7 +134,7 @@ export const EstimateDropdown: React.FC = observer((props) => { setQuery, }); - const dropdownOnChange = (val: number | null) => { + const dropdownOnChange = (val: string | undefined) => { onChange(val); handleClose(); }; @@ -164,13 +178,13 @@ export const EstimateDropdown: React.FC = observer((props) => { className={buttonClassName} isActive={isOpen} tooltipHeading="Estimate" - tooltipContent={selectedEstimate !== null ? selectedEstimate : placeholder} + tooltipContent={selectedEstimate ? selectedEstimate?.value : placeholder} showTooltip={showTooltip} variant={buttonVariant} > {!hideIcon && } {(selectedEstimate || placeholder) && BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && ( - {selectedEstimate !== null ? selectedEstimate : placeholder} + {selectedEstimate ? selectedEstimate?.value : placeholder} )} {dropdownArrow && (