-
Notifications
You must be signed in to change notification settings - Fork 20
Feature/#217 create tree component #790
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 24 commits
1811c48
1aad314
0ab0526
997b8a0
1cc1bd9
7e17f3a
9f44b4c
f5f3b80
fffa03c
4a020ad
d6fb800
0c5abc0
933ff76
2394d44
e12cb2d
4c24737
f7c6f0e
13ae182
47c3667
4e65c89
ac87418
8f8b05a
736f35a
f748eed
feda3dc
31c8e40
f49e6bb
8a93fa9
efb9f20
5f0cbb9
3b08933
13430d7
0f02295
e32bb77
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,213 @@ | ||
| import { fitSizeToShapeSizeRestrictions } from '@/common/utils/shapes'; | ||
| import { ElementSize, ShapeSizeRestrictions, Size } from '@/core/model'; | ||
| import { FONT_SIZE_VALUES } from '../../front-components/shape.const'; | ||
| import { useCanvasContext } from '@/core/providers'; | ||
| import { useEffect, useRef } from 'react'; | ||
|
|
||
| interface FileTreeSizeValues { | ||
| fontSize: number; | ||
| iconDimension: number; | ||
| elementHeight: number; | ||
| paddingX: number; | ||
| paddingY: number; | ||
| extraTextTopPadding: number; | ||
| iconTextSpacing: number; | ||
| indentationStep: number; | ||
| } | ||
|
|
||
| export const getFileTreeSizeValues = ( | ||
| size?: ElementSize | ||
| ): FileTreeSizeValues => { | ||
| switch (size) { | ||
| case 'XS': | ||
| return { | ||
| fontSize: 12, | ||
| iconDimension: 25, | ||
| elementHeight: 30, | ||
| paddingX: 25, | ||
| paddingY: 15, | ||
| extraTextTopPadding: 9, | ||
| iconTextSpacing: 8, | ||
| indentationStep: 15, | ||
| }; | ||
| case 'S': | ||
| return { | ||
| fontSize: FONT_SIZE_VALUES.NORMALTEXT, | ||
| iconDimension: 50, | ||
| elementHeight: 60, | ||
| paddingX: 30, | ||
| paddingY: 20, | ||
| extraTextTopPadding: 20, | ||
| iconTextSpacing: 10, | ||
| indentationStep: 27, | ||
| }; | ||
| default: | ||
| return { | ||
| fontSize: FONT_SIZE_VALUES.NORMALTEXT, | ||
| iconDimension: 50, | ||
| elementHeight: 60, | ||
| paddingX: 30, | ||
| paddingY: 20, | ||
| extraTextTopPadding: 20, | ||
| iconTextSpacing: 10, | ||
| indentationStep: 25, | ||
| }; | ||
| } | ||
| }; | ||
|
|
||
| export const joinTextContent = (text: string): string[] => { | ||
| return text.split(', '); | ||
| }; | ||
|
|
||
| // Symbol -> + Folder - Subfolder * File | ||
| // Level -> Level 0: no indentation in Folder / Level 1: 1 indentation (3 spaces) in Subfolder / Level 2: 2 indentations (6 spaces) in File | ||
| export interface FileTreeItem { | ||
| type: 'folder' | 'subfolder' | 'file'; | ||
| text: string; | ||
| level: number; | ||
| } | ||
|
|
||
| interface FileTreeDynamicSizeParams { | ||
| width: number; | ||
| elementHeight: number; | ||
| paddingY: number; | ||
| baseRestrictions: ShapeSizeRestrictions; | ||
| } | ||
|
|
||
| export const parseFileTreeText = (text: string): FileTreeItem[] => { | ||
| return text | ||
| .split('\n') | ||
| .map(line => { | ||
| // First detect indentation | ||
| const indentMatch = line.match(/^(\s*)/); | ||
| const level = indentMatch ? Math.floor(indentMatch[1].length / 3) : 0; | ||
| const trimmed = line.trim(); | ||
|
|
||
| if (trimmed === '') return null; | ||
|
|
||
| // Detect symbol | ||
| if (trimmed.startsWith('+ ')) { | ||
| return { | ||
| type: 'folder', | ||
| text: trimmed.substring(2).trim(), | ||
| level: level, | ||
| }; | ||
| } | ||
|
|
||
| if (trimmed.startsWith('- ')) { | ||
| return { | ||
| type: 'subfolder', | ||
| text: trimmed.substring(2).trim(), | ||
| level: level, | ||
| }; | ||
| } | ||
|
|
||
| if (trimmed.startsWith('* ')) { | ||
| return { | ||
| type: 'file', | ||
| text: trimmed.substring(2).trim(), | ||
| level: level, | ||
| }; | ||
| } | ||
|
|
||
| // No symbol: will be treated as a folder | ||
| return { | ||
| type: 'folder', | ||
| text: trimmed, | ||
| level: level, | ||
| }; | ||
| }) | ||
| .filter((item): item is FileTreeItem => item !== null); | ||
| }; | ||
|
|
||
| export const calculateFileTreeDynamicSize = ( | ||
| treeItems: FileTreeItem[], | ||
| params: FileTreeDynamicSizeParams | ||
| ): Size => { | ||
| const { width, elementHeight, paddingY, baseRestrictions } = params; | ||
|
|
||
| // Calculate minimum height required based on content | ||
| const minContentHeight = treeItems.length * elementHeight + paddingY * 2; | ||
|
|
||
| // Create dynamic constraints with adaptive minimum height | ||
| const dynamicRestrictions: ShapeSizeRestrictions = { | ||
| ...baseRestrictions, | ||
| minHeight: minContentHeight, | ||
| defaultHeight: Math.max( | ||
| baseRestrictions.defaultHeight || 200, | ||
| minContentHeight | ||
| ), | ||
| }; | ||
|
|
||
| const finalHeight = minContentHeight; | ||
|
|
||
| return fitSizeToShapeSizeRestrictions( | ||
| dynamicRestrictions, | ||
| width, | ||
| finalHeight | ||
| ); | ||
| }; | ||
|
|
||
| // Hook to resize edition text area based on content | ||
| export const useFileTreeResizeOnContentChange = ( | ||
|
||
| id: string, | ||
| coords: { x: number; y: number }, | ||
| text: string, | ||
| currentSize: Size, | ||
| calculatedSize: Size, | ||
| minHeight: number | ||
| ) => { | ||
| const previousText = useRef(text); | ||
| const { updateShapeSizeAndPosition } = useCanvasContext(); | ||
|
|
||
| useEffect(() => { | ||
| // Only update if the text has changed AND the height is different | ||
| const textChanged = previousText.current !== text; | ||
|
|
||
| const finalHeight = Math.max(calculatedSize.height, minHeight); | ||
| const finalSize = { ...calculatedSize, height: finalHeight }; | ||
|
|
||
| const heightChanged = finalHeight !== currentSize.height; | ||
|
|
||
| if (textChanged && heightChanged) { | ||
| previousText.current = text; | ||
| updateShapeSizeAndPosition(id, coords, finalSize, false); | ||
| } | ||
| }, [ | ||
| text, | ||
| calculatedSize.height, | ||
| currentSize.height, | ||
| id, | ||
| coords.x, | ||
| coords.y, | ||
| updateShapeSizeAndPosition, | ||
| ]); | ||
| }; | ||
|
|
||
| export const useFileTreeResizeOnSizeChange = ( | ||
|
||
| id: string, | ||
| coords: { x: number; y: number }, | ||
| currentWidth: number, | ||
| size?: ElementSize | ||
| ) => { | ||
| const previousSize = useRef(size); | ||
| const { updateShapeSizeAndPosition } = useCanvasContext(); | ||
|
|
||
| useEffect(() => { | ||
| // Only update if the size has changed | ||
| if (previousSize.current !== size) { | ||
| previousSize.current = size; | ||
|
|
||
| const newWidth = size === 'XS' ? 150 : 230; | ||
|
|
||
| if (currentWidth !== newWidth) { | ||
| updateShapeSizeAndPosition( | ||
| id, | ||
| coords, | ||
| { width: newWidth, height: currentWidth }, | ||
| false | ||
| ); | ||
| } | ||
| } | ||
| }, [size, currentWidth, id, coords.x, coords.y, updateShapeSizeAndPosition]); | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be a good idea to add unit tests to this method, including corner cases
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unit tests done!