diff --git a/src/frontend/apps/impress/package.json b/src/frontend/apps/impress/package.json index 290c80a8..ca6a12ba 100644 --- a/src/frontend/apps/impress/package.json +++ b/src/frontend/apps/impress/package.json @@ -21,12 +21,14 @@ "@blocknote/react": "0.23.2", "@blocknote/xl-docx-exporter": "0.23.2", "@blocknote/xl-pdf-exporter": "0.23.2", + "@fontsource/material-icons": "^5.1.1", "@gouvfr-lasuite/integration": "1.0.2", "@hocuspocus/provider": "2.15.2", "@openfun/cunningham-react": "2.9.4", "@react-pdf/renderer": "4.1.6", "@sentry/nextjs": "8.54.0", "@tanstack/react-query": "5.66.0", + "clsx": "2.1.1", "cmdk": "1.0.4", "crisp-sdk-web": "1.0.25", "docx": "9.1.1", @@ -38,11 +40,13 @@ "next": "15.1.6", "posthog-js": "1.215.6", "react": "*", + "react-arborist": "3.4.0", "react-aria-components": "1.6.0", "react-dom": "*", "react-i18next": "15.4.0", "react-intersection-observer": "9.15.1", "react-select": "5.10.0", + "sass": "1.83.4", "styled-components": "6.1.15", "use-debounce": "10.0.4", "y-protocols": "1.0.6", @@ -74,6 +78,7 @@ "stylelint-config-standard": "37.0.0", "stylelint-prettier": "5.0.3", "typescript": "*", + "use-resize-observer": "9.1.0", "webpack": "5.97.1", "workbox-webpack-plugin": "7.1.0" } diff --git a/src/frontend/apps/impress/src/components/DropButton.tsx b/src/frontend/apps/impress/src/components/DropButton.tsx index 2dbb26f8..dbedceb3 100644 --- a/src/frontend/apps/impress/src/components/DropButton.tsx +++ b/src/frontend/apps/impress/src/components/DropButton.tsx @@ -64,6 +64,14 @@ export const DropButton = ({ onOpenChange?.(isOpen); }; + const props = { + onClick: (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + onOpenChangeHandler(!isLocalOpen); + }, + }; + return ( <> onOpenChangeHandler(true)} aria-label={label} $css={buttonCss} + {...props} + > {button} diff --git a/src/frontend/apps/impress/src/components/DropdownMenu.tsx b/src/frontend/apps/impress/src/components/DropdownMenu.tsx index 2664e928..3e43d042 100644 --- a/src/frontend/apps/impress/src/components/DropdownMenu.tsx +++ b/src/frontend/apps/impress/src/components/DropdownMenu.tsx @@ -23,6 +23,7 @@ export type DropdownMenuProps = { buttonCss?: BoxProps['$css']; disabled?: boolean; topMessage?: string; + afterOpenChange?: (isOpen: boolean) => void; }; export const DropdownMenu = ({ @@ -34,6 +35,7 @@ export const DropdownMenu = ({ buttonCss, label, topMessage, + afterOpenChange, }: PropsWithChildren) => { const theme = useCunninghamTheme(); const spacings = theme.spacingsTokens(); @@ -43,6 +45,7 @@ export const DropdownMenu = ({ const onOpenChange = (isOpen: boolean) => { setIsOpen(isOpen); + afterOpenChange?.(isOpen); }; if (disabled) { diff --git a/src/frontend/apps/impress/src/components/Icon.tsx b/src/frontend/apps/impress/src/components/Icon.tsx index 224f87b6..199d0098 100644 --- a/src/frontend/apps/impress/src/components/Icon.tsx +++ b/src/frontend/apps/impress/src/components/Icon.tsx @@ -5,10 +5,19 @@ import { useCunninghamTheme } from '@/cunningham'; type IconProps = TextType & { iconName: string; + isFilled?: boolean; }; -export const Icon = ({ iconName, ...textProps }: IconProps) => { +export const Icon = ({ iconName, isFilled, ...textProps }: IconProps) => { return ( - + {iconName} ); diff --git a/src/frontend/apps/impress/src/components/common/loader/Loader.tsx b/src/frontend/apps/impress/src/components/common/loader/Loader.tsx new file mode 100644 index 00000000..6cadbc71 --- /dev/null +++ b/src/frontend/apps/impress/src/components/common/loader/Loader.tsx @@ -0,0 +1,9 @@ +import styles from './loader.module.scss'; + +interface LoaderProps { + size?: 'sm' | 'md' | 'lg' | 'xl'; +} + +export const Loader = ({ size = 'sm' }: LoaderProps) => { + return
; +}; diff --git a/src/frontend/apps/impress/src/components/common/loader/loader.module.scss b/src/frontend/apps/impress/src/components/common/loader/loader.module.scss new file mode 100644 index 00000000..75fc0fca --- /dev/null +++ b/src/frontend/apps/impress/src/components/common/loader/loader.module.scss @@ -0,0 +1,40 @@ +.loader { + border-radius: 50%; + background: + radial-gradient(farthest-side, #cecece 94%, #0000) top/3.8px 3.8px no-repeat, + conic-gradient(#0000 30%, #cecece); + -webkit-mask: radial-gradient( + farthest-side, + #0000 calc(100% - 3.8px), + #000 0 + ); + animation: spinner-c7wet2 1s infinite linear; + &.sm { + width: 16px; + height: 16px; + } + + &.md { + width: 24px; + height: 24px; + } + + &.lg { + width: 32px; + height: 32px; + } + + &.xl { + width: 40px; + height: 40px; + } +} + +.spinner { +} + +@keyframes spinner-c7wet2 { + 100% { + transform: rotate(1turn); + } +} diff --git a/src/frontend/apps/impress/src/components/common/tree/TreeView.tsx b/src/frontend/apps/impress/src/components/common/tree/TreeView.tsx new file mode 100644 index 00000000..89300386 --- /dev/null +++ b/src/frontend/apps/impress/src/components/common/tree/TreeView.tsx @@ -0,0 +1,342 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +import { clsx } from 'clsx'; +import { ReactNode, useCallback, useEffect, useRef, useState } from 'react'; +import { + CursorProps, + MoveHandler, + NodeApi, + NodeRendererProps, + Tree, +} from 'react-arborist'; + +import { + BaseType, + TreeViewDataType, + TreeViewMoveModeEnum, + TreeViewMoveResult, +} from '@/features/docs/doc-tree/types/tree'; + +import { Box } from '../../Box'; +import { Icon } from '../../Icon'; +import { Loader } from '../loader/Loader'; + +import styles from './treeview.module.scss'; + +export type TreeViewProps = { + treeData: TreeViewDataType[]; + width?: number | string; + selectedNodeId?: string; + rootNodeId: string; + renderNode: ( + props: NodeRendererProps>, + ) => React.ReactNode; + afterMove?: ( + result: TreeViewMoveResult, + newTreeData: TreeViewDataType[], + ) => void; +}; + +export const TreeView = ({ + treeData, + width, + rootNodeId, + renderNode, + afterMove, + selectedNodeId, +}: TreeViewProps) => { + const onMove3 = (args: { + dragIds: string[]; + dragNodes: NodeApi>[]; + parentId: string | null; + parentNode: NodeApi> | null; + index: number; + }): TreeViewMoveResult | null => { + const newData = JSON.parse( + JSON.stringify(treeData), + ) as TreeViewDataType[]; + + const sourceNodeId = args.dragNodes[0].data.id; + const sourceNode = args.dragNodes[0].data; + const oldParentId = sourceNode.parentId ?? rootNodeId; + const newIndex = args.index; + const targetNodeId = args.parentId ?? rootNodeId; + + const children = args.parentId + ? (args.parentNode?.children ?? []) + : newData; + + let result: TreeViewMoveResult | null = null; + + if (newIndex === 0) { + result = { + targetNodeId: targetNodeId ?? rootNodeId, + mode: TreeViewMoveModeEnum.FIRST_CHILD, + sourceNodeId, + oldParentId, + }; + } + if (newIndex === children.length) { + result = { + targetNodeId: targetNodeId ?? rootNodeId, + mode: TreeViewMoveModeEnum.LAST_CHILD, + sourceNodeId, + oldParentId, + }; + } + + const siblingIndex = newIndex - 1; + const sibling = children[siblingIndex]; + + if (sibling) { + result = { + targetNodeId: sibling.id, + mode: TreeViewMoveModeEnum.RIGHT, + sourceNodeId, + oldParentId, + }; + } + + const nextSiblingIndex = newIndex + 1; + const nextSibling = children[nextSiblingIndex]; + if (nextSibling) { + result = { + targetNodeId: nextSibling.id, + mode: TreeViewMoveModeEnum.LEFT, + sourceNodeId, + oldParentId, + }; + } + + return result; + }; + + const onMove = (args: { + dragIds: string[]; + dragNodes: NodeApi>[]; + parentId: string | null; + parentNode: NodeApi> | null; + index: number; + }) => { + // Création d'une copie profonde pour éviter les mutations directes + const newData = JSON.parse( + JSON.stringify(treeData), + ) as TreeViewDataType[]; + const draggedId = args.dragIds[0]; + + // Fonction helper pour trouver et supprimer un nœud dans l'arbre + const findAndRemoveNode = ( + items: TreeViewDataType[], + parentId?: string, + ): { + currentIndex: number; + newIndex: number; + parentId?: string; + draggedNode: TreeViewDataType; + } | null => { + items.forEach((item, index) => { + if (item.id === draggedId) { + return { + currentIndex: index, + }; + } + }); + + for (let i = 0; i < items.length; i++) { + if (items[i].id === draggedId) { + const currentIndex = i; + let newIndex = args.index; + if (currentIndex < newIndex) { + newIndex -= 1; + } + return { + currentIndex: i, + parentId, + newIndex, + draggedNode: items.splice(i, 1)[0], + }; + } + if (items[i].children?.length) { + const found = findAndRemoveNode( + items[i]?.children ?? [], + items[i].id, + ); + if (found) { + return found; + } + } + } + return null; + }; + + // Trouver et supprimer le nœud déplacé + const r = findAndRemoveNode(newData); + const draggedNode = r?.draggedNode; + const currentIndex = r?.currentIndex ?? -1; + const newIndex = r?.newIndex ?? -1; + if (!draggedNode || currentIndex < 0 || newIndex < 0) { + return; + } + + // Cas 1: Déplacement à la racine + if (!args.parentNode) { + draggedNode.parentId = rootNodeId; + newData.splice(newIndex, 0, draggedNode); + } + // Cas 2: Déplacement dans un dossier + else { + const targetParent = args.parentNode.data; + draggedNode.parentId = targetParent.id; + const findParentAndInsert = (items: TreeViewDataType[]) => { + for (const item of items) { + if (item.id === targetParent.id) { + item.children = item.children || []; + item.children.splice( + r.parentId === targetParent.id ? r.newIndex : args.index, + 0, + draggedNode, + ); + + return true; + } + if (item.children?.length) { + if (findParentAndInsert(item.children)) { + return true; + } + } + } + return false; + }; + + findParentAndInsert(newData); + } + + const moveResult = onMove3(args); + if (moveResult) { + afterMove?.(moveResult, newData); + } + }; + + return ( + >} + > + {(props) => renderNode(props)} + + ); +}; + +function Cursor({ top, left }: CursorProps) { + return ( +
+ ); +} + +export type TreeViewNodeProps = NodeRendererProps> & { + children: ReactNode; + onClick?: () => void; + loadChildren?: (node?: TreeViewDataType) => Promise[]>; +}; + +export const TreeViewNode = ({ + children, + onClick, + node, + dragHandle, + style, + loadChildren, +}: TreeViewNodeProps) => { + /* This node instance can do many things. See the API reference. */ + const timeoutRef = useRef(null); + const [isLoading, setIsLoading] = useState(false); + const hasChildren = + (node.data.childrenCount !== undefined && node.data.childrenCount > 0) || + (node.data.children?.length ?? 0) > 0; + + const isLeaf = node.isLeaf || !hasChildren; + + const hasLoadedChildren = node.children?.length ?? 0 > 0; + + const handleClick = useCallback(async () => { + if (isLeaf) { + return; + } + + if (hasLoadedChildren) { + node.toggle(); + return; + } + + setIsLoading(true); + await loadChildren?.(node.data); + setIsLoading(false); + node.open(); + }, [hasLoadedChildren, loadChildren, node, isLeaf]); + + useEffect(() => { + if (node.willReceiveDrop && !node.isOpen) { + timeoutRef.current = setTimeout(() => { + void handleClick(); + }, 200); + } + + if (timeoutRef.current && !node.willReceiveDrop) { + clearTimeout(timeoutRef.current); + } + }, [node, handleClick]); + return ( +
+ {isLeaf ? ( + + ) : ( + <> + {isLoading ? ( + + + + ) : ( + void handleClick()} + $variation="500" + $size="16px" + iconName={ + node.isOpen ? 'keyboard_arrow_down' : 'keyboard_arrow_right' + } + /> + )} + + )} + {children} +
+ ); +}; diff --git a/src/frontend/apps/impress/src/components/common/tree/treeStore.ts b/src/frontend/apps/impress/src/components/common/tree/treeStore.ts new file mode 100644 index 00000000..7ee5942c --- /dev/null +++ b/src/frontend/apps/impress/src/components/common/tree/treeStore.ts @@ -0,0 +1,157 @@ +import { create } from 'zustand'; + +import { Doc, getDoc } from '@/features/docs'; +import { TreeViewDataType } from '@/features/docs/doc-tree/types/tree'; + +interface TreeStore { + treeData: TreeViewDataType[]; + selectedNode: TreeViewDataType | null; + setSelectedNode: (node: TreeViewDataType | null) => void; + setTreeData: (data: TreeViewDataType[]) => void; + updateNode: (nodeId: string, newData: Partial>) => void; + addRootNode: (node: TreeViewDataType) => void; + removeNode: (nodeId: string) => void; + addChildNode: (parentId: string, newNode: TreeViewDataType) => void; + refreshNode: (nodeId: string) => void; + findNode: (nodeId: string) => TreeViewDataType | null; +} + +export const createTreeStore = ( + refreshCallback?: (id: string) => Promise>>, +) => + create>((set, get) => ({ + treeData: [], + selectedNode: null, + setSelectedNode: (node) => { + set({ selectedNode: node }); + }, + setTreeData: (data) => { + set({ treeData: data }); + }, + refreshNode: (nodeId) => { + set((state) => { + const node = state.findNode(nodeId); + if (!node) { + return state; + } + refreshCallback?.(nodeId) + .then((data) => { + state.updateNode(nodeId, { ...node, ...data }); + }) + .catch((error) => { + console.error(error); + }); + return state; + }); + }, + updateNode: (nodeId, newData) => { + set((state) => { + const updateNodeInTree = ( + nodes: TreeViewDataType[], + ): TreeViewDataType[] => { + return nodes.map((node) => { + if (node.id === nodeId) { + return { ...node, ...newData }; + } + if (node.children) { + return { + ...node, + children: updateNodeInTree(node.children), + }; + } + return node; + }); + }; + + return { + treeData: updateNodeInTree(state.treeData), + }; + }); + }, + + addRootNode: (node) => { + set((state) => ({ + treeData: [...state.treeData, node], + })); + }, + + removeNode: (nodeId) => { + set((state) => { + const removeNodeFromTree = ( + nodes: TreeViewDataType[], + ): TreeViewDataType[] => { + const filteredNodes = nodes.filter((node) => node.id !== nodeId); + + return filteredNodes.map((node) => { + if (node.children) { + return { + ...node, + children: removeNodeFromTree(node.children), + }; + } + return node; + }); + }; + + return { + treeData: removeNodeFromTree(state.treeData), + }; + }); + }, + + addChildNode: (parentId, newNode) => { + set((state) => { + const addChildToNode = ( + nodes: TreeViewDataType[], + ): TreeViewDataType[] => { + return nodes.map((node) => { + if (node.id === parentId) { + return { + ...node, + children: [...(node.children || []), newNode], + }; + } + if (node.children) { + return { + ...node, + children: addChildToNode(node.children), + }; + } + return node; + }); + }; + + return { + treeData: addChildToNode(state.treeData), + }; + }); + }, + + findNode: (nodeId) => { + const findNodeInTree = ( + nodes: TreeViewDataType[], + ): TreeViewDataType | null => { + for (const node of nodes) { + if (node.id === nodeId) { + return node; + } + if (node.children) { + const found = findNodeInTree(node.children); + if (found) { + return found; + } + } + } + return null; + }; + + return findNodeInTree(get().treeData); + }, + })); + +// Créer une instance du store +// export const useTreeStore = createTreeStore(); +export const useTreeStore = createTreeStore(async (docId) => { + const doc = await getDoc({ id: docId }); + return { ...doc, childrenCount: doc.numchild }; +}); diff --git a/src/frontend/apps/impress/src/components/common/tree/treeview.module.scss b/src/frontend/apps/impress/src/components/common/tree/treeview.module.scss new file mode 100644 index 00000000..03a0f766 --- /dev/null +++ b/src/frontend/apps/impress/src/components/common/tree/treeview.module.scss @@ -0,0 +1,46 @@ +.container { + [role='treeitem'] { + display: flex; + align-items: center; + } +} + +.node { + width: 100%; + // padding-left: 0px !important; + // margin-left: 180px; + // min-width: 300px; + height: calc(100% - 2px); + overflow: hidden; + display: flex; + align-items: center; + flex-wrap: nowrap; + border-radius: 4px; + border: 1.5px solid rgba(0, 0, 0, 0); + cursor: pointer; + + padding: var(--c--theme--spacings--4xs) 0; + gap: var(--c--theme--spacings--3xs); + + &:not(.willReceiveDrop, .selected):hover { + background-color: var(--c--theme--colors--greyscale-100); + } + + &.selected { + background-color: var(--c--theme--colors--greyscale-100); + + font-weight: 700; + } + + &.willReceiveDrop { + background-color: var(--c--theme--colors--primary-100); + border: 1.5px solid var(--c--theme--colors--primary-500); + } +} + +.cursor { + position: absolute; + width: 100%; + height: 0; + border-top: 2px solid var(--c--theme--colors--primary-500); +} diff --git a/src/frontend/apps/impress/src/core/AppProvider.tsx b/src/frontend/apps/impress/src/core/AppProvider.tsx index 52c227e7..bf155405 100644 --- a/src/frontend/apps/impress/src/core/AppProvider.tsx +++ b/src/frontend/apps/impress/src/core/AppProvider.tsx @@ -41,7 +41,10 @@ export function AppProvider({ children }: { children: React.ReactNode }) { - {children} + + {children} +
+ diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocTitle.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocTitle.tsx index fb9dea73..e1dd3c0d 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocTitle.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocTitle.tsx @@ -10,6 +10,7 @@ import { useTranslation } from 'react-i18next'; import { css } from 'styled-components'; import { Box, Text } from '@/components'; +import { useTreeStore } from '@/components/common/tree/treeStore'; import { useCunninghamTheme } from '@/cunningham'; import { Doc, @@ -52,6 +53,8 @@ export const DocTitleText = ({ title }: DocTitleTextProps) => { const DocTitleInput = ({ doc }: DocTitleProps) => { const { isDesktop } = useResponsiveStore(); + const { updateNode, setSelectedNode } = useTreeStore(); + const { t } = useTranslation(); const { colorsTokens } = useCunninghamTheme(); const [titleDisplay, setTitleDisplay] = useState(doc.title); @@ -64,7 +67,7 @@ const DocTitleInput = ({ doc }: DocTitleProps) => { listInvalideQueries: [KEY_DOC, KEY_LIST_DOC], onSuccess(data) { toast(t('Document title updated successfully'), VariantType.SUCCESS); - + updateNode(doc.id, { title: data.title }); // Broadcast to every user connected to the document broadcast(`${KEY_DOC}-${data.id}`); }, diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/components/ModalRenameDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/components/ModalRenameDoc.tsx new file mode 100644 index 00000000..63783aa6 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-management/components/ModalRenameDoc.tsx @@ -0,0 +1,65 @@ +import { Button, Modal, ModalSize } from '@openfun/cunningham-react'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { Box, Text } from '@/components'; + +import { Doc } from '../types'; + +interface ModalRenameDocProps { + onClose: () => void; + doc: Doc; +} + +export const ModalRenameDoc = ({ onClose, doc }: ModalRenameDocProps) => { + const { t } = useTranslation(); + const [title, setTitle] = useState(doc.title); + return ( + + {t('Rename')} + + } + isOpen + onClose={onClose} + size={ModalSize.SMALL} + rightActions={ + <> + + + + } + > + + { + e.stopPropagation(); + e.preventDefault(); + }} + defaultValue={title} + // label={t('Nom du document')} + onChange={(e) => { + e.stopPropagation(); + e.preventDefault(); + setTitle(e.target.value); + }} + /> + + + ); +}; diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/types.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/types.tsx index 8462df0e..f99347ec 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/types.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/types.tsx @@ -45,6 +45,7 @@ export interface Doc { nb_accesses: number; created_at: string; updated_at: string; + numchild: number; abilities: { accesses_manage: boolean; accesses_view: boolean; diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx index 9f26d059..87250b71 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx @@ -57,6 +57,7 @@ export const DocShareModal = ({ doc, onClose }: Props) => { const canShare = doc.abilities.accesses_manage; const canViewAccesses = doc.abilities.accesses_view; const showMemberSection = inputValue === '' && selectedUsers.length === 0; + const showFooter = selectedUsers.length === 0 && !inputValue; const onSelect = (user: User) => { diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/api/useCreateChildren.tsx b/src/frontend/apps/impress/src/features/docs/doc-tree/api/useCreateChildren.tsx new file mode 100644 index 00000000..b9f774a8 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-tree/api/useCreateChildren.tsx @@ -0,0 +1,44 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { APIError, errorCauses, fetchAPI } from '@/api'; + +import { Doc, KEY_LIST_DOC } from '../../doc-management'; + +export type CreateDocParam = Pick & { + parentId: string; +}; + +export const createDocChildren = async ({ + title, + parentId, +}: CreateDocParam): Promise => { + const response = await fetchAPI(`documents/${parentId}/children/`, { + method: 'POST', + body: JSON.stringify({ + title, + }), + }); + + if (!response.ok) { + throw new APIError('Failed to create the doc', await errorCauses(response)); + } + + return response.json() as Promise; +}; + +interface CreateDocProps { + onSuccess: (data: Doc) => void; +} + +export function useCreateChildrenDoc({ onSuccess }: CreateDocProps) { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: createDocChildren, + onSuccess: (data) => { + void queryClient.resetQueries({ + queryKey: [KEY_LIST_DOC], + }); + onSuccess(data); + }, + }); +} diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/api/useDocChildren.tsx b/src/frontend/apps/impress/src/features/docs/doc-tree/api/useDocChildren.tsx new file mode 100644 index 00000000..839ed535 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-tree/api/useDocChildren.tsx @@ -0,0 +1,58 @@ +import { UseQueryOptions, useQuery } from '@tanstack/react-query'; + +import { APIError, errorCauses, fetchAPI, useAPIInfiniteQuery } from '@/api'; + +import { DocsResponse } from '../../doc-management'; + +export type DocsChildrenParams = { + docId: string; + page: number; + page_size?: number; +}; + +export const getDocChildren = async ( + params: DocsChildrenParams, +): Promise => { + const { docId, page, page_size } = params; + const searchParams = new URLSearchParams(); + + if (page) { + searchParams.set('page', page.toString()); + } + if (page_size) { + searchParams.set('page_size', page_size.toString()); + } + + const response = await fetchAPI( + `documents/${docId}/children/?${searchParams.toString()}`, + ); + + if (!response.ok) { + throw new APIError( + 'Failed to get the doc children', + await errorCauses(response), + ); + } + + return response.json() as Promise; +}; + +export const KEY_LIST_DOC_CHILDREN = 'doc-children'; + +export function useDocChildren( + params: DocsChildrenParams, + queryConfig?: Omit< + UseQueryOptions, + 'queryKey' | 'queryFn' + >, +) { + return useQuery({ + queryKey: [KEY_LIST_DOC_CHILDREN, params], + queryFn: () => getDocChildren(params), + ...queryConfig, + }); +} + +export const useInfiniteDocChildren = (params: DocsChildrenParams) => { + return useAPIInfiniteQuery(KEY_LIST_DOC_CHILDREN, getDocChildren, params); +}; diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/assets/doc-s.svg b/src/frontend/apps/impress/src/features/docs/doc-tree/assets/doc-s.svg new file mode 100644 index 00000000..790684c6 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-tree/assets/doc-s.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/assets/doc-tree-logo.png b/src/frontend/apps/impress/src/features/docs/doc-tree/assets/doc-tree-logo.png new file mode 100644 index 00000000..d816d8f1 Binary files /dev/null and b/src/frontend/apps/impress/src/features/docs/doc-tree/assets/doc-tree-logo.png differ diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTree.tsx b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTree.tsx new file mode 100644 index 00000000..ed781957 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTree.tsx @@ -0,0 +1,127 @@ +import { useEffect } from 'react'; +import { css } from 'styled-components'; + +import { fetchAPI } from '@/api'; +import { Box, SeparatedSection, StyledLink } from '@/components'; +import { TreeView } from '@/components/common/tree/TreeView'; +import { useTreeStore } from '@/components/common/tree/treeStore'; +import { useCunninghamTheme } from '@/cunningham'; + +import { Doc, useDoc } from '../../doc-management'; +import { SimpleDocItem } from '../../docs-grid'; +import { useInfiniteDocChildren } from '../api/useDocChildren'; +import { TreeViewDataType, TreeViewMoveResult } from '../types/tree'; + +import { DocTreeItem } from './DocTreeItem'; + +type Props = { + docId: Doc['id']; +}; + +export type DocTreeDataType = TreeViewDataType; +export const DocTree = ({ docId }: Props) => { + const { spacingsTokens } = useCunninghamTheme(); + const spacing = spacingsTokens(); + const { data: rootNode } = useDoc({ id: docId }); + const { + selectedNode, + setSelectedNode, + refreshNode, + setTreeData: setTreeDataStore, + treeData: treeDataStore, + } = useTreeStore(); + + const { data, isLoading, isFetching, isRefetching } = useInfiniteDocChildren({ + docId, + page_size: 25, + page: 1, + }); + + const afterMove = async ( + result: TreeViewMoveResult, + newTreeData: TreeViewDataType[], + ) => { + const { targetNodeId, mode: position, sourceNodeId, oldParentId } = result; + await fetchAPI(`documents/${sourceNodeId}/move/`, { + method: 'POST', + body: JSON.stringify({ + target_document_id: targetNodeId, + position, + }), + }); + + setTreeDataStore(newTreeData); + if (oldParentId) { + refreshNode(oldParentId); + } + refreshNode(targetNodeId); + }; + + useEffect(() => { + if (!data) { + return; + } + + const newChildren = data.pages.flatMap((page) => page.results); + const newData: DocTreeDataType[] = newChildren.map((child) => ({ + ...child, + childrenCount: child.numchild, + children: [], + parentId: docId, + })); + setTreeDataStore(newData); + }, [data, setTreeDataStore, docId]); + + const isRootNodeSelected = !selectedNode + ? true + : selectedNode?.id === rootNode?.id; + + if (isLoading || isFetching || isRefetching) { + return
Loading...
; + } + + if (!data) { + return
No data
; + } + + return ( + <> + + + + {rootNode && ( + { + setSelectedNode(rootNode); + }} + > + + + )} + + + + + } + afterMove={(result, newTreeData) => { + void afterMove(result, newTreeData); + }} + /> + + + ); +}; diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTreeItem.tsx b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTreeItem.tsx new file mode 100644 index 00000000..48e6c10d --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTreeItem.tsx @@ -0,0 +1,155 @@ +import { useModal } from '@openfun/cunningham-react'; +import { useRouter } from 'next/navigation'; +import { useState } from 'react'; +import { NodeRendererProps } from 'react-arborist'; +import { createPortal } from 'react-dom'; +import { useTranslation } from 'react-i18next'; + +import { Box, BoxButton, DropdownMenu, Icon } from '@/components'; +import { TreeViewNode } from '@/components/common/tree/TreeView'; +import { useTreeStore } from '@/components/common/tree/treeStore'; +import { useCunninghamTheme } from '@/cunningham'; +import { useLeftPanelStore } from '@/features/left-panel'; + +import { ModalRemoveDoc } from '../../doc-management'; +import { ModalRenameDoc } from '../../doc-management/components/ModalRenameDoc'; +import { DocShareModal } from '../../doc-share'; +import { LightDocItem } from '../../docs-grid/components/LightDocItem'; +import { useCreateChildrenDoc } from '../api/useCreateChildren'; +import { useDocChildren } from '../api/useDocChildren'; +import { TreeViewDataType } from '../types/tree'; + +import { DocTreeDataType } from './DocTree'; + +type DocTreeItemProps = NodeRendererProps>; + +export const DocTreeItem = ({ node, ...props }: DocTreeItemProps) => { + const data = node.data; + const deleteModal = useModal(); + const shareModal = useModal(); + const renameModal = useModal(); + const [isOpen, setIsOpen] = useState(false); + const { updateNode, setSelectedNode } = useTreeStore(); + const { spacingsTokens } = useCunninghamTheme(); + const { refetch } = useDocChildren( + { + docId: data.id, + page: 1, + }, + { enabled: false }, + ); + + const { t } = useTranslation(); + const router = useRouter(); + const { togglePanel } = useLeftPanelStore(); + const { mutate: createChildrenDoc } = useCreateChildrenDoc({ + onSuccess: (doc) => { + const actualChildren = node.data.children ?? []; + if (actualChildren.length === 0) { + loadChildren() + .then(() => { + node.open(); + router.push(`/docs/${doc.id}`); + togglePanel(); + }) + .catch(console.error); + } else { + updateNode(node.id, { + ...node.data, + children: [...actualChildren, doc], + }); + node.open(); + router.push(`/docs/${doc.id}`); + togglePanel(); + } + setSelectedNode(doc); + }, + }); + const spacing = spacingsTokens(); + + const loadChildren = async () => { + const data = await refetch(); + console.log(data, node); + const childs = data.data?.results ?? []; + const newChilds: TreeViewDataType[] = childs.map( + (child) => ({ + ...child, + childrenCount: child.numchild, + children: [], + parentId: node.id, + }), + ); + node.data.children = newChilds; + updateNode(node.id, { ...node.data, children: newChilds }); + return newChilds; + }; + + const options = [ + { + label: t('Rename'), + icon: 'edit', + callback: renameModal.open, + }, + { + label: t('Share'), + icon: 'group', + callback: shareModal.open, + }, + { + label: t('Delete'), + icon: 'delete', + callback: deleteModal.open, + }, + ]; + return ( + <> + router.push(`/docs/${node.data.id}`)} + node={node} + {...props} + loadChildren={loadChildren} + > + + + + + { + e.stopPropagation(); + e.preventDefault(); + createChildrenDoc({ + title: t('Untitled page'), + parentId: node.id, + }); + }} + color="primary-text" + > + + + + } + /> + + {deleteModal.isOpen && ( + + )} + {shareModal.isOpen && ( + + )} + {renameModal.isOpen && + createPortal( + , + document.getElementById('modals') as HTMLElement, + )} + + ); +}; diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/stores/useDocRootTree.tsx b/src/frontend/apps/impress/src/features/docs/doc-tree/stores/useDocRootTree.tsx new file mode 100644 index 00000000..b43817c2 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-tree/stores/useDocRootTree.tsx @@ -0,0 +1,15 @@ +import { create } from 'zustand'; + +import { Doc } from '@/features/docs/doc-management'; + +export interface DocRootTreeStore { + rootId?: Doc['id']; + setRootId: (id?: Doc['id']) => void; +} + +export const useDocRootTreeStore = create((set) => ({ + rootId: undefined, + setRootId: (id?: string) => { + set({ rootId: id }); + }, +})); diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/types/tree.tsx b/src/frontend/apps/impress/src/features/docs/doc-tree/types/tree.tsx new file mode 100644 index 00000000..2ea9c5b7 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-tree/types/tree.tsx @@ -0,0 +1,22 @@ +export type BaseType = T & { + id: string; + childrenCount?: number; + parentId?: string; + children?: BaseType[]; +}; + +export type TreeViewDataType = BaseType; + +export enum TreeViewMoveModeEnum { + FIRST_CHILD = 'first-child', + LAST_CHILD = 'last-child', + LEFT = 'left', + RIGHT = 'right', +} + +export type TreeViewMoveResult = { + targetNodeId: string; + mode: TreeViewMoveModeEnum; + sourceNodeId: string; + oldParentId?: string; +}; diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/assets/doc-s.svg b/src/frontend/apps/impress/src/features/docs/docs-grid/assets/doc-s.svg new file mode 100644 index 00000000..790684c6 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/docs-grid/assets/doc-s.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/components/LightDocItem.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/components/LightDocItem.tsx new file mode 100644 index 00000000..8c743a5e --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/docs-grid/components/LightDocItem.tsx @@ -0,0 +1,70 @@ +import { ReactNode } from 'react'; +import { css } from 'styled-components'; + +import { Box, Text } from '@/components'; +import { useCunninghamTheme } from '@/cunningham'; +import { Doc } from '@/features/docs/doc-management'; + +import Logo from './../assets/doc-s.svg'; + +const ItemTextCss = css` + overflow: hidden; + text-overflow: ellipsis; + white-space: initial; + display: -webkit-box; + line-clamp: 1; + width: 100%; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; +`; + +type Props = { + doc: Doc; + showActions?: boolean; + rightContent?: ReactNode; +}; + +export const LightDocItem = ({ + doc, + rightContent, + showActions = false, +}: Props) => { + const { spacingsTokens } = useCunninghamTheme(); + const spacing = spacingsTokens(); + return ( + + + + + + + {doc.title} + + {rightContent && ( + + {rightContent} + + )} + + ); +}; diff --git a/src/frontend/apps/impress/src/features/left-panel/assets/doc-s.svg b/src/frontend/apps/impress/src/features/left-panel/assets/doc-s.svg new file mode 100644 index 00000000..790684c6 --- /dev/null +++ b/src/frontend/apps/impress/src/features/left-panel/assets/doc-s.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/frontend/apps/impress/src/features/left-panel/assets/doc-tree-logo.png b/src/frontend/apps/impress/src/features/left-panel/assets/doc-tree-logo.png new file mode 100644 index 00000000..d816d8f1 Binary files /dev/null and b/src/frontend/apps/impress/src/features/left-panel/assets/doc-tree-logo.png differ diff --git a/src/frontend/apps/impress/src/features/left-panel/components/LeftPanelDocContent.tsx b/src/frontend/apps/impress/src/features/left-panel/components/LeftPanelDocContent.tsx index 7439b466..3b57582e 100644 --- a/src/frontend/apps/impress/src/features/left-panel/components/LeftPanelDocContent.tsx +++ b/src/frontend/apps/impress/src/features/left-panel/components/LeftPanelDocContent.tsx @@ -1,14 +1,12 @@ -import { css } from 'styled-components'; - -import { Box, SeparatedSection } from '@/components'; -import { useCunninghamTheme } from '@/cunningham'; +import { Box } from '@/components'; import { useDocStore } from '@/features/docs/doc-management'; -import { SimpleDocItem } from '@/features/docs/docs-grid'; +import { DocTree } from '@/features/docs/doc-tree/components/DocTree'; +import { useDocRootTreeStore } from '@/features/docs/doc-tree/stores/useDocRootTree'; export const LeftPanelDocContent = () => { + const { rootId } = useDocRootTreeStore(); const { currentDoc } = useDocStore(); - const { spacingsTokens } = useCunninghamTheme(); - const spacing = spacingsTokens(); + if (!currentDoc) { return null; } @@ -19,19 +17,7 @@ export const LeftPanelDocContent = () => { $width="100%" $css="width: 100%; overflow-y: auto; overflow-x: hidden;" > - - - - - - - + {rootId && } ); }; diff --git a/src/frontend/apps/impress/src/features/left-panel/components/LeftPanelHeader.tsx b/src/frontend/apps/impress/src/features/left-panel/components/LeftPanelHeader.tsx index 9a184ab5..23613a43 100644 --- a/src/frontend/apps/impress/src/features/left-panel/components/LeftPanelHeader.tsx +++ b/src/frontend/apps/impress/src/features/left-panel/components/LeftPanelHeader.tsx @@ -4,22 +4,37 @@ import { useRouter } from 'next/navigation'; import { PropsWithChildren } from 'react'; import { Box, Icon, SeparatedSection } from '@/components'; +import { useTreeStore } from '@/components/common/tree/treeStore'; import { useAuth } from '@/features/auth'; -import { useCreateDoc } from '@/features/docs/doc-management'; +import { useCreateDoc, useDocStore } from '@/features/docs/doc-management'; import { DocSearchModal } from '@/features/docs/doc-search'; -import { useCmdK } from '@/hook/useCmdK'; +import { useCreateChildrenDoc } from '@/features/docs/doc-tree/api/useCreateChildren'; import { useLeftPanelStore } from '../stores'; -export const LeftPanelHeader = ({ children }: PropsWithChildren) => { +type Props = PropsWithChildren<{}>; + +export const LeftPanelHeader = ({ children }: Props) => { const router = useRouter(); + const { currentDoc } = useDocStore(); + const treeStore = useTreeStore(); + const searchModal = useModal(); const { authenticated } = useAuth(); - useCmdK(searchModal.open); const { togglePanel } = useLeftPanelStore(); const { mutate: createDoc } = useCreateDoc({ onSuccess: (doc) => { + router.push(`/docs/${doc.id}`); + treeStore.setSelectedNode(doc); + togglePanel(); + }, + }); + + const { mutate: createChildrenDoc } = useCreateChildrenDoc({ + onSuccess: (doc) => { + treeStore.addRootNode(doc); + treeStore.setSelectedNode(doc); router.push(`/docs/${doc.id}`); togglePanel(); }, @@ -31,7 +46,14 @@ export const LeftPanelHeader = ({ children }: PropsWithChildren) => { }; const createNewDoc = () => { - createDoc(); + if (currentDoc) { + createChildrenDoc({ + title: t('Untitled page'), + parentId: currentDoc.id, + }); + } else { + createDoc(); + } }; return ( diff --git a/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx b/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx index 1462c16a..55d5c037 100644 --- a/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx +++ b/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx @@ -14,6 +14,7 @@ import { useDoc, useDocStore, } from '@/features/docs/doc-management/'; +import { useDocRootTreeStore } from '@/features/docs/doc-tree/stores/useDocRootTree'; import { MainLayout } from '@/layouts'; import { useBroadcastStore } from '@/stores'; import { NextPageWithLayout } from '@/types/next'; @@ -60,6 +61,7 @@ const DocPage = ({ id }: DocProps) => { const [doc, setDoc] = useState(); const { setCurrentDoc } = useDocStore(); + const { setRootId, rootId } = useDocRootTreeStore(); const { addTask } = useBroadcastStore(); const queryClient = useQueryClient(); const { replace } = useRouter(); @@ -73,6 +75,13 @@ const DocPage = ({ id }: DocProps) => { } }, [doc?.title]); + useEffect(() => { + return () => { + console.log('exit'); + setRootId(undefined); + }; + }, [setRootId]); + useEffect(() => { if (!docQuery || isFetching) { return; @@ -80,7 +89,10 @@ const DocPage = ({ id }: DocProps) => { setDoc(docQuery); setCurrentDoc(docQuery); - }, [docQuery, setCurrentDoc, isFetching]); + if (!rootId) { + setRootId(docQuery.id); + } + }, [docQuery, setCurrentDoc, setRootId, rootId, isFetching]); /** * We add a broadcast task to reset the query cache diff --git a/src/frontend/apps/impress/src/pages/docs/[id]/test.tsx b/src/frontend/apps/impress/src/pages/docs/[id]/test.tsx new file mode 100644 index 00000000..681b639f --- /dev/null +++ b/src/frontend/apps/impress/src/pages/docs/[id]/test.tsx @@ -0,0 +1,37 @@ +import { TreeViewDataType } from '@/features/docs/doc-tree/types/tree'; +import { + DataType, + LeftPanelDocContent, +} from '@/features/left-panel/components/LeftPanelDocContent'; + +const initialData: TreeViewDataType[] = [ + { id: 'Noeud #1', name: 'Noeud #1', children: [] }, + { id: 'Noeud #2', name: 'Noeud #2', children: [] }, + { + id: 'Noeud #3', + name: 'Noeud #3', + childrenCount: 0, + children: [], + }, + { + id: 'Noeud #4', + name: 'Noeud #4', + children: [ + { id: 'Noeud #4.1', name: 'Noeud #4.1' }, + { id: 'Noeud #4.2', name: 'Noeud #4.2' }, + { id: 'Noeud #4.3', name: 'Noeud #4.3' }, + ], + }, + { id: 'Noeud #5', name: 'Noeud #5', children: [] }, + { id: 'Noeud #6', name: 'Noeud #6', children: [] }, + { id: 'Noeud #7', name: 'Noeud #7', children: [] }, + { + id: 'Noeud #8 fjdsk nfjksdn fjksd nfjdks nkjfsdn fjkds', + name: 'Noeud #8 hfi sfd hjk sd shjf bdsjhs fbdjhfsdbj kj bq', + children: [], + }, +]; + +export default function Test() { + return ; +} diff --git a/src/frontend/apps/impress/src/pages/globals.css b/src/frontend/apps/impress/src/pages/globals.css index 930d8359..a883cb10 100644 --- a/src/frontend/apps/impress/src/pages/globals.css +++ b/src/frontend/apps/impress/src/pages/globals.css @@ -1,4 +1,7 @@ @import url('../cunningham/cunningham-style.css'); +@import url("@fontsource/material-icons"); + + body { margin: 0; @@ -41,3 +44,26 @@ main ::-webkit-scrollbar-thumb:hover, cursor: pointer; outline: inherit; } + +.material-icons-filled { + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; /* Preferred icon size */ + display: inline-block; + line-height: 1; + text-transform: none; + letter-spacing: normal; + word-wrap: normal; + white-space: nowrap; + direction: ltr; + + /* Support for all WebKit browsers. */ + -webkit-font-smoothing: antialiased; + /* Support for Safari and Chrome. */ + text-rendering: optimizeLegibility; + /* Support for Firefox. */ + -moz-osx-font-smoothing: grayscale; + /* Support for IE. */ + font-feature-settings: 'liga'; +} diff --git a/src/frontend/yarn.lock b/src/frontend/yarn.lock index 38330b15..59da0562 100644 --- a/src/frontend/yarn.lock +++ b/src/frontend/yarn.lock @@ -969,6 +969,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.9.2": + version "7.26.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.9.tgz#aa4c6facc65b9cb3f87d75125ffd47781b475433" + integrity sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.25.9", "@babel/template@^7.3.3": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" @@ -1482,6 +1489,11 @@ resolved "https://registry.yarnpkg.com/@fontsource/material-icons-outlined/-/material-icons-outlined-5.0.13.tgz#f8f2a669cb5bdc45fb3ca41f057bc149e3484695" integrity sha512-mQxKJcFiwclTJd0G5fUg0gJ/ZszdaZRSIMFkvbPvMUteA9aSFzFswHr9Yuacw+x4wlBl7GlsVCujAPaBeLv/dw== +"@fontsource/material-icons@^5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@fontsource/material-icons/-/material-icons-5.1.1.tgz#5b9b70766a8161af627b4c1b7c8e9b129747d0a2" + integrity sha512-l1EhBIh9US1RMiiKEJ+/FTFvHZIU3cpn0MmxSZA6Ip2C/Szwca6h2xqNg/OTIOFWADune7b/rhtypeDbjHrZzA== + "@formatjs/ecma402-abstract@2.3.2": version "2.3.2" resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.2.tgz#0ee291effe7ee2c340742a6c95d92eacb5e6c00a" @@ -1988,6 +2000,11 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@juggle/resize-observer@^3.3.1": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60" + integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA== + "@keyv/serialize@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@keyv/serialize/-/serialize-1.0.2.tgz#72507c4be94d8914434a4aa80661f8ac6131967f" @@ -2439,6 +2456,95 @@ dependencies: "@opentelemetry/core" "^1.1.0" +"@parcel/watcher-android-arm64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz#507f836d7e2042f798c7d07ad19c3546f9848ac1" + integrity sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA== + +"@parcel/watcher-darwin-arm64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz#3d26dce38de6590ef79c47ec2c55793c06ad4f67" + integrity sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw== + +"@parcel/watcher-darwin-x64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz#99f3af3869069ccf774e4ddfccf7e64fd2311ef8" + integrity sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg== + +"@parcel/watcher-freebsd-x64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz#14d6857741a9f51dfe51d5b08b7c8afdbc73ad9b" + integrity sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ== + +"@parcel/watcher-linux-arm-glibc@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz#43c3246d6892381db473bb4f663229ad20b609a1" + integrity sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA== + +"@parcel/watcher-linux-arm-musl@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz#663750f7090bb6278d2210de643eb8a3f780d08e" + integrity sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q== + +"@parcel/watcher-linux-arm64-glibc@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz#ba60e1f56977f7e47cd7e31ad65d15fdcbd07e30" + integrity sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w== + +"@parcel/watcher-linux-arm64-musl@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz#f7fbcdff2f04c526f96eac01f97419a6a99855d2" + integrity sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg== + +"@parcel/watcher-linux-x64-glibc@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz#4d2ea0f633eb1917d83d483392ce6181b6a92e4e" + integrity sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A== + +"@parcel/watcher-linux-x64-musl@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz#277b346b05db54f55657301dd77bdf99d63606ee" + integrity sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg== + +"@parcel/watcher-win32-arm64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz#7e9e02a26784d47503de1d10e8eab6cceb524243" + integrity sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw== + +"@parcel/watcher-win32-ia32@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz#2d0f94fa59a873cdc584bf7f6b1dc628ddf976e6" + integrity sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ== + +"@parcel/watcher-win32-x64@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz#ae52693259664ba6f2228fa61d7ee44b64ea0947" + integrity sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA== + +"@parcel/watcher@^2.4.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.5.1.tgz#342507a9cfaaf172479a882309def1e991fb1200" + integrity sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg== + dependencies: + detect-libc "^1.0.3" + is-glob "^4.0.3" + micromatch "^4.0.5" + node-addon-api "^7.0.0" + optionalDependencies: + "@parcel/watcher-android-arm64" "2.5.1" + "@parcel/watcher-darwin-arm64" "2.5.1" + "@parcel/watcher-darwin-x64" "2.5.1" + "@parcel/watcher-freebsd-x64" "2.5.1" + "@parcel/watcher-linux-arm-glibc" "2.5.1" + "@parcel/watcher-linux-arm-musl" "2.5.1" + "@parcel/watcher-linux-arm64-glibc" "2.5.1" + "@parcel/watcher-linux-arm64-musl" "2.5.1" + "@parcel/watcher-linux-x64-glibc" "2.5.1" + "@parcel/watcher-linux-x64-musl" "2.5.1" + "@parcel/watcher-win32-arm64" "2.5.1" + "@parcel/watcher-win32-ia32" "2.5.1" + "@parcel/watcher-win32-x64" "2.5.1" + "@pkgr/core@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" @@ -3355,6 +3461,21 @@ "@react-types/shared" "^3.27.0" "@swc/helpers" "^0.5.0" +"@react-dnd/asap@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@react-dnd/asap/-/asap-4.0.1.tgz#5291850a6b58ce6f2da25352a64f1b0674871aab" + integrity sha512-kLy0PJDDwvwwTXxqTFNAAllPHD73AycE9ypWeln/IguoGBEbvFcPDbCV03G52bEcC5E+YgupBE0VzHGdC8SIXg== + +"@react-dnd/invariant@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@react-dnd/invariant/-/invariant-2.0.0.tgz#09d2e81cd39e0e767d7da62df9325860f24e517e" + integrity sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw== + +"@react-dnd/shallowequal@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz#a3031eb54129f2c66b2753f8404266ec7bf67f0a" + integrity sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg== + "@react-pdf/fns@3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@react-pdf/fns/-/fns-3.0.0.tgz#2e0137d48b14c531b2f6a9214cb36ea2a7aea3ba" @@ -6313,6 +6434,13 @@ chokidar@^3.5.2, chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" +chokidar@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" + integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== + dependencies: + readdirp "^4.0.1" + chromatic@11.7.1: version "11.7.1" resolved "https://registry.yarnpkg.com/chromatic/-/chromatic-11.7.1.tgz#9de59dd9d0e2a847627bccd959f05881335b524e" @@ -6362,7 +6490,7 @@ clone@^2.1.2: resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== -clsx@^2.0.0, clsx@^2.1.1: +clsx@2.1.1, clsx@^2.0.0, clsx@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== @@ -6882,6 +7010,11 @@ destroy@1.2.0: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== + detect-libc@^2.0.2, detect-libc@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" @@ -6939,6 +7072,15 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +dnd-core@14.0.1: + version "14.0.1" + resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-14.0.1.tgz#76d000e41c494983210fb20a48b835f81a203c2e" + integrity sha512-+PVS2VPTgKFPYWo3vAFEA8WPbTf7/xo43TifH9G8S1KqnrQu0o77A3unrF5yOugy4mIz7K5wAVFHUcha7wsz6A== + dependencies: + "@react-dnd/asap" "^4.0.0" + "@react-dnd/invariant" "^2.0.0" + redux "^4.1.1" + doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -8815,6 +8957,11 @@ immediate@~3.0.5: resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== +immutable@^5.0.2: + version "5.0.3" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.0.3.tgz#aa037e2313ea7b5d400cd9298fa14e404c933db1" + integrity sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw== + import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -10284,6 +10431,11 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== +"memoize-one@>=3.1.1 <6": + version "5.2.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" + integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== + memoize-one@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" @@ -10619,7 +10771,7 @@ micromark@^3.0.0: micromark-util-types "^1.0.1" uvu "^0.5.0" -micromatch@^4.0.4, micromatch@^4.0.8: +micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -10800,6 +10952,11 @@ node-abi@^3.61.0: dependencies: semver "^7.3.5" +node-addon-api@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" + integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== + node-ensure@^0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/node-ensure/-/node-ensure-0.0.0.tgz#ecae764150de99861ec5c810fd5d096b183932a7" @@ -11709,6 +11866,17 @@ raw-body@2.5.2: iconv-lite "0.4.24" unpipe "1.0.0" +react-arborist@3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/react-arborist/-/react-arborist-3.4.0.tgz#8ef3de2c81d3b8cea0f4f4575c1971bd80c556c5" + integrity sha512-QI46oRGXJr0oaQfqqVobIiIoqPp5Y5gM69D2A2P7uHVif+X75XWnScR5drC7YDKgJ4CXVaDeFwnYKOWRRfncMg== + dependencies: + react-dnd "^14.0.3" + react-dnd-html5-backend "^14.0.3" + react-window "^1.8.10" + redux "^5.0.0" + use-sync-external-store "^1.2.0" + react-aria-components@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/react-aria-components/-/react-aria-components-1.3.2.tgz#dee58665210330ec12843e6393ef5cc28ff9a9da" @@ -11871,6 +12039,24 @@ react-aria@^3.34.2, react-aria@^3.37.0: "@react-aria/visually-hidden" "^3.8.19" "@react-types/shared" "^3.27.0" +react-dnd-html5-backend@^14.0.3: + version "14.1.0" + resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-14.1.0.tgz#b35a3a0c16dd3a2bfb5eb7ec62cf0c2cace8b62f" + integrity sha512-6ONeqEC3XKVf4eVmMTe0oPds+c5B9Foyj8p/ZKLb7kL2qh9COYxiBHv3szd6gztqi/efkmriywLUVlPotqoJyw== + dependencies: + dnd-core "14.0.1" + +react-dnd@^14.0.3: + version "14.0.5" + resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-14.0.5.tgz#ecf264e220ae62e35634d9b941502f3fca0185ed" + integrity sha512-9i1jSgbyVw0ELlEVt/NkCUkxy1hmhJOkePoCH713u75vzHGyXhPDm28oLfc2NMSBjZRM1Y+wRjHXJT3sPrTy+A== + dependencies: + "@react-dnd/invariant" "^2.0.0" + "@react-dnd/shallowequal" "^2.0.0" + dnd-core "14.0.1" + fast-deep-equal "^3.1.3" + hoist-non-react-statics "^3.3.2" + react-dom@*, react-dom@18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" @@ -12058,6 +12244,14 @@ react-transition-group@^4.3.0: loose-envify "^1.4.0" prop-types "^15.6.2" +react-window@^1.8.10: + version "1.8.11" + resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.11.tgz#a857b48fa85bd77042d59cc460964ff2e0648525" + integrity sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ== + dependencies: + "@babel/runtime" "^7.0.0" + memoize-one ">=3.1.1 <6" + react@*, react@18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" @@ -12087,6 +12281,11 @@ readable-stream@~2.3.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readdirp@^4.0.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" + integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -12102,6 +12301,18 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" +redux@^4.1.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" + integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== + dependencies: + "@babel/runtime" "^7.9.2" + +redux@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-5.0.1.tgz#97fa26881ce5746500125585d5642c77b6e9447b" + integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w== + reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: version "1.0.10" resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" @@ -12498,6 +12709,17 @@ safe-regex-test@^1.0.3, safe-regex-test@^1.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +sass@1.83.4: + version "1.83.4" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.83.4.tgz#5ccf60f43eb61eeec300b780b8dcb85f16eec6d1" + integrity sha512-B1bozCeNQiOgDcLd33e2Cs2U60wZwjUUXzh900ZyQF5qUasvMdDZYbQ566LJu7cqR+sAHlAfO6RMkaID5s6qpA== + dependencies: + chokidar "^4.0.0" + immutable "^5.0.2" + source-map-js ">=0.6.2 <2.0.0" + optionalDependencies: + "@parcel/watcher" "^2.4.1" + sax@^1.2.4: version "1.4.1" resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" @@ -12806,7 +13028,7 @@ source-list-map@^2.0.0: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== -source-map-js@^1.0.1, source-map-js@^1.0.2, source-map-js@^1.2.1: +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.0.2, source-map-js@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== @@ -13911,6 +14133,13 @@ use-latest@^1.2.1: dependencies: use-isomorphic-layout-effect "^1.1.1" +use-resize-observer@9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/use-resize-observer/-/use-resize-observer-9.1.0.tgz#14735235cf3268569c1ea468f8a90c5789fc5c6c" + integrity sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow== + dependencies: + "@juggle/resize-observer" "^3.3.1" + use-sidecar@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.3.tgz#10e7fd897d130b896e2c546c63a5e8233d00efdb"