import styled from "@emotion/styled"; import * as _ from "lodash"; import { inject, observer } from "mobx-react"; import React, { useRef } from "react"; import { DragDropContext, Draggable, DraggableProvided, DraggableStateSnapshot, Droppable, } from "react-beautiful-dnd"; import ReactDOM from "react-dom"; import { TiDocumentText } from "react-icons/ti"; import { v4 } from "uuid"; import { LIGHT_PRIMARY_THREE, LIGHT_SECONDARY_FOUR, LIGHT_SHADOW, } from "../../../shared/colors"; import { SlideBoard, SlideBoardItemStatus, SlideIdea, SlideObjectType, } from "../../../shared/types"; import { SlideshowStore, SocketActions, StoreProps, } from "../../platform/SlideshowStore"; import withPlatform, { PlatformProps } from "../../platform/withPlatform"; import { canvasStyleDictionary } from "./canvasStyle"; import Resizer from "./Resizer"; import { currentKeys, CurrentScreen, getCanvasPosition, } from "./windowListeners"; /* * 7.9k -> 6.6k -> 6.2k -> 7.7k -> 6.7k Tarzaned * 1.8k -> 7.8k -> 18.1k -> 25.8k -> 25.8k Tyler1 */ const portal: HTMLElement = document.createElement("div"); portal.classList.add("my-super-cool-portal"); document.body.appendChild(portal); const prefilledText = [{ type: "paragraph", children: [{ text: "" }] }]; type Props = { slideBoard: SlideBoard; boardItems: SlideIdea[]; } & PlatformProps & StoreProps; type PortalAwareDivProps = { provided: DraggableProvided; snapshot: DraggableStateSnapshot; currentSlideId: string; children: any; }; function PortalAwareDiv(props: PortalAwareDivProps) { const child = ( { if (e.button !== 1 && !(e.button === 0 && currentKeys["Space"])) { e.stopPropagation(); } }} ref={props.provided.innerRef} {...props.provided.draggableProps} {...props.provided.dragHandleProps} currentSlideId={ props.snapshot.isDragging ? props.currentSlideId : undefined } style={getStyle(props.provided.draggableProps.style, props.snapshot)} > {props.children} ); if (!props.snapshot.isDragging) { return child; } return ReactDOM.createPortal(child, portal); } function getStyle(style: any, snapshot: DraggableStateSnapshot) { if (!snapshot.isDropAnimating) { return style; } return { ...style, transitionDuration: `0.001s`, }; } // Hell of a function to write function reorderIndices( destination: { id: string; index: number } | null, draggableId: string, slideBoard: SlideBoard, boardItems: SlideIdea[], store: SlideshowStore ) { const movingBoardItem = boardItems.find( (slideIdea) => slideIdea.id === draggableId )!; const previousStatusItems = boardItems .filter((slideIdea) => { return ( slideIdea.status === movingBoardItem.status && slideIdea.id !== movingBoardItem.id ); }) .map((slideIdea) => { if (slideIdea.index > movingBoardItem.index) { return { ...slideIdea, index: slideIdea.index - 1, }; } return slideIdea; }); const newItemList = destination ? movingBoardItem.status === destination.id ? previousStatusItems : boardItems.filter((slideIdea) => { return ( slideIdea.status === destination.id && slideIdea.id !== movingBoardItem.id ); }) : []; const newStatusItems = destination ? newItemList.map((slideIdea) => { if (slideIdea.index >= destination.index) { return { ...slideIdea, index: slideIdea.index + 1, }; } return slideIdea; }) : []; const updatedItems = destination ? movingBoardItem.status === destination.id ? newStatusItems : [...newStatusItems, ...previousStatusItems] : previousStatusItems; updatedItems.map((updatedItem) => { store.emit(SocketActions.UPDATE_SLIDE_OBJECT, { id: updatedItem.id, slideId: store.currentSlide.get()!, data: updatedItem, }); }); // We're going to batch update the board items here, which is a bad idea. // Probably need a batch update layer to make things smoother if (destination) { store.emit(SocketActions.UPDATE_SLIDE_OBJECT, { id: movingBoardItem.id, slideId: store.currentSlide.get()!, data: { ...movingBoardItem, status: destination.id, index: destination.index, }, }); } else { store.emit(SocketActions.UPDATE_SLIDE_OBJECT, { id: movingBoardItem.id, slideId: store.currentSlide.get()!, data: { ...movingBoardItem, boardId: null, x: CurrentScreen.createBoardItemCanvasX, y: CurrentScreen.createBoardItemCanvasY, width: 150, height: 50, }, }); store.emit(SocketActions.UPDATE_SLIDE_OBJECT, { id: slideBoard.id, slideId: store.currentSlide.get()!, data: { ...slideBoard, boardItems: slideBoard.boardItems.filter( (boardItemId) => boardItemId !== movingBoardItem.id ), }, }); } } function SlideObjectBoard(props: Props) { const boardContainerRef = useRef(null); const currentSlideId = props.store.currentSlide.get()!; // React DND is fucking broken - it renders the elements in order that // they are presented via .map, not the actual index of the elements. const notStartedItems = props.boardItems .filter((boardItem) => { return boardItem.status === SlideBoardItemStatus.NOT_STARTED; }) .sort((a, b) => { return a.index - b.index; }); const inProgressItems = props.boardItems .filter((boardItem) => { return boardItem.status === SlideBoardItemStatus.IN_PROGRESS; }) .sort((a, b) => { return a.index - b.index; }); const completedItems = props.boardItems .filter((boardItem) => { return boardItem.status === SlideBoardItemStatus.COMPLETED; }) .sort((a, b) => { return a.index - b.index; }); return ( { props.store.setSelectedSlideElement(props.slideBoard.id); e.stopPropagation(); }} onMouseDown={(e) => { console.log("MOUSE DOWN"); console.log(e); if ( props.store.selectedSlideElement.get() === props.slideBoard.id && e.button !== 1 && !(e.button === 0 && currentKeys["Space"]) ) { const canvasPosition = getCanvasPosition(e, props.store); CurrentScreen.canvasX = canvasPosition.x; CurrentScreen.canvasY = canvasPosition.y; props.store.currentDragElement.set(props.slideBoard.id); e.stopPropagation(); } }} onMouseMove={(e) => {}} onMouseUp={(e) => {}} >
{ const newId = v4(); const newData = { status: SlideBoardItemStatus.NOT_STARTED, boardId: props.slideBoard.id, title: "", description: prefilledText, index: notStartedItems.length, type: SlideObjectType.IDEA, x: 0, y: 0, width: 50, height: 50, }; props.store.emit(SocketActions.CREATE_SLIDE_OBJECT, { id: newId, slideId: props.store.currentSlide.get()!, ...newData, }); props.store.emit(SocketActions.UPDATE_SLIDE_OBJECT, { id: props.slideBoard.id, slideId: props.store.currentSlide.get()!, data: { ...props.slideBoard, boardItems: [...props.slideBoard.boardItems, newId], }, }); e.stopPropagation(); }} > New
{ const { source, destination, draggableId } = result; reorderIndices( destination ? { id: destination.droppableId, index: destination.index, } : null, draggableId, props.slideBoard, props.boardItems, props.store ); console.log(result); }} > Not started {notStartedItems.length} {(provided, snapshot) => { return ( {notStartedItems.map((boardItem) => ( {(provided, snapshot) => { return ( props.store.showBoardModal.set( boardItem.id ) } > {!!boardItem.description && !_.isEqual( boardItem.description, prefilledText ) && ( )} {boardItem.title || "Untitled"} ); }} ))} ); }} In progress {inProgressItems.length} {(provided, snapshot) => { return ( {inProgressItems.map((boardItem) => { return ( {(provided, snapshot) => { return ( props.store.showBoardModal.set( boardItem.id ) } > {!!boardItem.description && !_.isEqual( boardItem.description, prefilledText ) && ( )} {boardItem.title || "Untitled"} ); }} ); })} ); }} Completed {completedItems.length} {(provided, snapshot) => { return ( {completedItems.map((boardItem) => ( {(provided, snapshot) => { return ( props.store.showBoardModal.set( boardItem.id ) } > {!!boardItem.description && !_.isEqual( boardItem.description, prefilledText ) && ( )} {boardItem.title || "Untitled"} ); }} ))} ); }}
); } export default withPlatform(inject("store")(observer(SlideObjectBoard))); const Count = styled.div` margin-left: 8px; font-size: 14px; color: ${LIGHT_SECONDARY_FOUR}; `; const PortalDiv = styled.div<{ currentSlideId?: string }>` ${(props) => props.currentSlideId && `transform: scale(${canvasStyleDictionary[props.currentSlideId]})`} `; const CreateNewButton = styled.div` user-select: none; cursor: pointer; margin-left: auto; display: flex; align-items: center; justify-content: center; white-space: nowrap; border-radius: 3px; height: 24px; color: white; line-height: 1.2; background: rgb(46, 170, 220); padding-left: 8px; padding-right: 8px; font-size: 14px; font-weight: 500; transition: 0.12s ease-in-out; :hover { background: rgb(36, 160, 210); } `; const NotStartedTitle = styled.div` display: inline-flex; align-items: center; flex-shrink: 1; min-width: 0px; height: 18px; border-radius: 3px; padding-left: 6px; padding-right: 6px; font-size: 14px; line-height: 120%; color: rgb(55, 53, 47); background: rgba(255, 0, 26, 0.2); margin: 0px; `; const InProgressTitle = styled.div` display: inline-flex; align-items: center; flex-shrink: 1; min-width: 0px; height: 18px; border-radius: 3px; padding-left: 6px; padding-right: 6px; font-size: 14px; line-height: 120%; color: rgb(55, 53, 47); background: rgba(233, 168, 0, 0.2); rgba(0, 135, 107, 0.2) margin: 0px; `; const CompletedTitle = styled.div` display: inline-flex; align-items: center; flex-shrink: 1; min-width: 0px; height: 18px; border-radius: 3px; padding-left: 6px; padding-right: 6px; font-size: 14px; line-height: 120%; color: rgb(55, 53, 47); background: rgba(0, 135, 107, 0.2); margin: 0px; `; const BoardTitle = styled.div` display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; width: 100%; `; const BoardItem = styled.div<{ empty: boolean }>` background-color: ${LIGHT_PRIMARY_THREE}; display: flex; align-items: center; width: 244px; height: 40px; border-radius: 4px; margin-bottom: 8px; box-shadow: 0px 0px 0px 1px rgba(15, 15, 15, 0.1), 0px 2px 4px rgba(15, 15, 15, 0.1); padding-left: 8px; padding-right: 8px; ${(props) => props.empty && `color: ${LIGHT_SECONDARY_FOUR};`} `; const BoardColumn = styled.div` display: flex; flex-direction: column; width: 260px; height: 100%; `; const DragDropContainer = styled.div` height: 100%; width: 100%; `; const Break = styled.div` height: 4px; `; const Column = styled.div` display: flex; flex-direction: column; margin-right: 8px; :last-of-type { margin-right: 0px; } `; const ColumnHeader = styled.div` height: 44px; display: flex; align-items: center; `; const Board = styled.div` display: flex; width: 100%; height: 100%; border-top: 1px solid rgba(55, 53, 47, 0.16); `; const HeaderContainer = styled.div` display: flex; height: 42px; padding-bottom: 2px; align-items: center; `; const Header = styled.div` display: flex; width: 100%; align-items: center; `; const Title = styled.div``; const BoardContainer = styled.div` position: relative; display: flex; align-items: center; justify-content: center; cursor: default; width: 100%; height: 100%; background-color: ${LIGHT_PRIMARY_THREE}; border-radius: 8px; box-shadow: 0px 1px 5px 0px ${LIGHT_SHADOW}; `; const ContentContainer = styled.div` display: flex; flex-direction: column; width: calc(100% - 40px); height: calc(100% - 40px); margin: 20px; background-color: ${LIGHT_PRIMARY_THREE}; `;