wip
This commit is contained in:
@@ -25,13 +25,14 @@
|
||||
"@dnd-kit/modifiers": "9.0.0",
|
||||
"@fontsource/material-icons": "5.2.5",
|
||||
"@gouvfr-lasuite/integration": "1.0.2",
|
||||
"@gouvfr-lasuite/ui-kit": "0.1.3",
|
||||
"@gouvfr-lasuite/ui-kit": "/Users/melde/Documents/societes/melde/clients/dinum/design-system",
|
||||
"@hocuspocus/provider": "2.15.2",
|
||||
"@openfun/cunningham-react": "3.0.0",
|
||||
"@react-pdf/renderer": "4.1.6",
|
||||
"@sentry/nextjs": "9.3.0",
|
||||
"@tanstack/react-query": "5.67.1",
|
||||
"canvg": "4.0.3",
|
||||
"clsx": "2.1.1",
|
||||
"cmdk": "1.0.4",
|
||||
"crisp-sdk-web": "1.0.25",
|
||||
"docx": "9.1.1",
|
||||
|
||||
@@ -5,19 +5,10 @@ import { useCunninghamTheme } from '@/cunningham';
|
||||
|
||||
type IconProps = TextType & {
|
||||
iconName: string;
|
||||
isFilled?: boolean;
|
||||
};
|
||||
export const Icon = ({ iconName, isFilled, ...textProps }: IconProps) => {
|
||||
export const Icon = ({ iconName, ...textProps }: IconProps) => {
|
||||
return (
|
||||
<Text
|
||||
$isMaterialIcon={!isFilled}
|
||||
{...textProps}
|
||||
className={
|
||||
isFilled
|
||||
? `material-icons-filled ${textProps.className}`
|
||||
: textProps.className
|
||||
}
|
||||
>
|
||||
<Text $isMaterialIcon={textProps.$isMaterialIcon ?? true} {...textProps}>
|
||||
{iconName}
|
||||
</Text>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import clsx from 'clsx';
|
||||
import { CSSProperties, ComponentPropsWithRef, forwardRef } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
@@ -11,7 +12,7 @@ type TextSizes = keyof typeof sizes;
|
||||
export interface TextProps extends BoxProps {
|
||||
as?: 'p' | 'span' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
||||
$elipsis?: boolean;
|
||||
$isMaterialIcon?: boolean;
|
||||
$isMaterialIcon?: boolean | 'filled';
|
||||
$weight?: CSSProperties['fontWeight'];
|
||||
$textAlign?: CSSProperties['textAlign'];
|
||||
$size?: TextSizes | (string & {});
|
||||
@@ -58,13 +59,20 @@ export const TextStyled = styled(Box)<TextProps>`
|
||||
|
||||
const Text = forwardRef<HTMLElement, ComponentPropsWithRef<typeof TextStyled>>(
|
||||
({ className, $isMaterialIcon, ...props }, ref) => {
|
||||
const isFilled = $isMaterialIcon === 'filled';
|
||||
const isMaterialIcon =
|
||||
typeof $isMaterialIcon === 'boolean' && $isMaterialIcon;
|
||||
|
||||
return (
|
||||
<TextStyled
|
||||
ref={ref}
|
||||
as="span"
|
||||
$theme="greyscale"
|
||||
$variation="text"
|
||||
className={`${className || ''}${$isMaterialIcon ? ' material-icons' : ''}`}
|
||||
className={clsx(className || '', {
|
||||
'material-icons': isMaterialIcon,
|
||||
'material-icons-filled': isFilled,
|
||||
})}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
|
||||
import { Tooltip } from '@openfun/cunningham-react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
@@ -11,13 +12,13 @@ import {
|
||||
Doc,
|
||||
KEY_DOC,
|
||||
KEY_LIST_DOC,
|
||||
KEY_SUB_DOC,
|
||||
useTrans,
|
||||
useUpdateDoc,
|
||||
} from '@/docs/doc-management';
|
||||
import { useBroadcastStore, useResponsiveStore } from '@/stores';
|
||||
|
||||
import { useDocTreeData } from '../../doc-tree/context/DocTreeContext';
|
||||
|
||||
import { useDocTreeStore } from '../../doc-tree/context/DocTreeContext';
|
||||
interface DocTitleProps {
|
||||
doc: Doc;
|
||||
}
|
||||
@@ -53,22 +54,27 @@ export const DocTitleText = ({ title }: DocTitleTextProps) => {
|
||||
const DocTitleInput = ({ doc }: DocTitleProps) => {
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
const { t } = useTranslation();
|
||||
const queryClient = useQueryClient();
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
const treeStore = useDocTreeStore();
|
||||
const [titleDisplay, setTitleDisplay] = useState(doc.title);
|
||||
const data = useDocTreeData();
|
||||
|
||||
const { untitledDocument } = useTrans();
|
||||
|
||||
const { broadcast } = useBroadcastStore();
|
||||
|
||||
const { mutate: updateDoc } = useUpdateDoc({
|
||||
listInvalideQueries: [KEY_DOC, KEY_LIST_DOC],
|
||||
listInvalideQueries: [KEY_LIST_DOC],
|
||||
onSuccess(updatedDoc) {
|
||||
// Broadcast to every user connected to the document
|
||||
broadcast(`${KEY_DOC}-${updatedDoc.id}`);
|
||||
data?.tree?.updateNode(updatedDoc.id, { title: updatedDoc.title });
|
||||
if (updatedDoc.id === data?.root?.id) {
|
||||
void data?.refreshRoot();
|
||||
if (updatedDoc.id === treeStore.root?.id) {
|
||||
treeStore.setRoot(updatedDoc);
|
||||
}
|
||||
queryClient.setQueryData(
|
||||
[KEY_SUB_DOC, { id: updatedDoc.id }],
|
||||
updatedDoc,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Doc } from '../types';
|
||||
|
||||
export type DocParams = {
|
||||
id: string;
|
||||
isTree?: boolean;
|
||||
};
|
||||
|
||||
export const getDoc = async ({ id }: DocParams): Promise<Doc> => {
|
||||
@@ -19,14 +20,15 @@ export const getDoc = async ({ id }: DocParams): Promise<Doc> => {
|
||||
};
|
||||
|
||||
export const KEY_DOC = 'doc';
|
||||
export const KEY_SUB_DOC = 'sub-doc';
|
||||
export const KEY_DOC_VISIBILITY = 'doc-visibility';
|
||||
|
||||
export function useDoc(
|
||||
param: DocParams,
|
||||
queryConfig?: UseQueryOptions<Doc, APIError, Doc>,
|
||||
queryConfig?: Omit<UseQueryOptions<Doc, APIError, Doc>, 'queryFn'>,
|
||||
) {
|
||||
return useQuery<Doc, APIError, Doc>({
|
||||
queryKey: [KEY_DOC, param],
|
||||
queryKey: queryConfig?.queryKey ?? [KEY_DOC, param],
|
||||
queryFn: () => getDoc(param),
|
||||
...queryConfig,
|
||||
});
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ export const ModalRemoveDoc = ({
|
||||
{!isError && (
|
||||
<Text $size="sm" $variation="600">
|
||||
{t('Are you sure you want to delete the document "{{title}}"?', {
|
||||
title: doc.title,
|
||||
title: doc.title ?? t('Untitled document'),
|
||||
})}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
+8
-8
@@ -15,7 +15,7 @@ import {
|
||||
import { Doc, useInfiniteDocs } from '@/docs/doc-management';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { useDocTreeData } from '../../doc-tree/context/DocTreeContext';
|
||||
import { useDocTreeStore } from '../../doc-tree/context/DocTreeContext';
|
||||
import EmptySearchIcon from '../assets/illustration-docs-empty.png';
|
||||
|
||||
import {
|
||||
@@ -36,8 +36,9 @@ export const DocSearchModal = ({
|
||||
...modalProps
|
||||
}: DocSearchModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const tree = useDocTreeData();
|
||||
|
||||
const router = useRouter();
|
||||
const treeStore = useDocTreeStore();
|
||||
|
||||
const [search, setSearch] = useState('');
|
||||
const [filters, setFilters] = useState<DocSearchFiltersValues>(
|
||||
@@ -56,17 +57,16 @@ export const DocSearchModal = ({
|
||||
page: 1,
|
||||
title: search,
|
||||
...filters,
|
||||
parent_id: tree?.root?.id,
|
||||
parent_id: treeStore?.root?.id,
|
||||
});
|
||||
const loading = isFetching || isRefetching || isLoading;
|
||||
const handleInputSearch = useDebouncedCallback(setSearch, 300);
|
||||
|
||||
const handleSelect = (doc: Doc) => {
|
||||
if (tree?.initialRootId !== doc.id) {
|
||||
tree?.tree.resetTree([]);
|
||||
tree?.tree.setSelectedNode(doc);
|
||||
tree?.setRoot(doc);
|
||||
tree?.setInitialTargetId(doc.id);
|
||||
if (treeStore?.initialRootId !== doc.id) {
|
||||
treeStore.setSelectedNode(doc);
|
||||
treeStore.setRoot(doc);
|
||||
treeStore.setInitialTargetId(doc.id);
|
||||
}
|
||||
router.push(`/docs/${doc.id}`);
|
||||
modalProps.onClose?.();
|
||||
|
||||
+9
-5
@@ -3,6 +3,7 @@ import {
|
||||
VariantType,
|
||||
useToastProvider,
|
||||
} from '@openfun/cunningham-react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
@@ -11,9 +12,8 @@ import { APIError } from '@/api';
|
||||
import { Box } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { User } from '@/features/auth';
|
||||
import { Doc, Role } from '@/features/docs';
|
||||
import { Doc, KEY_SUB_DOC, Role } from '@/features/docs';
|
||||
|
||||
import { useDocTreeData } from '../../doc-tree/context/DocTreeContext';
|
||||
import { useCreateDocAccess, useCreateDocInvitation } from '../api';
|
||||
import { OptionType } from '../types';
|
||||
|
||||
@@ -40,7 +40,7 @@ export const DocShareAddMemberList = ({
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToastProvider();
|
||||
const treeData = useDocTreeData();
|
||||
const queryClient = useQueryClient();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
|
||||
const [invitationRole, setInvitationRole] = useState<Role>(Role.EDITOR);
|
||||
@@ -100,7 +100,9 @@ export const DocShareAddMemberList = ({
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
void treeData?.tree.refreshNode(doc.id);
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [KEY_SUB_DOC, { id: doc.id }],
|
||||
});
|
||||
},
|
||||
},
|
||||
)
|
||||
@@ -111,7 +113,9 @@ export const DocShareAddMemberList = ({
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
void treeData?.tree.refreshNode(doc.id);
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [KEY_SUB_DOC, { id: doc.id }],
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
+9
-5
@@ -1,4 +1,5 @@
|
||||
import { VariantType, useToastProvider } from '@openfun/cunningham-react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
@@ -8,10 +9,9 @@ import {
|
||||
IconOptions,
|
||||
} from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Doc, Role } from '@/docs/doc-management';
|
||||
import { Doc, KEY_SUB_DOC, Role } from '@/docs/doc-management';
|
||||
import { User } from '@/features/auth';
|
||||
|
||||
import { useDocTreeData } from '../../doc-tree/context/DocTreeContext';
|
||||
import { useDeleteDocInvitation, useUpdateDocInvitation } from '../api';
|
||||
import { Invitation } from '../types';
|
||||
|
||||
@@ -24,9 +24,9 @@ type Props = {
|
||||
};
|
||||
export const DocShareInvitationItem = ({ doc, invitation }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const queryClient = useQueryClient();
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
const spacing = spacingsTokens();
|
||||
const treeData = useDocTreeData();
|
||||
const fakeUser: User = {
|
||||
id: invitation.email,
|
||||
full_name: invitation.email,
|
||||
@@ -40,7 +40,9 @@ export const DocShareInvitationItem = ({ doc, invitation }: Props) => {
|
||||
|
||||
const { mutate: updateDocInvitation } = useUpdateDocInvitation({
|
||||
onSuccess: () => {
|
||||
void treeData?.tree.refreshNode(doc.id);
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [KEY_SUB_DOC, { id: doc.id }],
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
toast(
|
||||
@@ -55,7 +57,9 @@ export const DocShareInvitationItem = ({ doc, invitation }: Props) => {
|
||||
|
||||
const { mutate: removeDocInvitation } = useDeleteDocInvitation({
|
||||
onSuccess: () => {
|
||||
void treeData?.tree.refreshNode(doc.id);
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [KEY_SUB_DOC, { id: doc.id }],
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
toast(
|
||||
|
||||
+10
-5
@@ -1,4 +1,5 @@
|
||||
import { VariantType, useToastProvider } from '@openfun/cunningham-react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
@@ -8,10 +9,9 @@ import {
|
||||
IconOptions,
|
||||
} from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Access, Doc, Role } from '@/docs/doc-management/';
|
||||
import { Access, Doc, KEY_SUB_DOC, Role } from '@/docs/doc-management/';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { useDocTreeData } from '../../doc-tree/context/DocTreeContext';
|
||||
import { useDeleteDocAccess, useUpdateDocAccess } from '../api';
|
||||
import { useWhoAmI } from '../hooks/';
|
||||
|
||||
@@ -26,16 +26,19 @@ export const DocShareMemberItem = ({ doc, access }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { isLastOwner, isOtherOwner } = useWhoAmI(access);
|
||||
const { toast } = useToastProvider();
|
||||
const treeData = useDocTreeData();
|
||||
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
const queryClient = useQueryClient();
|
||||
const spacing = spacingsTokens();
|
||||
const isNotAllowed =
|
||||
isOtherOwner || !!isLastOwner || !doc.abilities.accesses_manage;
|
||||
|
||||
const { mutate: updateDocAccess } = useUpdateDocAccess({
|
||||
onSuccess: () => {
|
||||
void treeData?.tree.refreshNode(doc.id);
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [KEY_SUB_DOC, { id: doc.id }],
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
toast(t('Error during invitation update'), VariantType.ERROR, {
|
||||
@@ -46,7 +49,9 @@ export const DocShareMemberItem = ({ doc, access }: Props) => {
|
||||
|
||||
const { mutate: removeDocAccess } = useDeleteDocAccess({
|
||||
onSuccess: () => {
|
||||
void treeData?.tree.refreshNode(doc.id);
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [KEY_SUB_DOC, { id: doc.id }],
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
toast(t('Error while deleting invitation'), VariantType.ERROR, {
|
||||
|
||||
+45
-11
@@ -1,14 +1,17 @@
|
||||
import { TreeViewItem, TreeViewNodeProps } from '@gouvfr-lasuite/ui-kit';
|
||||
import {
|
||||
TreeViewItem,
|
||||
TreeViewNodeProps,
|
||||
useTree,
|
||||
} from '@gouvfr-lasuite/ui-kit';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, Icon, Text } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Doc } from '@/features/docs/doc-management';
|
||||
import { Doc, KEY_SUB_DOC, useDoc } from '@/features/docs/doc-management';
|
||||
import { useLeftPanelStore } from '@/features/left-panel';
|
||||
|
||||
import { useDocTreeData } from '../context/DocTreeContext';
|
||||
|
||||
import Logo from './../assets/sub-page-logo.svg';
|
||||
import { DocTreeItemActions } from './DocTreeItemActions';
|
||||
|
||||
@@ -24,18 +27,43 @@ const ItemTextCss = css`
|
||||
`;
|
||||
|
||||
type Props = TreeViewNodeProps<Doc> & {
|
||||
treeData: ReturnType<typeof useTree<Doc>>;
|
||||
doc: Doc;
|
||||
setSelectedNode: (node: Doc) => void;
|
||||
};
|
||||
|
||||
export const DocSubPageItem = ({ doc, setSelectedNode, ...props }: Props) => {
|
||||
export const DocSubPageItem = ({
|
||||
doc,
|
||||
setSelectedNode,
|
||||
treeData,
|
||||
...props
|
||||
}: Props) => {
|
||||
const { loadChildren, node } = props;
|
||||
|
||||
const isInitialLoad = useRef(false);
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
const spacing = spacingsTokens();
|
||||
const router = useRouter();
|
||||
const { togglePanel } = useLeftPanelStore();
|
||||
const treeData = useDocTreeData();
|
||||
|
||||
const { data: docQuery } = useDoc(
|
||||
{ isTree: true, id: doc.id },
|
||||
{
|
||||
initialData: doc,
|
||||
queryKey: [KEY_SUB_DOC, { id: doc.id }],
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (docQuery && isInitialLoad.current === true) {
|
||||
console.log('docQuery', docQuery);
|
||||
treeData?.updateNode(docQuery.id, docQuery);
|
||||
}
|
||||
if (docQuery) {
|
||||
isInitialLoad.current = true;
|
||||
}
|
||||
}, [docQuery, treeData]);
|
||||
|
||||
const afterCreate = (createdDoc: Doc) => {
|
||||
const actualChildren = node.data.children ?? [];
|
||||
@@ -46,7 +74,7 @@ export const DocSubPageItem = ({ doc, setSelectedNode, ...props }: Props) => {
|
||||
node.open();
|
||||
|
||||
router.push(`/docs/${doc.id}`);
|
||||
treeData?.tree.setChildren(node.data.value.id, allChildren);
|
||||
treeData?.setChildren(node.data.value.id, allChildren);
|
||||
togglePanel();
|
||||
})
|
||||
.catch(console.error);
|
||||
@@ -57,7 +85,7 @@ export const DocSubPageItem = ({ doc, setSelectedNode, ...props }: Props) => {
|
||||
childrenCount: 0,
|
||||
parentId: node.id,
|
||||
};
|
||||
treeData?.tree.addChild(node.data.value.id, newDoc);
|
||||
treeData?.addChild(node.data.value.id, newDoc);
|
||||
node.open();
|
||||
router.push(`/docs/${createdDoc.id}`);
|
||||
togglePanel();
|
||||
@@ -72,7 +100,7 @@ export const DocSubPageItem = ({ doc, setSelectedNode, ...props }: Props) => {
|
||||
<TreeViewItem
|
||||
{...props}
|
||||
loadChildren={() =>
|
||||
treeData?.tree.handleLoadChildren(props.node.data.value.id)
|
||||
treeData?.handleLoadChildren(props.node.data.value.id)
|
||||
}
|
||||
onClick={() => {
|
||||
setSelectedNode(props.node.data.value as Doc);
|
||||
@@ -122,7 +150,12 @@ export const DocSubPageItem = ({ doc, setSelectedNode, ...props }: Props) => {
|
||||
{doc.title}
|
||||
</Text>
|
||||
{doc.nb_accesses_direct > 1 && (
|
||||
<Icon isFilled iconName="group" $size="16px" $variation="400" />
|
||||
<Icon
|
||||
$isMaterialIcon="filled"
|
||||
iconName="group"
|
||||
$size="16px"
|
||||
$variation="400"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -134,6 +167,7 @@ export const DocSubPageItem = ({ doc, setSelectedNode, ...props }: Props) => {
|
||||
>
|
||||
<DocTreeItemActions
|
||||
doc={doc}
|
||||
treeData={treeData}
|
||||
parentId={node.data.parentKey}
|
||||
onCreateSuccess={afterCreate}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { OpenMap, TreeView, TreeViewMoveResult } from '@gouvfr-lasuite/ui-kit';
|
||||
import {
|
||||
OpenMap,
|
||||
TreeView,
|
||||
TreeViewMoveResult,
|
||||
useTree,
|
||||
} from '@gouvfr-lasuite/ui-kit';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { css } from 'styled-components';
|
||||
@@ -6,11 +12,18 @@ import { css } from 'styled-components';
|
||||
import { Box, SeparatedSection, StyledLink } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
|
||||
import { Doc } from '../../doc-management';
|
||||
import {
|
||||
Doc,
|
||||
KEY_DOC,
|
||||
LinkReach,
|
||||
LinkRole,
|
||||
getDoc,
|
||||
} from '../../doc-management';
|
||||
import { SimpleDocItem } from '../../docs-grid';
|
||||
import { getDocChildren } from '../api/useDocChildren';
|
||||
import { useDocTree } from '../api/useDocTree';
|
||||
import { useMoveDoc } from '../api/useMove';
|
||||
import { useDocTreeData } from '../context/DocTreeContext';
|
||||
import { subPageToTree, useDocTreeStore } from '../context/DocTreeContext';
|
||||
|
||||
import { DocSubPageItem } from './DocSubPageItem';
|
||||
import { DocTreeItemActions } from './DocTreeItemActions';
|
||||
@@ -20,8 +33,24 @@ type DocTreeProps = {
|
||||
};
|
||||
export const DocTree = ({ initialTargetId }: DocTreeProps) => {
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
const queryClient = useQueryClient();
|
||||
const store = useDocTreeStore();
|
||||
|
||||
const spacing = spacingsTokens();
|
||||
const treeData = useDocTreeData();
|
||||
|
||||
const treeData = useTree(
|
||||
[],
|
||||
async (docId) => {
|
||||
const doc = await getDoc({ id: docId });
|
||||
const newDoc = { ...doc, childrenCount: doc.numchild };
|
||||
void queryClient.setQueryData([KEY_DOC, { id: docId }], newDoc);
|
||||
return newDoc;
|
||||
},
|
||||
async (docId) => {
|
||||
const doc = await getDocChildren({ docId: docId });
|
||||
return subPageToTree(doc.results ?? []);
|
||||
},
|
||||
);
|
||||
const router = useRouter();
|
||||
const [initialOpenState, setInitialOpenState] = useState<OpenMap | undefined>(
|
||||
undefined,
|
||||
@@ -39,42 +68,47 @@ export const DocTree = ({ initialTargetId }: DocTreeProps) => {
|
||||
targetDocumentId: result.targetModeId,
|
||||
position: result.mode,
|
||||
});
|
||||
treeData?.tree.handleMove(result);
|
||||
treeData?.handleMove(result);
|
||||
};
|
||||
useEffect(() => {
|
||||
|
||||
const buildDocTree = (data?: Doc) => {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
const { children: rootChildren, ...root } = data;
|
||||
const children = rootChildren ?? [];
|
||||
treeData?.setRoot(root);
|
||||
store.setRoot(root);
|
||||
const initialOpenState: OpenMap = {};
|
||||
initialOpenState[root.id] = true;
|
||||
const serialize = (children: Doc[]) => {
|
||||
children.forEach((child) => {
|
||||
child.childrenCount = child.numchild ?? 0;
|
||||
if (child?.children?.length && child?.children?.length > 0) {
|
||||
initialOpenState[child.id] = true;
|
||||
}
|
||||
serialize(child.children ?? []);
|
||||
});
|
||||
};
|
||||
serialize(children);
|
||||
console.log(children);
|
||||
subPageToTree(children, (child) => {
|
||||
if (child?.children?.length && child?.children?.length > 0) {
|
||||
initialOpenState[child.id] = true;
|
||||
}
|
||||
});
|
||||
|
||||
treeData?.tree.resetTree(children);
|
||||
treeData.resetTree(children);
|
||||
setInitialOpenState(initialOpenState);
|
||||
if (initialTargetId === root.id) {
|
||||
treeData?.tree.setSelectedNode(root);
|
||||
treeData?.setSelectedNode(root);
|
||||
} else {
|
||||
treeData?.tree.selectNodeById(initialTargetId);
|
||||
treeData?.selectNodeById(initialTargetId);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (treeData?.selectedNode?.id !== store.selectedNode?.id) {
|
||||
store.setSelectedNode(treeData?.selectedNode ?? null);
|
||||
}
|
||||
}, [store, treeData?.selectedNode]);
|
||||
|
||||
useEffect(() => {
|
||||
buildDocTree(data);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [data]);
|
||||
|
||||
const rootIsSelected = treeData?.tree.selectedNode?.id === treeData?.root?.id;
|
||||
const rootIsSelected = treeData?.selectedNode?.id === store.root?.id;
|
||||
|
||||
if (!initialTargetId || !treeData) {
|
||||
if (!initialTargetId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -110,32 +144,33 @@ export const DocTree = ({ initialTargetId }: DocTreeProps) => {
|
||||
}
|
||||
`}
|
||||
>
|
||||
{treeData.root !== null && (
|
||||
{store.root !== null && (
|
||||
<StyledLink
|
||||
$css={css`
|
||||
width: 100%;
|
||||
`}
|
||||
href={`/docs/${treeData.root.id}`}
|
||||
href={`/docs/${store.root.id}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
treeData.tree.setSelectedNode(treeData.root ?? undefined);
|
||||
router.push(`/docs/${treeData?.root?.id}`);
|
||||
treeData?.setSelectedNode(store.root ?? undefined);
|
||||
router.push(`/docs/${store.root?.id}`);
|
||||
}}
|
||||
>
|
||||
<Box $direction="row" $align="center" $width="100%">
|
||||
<SimpleDocItem doc={treeData.root} showAccesses={true} />
|
||||
<SimpleDocItem doc={store.root} showAccesses={true} />
|
||||
<div className="doc-tree-root-item-actions">
|
||||
<DocTreeItemActions
|
||||
doc={treeData.root}
|
||||
doc={store.root}
|
||||
treeData={treeData}
|
||||
onCreateSuccess={(createdDoc) => {
|
||||
const newDoc = {
|
||||
...createdDoc,
|
||||
children: [],
|
||||
childrenCount: 0,
|
||||
parentId: treeData.root?.id ?? undefined,
|
||||
parentId: store.root?.id ?? undefined,
|
||||
};
|
||||
treeData?.tree.addChild(null, newDoc);
|
||||
treeData?.addChild(null, newDoc);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -145,27 +180,54 @@ export const DocTree = ({ initialTargetId }: DocTreeProps) => {
|
||||
</Box>
|
||||
</Box>
|
||||
</SeparatedSection>
|
||||
<button
|
||||
onClick={() => {
|
||||
const children = data?.children ?? [];
|
||||
const newDoc = {
|
||||
id: '1',
|
||||
children: [],
|
||||
childrenCount: 0,
|
||||
title: 'TITI',
|
||||
creator: 'test',
|
||||
is_favorite: false,
|
||||
};
|
||||
|
||||
{initialOpenState && (
|
||||
// Ajout des propriétés manquantes pour correspondre au type Doc
|
||||
const completeDoc = {
|
||||
...newDoc,
|
||||
link_reach: LinkReach.PUBLIC,
|
||||
link_role: LinkRole.EDITOR,
|
||||
user_roles: [],
|
||||
// Ajoutez ici les autres propriétés requises par le type Doc
|
||||
};
|
||||
|
||||
children.push(completeDoc);
|
||||
buildDocTree(data);
|
||||
}}
|
||||
>
|
||||
Refresh
|
||||
</button>
|
||||
|
||||
{initialOpenState && treeData.nodes.length > 0 && (
|
||||
<TreeView
|
||||
handleMove={handleMove}
|
||||
initialOpenState={initialOpenState}
|
||||
selectedNodeId={
|
||||
treeData.tree.selectedNode?.id ??
|
||||
treeData.initialTargetId ??
|
||||
undefined
|
||||
treeData.selectedNode?.id ?? store.initialTargetId ?? undefined
|
||||
}
|
||||
treeData={treeData.tree.nodes ?? []}
|
||||
rootNodeId={treeData.root?.id ?? ''}
|
||||
treeData={treeData.nodes ?? []}
|
||||
rootNodeId={store.root?.id ?? ''}
|
||||
renderNode={(props) => {
|
||||
if (treeData === undefined) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<DocSubPageItem
|
||||
{...props}
|
||||
treeData={treeData}
|
||||
doc={props.node.data.value as Doc}
|
||||
loadChildren={(node) =>
|
||||
treeData.tree.handleLoadChildren(node.id)
|
||||
}
|
||||
setSelectedNode={treeData.tree.setSelectedNode}
|
||||
loadChildren={(node) => treeData.handleLoadChildren(node.id)}
|
||||
setSelectedNode={(node) => treeData.setSelectedNode(node)}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
|
||||
+27
-14
@@ -1,4 +1,8 @@
|
||||
import { DropdownMenu, DropdownMenuOption } from '@gouvfr-lasuite/ui-kit';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuOption,
|
||||
useTree,
|
||||
} from '@gouvfr-lasuite/ui-kit';
|
||||
import { useModal } from '@openfun/cunningham-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Fragment, useState } from 'react';
|
||||
@@ -10,11 +14,12 @@ import { useLeftPanelStore } from '@/features/left-panel';
|
||||
|
||||
import { Doc, ModalRemoveDoc } from '../../doc-management';
|
||||
import { useCreateChildrenDoc } from '../api/useCreateChildren';
|
||||
import { useDocTreeData } from '../context/DocTreeContext';
|
||||
import { useDocTreeStore } from '../context/DocTreeContext';
|
||||
|
||||
type DocTreeItemActionsProps = {
|
||||
doc: Doc;
|
||||
parentId?: string | null;
|
||||
treeData: ReturnType<typeof useTree<Doc>>;
|
||||
onCreateSuccess?: (newDoc: Doc) => void;
|
||||
};
|
||||
|
||||
@@ -22,14 +27,16 @@ export const DocTreeItemActions = ({
|
||||
doc,
|
||||
parentId,
|
||||
onCreateSuccess,
|
||||
treeData,
|
||||
}: DocTreeItemActionsProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
const treeStore = useDocTreeStore();
|
||||
const { t } = useTranslation();
|
||||
const deleteModal = useModal();
|
||||
const { togglePanel } = useLeftPanelStore();
|
||||
|
||||
const treeData = useDocTreeData();
|
||||
const options: DropdownMenuOption[] = [
|
||||
{
|
||||
label: t('Delete'),
|
||||
@@ -43,22 +50,23 @@ export const DocTreeItemActions = ({
|
||||
onCreateSuccess?.(doc);
|
||||
|
||||
togglePanel();
|
||||
treeData?.tree.setSelectedNode(doc);
|
||||
treeData.setSelectedNode(doc);
|
||||
router.push(`/docs/${doc.id}`);
|
||||
},
|
||||
});
|
||||
|
||||
const afterDelete = () => {
|
||||
if (parentId) {
|
||||
router.push(`/docs/${parentId}`);
|
||||
treeData?.tree.selectNodeById(parentId);
|
||||
treeData?.tree.deleteNode(doc.id);
|
||||
void treeData?.tree.refreshNode(parentId);
|
||||
} else if (doc.id === treeData?.root?.id && !parentId) {
|
||||
treeData?.selectNodeById(parentId);
|
||||
treeData?.deleteNode(doc.id);
|
||||
void treeData?.refreshNode(parentId);
|
||||
} else if (doc.id === treeStore.root?.id && !parentId) {
|
||||
router.push(`/docs/`);
|
||||
} else if (treeData && treeData.root) {
|
||||
router.push(`/docs/${treeData.root.id}`);
|
||||
treeData?.tree.deleteNode(doc.id);
|
||||
treeData?.tree.setSelectedNode(treeData.root);
|
||||
} else if (treeStore.root) {
|
||||
router.push(`/docs/${treeStore.root.id}`);
|
||||
treeData?.deleteNode(doc.id);
|
||||
treeData?.setSelectedNode(treeStore.root);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -84,7 +92,7 @@ export const DocTreeItemActions = ({
|
||||
setIsOpen(!isOpen);
|
||||
}}
|
||||
iconName="more_horiz"
|
||||
isFilled
|
||||
$isMaterialIcon="filled"
|
||||
$theme="primary"
|
||||
$variation="600"
|
||||
/>
|
||||
@@ -101,7 +109,12 @@ export const DocTreeItemActions = ({
|
||||
}}
|
||||
color="primary"
|
||||
>
|
||||
<Icon $variation="800" $theme="primary" isFilled iconName="add_box" />
|
||||
<Icon
|
||||
$variation="800"
|
||||
$theme="primary"
|
||||
$isMaterialIcon="filled"
|
||||
iconName="add_box"
|
||||
/>
|
||||
</BoxButton>
|
||||
</Box>
|
||||
{deleteModal.isOpen && (
|
||||
|
||||
@@ -1,94 +1,37 @@
|
||||
import { TreeViewDataType, useTree } from '@gouvfr-lasuite/ui-kit';
|
||||
import React, { ReactNode, createContext, useContext, useState } from 'react';
|
||||
import { TreeViewDataType } from '@gouvfr-lasuite/ui-kit';
|
||||
import { create } from 'zustand';
|
||||
|
||||
import { Doc, getDoc } from '../../doc-management';
|
||||
import { getDocChildren } from '../api/useDocChildren';
|
||||
import { Doc } from '../../doc-management';
|
||||
|
||||
// import { useTree2 } from './useTree2';
|
||||
|
||||
// Interface pour le contexte de l'arbre de documents
|
||||
interface DocTreeContextType {
|
||||
tree: ReturnType<typeof useTree<Doc>>;
|
||||
root: Doc | null;
|
||||
initialTargetId?: string | null;
|
||||
initialRootId?: string | null;
|
||||
setRoot: (doc: Doc | null) => void;
|
||||
setInitialTargetId: (id: string) => void;
|
||||
refreshRoot: () => Promise<void>;
|
||||
}
|
||||
|
||||
// Création du contexte avec une valeur par défaut
|
||||
const DocTreeContext = createContext<DocTreeContextType | undefined>(undefined);
|
||||
|
||||
// Props pour le provider
|
||||
interface DocTreeProviderProps {
|
||||
children: ReactNode;
|
||||
initialData?: TreeViewDataType<Doc>[];
|
||||
initialRootId?: string;
|
||||
initialTargetId?: string;
|
||||
}
|
||||
|
||||
// Provider qui expose les fonctionnalités de l'arbre
|
||||
export const DocTreeProvider: React.FC<DocTreeProviderProps> = ({
|
||||
children,
|
||||
initialData = [],
|
||||
initialRootId,
|
||||
initialTargetId: targetId,
|
||||
}) => {
|
||||
const [root, setRoot] = useState<Doc | null>(null);
|
||||
const [initialTargetId, setInitialTargetId] = useState<string | null>(
|
||||
targetId ?? null,
|
||||
);
|
||||
|
||||
const tree = useTree(
|
||||
initialData,
|
||||
async (docId) => {
|
||||
const doc = await getDoc({ id: docId });
|
||||
|
||||
return { ...doc, childrenCount: doc.numchild };
|
||||
},
|
||||
async (docId) => {
|
||||
const doc = await getDocChildren({ docId: docId });
|
||||
return subPageToTree(doc.results ?? []);
|
||||
},
|
||||
);
|
||||
|
||||
const refreshRoot = async () => {
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
const doc = await getDoc({ id: root.id });
|
||||
setRoot(doc);
|
||||
};
|
||||
|
||||
const value: DocTreeContextType = {
|
||||
tree,
|
||||
root,
|
||||
setRoot,
|
||||
initialTargetId,
|
||||
initialRootId,
|
||||
setInitialTargetId,
|
||||
refreshRoot,
|
||||
};
|
||||
|
||||
return (
|
||||
<DocTreeContext.Provider value={value}>{children}</DocTreeContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
// Hook personnalisé pour utiliser le contexte
|
||||
export const useDocTreeData = (): DocTreeContextType | undefined => {
|
||||
const context = useContext(DocTreeContext);
|
||||
if (context === undefined) {
|
||||
return;
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export const subPageToTree = (children: Doc[]): TreeViewDataType<Doc>[] => {
|
||||
export const subPageToTree = (
|
||||
children: Doc[],
|
||||
callback?: (doc: Doc) => void,
|
||||
): TreeViewDataType<Doc>[] => {
|
||||
children.forEach((child) => {
|
||||
child.childrenCount = child.numchild ?? 0;
|
||||
subPageToTree(child.children ?? []);
|
||||
callback?.(child);
|
||||
subPageToTree(child.children ?? [], callback);
|
||||
});
|
||||
return children;
|
||||
};
|
||||
|
||||
interface DocTreeStore {
|
||||
initialTargetId?: string | null;
|
||||
initialRootId?: string | null;
|
||||
setRoot: (doc: Doc | null) => void;
|
||||
root: Doc | null;
|
||||
setInitialTargetId: (id: string) => void;
|
||||
setSelectedNode: (node: Doc | null) => void;
|
||||
selectedNode: Doc | null;
|
||||
}
|
||||
|
||||
export const useDocTreeStore = create<DocTreeStore>((set) => ({
|
||||
root: null,
|
||||
selectedNode: null,
|
||||
initialTargetId: undefined,
|
||||
initialRootId: undefined,
|
||||
|
||||
setRoot: (doc) => set({ root: doc }),
|
||||
setInitialTargetId: (id) => set({ initialTargetId: id }),
|
||||
setSelectedNode: (node) => set({ selectedNode: node }),
|
||||
}));
|
||||
|
||||
+6
-5
@@ -1,13 +1,14 @@
|
||||
import { Box } from '@/components';
|
||||
import { useDocStore } from '@/docs/doc-management';
|
||||
import { DocTree } from '@/features/docs/doc-tree/components/DocTree';
|
||||
import { useDocTreeData } from '@/features/docs/doc-tree/context/DocTreeContext';
|
||||
import { useDocTreeStore } from '@/features/docs/doc-tree/context/DocTreeContext';
|
||||
|
||||
export const LeftPanelDocContent = () => {
|
||||
const { currentDoc } = useDocStore();
|
||||
const tree = useDocTreeData();
|
||||
|
||||
if (!currentDoc || !tree) {
|
||||
const treeStore = useDocTreeStore();
|
||||
|
||||
if (!currentDoc || !treeStore.initialTargetId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -17,8 +18,8 @@ export const LeftPanelDocContent = () => {
|
||||
$width="100%"
|
||||
$css="width: 100%; overflow-y: auto; overflow-x: hidden;"
|
||||
>
|
||||
{tree.initialTargetId && (
|
||||
<DocTree initialTargetId={tree.initialTargetId} />
|
||||
{treeStore.initialTargetId && (
|
||||
<DocTree initialTargetId={treeStore.initialTargetId} />
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useAuth } from '@/features/auth';
|
||||
import { useCreateDoc, useDocStore } from '@/features/docs/doc-management';
|
||||
import { DocSearchTarget } from '@/features/docs/doc-search/components/DocSearchFilters';
|
||||
import { useCreateChildrenDoc } from '@/features/docs/doc-tree/api/useCreateChildren';
|
||||
import { useDocTreeData } from '@/features/docs/doc-tree/context/DocTreeContext';
|
||||
import { useDocTreeStore } from '@/features/docs/doc-tree/context/DocTreeContext';
|
||||
import { useCmdK } from '@/hook/useCmdK';
|
||||
|
||||
import { useLeftPanelStore } from '../stores';
|
||||
@@ -18,8 +18,8 @@ export const LeftPanelHeader = ({ children }: PropsWithChildren) => {
|
||||
const router = useRouter();
|
||||
const searchModal = useModal();
|
||||
const { authenticated } = useAuth();
|
||||
const docTreeData = useDocTreeData();
|
||||
const tree = docTreeData?.tree;
|
||||
const treeStore = useDocTreeStore();
|
||||
|
||||
const { currentDoc } = useDocStore();
|
||||
const isDoc = router.pathname === '/docs/[id]';
|
||||
|
||||
@@ -43,8 +43,8 @@ export const LeftPanelHeader = ({ children }: PropsWithChildren) => {
|
||||
|
||||
const { mutate: createChildrenDoc } = useCreateChildrenDoc({
|
||||
onSuccess: (doc) => {
|
||||
tree?.addRootNode(doc);
|
||||
tree?.selectNodeById(doc.id);
|
||||
treeStore.treeData?.addRootNode(doc);
|
||||
treeStore.treeData?.selectNodeById(doc.id);
|
||||
void router.push(`/docs/${doc.id}`);
|
||||
togglePanel();
|
||||
},
|
||||
@@ -56,10 +56,10 @@ export const LeftPanelHeader = ({ children }: PropsWithChildren) => {
|
||||
};
|
||||
|
||||
const createNewDoc = () => {
|
||||
if (docTreeData && docTreeData.root && isDoc) {
|
||||
if (treeStore.root && isDoc) {
|
||||
createChildrenDoc({
|
||||
title: t('Untitled page'),
|
||||
parentId: docTreeData.root.id,
|
||||
parentId: treeStore.root.id,
|
||||
});
|
||||
} else {
|
||||
createDoc();
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
useDoc,
|
||||
useDocStore,
|
||||
} from '@/features/docs/doc-management/';
|
||||
import { DocTreeProvider } from '@/features/docs/doc-tree/context/DocTreeContext';
|
||||
import { useDocTreeStore } from '@/features/docs/doc-tree/context/DocTreeContext';
|
||||
import { MainLayout } from '@/layouts';
|
||||
import { useBroadcastStore } from '@/stores';
|
||||
import { NextPageWithLayout } from '@/types/next';
|
||||
@@ -25,6 +25,14 @@ export function DocLayout() {
|
||||
query: { id },
|
||||
} = useRouter();
|
||||
|
||||
const treeStore = useDocTreeStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof id === 'string' && !treeStore.initialTargetId) {
|
||||
treeStore.setInitialTargetId(id);
|
||||
}
|
||||
}, [id, treeStore]);
|
||||
|
||||
if (typeof id !== 'string') {
|
||||
return null;
|
||||
}
|
||||
@@ -35,11 +43,9 @@ export function DocLayout() {
|
||||
<meta name="robots" content="noindex" />
|
||||
</Head>
|
||||
|
||||
<DocTreeProvider initialTargetId={id}>
|
||||
<MainLayout>
|
||||
<DocPage id={id} />
|
||||
</MainLayout>
|
||||
</DocTreeProvider>
|
||||
<MainLayout>
|
||||
{treeStore.initialTargetId && <DocPage id={id} />}
|
||||
</MainLayout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -59,6 +65,8 @@ const DocPage = ({ id }: DocProps) => {
|
||||
{
|
||||
staleTime: 0,
|
||||
queryKey: [KEY_DOC, { id }],
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -89,7 +97,6 @@ const DocPage = ({ id }: DocProps) => {
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
console.log('unmount');
|
||||
setCurrentDoc(undefined);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
@@ -1640,10 +1640,8 @@
|
||||
resolved "https://registry.yarnpkg.com/@gouvfr-lasuite/integration/-/integration-1.0.2.tgz#ed0000f4b738c5a19bb60f5b80a9a2f5d9414234"
|
||||
integrity sha512-npOotZQSyu6SffHiPP+jQVOkJ3qW2KE2cANhEK92sNLX9uZqQaCqljO5GhzsBmh0lB76fiXnrr9i8SIpnDUSZg==
|
||||
|
||||
"@gouvfr-lasuite/ui-kit@0.1.3":
|
||||
"@gouvfr-lasuite/ui-kit@file:../../../design-system":
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@gouvfr-lasuite/ui-kit/-/ui-kit-0.1.3.tgz#1be7f1bdf12e7428e630d6ce11fbc77c4c9b9b21"
|
||||
integrity sha512-ba3ZrAIhX84cofa2IwiWhgE0wzz85+ySbOTvB1lP9jeWYvWn/N5HsnxphA9bEMIrx1Yi91upzmYLvjHRoDq1Ww==
|
||||
dependencies:
|
||||
"@dnd-kit/core" "6.3.1"
|
||||
"@dnd-kit/modifiers" "9.0.0"
|
||||
|
||||
Reference in New Issue
Block a user