DivBucket / src / Components / SideBar / SideBar.jsx
SideBar.jsx
Raw
import styles from './sidebar.module.css';
import { IoAddOutline, IoLayers } from 'react-icons/io5';
import { useRef, useState } from 'react';
import { GiSquare } from 'react-icons/gi';
import { LuHeading1, LuLetterText, LuSquareArrowRight } from 'react-icons/lu';
import { PiImageLight } from 'react-icons/pi';
import { RiText } from 'react-icons/ri';
import { useDispatch, useSelector } from 'react-redux';
import { MdKeyboardArrowDown, MdKeyboardArrowRight } from 'react-icons/md';
import { addNode, moveItem, updateActiveNode, updateActiveTab } from '../../store/reducers/treeReducer';
import { useContextMenu } from '../../utils/hooks/useContextMenu';
import ContextMenu from '../ContextMenu/ContextMenu';
import { GetIconOfType } from '../Cssbar/Cssbar';
import { GrDrag } from 'react-icons/gr';
import { FaFile } from 'react-icons/fa';
import { VscNewFile } from 'react-icons/vsc';
import { createTemplate } from '../../utils/template';
import { IoIosList } from 'react-icons/io';
import { GoHorizontalRule } from 'react-icons/go';
import { TbLayoutNavbar } from 'react-icons/tb';

export default () => {

    const [tab, setTab] = useState(0);

    const handleDragStart = (e, type) => {
        e.dataTransfer.setData("type", type);
    }
    const handleTabClick = (ind) => {
        if (tab === ind) setTab(null);
        else setTab(ind);
    }

    return (
        <div className={styles.sidebar}>
            <div className={styles.cont}>
                <div onClick={() => handleTabClick(0)} className={`${tab === 0 && styles.activetab} ${styles.it}`}><IoAddOutline className={styles.it0} size={23} /></div>
                <div onClick={() => handleTabClick(1)} className={`${tab === 1 && styles.activetab} ${styles.it}`}><IoLayers className={styles.it0} size={23} /></div>
                {/* <div onClick={() => handleTabClick(2)} className={`${tab === 2 && styles.activetab} ${styles.it}`}><CiSearch className={styles.it0} strokeWidth={'1'} size={23} /></div> */}
            </div>
            {
                tab !== null &&
                <div className={styles.a}>
                    {
                        tab === 0 ?
                            <div className={styles.a0}>
                                <div className={styles.head}>Elements</div>
                                <div className={styles.a1}>
                                    <div className={styles.a10} draggable onDragStart={(e) => handleDragStart(e, "Block")}>
                                        <div><GiSquare size={40} /></div>
                                        <div>Div</div>
                                    </div>
                                    <div className={styles.a10} draggable onDragStart={(e) => handleDragStart(e, "Row")} title='horizontal flex box'>
                                        <div><LuSquareArrowRight size={40} /></div>
                                        <div>H-Flex</div>
                                    </div>
                                    <div className={styles.a10} draggable onDragStart={(e) => handleDragStart(e, "Heading")}>
                                        <div><LuHeading1 size={40} /></div>
                                        <div>Heading</div>
                                    </div>
                                    <div className={styles.a10} draggable onDragStart={(e) => handleDragStart(e, "Text")}>
                                        <div><RiText size={30} style={{ margin: '7px 0' }} /></div>
                                        <div>Text</div>
                                    </div>
                                    <div className={styles.a10} draggable onDragStart={(e) => handleDragStart(e, "Paragraph")}>
                                        <div><LuLetterText size={40} /></div>
                                        <div>Paragraph</div>
                                    </div>
                                    <div className={styles.a10} draggable onDragStart={(e) => handleDragStart(e, "Image")}>
                                        <div><PiImageLight size={40} /></div>
                                        <div>Image</div>
                                    </div>
                                    <div className={styles.a10} draggable onDragStart={(e) => handleDragStart(e, "List")}>
                                        <div><IoIosList size={40} /></div>
                                        <div>List</div>
                                    </div>
                                    <div className={styles.a10} draggable onDragStart={(e) => handleDragStart(e, "ListItem")}>
                                        <div><GoHorizontalRule size={40} /></div>
                                        <div>List Item</div>
                                    </div>
                                    {/* <div className={styles.a10} draggable onDragStart={(e) => handleDragStart(e, "Video")}>
                                        <div><PiVideoLight size={40} /></div>
                                        <div>Video</div>
                                    </div> */}
                                </div>
                                <div className={styles.head} style={{ marginTop: '15px' }}>Layouts</div>
                                <div className={styles.a1}>
                                    <div className={styles.a10} draggable onDragStart={(e) => handleDragStart(e, "Navbar")}>
                                        <div><TbLayoutNavbar size={40} /></div>
                                        <div>Navbar</div>
                                    </div>
                                </div>
                            </div> :
                            tab == 1 ?
                                <Explorer /> :
                                <>
                                </>
                    }
                </div>
            }
        </div>
    );
}

const Explorer = () => {

    const draggedNode = useRef(null);
    const dragWrapperRef = useRef(null);
    const dispatch = useDispatch();
    const tabs = useSelector(state => state.treeReducer.tree.tabs);
    const activeTab = useSelector(state => state.treeReducer.activeTab);
    const [newPage, setNewPage] = useState(false);
    const [newPageName, setNewPageName] = useState('My Page');

    const handleDragStart = (e) => {
        var dragWrapper = document.createElement('div');
        var dragImage = document.createElement('div');
        dragImage.innerText = e.target.innerText;
        dragWrapper.classList.add(styles.dragwrapper);
        dragImage.classList.add(styles.dragimage);
        dragWrapper.appendChild(dragImage)
        document.body.appendChild(dragWrapper);
        e.dataTransfer.setDragImage(dragImage, -10, -10);

        draggedNode.current = e.target;
        dragWrapperRef.current = dragWrapper;
        e.target.classList.add(styles.removingitem);
    }
    const handleDragEnd = (e) => {
        e.target.classList.remove(styles.removingitem);
        if (dragWrapperRef && dragWrapperRef.current)
            dragWrapperRef.current.remove();
        dragWrapperRef.current = null;
        draggedNode.current = null;
    }
    const handleDragOver = (e) => {
        e.preventDefault();
        e.stopPropagation();
        const targetNode = e.target.getAttribute('data-id');
        if (!draggedNode.current || !targetNode || targetNode === draggedNode.current.getAttribute('data-id')) return;
        const rect = e.target.getBoundingClientRect();
        const diff = e.clientY - rect.top;
        if (!tabs.includes(Number(targetNode)) && diff <= rect.height / 3) {
            e.target.classList.remove(styles.dragbottom, styles.dragmiddle);
            e.target.classList.add(styles.dragtop);
        } else if (diff <= (rect.height * 2) / 3) {
            e.target.classList.remove(styles.dragbottom, styles.dragtop);
            e.target.classList.add(styles.dragmiddle);
        } else {
            e.target.classList.remove(styles.dragtop, styles.dragmiddle);
            e.target.classList.add(styles.dragbottom);
        }
    }
    const handleDrop = (e) => {
        if (!draggedNode.current) return;
        e.target.classList.remove(styles.dragtop, styles.dragmiddle, styles.dragbottom);
        const targetNode = e.target.getAttribute('data-id');
        const _draggedNode = draggedNode.current.getAttribute('data-id');
        if (!targetNode || targetNode === _draggedNode) return;
        const rect = e.target.getBoundingClientRect();
        const diff = e.clientY - rect.top;
        if (!tabs.includes(Number(targetNode)) && diff <= rect.height / 3) {
            dispatch(moveItem({ node: _draggedNode, referenceNode: targetNode, pos: 0 }))
        } else if (diff <= (rect.height * 2) / 3) {
            dispatch(moveItem({ node: _draggedNode, referenceNode: targetNode, pos: -1 }))
        } else {
            dispatch(moveItem({ node: _draggedNode, referenceNode: targetNode, pos: 1 }))
        }
        if (dragWrapperRef && dragWrapperRef.current)
            dragWrapperRef.current.remove();
        dragWrapperRef.current = null;
    }
    const handleDragLeave = (e) => {
        e.target.classList.remove(styles.dragtop, styles.dragmiddle, styles.dragbottom);
    }
    const handleAddPage = () => {
        if (!newPageName.length) return;
        setNewPage(false);
        setNewPageName('My Page');
        const child = createTemplate({ type: 'Tab', name: newPageName, dispatch })
        dispatch(addNode({ parent: 'tabs', child }))
        dispatch(updateActiveTab({ tab: child }))
    }

    return (
        <>
            <div className={`${styles.head} ${styles.exp}`}>
                <div>EXPLORER</div>
                <div title='add page' style={{ cursor: 'pointer' }} onClick={() => setNewPage(f => !f)}>
                    <VscNewFile size={15} color='white' />
                </div>
            </div>
            <div
                className={styles.rlist}
                onDragStart={handleDragStart}
                onDragEnd={handleDragEnd}
                onDragOver={handleDragOver}
                onDragLeave={handleDragLeave}
                onDrop={handleDrop}
            >
                {
                    newPage &&
                    <div className={styles.newpage}>
                        <input
                            size={5}
                            value={newPageName}
                            onFocus={e => e.target.select()}
                            type='text'
                            onKeyUp={e => {
                                if (e.key === 'Enter') {
                                    handleAddPage();
                                }
                            }}
                            onChange={e => {
                                setNewPageName(e.target.value);
                            }}
                        />
                        <div onClick={() => handleAddPage()}>Add Page</div>
                    </div>
                }
                {
                    tabs.map((tab, ind) => (
                        <RLItem
                            key={tab + ind + ""}
                            node={tab}
                            pleft={5}
                            myTab={tab}
                        />
                    ))
                }
            </div>
        </>
    );
}

const RecursiveList = ({ start, pleft, myTab }) => {

    const tree = useSelector(state => state.treeReducer.tree);

    return (
        <>
            {
                tree[start].map(node => (
                    <RLItem
                        key={node}
                        node={node}
                        pleft={pleft}
                        myTab={myTab}
                    />
                ))
            }
        </>
    );
}

const RLItem = ({ node, pleft, myTab }) => {

    const type = useSelector(state => state.treeReducer.dataMap[node].type);
    const unit = useSelector(state => state.treeReducer.dataMap[node].unit);
    const [active, setActive] = useState(true);
    const activeNodeId = useSelector(state => state.treeReducer.activeNodeId);
    const activeTab = useSelector(state => state.treeReducer.activeTab);
    const name = useSelector(state => state.treeReducer.dataMap[node].name);
    const { clicked, setClicked, points, setPoints } = useContextMenu();
    const dispatch = useDispatch();

    return (
        <div className={styles.rlistitem} >
            <div
                data-id={node}
                draggable={type !== "root"}
                style={{ paddingLeft: pleft + "px" }}
                className={`${styles.rliwrap} ${activeNodeId === node ? styles.activeItemClass : ''} ${unit && styles.redrag}`}
                onClick={() => {
                    if (myTab !== activeTab)
                        dispatch(updateActiveTab({ tab: myTab }))
                    if (activeNodeId !== node)
                        dispatch(updateActiveNode({ id: node }))
                }}
                onDoubleClick={() => setActive(f => !f)}
                onContextMenu={e => {
                    e.preventDefault();
                    setClicked(true);
                    setPoints({ x: e.pageX, y: e.pageY });
                    dispatch(updateActiveNode({ id: node }))
                }}

            >
                <div
                    className={styles.rli0}
                    onClick={e => { setActive(f => !f) }}
                >
                    {
                        !unit ?
                            active ?
                                <MdKeyboardArrowDown size={17} color='var(--text_0)' /> :
                                <MdKeyboardArrowRight size={17} color='var(--text_0)' /> :
                            <MdKeyboardArrowRight size={17} color='transparent' />

                    }
                </div>
                <div onClick={e => { setActive(f => !f) }} className={styles.rli0}>
                    {GetIconOfType(type)}
                    {name}
                </div>
                <div className={`${styles.grdrag} ${type === 'root' && styles.grshow}`}>
                    {
                        type === 'root' ?
                            <FaFile /> :
                            <GrDrag />
                    }
                </div>
                {
                    clicked &&
                    <ContextMenu
                        id={node}
                        points={points}
                        sidebar={true}
                        setClicked={setClicked}
                    />
                }
            </div>
            {
                active &&
                <RecursiveList start={node} pleft={pleft + 10} myTab={myTab} />
            }
        </div>
    );

}