DND in progress
This commit is contained in:
parent
4112db4626
commit
d009f80d7c
4 changed files with 149 additions and 85 deletions
|
@ -24,12 +24,74 @@ import BlackCheckboxOutline from '../../widgets/icons/blackCheckboxOutline'
|
|||
|
||||
import ShareBoardDialog from './shareBoard'
|
||||
|
||||
const valueCategoriesInitialValue: StatusCategory[] = [
|
||||
{
|
||||
id: 'category_id_1',
|
||||
title: 'Not Started',
|
||||
options: [
|
||||
{id: 'status_id_1', value: 'Pending Design', color: 'propColorPurple'},
|
||||
{id: 'status_id_2', value: 'TODO', color: 'propColorYellow'},
|
||||
{id: 'status_id_3', value: 'Pending Specs', color: 'propColorGray'},
|
||||
],
|
||||
emptyState: {
|
||||
icon: (<BlackCheckboxOutline/>),
|
||||
color: '--sys-dnd-indicator-rgb',
|
||||
text: (
|
||||
<FormattedMessage
|
||||
id='statusProperty.configDialog.todo.emptyText'
|
||||
defaultMessage='Drag statuses here to consider tasks with these statuses “Not Started”'
|
||||
/>
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'category_id_2',
|
||||
title: 'In progress',
|
||||
options: [
|
||||
{id: 'status_id_4', value: 'In Progress', color: 'propColorBrown'},
|
||||
{id: 'status_id_5', value: 'In Review', color: 'propColorRed'},
|
||||
{id: 'status_id_6', value: 'In QA', color: 'propColorPink'},
|
||||
{id: 'status_id_7', value: 'Awaiting Cherrypick', color: 'propColorOrange'},
|
||||
],
|
||||
emptyState: {
|
||||
icon: (<ClockOutline/>),
|
||||
color: '--away-indicator-rgb',
|
||||
text: (
|
||||
<FormattedMessage
|
||||
id='statusProperty.configDialog.inProgress.emptyText'
|
||||
defaultMessage='Drag statuses here to consider tasks with these statuses “in progress”'
|
||||
/>
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'category_id_3',
|
||||
title: 'Completed',
|
||||
options: [
|
||||
{id: 'status_id_20', value: 'Done', color: 'propColorPink'},
|
||||
{id: 'status_id_21', value: 'Branch Cut', color: 'propColorGreen'},
|
||||
{id: 'status_id_22', value: 'Released', color: 'propColorDefault'},
|
||||
],
|
||||
emptyState: {
|
||||
icon: (<CheckIcon/>),
|
||||
color: '--online-indicator-rgb',
|
||||
text: (
|
||||
<FormattedMessage
|
||||
id='statusProperty.configDialog.complete.emptyText'
|
||||
defaultMessage='Drag statuses here to consider tasks with these statuses ”Done”'
|
||||
/>
|
||||
),
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
type Props = {
|
||||
enableSharedBoards: boolean
|
||||
}
|
||||
const ShareBoardButton = (props: Props) => {
|
||||
const [showShareDialog, setShowShareDialog] = useState(false)
|
||||
const board = useAppSelector(getCurrentBoard)
|
||||
const [valueCategories, setValueCategories] = useState<StatusCategory[]>(valueCategoriesInitialValue)
|
||||
|
||||
const iconForBoardType = () => {
|
||||
if (board.type === BoardTypeOpen) {
|
||||
|
@ -38,67 +100,6 @@ const ShareBoardButton = (props: Props) => {
|
|||
return <LockOutline/>
|
||||
}
|
||||
|
||||
const valueCategories: StatusCategory[] = [
|
||||
{
|
||||
id: 'category_id_1',
|
||||
title: 'Not Started',
|
||||
options: [
|
||||
{id: 'status_id_1', value: 'Pending Design', color: 'propColorPurple'},
|
||||
{id: 'status_id_2', value: 'TODO', color: 'propColorYellow'},
|
||||
{id: 'status_id_3', value: 'Pending Specs', color: 'propColorGray'},
|
||||
],
|
||||
emptyState: {
|
||||
icon: (<BlackCheckboxOutline/>),
|
||||
color: '--sys-dnd-indicator-rgb',
|
||||
text: (
|
||||
<FormattedMessage
|
||||
id='statusProperty.configDialog.todo.emptyText'
|
||||
defaultMessage='Drag statuses here to consider tasks with these statuses “Not Started”'
|
||||
/>
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'category_id_2',
|
||||
title: 'In progress',
|
||||
options: [
|
||||
{id: 'status_id_4', value: 'In Progress', color: 'propColorBrown'},
|
||||
{id: 'status_id_5', value: 'In Review', color: 'propColorRed'},
|
||||
{id: 'status_id_6', value: 'In QA', color: 'propColorPink'},
|
||||
{id: 'status_id_7', value: 'Awaiting Cherrypick', color: 'propColorOrange'},
|
||||
],
|
||||
emptyState: {
|
||||
icon: (<ClockOutline/>),
|
||||
color: '--away-indicator-rgb',
|
||||
text: (
|
||||
<FormattedMessage
|
||||
id='statusProperty.configDialog.inProgress.emptyText'
|
||||
defaultMessage='Drag statuses here to consider tasks with these statuses “in progress”'
|
||||
/>
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'category_id_3',
|
||||
title: 'Completed',
|
||||
options: [
|
||||
{id: 'status_id_20', value: 'Done', color: 'propColorPink'},
|
||||
{id: 'status_id_21', value: 'Branch Cut', color: 'propColorGreen'},
|
||||
{id: 'status_id_22', value: 'Released', color: 'propColorDefault'},
|
||||
],
|
||||
emptyState: {
|
||||
icon: (<CheckIcon/>),
|
||||
color: '--online-indicator-rgb',
|
||||
text: (
|
||||
<FormattedMessage
|
||||
id='statusProperty.configDialog.complete.emptyText'
|
||||
defaultMessage='Drag statuses here to consider tasks with these statuses ”Done”'
|
||||
/>
|
||||
),
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className='ShareBoardButton'>
|
||||
<Button
|
||||
|
@ -127,7 +128,7 @@ const ShareBoardButton = (props: Props) => {
|
|||
<EditStatusPropertyDialog
|
||||
valueCategories={valueCategories}
|
||||
onClose={() => setShowShareDialog(false)}
|
||||
onUpdate={(updatedValue) => console.log(updatedValue)}
|
||||
onUpdate={(updatedValue) => setValueCategories(updatedValue)}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useEffect, useState} from 'react'
|
||||
import React, {useCallback, useEffect, useState} from 'react'
|
||||
import {FormattedMessage} from 'react-intl'
|
||||
|
||||
import {useCallback} from 'preact/hooks'
|
||||
|
||||
import {DragDropContext, Droppable, Draggable} from 'react-beautiful-dnd'
|
||||
import {DragDropContext, Droppable, Draggable, DropReason, DropResult} from 'react-beautiful-dnd'
|
||||
|
||||
import PlusIcon from '../../../widgets/icons/plus'
|
||||
|
||||
|
@ -14,11 +12,12 @@ import InfoIcon from '../../../widgets/icons/info'
|
|||
|
||||
import './editStatusDialog.scss'
|
||||
import ActionDialog from '../../actionDialog/actionDialog'
|
||||
import Label from '../../../widgets/label'
|
||||
import {IPropertyOption} from '../../../blocks/board'
|
||||
import DragHandle from '../../../widgets/icons/dragHandle'
|
||||
import EditIcon from '../../../widgets/icons/edit'
|
||||
|
||||
import {IDType, Utils} from '../../../utils'
|
||||
|
||||
import EditableLabel from './editableLabel/editableLabel'
|
||||
|
||||
export type StatusCategoryEmptyState = {
|
||||
|
@ -29,6 +28,7 @@ export type StatusCategoryEmptyState = {
|
|||
|
||||
export type EditablePropertyOption = IPropertyOption & {
|
||||
editing?: boolean
|
||||
focused?: boolean
|
||||
}
|
||||
|
||||
export type StatusCategory = {
|
||||
|
@ -46,9 +46,9 @@ type Props = {
|
|||
|
||||
const EditStatusPropertyDialog = (props: Props): JSX.Element => {
|
||||
const [valueCategories, setValueCategories] = useState<StatusCategory[]>([])
|
||||
const [focusedValueID, setFocusedValueID] = useState<string>()
|
||||
|
||||
useEffect(() => {
|
||||
console.log('setting: ' + props.valueCategories.length)
|
||||
setValueCategories(props.valueCategories)
|
||||
}, [props.valueCategories])
|
||||
|
||||
|
@ -59,11 +59,33 @@ const EditStatusPropertyDialog = (props: Props): JSX.Element => {
|
|||
/>
|
||||
)
|
||||
|
||||
const generateValueRow = (option: EditablePropertyOption, index: number): JSX.Element => {
|
||||
const handleAddNewValue = (statusCategoryID: string, newOptionValue: IPropertyOption): void => {
|
||||
const categoryIndex = valueCategories.findIndex((valueCategory) => valueCategory.id === statusCategoryID)
|
||||
if (categoryIndex < 0) {
|
||||
Utils.logError(`category with ID: ${statusCategoryID} not found`)
|
||||
return
|
||||
}
|
||||
|
||||
const valueIndex = valueCategories[categoryIndex].options.findIndex((option) => option.id === newOptionValue.id)
|
||||
if (valueIndex < 0) {
|
||||
Utils.logError(`Value with ID ${newOptionValue.id} not found`)
|
||||
return
|
||||
}
|
||||
|
||||
const updatedValueCategories = [...valueCategories]
|
||||
updatedValueCategories[categoryIndex].options[valueIndex] = newOptionValue
|
||||
|
||||
setFocusedValueID('')
|
||||
setValueCategories(updatedValueCategories)
|
||||
props.onUpdate(updatedValueCategories)
|
||||
}
|
||||
|
||||
const generateValueRow = (categoryID: string, option: EditablePropertyOption, index: number): JSX.Element => {
|
||||
return (
|
||||
<Draggable
|
||||
draggableId={option.id}
|
||||
index={index}
|
||||
key={index}
|
||||
>
|
||||
{(provided) => (
|
||||
<div
|
||||
|
@ -80,7 +102,9 @@ const EditStatusPropertyDialog = (props: Props): JSX.Element => {
|
|||
</div>
|
||||
<EditableLabel
|
||||
option={option}
|
||||
editing={option.editing}
|
||||
editing={option.id === focusedValueID}
|
||||
focus={option.id === focusedValueID}
|
||||
onBlur={(newOptionValue: IPropertyOption) => handleAddNewValue(categoryID, newOptionValue)}
|
||||
/>
|
||||
<div className={`colorEditor ${option.color} withBorder`}/>
|
||||
<EditIcon/>
|
||||
|
@ -110,27 +134,45 @@ const EditStatusPropertyDialog = (props: Props): JSX.Element => {
|
|||
}
|
||||
|
||||
const handleAddCategoryValue = (categoryID: string) => {
|
||||
console.log('asdasd')
|
||||
const categoryIndex = valueCategories.findIndex((valueCategory) => valueCategory.id === categoryID)
|
||||
if (categoryIndex < 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const newOption: EditablePropertyOption = {
|
||||
id: '',
|
||||
id: Utils.createGuid(IDType.None),
|
||||
value: '',
|
||||
color: 'propColorOrange',
|
||||
editing: true,
|
||||
}
|
||||
|
||||
const updatedValueCategories = [...valueCategories]
|
||||
updatedValueCategories[categoryIndex].options.unshift(newOption)
|
||||
|
||||
setFocusedValueID(newOption.id)
|
||||
setValueCategories(updatedValueCategories)
|
||||
}
|
||||
|
||||
const onDragEndHandler = () => {}
|
||||
const onDragEndHandler = useCallback(async (result: DropResult) => {
|
||||
const {destination, source, type} = result
|
||||
|
||||
console.log(valueCategories)
|
||||
if (type !== 'statusCategory' || !destination) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`destination: ${destination} source: ${source} type: ${type}`)
|
||||
|
||||
const updatedValues = Array.from(valueCategories)
|
||||
|
||||
const sourceCategoryIndex = updatedValues.findIndex((valueCategory) => valueCategory.id === source.droppableId)
|
||||
const destinationCategoryIndex = updatedValues.findIndex((valueCategory) => valueCategory.id === destination.droppableId)
|
||||
|
||||
const draggedObject = valueCategories[sourceCategoryIndex].options[source.index]
|
||||
|
||||
updatedValues[sourceCategoryIndex].options.splice(source.index, 1)
|
||||
updatedValues[destinationCategoryIndex].options.splice(destination.index, 0, draggedObject)
|
||||
|
||||
setValueCategories(updatedValues)
|
||||
}, [valueCategories])
|
||||
|
||||
return (
|
||||
<ActionDialog
|
||||
|
@ -165,7 +207,10 @@ const EditStatusPropertyDialog = (props: Props): JSX.Element => {
|
|||
<PlusIcon/>
|
||||
</div>
|
||||
</div>
|
||||
<Droppable droppableId={`categorySwimlane_${valueCategory.id}`}>
|
||||
<Droppable
|
||||
type='statusCategory'
|
||||
droppableId={valueCategory.id}
|
||||
>
|
||||
{(provided) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
|
@ -179,7 +224,7 @@ const EditStatusPropertyDialog = (props: Props): JSX.Element => {
|
|||
}
|
||||
{
|
||||
valueCategory.options.length > 0 &&
|
||||
valueCategory.options.map((option, index) => generateValueRow(option, index))
|
||||
valueCategory.options.map((option, index) => generateValueRow(valueCategory.id, option, index))
|
||||
}
|
||||
</div>
|
||||
{provided.placeholder}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
.Label {
|
||||
.EditableLabel {
|
||||
input {
|
||||
border: none;
|
||||
background: none;
|
||||
border-bottom: 1px solid;
|
||||
border-radius: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react'
|
||||
import React, {useState} from 'react'
|
||||
|
||||
import Label from '../../../../widgets/label'
|
||||
|
||||
|
@ -11,23 +11,40 @@ import './editableLabel.scss'
|
|||
type Props = {
|
||||
option: IPropertyOption
|
||||
editing?: boolean
|
||||
focus?: boolean
|
||||
onBlur?: (newOptionValue: IPropertyOption) => void
|
||||
}
|
||||
|
||||
const EditableLabel = (props: Props): JSX.Element => {
|
||||
const {option} = props
|
||||
const [value, setValue] = useState<string>(props.option.value)
|
||||
|
||||
const displayValue = (<span>{option.value}</span>)
|
||||
const handleOnBlur = () => {
|
||||
const newOptionValue = {
|
||||
...props.option,
|
||||
value,
|
||||
}
|
||||
|
||||
if (props.onBlur) {
|
||||
props.onBlur(newOptionValue)
|
||||
}
|
||||
}
|
||||
|
||||
const displayValue = (<span>{props.option.value}</span>)
|
||||
const editValue = (
|
||||
<input
|
||||
defaultValue={props.option.value}
|
||||
autoFocus={props.focus}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
onBlur={handleOnBlur}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<Label
|
||||
key={option.id}
|
||||
color={option.color}
|
||||
title={option.value}
|
||||
key={props.option.id}
|
||||
className='EditableLabel'
|
||||
color={props.option.color}
|
||||
title={props.option.value}
|
||||
>
|
||||
{ props.editing ? editValue : displayValue }
|
||||
</Label>
|
||||
|
|
Loading…
Reference in a new issue