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};
`;