Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
PanchoutNathan committed Jan 28, 2025
1 parent 7004832 commit 58bf8d7
Show file tree
Hide file tree
Showing 9 changed files with 14,794 additions and 14,125 deletions.
3 changes: 3 additions & 0 deletions src/frontend/apps/impress/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@openfun/cunningham-react": "2.9.4",
"@sentry/nextjs": "8.47.0",
"@tanstack/react-query": "5.62.11",
"clsx": "^2.1.1",
"cmdk": "1.0.4",
"crisp-sdk-web": "1.0.25",
"i18next": "24.2.0",
Expand All @@ -32,11 +33,13 @@
"luxon": "3.5.0",
"next": "15.1.3",
"react": "*",
"react-arborist": "^3.4.0",
"react-aria-components": "1.5.0",
"react-dom": "*",
"react-i18next": "15.4.0",
"react-intersection-observer": "9.13.1",
"react-select": "5.9.0",
"sass": "^1.83.4",
"styled-components": "6.1.13",
"use-debounce": "10.0.4",
"y-protocols": "1.0.6",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import styles from './loader.module.scss';

interface LoaderProps {
size?: 'sm' | 'md' | 'lg' | 'xl';
}

export const Loader = ({ size = 'sm' }: LoaderProps) => {
return <div className={[styles.loader, styles[size]].join(' ')} />;
};
Original file line number Diff line number Diff line change
@@ -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);
}
}
314 changes: 314 additions & 0 deletions src/frontend/apps/impress/src/components/common/tree/TreeView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
/* eslint-disable jsx-a11y/no-static-element-interactions */
import { clsx } from 'clsx';
import { useEffect, useRef, useState } from 'react';
import { MoveHandler, NodeApi, NodeRendererProps, Tree } from 'react-arborist';

import { Box } from '../../Box';
import { Icon } from '../../Icon';
import { Loader } from '../loader/Loader';

import styles from './treeview.module.scss';

type BaseType<T> = T & {
id: string;
childrenCount?: number;
children?: BaseType<T>[];
};

export type TreeViewDataType<T> = BaseType<T>;

const addAllChildren = <T,>(
data: TreeViewDataType<T>[],
): TreeViewDataType<T>[] => {
return data.map((item) => {
return { ...item, children: item.children ?? [] };
});
};

export type TreeViewProps<T> = {
data: TreeViewDataType<T>[];
allCanBeFolder?: boolean;
rootNode: BaseType<T>;
renderNode?: (node: TreeViewDataType<T>) => React.ReactNode;
loadChildren?: (node: TreeViewDataType<T>) => Promise<TreeViewDataType<T>[]>;
afterMove?: (
nodeId: string,
newIndex: number,
newParentId: string | null,
) => void;
};

type ArgumentTypes<T> = Parameters<MoveHandler<TreeViewDataType<T>>>;

export const TreeView = <T,>({
data: initialData,
allCanBeFolder,
rootNode,
renderNode,
loadChildren,
afterMove,
}: TreeViewProps<T>) => {
const [data, setData] = useState(
allCanBeFolder ? addAllChildren(initialData) : initialData,
);

const onMove3 = (args: {
dragIds: string[];
dragNodes: NodeApi<BaseType<T>>[];
parentId: string | null;
parentNode: NodeApi<BaseType<T>> | null;
index: number;
}): {
targetNodeId: string;
mode: 'first-child' | 'last-child' | 'left' | 'right';
} | null => {
const newData = JSON.parse(JSON.stringify(data)) as TreeViewDataType<T>[];

const newIndex = args.index;
const targetNodeId = args.parentId ?? rootNode.id;
const children = args.parentId
? (args.parentNode?.children ?? [])
: newData;

if (newIndex === 0) {
return { targetNodeId: targetNodeId, mode: 'first-child' };
}
if (newIndex === children.length) {
return { targetNodeId: targetNodeId, mode: 'last-child' };
}

const siblingIndex = newIndex - 1;
const sibling = children[siblingIndex];

if (sibling) {
return { targetNodeId: sibling.id, mode: 'right' };
}

const nextSiblingIndex = newIndex + 1;
const nextSibling = children[nextSiblingIndex];
if (nextSibling) {
return { targetNodeId: nextSibling.id, mode: 'left' };
}

return null;
};

const onMove = (args: {
dragIds: string[];
dragNodes: NodeApi<BaseType<T>>[];
parentId: string | null;
parentNode: NodeApi<BaseType<T>> | null;
index: number;
}) => {
// Création d'une copie profonde pour éviter les mutations directes
const newData = JSON.parse(JSON.stringify(data)) as TreeViewDataType<T>[];
const draggedId = args.dragIds[0];

// Fonction helper pour trouver et supprimer un nœud dans l'arbre
const findAndRemoveNode = (
items: TreeViewDataType<T>[],
parentId?: string,
): {
currentIndex: number;
newIndex: number;
parentId?: string;
draggedNode: TreeViewDataType<T>;
} | null => {
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) {
newData.splice(newIndex, 0, draggedNode);
afterMove?.(draggedNode.id, newIndex, null);
}
// Cas 2: Déplacement dans un dossier
else {
const targetParent = args.parentNode.data;
const findParentAndInsert = (items: TreeViewDataType<T>[]) => {
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,
);
afterMove?.(draggedNode.id, newIndex, targetParent.id);
return true;
}
if (item.children?.length) {
if (findParentAndInsert(item.children)) {
return true;
}
}
}
return false;
};
// console.log('newData', newData);
findParentAndInsert(newData);
}

console.log('onMove --- ', onMove3(args));
setData(newData);
};

useEffect(() => {
// console.log('initialData', initialData);
setData(allCanBeFolder ? addAllChildren(initialData) : initialData);
}, [initialData, setData, allCanBeFolder]);

return (
<Box className={styles.container}>
<Tree
data={data}
openByDefault={false}
height={1000}
indent={20}
width={280}
rowHeight={28}
overscanCount={1}
paddingTop={30}
paddingBottom={10}
padding={25}
onMove={onMove as MoveHandler<TreeViewDataType<T>>}
>
{(props) => (
<Node
{...props}
renderNode={renderNode}
loadChildren={loadChildren}
/>
)}
</Tree>
e
</Box>
);
};

export type NodeProps<T> = NodeRendererProps<TreeViewDataType<T>> & {
renderNode?: (node: TreeViewDataType<T>) => React.ReactNode;
loadChildren?: (node: TreeViewDataType<T>) => Promise<TreeViewDataType<T>[]>;
};

export const Node = <T,>({
renderNode,
node,
dragHandle,
style,
loadChildren,
}: NodeProps<T>) => {
/* This node instance can do many things. See the API reference. */
const timeoutRef = useRef<NodeJS.Timeout | null>(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 hasLoadedAllChildren =
!hasChildren || node.data.childrenCount === undefined
? true
: node.children?.length === (node.data.childrenCount ?? 0);

const handleClick = async () => {
// console.log('clicked', isLeaf, hasLoadedAllChildren);
if (isLeaf) {
return;
}

if (hasLoadedAllChildren) {
node.toggle();
return;
}

// console.log('loading');
setIsLoading(true);
await loadChildren?.(node.data);
setIsLoading(false);
node.toggle();
// console.log('loading off');
};

useEffect(() => {
if (node.willReceiveDrop && !node.isOpen) {
timeoutRef.current = setTimeout(() => {
void handleClick();
}, 500);
}

if (timeoutRef.current && !node.willReceiveDrop) {
clearTimeout(timeoutRef.current);
}
}, [node]);

return (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
<div
className={clsx(styles.node, {
[styles.willReceiveDrop]: node.willReceiveDrop,
[styles.selected]: node.isSelected,
})}
style={{
...style,
}}
ref={dragHandle}
>
{isLeaf ? (
<Box $padding={{ left: '24px' }} />
) : (
<>
{isLoading ? (
<Box $padding={{ horizontal: '4px' }}>
<Loader />
</Box>
) : (
<Icon
onClick={() => void handleClick()}
$variation="400"
iconName={
node.isOpen ? 'keyboard_arrow_down' : 'keyboard_arrow_right'
}
/>
)}
</>
)}
{renderNode?.(node.data)}
</div>
);
};
Loading

0 comments on commit 58bf8d7

Please sign in to comment.