From 45a92636cbe82f7a615d0a921fd050153a906dac Mon Sep 17 00:00:00 2001 From: Jordan Knott Date: Thu, 16 Jul 2020 21:14:26 -0500 Subject: [PATCH] change: add loading state to project board --- frontend/src/Projects/Project/Board/index.tsx | 67 ++++++++++--- frontend/src/Projects/Project/index.tsx | 2 + .../src/shared/components/Button/index.tsx | 30 ++++-- .../shared/components/EmptyBoard/index.tsx | 94 +++++++++++++++++++ .../shared/components/TaskDetails/index.tsx | 60 +++++++----- 5 files changed, 207 insertions(+), 46 deletions(-) create mode 100644 frontend/src/shared/components/EmptyBoard/index.tsx diff --git a/frontend/src/Projects/Project/Board/index.tsx b/frontend/src/Projects/Project/Board/index.tsx index 271a59e..fa9cf7e 100644 --- a/frontend/src/Projects/Project/Board/index.tsx +++ b/frontend/src/Projects/Project/Board/index.tsx @@ -2,9 +2,9 @@ import React, { useState, useRef, useContext, useEffect } from 'react'; import { MENU_TYPES } from 'shared/components/TopNavbar'; import updateApolloCache from 'shared/utils/cache'; import GlobalTopNavbar, { ProjectPopup } from 'App/TopNavbar'; +import LabelManagerEditor from '../LabelManagerEditor'; import styled, { css } from 'styled-components/macro'; import { Bolt, ToggleOn, Tags, CheckCircle, Sort, Filter } from 'shared/icons'; -import LabelManagerEditor from '../LabelManagerEditor'; import { usePopup, Popup } from 'shared/components/PopupMenu'; import { useParams, Route, useRouteMatch, useHistory, RouteComponentProps, useLocation } from 'react-router-dom'; import { @@ -47,6 +47,7 @@ import DueDateManager from 'shared/components/DueDateManager'; import UserIDContext from 'App/context'; import LabelManager from 'shared/components/PopupMenu/LabelManager'; import LabelEditor from 'shared/components/PopupMenu/LabelEditor'; +import EmptyBoard from 'shared/components/EmptyBoard'; const ProjectBar = styled.div` display: flex; @@ -103,12 +104,18 @@ const initialQuickCardEditorState: QuickCardEditorState = { }; type ProjectBoardProps = { - onCardLabelClick: () => void; - cardLabelVariant: CardLabelVariant; - projectID: string; + onCardLabelClick?: () => void; + cardLabelVariant?: CardLabelVariant; + projectID?: string; + loading?: boolean; }; -const ProjectBoard: React.FC = ({ projectID, onCardLabelClick, cardLabelVariant }) => { +const ProjectBoard: React.FC = ({ + projectID, + onCardLabelClick, + cardLabelVariant, + loading: isLoading = false, +}) => { const [assignTask] = useAssignTaskMutation(); const [unassignTask] = useUnassignTaskMutation(); const $labelsRef = useRef(null); @@ -170,7 +177,7 @@ const ProjectBoard: React.FC = ({ projectID, onCardLabelClick const [updateTaskGroupName] = useUpdateTaskGroupNameMutation({}); const { loading, data } = useFindProjectQuery({ - variables: { projectId: projectID }, + variables: { projectId: projectID ?? '' }, }); const [updateTaskDueDate] = useUpdateTaskDueDateMutation(); @@ -256,7 +263,7 @@ const ProjectBoard: React.FC = ({ projectID, onCardLabelClick }; const onCreateList = (listName: string) => { - if (data) { + if (data && projectID) { const [lastColumn] = data.findProject.taskGroups.sort((a, b) => a.position - b.position).slice(-1); let position = 65535; if (lastColumn) { @@ -266,8 +273,42 @@ const ProjectBoard: React.FC = ({ projectID, onCardLabelClick } }; - if (loading) { - return loading; + if (loading || isLoading) { + return ( + <> + + + + + All Tasks + + + + Filter + + + + Sort + + + + + + Labels + + + + Fields + + + + Rules + + + + + + ); } if (data) { labelsRef.current = data.findProject.labels; @@ -323,7 +364,7 @@ const ProjectBoard: React.FC = ({ projectID, onCardLabelClick taskLabels={null} labelColors={data.labelColors} labels={labelsRef} - projectID={projectID} + projectID={projectID ?? ''} />, ); }} @@ -345,8 +386,8 @@ const ProjectBoard: React.FC = ({ projectID, onCardLabelClick onTaskClick={task => { history.push(`${match.url}/c/${task.id}`); }} - onCardLabelClick={onCardLabelClick} - cardLabelVariant={cardLabelVariant} + onCardLabelClick={onCardLabelClick ?? (() => {})} + cardLabelVariant={cardLabelVariant ?? 'large'} onTaskDrop={(droppedTask, previousTaskGroupID) => { updateTaskLocation({ variables: { @@ -475,7 +516,7 @@ const ProjectBoard: React.FC = ({ projectID, onCardLabelClick labelColors={data.labelColors} labels={labelsRef} taskLabels={taskLabelsRef} - projectID={projectID} + projectID={projectID ?? ''} />, ); }} diff --git a/frontend/src/Projects/Project/index.tsx b/frontend/src/Projects/Project/index.tsx index 654d533..4b778a2 100644 --- a/frontend/src/Projects/Project/index.tsx +++ b/frontend/src/Projects/Project/index.tsx @@ -39,6 +39,7 @@ import Input from 'shared/components/Input'; import Member from 'shared/components/Member'; import Board from './Board'; import Details from './Details'; +import EmptyBoard from 'shared/components/EmptyBoard'; const CARD_LABEL_VARIANT_STORAGE_KEY = 'card_label_variant'; @@ -202,6 +203,7 @@ const Project = () => { return ( <> {}} name="" projectID={null} /> + ); } diff --git a/frontend/src/shared/components/Button/index.tsx b/frontend/src/shared/components/Button/index.tsx index 4cfd0be..27b96a8 100644 --- a/frontend/src/shared/components/Button/index.tsx +++ b/frontend/src/shared/components/Button/index.tsx @@ -1,11 +1,11 @@ import React, { useRef } from 'react'; import styled, { css } from 'styled-components/macro'; -const Text = styled.span<{ fontSize: string }>` +const Text = styled.span<{ fontSize: string; justifyTextContent: string }>` position: relative; display: flex; align-items: center; - justify-content: center; + justify-content: ${props => props.justifyTextContent}; transition: all 0.2s ease; font-size: ${props => props.fontSize}; color: rgba(${props => props.theme.colors.text.secondary}); @@ -112,6 +112,7 @@ type ButtonProps = { type?: 'button' | 'submit'; className?: string; onClick?: ($target: React.RefObject) => void; + justifyTextContent?: string; }; const Button: React.FC = ({ @@ -120,6 +121,7 @@ const Button: React.FC = ({ color = 'primary', variant = 'filled', type = 'button', + justifyTextContent = 'center', onClick, className, children, @@ -134,7 +136,9 @@ const Button: React.FC = ({ case 'filled': return ( - {children} + + {children} + ); case 'outline': @@ -147,13 +151,17 @@ const Button: React.FC = ({ disabled={disabled} color={color} > - {children} + + {children} + ); case 'flat': return ( - {children} + + {children} + ); case 'lineDown': @@ -166,7 +174,9 @@ const Button: React.FC = ({ disabled={disabled} color={color} > - {children} + + {children} + ); @@ -180,13 +190,17 @@ const Button: React.FC = ({ disabled={disabled} color={color} > - {children} + + {children} + ); case 'relief': return ( - {children} + + {children} + ); default: diff --git a/frontend/src/shared/components/EmptyBoard/index.tsx b/frontend/src/shared/components/EmptyBoard/index.tsx new file mode 100644 index 0000000..942a4b6 --- /dev/null +++ b/frontend/src/shared/components/EmptyBoard/index.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import styled, { keyframes } from 'styled-components/macro'; +import { mixin } from 'shared/utils/styles'; + +export const BoardContainer = styled.div` + position: relative; + overflow-y: auto; + outline: none; + flex-grow: 1; +`; + +export const BoardWrapper = styled.div` + display: flex; + + user-select: none; + white-space: nowrap; + margin-bottom: 8px; + overflow-x: auto; + overflow-y: hidden; + padding-bottom: 8px; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; +`; +export const Container = styled.div` + width: 272px; + margin: 0 4px; + height: 100%; + box-sizing: border-box; + display: inline-block; + vertical-align: top; + white-space: nowrap; +`; + +export const defaultBaseColor = '#10163a'; + +export const defaultHighlightColor = mixin.lighten('#10163a', 0.25); + +export const skeletonKeyframes = keyframes` + 0% { + background-position: -200px 0; + } + 100% { + background-position: calc(200px + 100%) 0; + } + `; + +export const Wrapper = styled.div` + // background-color: #ebecf0; + // background: rgb(244, 245, 247); + min-height: 120px; + opacity: 0.8; + background: #10163a; + color: #c2c6dc; + + border-radius: 5px; + box-sizing: border-box; + display: flex; + flex-direction: column; + max-height: 100%; + position: relative; + white-space: normal; + + background-image: linear-gradient(90deg, ${defaultBaseColor}, ${defaultHighlightColor}, ${defaultBaseColor}); + background-size: 200px 100%; + background-repeat: no-repeat; + + animation: ${skeletonKeyframes} 1.2s ease-in-out infinite; +`; + +const EmptyBoard: React.FC = () => { + return ( + + + + + + + + + + + + + + + + + ); +}; + +export default EmptyBoard; diff --git a/frontend/src/shared/components/TaskDetails/index.tsx b/frontend/src/shared/components/TaskDetails/index.tsx index d8a4a11..07b43cc 100644 --- a/frontend/src/shared/components/TaskDetails/index.tsx +++ b/frontend/src/shared/components/TaskDetails/index.tsx @@ -1,5 +1,5 @@ -import React, {useState, useRef, useEffect} from 'react'; -import {Bin, Cross, Plus} from 'shared/icons'; +import React, { useState, useRef, useEffect } from 'react'; +import { Bin, Cross, Plus } from 'shared/icons'; import useOnOutsideClick from 'shared/hooks/onOutsideClick'; import ReactMarkdown from 'react-markdown'; @@ -9,7 +9,7 @@ import { getNewDraggablePosition, getAfterDropDraggableList, } from 'shared/utils/draggables'; -import {DragDropContext, Droppable, Draggable, DropResult} from 'react-beautiful-dnd'; +import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd'; import TaskAssignee from 'shared/components/TaskAssignee'; import moment from 'moment'; @@ -54,7 +54,7 @@ import { MetaDetailTitle, MetaDetailContent, } from './Styles'; -import Checklist, {ChecklistItem, ChecklistItems} from '../Checklist'; +import Checklist, { ChecklistItem, ChecklistItems } from '../Checklist'; import styled from 'styled-components'; const ChecklistContainer = styled.div``; @@ -69,7 +69,7 @@ type TaskLabelProps = { onClick: ($target: React.RefObject) => void; }; -const TaskLabelItem: React.FC = ({label, onClick}) => { +const TaskLabelItem: React.FC = ({ label, onClick }) => { const $label = useRef(null); return ( = ({label, onClick}) => { ); }; -const TaskContent: React.FC = ({description, onEditContent}) => { +const TaskContent: React.FC = ({ description, onEditContent }) => { return description === '' ? ( Add a more detailed description ) : ( - - - - ); + + + + ); }; type DetailsEditorProps = { @@ -214,7 +214,7 @@ const TaskDetails: React.FC = ({ onOpenAddLabelPopup(task, $target); }; - const onDragEnd = ({draggableId, source, destination, type}: DropResult) => { + const onDragEnd = ({ draggableId, source, destination, type }: DropResult) => { if (typeof destination === 'undefined') return; if (!isPositionChanged(source, destination)) return; @@ -233,7 +233,7 @@ const TaskDetails: React.FC = ({ }; beforeDropDraggables = getSortedDraggables( task.checklists.map(checklist => { - return {id: checklist.id, position: checklist.position}; + return { id: checklist.id, position: checklist.position }; }), ); if (droppedDraggable === null || beforeDropDraggables === null) { @@ -249,9 +249,9 @@ const TaskDetails: React.FC = ({ const newPosition = getNewDraggablePosition(afterDropDraggables, destination.index); console.log(droppedGroup); console.log(`positiion: ${newPosition}`); - onChecklistDrop({...droppedGroup, position: newPosition}); + onChecklistDrop({ ...droppedGroup, position: newPosition }); } else { - throw {error: 'task group can not be found'}; + throw { error: 'task group can not be found' }; } } else { const targetChecklist = task.checklists.findIndex( @@ -266,7 +266,7 @@ const TaskDetails: React.FC = ({ }; beforeDropDraggables = getSortedDraggables( task.checklists[targetChecklist].items.map(item => { - return {id: item.id, position: item.position}; + return { id: item.id, position: item.position }; }), ); if (droppedDraggable === null || beforeDropDraggables === null) { @@ -379,8 +379,8 @@ const TaskDetails: React.FC = ({ }} /> ) : ( - - )} + + )} {dropProvided => ( @@ -438,7 +438,9 @@ const TaskDetails: React.FC = ({ complete={item.complete} onDeleteItem={onDeleteItem} onChangeName={onChangeItemName} - onToggleItem={(itemID, complete) => onToggleChecklistItem(item.id, complete)} + onToggleItem={(itemID, complete) => + onToggleChecklistItem(item.id, complete) + } /> )} @@ -461,15 +463,23 @@ const TaskDetails: React.FC = ({ ADD TO CARD - onToggleTaskComplete(task)}> + onToggleTaskComplete(task)}> {task.complete ? 'Mark Incomplete' : 'Mark Complete'} - onAddMember($target)}>Members - onAddLabel($target)}>Labels - onAddChecklist($target)}>Checklist - onOpenDueDatePopop(task, $target)}>Due Date - Attachment - Cover + onAddMember($target)}> + Members + + onAddLabel($target)}> + Labels + + onAddChecklist($target)}> + Checklist + + onOpenDueDatePopop(task, $target)}> + Due Date + + Attachment + Cover