Migrating property menu to the new menu system
This commit is contained in:
parent
4aa5a6a79b
commit
eea16494cb
3 changed files with 136 additions and 145 deletions
|
@ -4,14 +4,12 @@ import React from 'react'
|
|||
import {FormattedMessage, IntlShape, injectIntl} from 'react-intl'
|
||||
|
||||
import {BlockIcons} from '../blockIcons'
|
||||
import {MutableCommentBlock} from '../blocks/commentBlock'
|
||||
import {MutableTextBlock} from '../blocks/textBlock'
|
||||
import {BoardTree} from '../viewModel/boardTree'
|
||||
import {CardTree, MutableCardTree} from '../viewModel/cardTree'
|
||||
import mutator from '../mutator'
|
||||
import {OctoListener} from '../octoListener'
|
||||
import {OctoUtils} from '../octoUtils'
|
||||
import {PropertyMenu} from '../propertyMenu'
|
||||
import {Utils} from '../utils'
|
||||
|
||||
import MenuWrapper from '../widgets/menuWrapper'
|
||||
|
@ -22,6 +20,7 @@ import {Editable} from './editable'
|
|||
import {MarkdownEditor} from './markdownEditor'
|
||||
import ContentBlock from './contentBlock'
|
||||
import CommentsList from './commentsList'
|
||||
import PropertyMenu from './propertyMenu'
|
||||
|
||||
import './cardDetail.scss'
|
||||
|
||||
|
@ -168,43 +167,13 @@ class CardDetail extends React.Component<Props, State> {
|
|||
key={propertyTemplate.id}
|
||||
className='octo-propertyrow'
|
||||
>
|
||||
<div
|
||||
className='octo-button octo-propertyname'
|
||||
onClick={(e) => {
|
||||
const menu = PropertyMenu.shared
|
||||
menu.property = propertyTemplate
|
||||
menu.onNameChanged = (propertyName) => {
|
||||
Utils.log('menu.onNameChanged')
|
||||
mutator.renameProperty(board, propertyTemplate.id, propertyName)
|
||||
}
|
||||
|
||||
menu.onMenuClicked = async (command) => {
|
||||
switch (command) {
|
||||
case 'type-text':
|
||||
await mutator.changePropertyType(board, propertyTemplate, 'text')
|
||||
break
|
||||
case 'type-number':
|
||||
await mutator.changePropertyType(board, propertyTemplate, 'number')
|
||||
break
|
||||
case 'type-createdTime':
|
||||
await mutator.changePropertyType(board, propertyTemplate, 'createdTime')
|
||||
break
|
||||
case 'type-updatedTime':
|
||||
await mutator.changePropertyType(board, propertyTemplate, 'updatedTime')
|
||||
break
|
||||
case 'type-select':
|
||||
await mutator.changePropertyType(board, propertyTemplate, 'select')
|
||||
break
|
||||
case 'delete':
|
||||
await mutator.deleteProperty(boardTree, propertyTemplate.id)
|
||||
break
|
||||
default:
|
||||
Utils.assertFailure(`Unhandled menu id: ${command}`)
|
||||
}
|
||||
}
|
||||
menu.showAtElement(e.target as HTMLElement)
|
||||
}}
|
||||
>{propertyTemplate.name}</div>
|
||||
<MenuWrapper>
|
||||
<div className='octo-button octo-propertyname'>{propertyTemplate.name}</div>
|
||||
<PropertyMenu
|
||||
property={propertyTemplate}
|
||||
boardTree={boardTree}
|
||||
/>
|
||||
</MenuWrapper>
|
||||
{OctoUtils.propertyValueEditableElement(card, propertyTemplate)}
|
||||
</div>
|
||||
)
|
||||
|
@ -276,10 +245,6 @@ class CardDetail extends React.Component<Props, State> {
|
|||
</>
|
||||
)
|
||||
}
|
||||
|
||||
close() {
|
||||
PropertyMenu.shared.hide()
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(CardDetail)
|
||||
|
|
128
webapp/src/components/propertyMenu.tsx
Normal file
128
webapp/src/components/propertyMenu.tsx
Normal file
|
@ -0,0 +1,128 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react'
|
||||
|
||||
import {IPropertyTemplate, PropertyType} from '../blocks/board'
|
||||
import {BoardTree} from '../viewModel/boardTree'
|
||||
import {Utils} from '../utils'
|
||||
import mutator from '../mutator'
|
||||
|
||||
import Menu from '../widgets/menu'
|
||||
|
||||
type Props = {
|
||||
property: IPropertyTemplate
|
||||
boardTree: BoardTree
|
||||
}
|
||||
|
||||
type State = {
|
||||
name: string
|
||||
}
|
||||
|
||||
export default class PropertyMenu extends React.Component<Props, State> {
|
||||
private nameTextbox = React.createRef<HTMLInputElement>()
|
||||
|
||||
public shouldComponentUpdate(): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
name: (this.props.property && this.props.property.name) || '',
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
this.nameTextbox.current.focus()
|
||||
document.execCommand('selectAll', false, null)
|
||||
}
|
||||
|
||||
private typeDisplayName(type: PropertyType): string {
|
||||
switch (type) {
|
||||
case 'text': return 'Text'
|
||||
case 'number': return 'Number'
|
||||
case 'select': return 'Select'
|
||||
case 'multiSelect': return 'Multi Select'
|
||||
case 'person': return 'Person'
|
||||
case 'file': return 'File or Media'
|
||||
case 'checkbox': return 'Checkbox'
|
||||
case 'url': return 'URL'
|
||||
case 'email': return 'Email'
|
||||
case 'phone': return 'Phone'
|
||||
case 'createdTime': return 'Created Time'
|
||||
case 'createdBy': return 'Created By'
|
||||
case 'updatedTime': return 'Updated Time'
|
||||
case 'updatedBy': return 'Updated By'
|
||||
default: {
|
||||
Utils.assertFailure(`typeDisplayName, unhandled type: ${type}`)
|
||||
return type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private saveName = (): void => {
|
||||
if (this.state.name !== this.props.property.name) {
|
||||
Utils.log('menu.onNameChanged')
|
||||
mutator.renameProperty(this.props.boardTree.board, this.props.property.id, this.state.name)
|
||||
}
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
const {boardTree, property} = this.props
|
||||
const {board} = boardTree
|
||||
return (
|
||||
<Menu>
|
||||
<input
|
||||
ref={this.nameTextbox}
|
||||
type='text'
|
||||
className='menu-textbox'
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onChange={(e) => this.setState({name: e.target.value})}
|
||||
value={this.state.name}
|
||||
onBlur={this.saveName}
|
||||
onKeyDown={(e) => {
|
||||
if (e.keyCode === 13 || e.keyCode === 27) {
|
||||
this.saveName()
|
||||
e.stopPropagation()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Menu.SubMenu
|
||||
id='type'
|
||||
name={this.typeDisplayName(this.props.property.type)}
|
||||
>
|
||||
<Menu.Text
|
||||
id='text'
|
||||
name='Text'
|
||||
onClick={() => mutator.changePropertyType(board, property, 'text')}
|
||||
/>
|
||||
<Menu.Text
|
||||
id='number'
|
||||
name='Number'
|
||||
onClick={() => mutator.changePropertyType(board, property, 'number')}
|
||||
/>
|
||||
<Menu.Text
|
||||
id='select'
|
||||
name='Select'
|
||||
onClick={() => mutator.changePropertyType(board, property, 'select')}
|
||||
/>
|
||||
<Menu.Text
|
||||
id='createdTime'
|
||||
name='Created Time'
|
||||
onClick={() => mutator.changePropertyType(board, property, 'createdTime')}
|
||||
/>
|
||||
<Menu.Text
|
||||
id='updatedTime'
|
||||
name='Updated Time'
|
||||
onClick={() => mutator.changePropertyType(board, property, 'updatedTime')}
|
||||
/>
|
||||
</Menu.SubMenu>
|
||||
<Menu.Text
|
||||
id='delete'
|
||||
name='Delete'
|
||||
onClick={() => mutator.deleteProperty(boardTree, property.id)}
|
||||
/>
|
||||
</Menu>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {IPropertyTemplate, PropertyType} from './blocks/board'
|
||||
import {Menu} from './menu'
|
||||
import {Utils} from './utils'
|
||||
|
||||
class PropertyMenu extends Menu {
|
||||
static shared = new PropertyMenu()
|
||||
|
||||
property: IPropertyTemplate
|
||||
onNameChanged?: (name: string) => void
|
||||
|
||||
private nameTextbox: HTMLElement
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
const typeMenuOptions = [
|
||||
{id: 'text', name: 'Text'},
|
||||
{id: 'number', name: 'Number'},
|
||||
{id: 'select', name: 'Select'},
|
||||
{id: 'createdTime', name: 'Created Time'},
|
||||
{id: 'updatedTime', name: 'Updated Time'},
|
||||
]
|
||||
this.subMenuOptions.set('type', typeMenuOptions)
|
||||
}
|
||||
|
||||
createMenuElement(): HTMLElement {
|
||||
const menu = Utils.htmlToElement('<div class="menu noselect" style="min-width: 200px;"></div>')
|
||||
|
||||
const ul = menu.appendChild(Utils.htmlToElement('<ul class="menu-options"></ul>'))
|
||||
|
||||
const nameTextbox = ul.appendChild(Utils.htmlToElement('<li class="menu-textbox"></li>'))
|
||||
this.nameTextbox = nameTextbox
|
||||
let propertyValue = this.property ? this.property.name : ''
|
||||
nameTextbox.innerText = propertyValue
|
||||
nameTextbox.contentEditable = 'true'
|
||||
nameTextbox.onclick = (e) => {
|
||||
e.stopPropagation()
|
||||
}
|
||||
nameTextbox.onblur = () => {
|
||||
if (nameTextbox.innerText !== propertyValue) {
|
||||
propertyValue = nameTextbox.innerText
|
||||
if (this.onNameChanged) {
|
||||
this.onNameChanged(nameTextbox.innerText)
|
||||
}
|
||||
}
|
||||
}
|
||||
nameTextbox.onmouseenter = () => {
|
||||
this.hideSubMenu()
|
||||
}
|
||||
nameTextbox.onkeydown = (e) => {
|
||||
if (e.keyCode === 13 || e.keyCode === 27) {
|
||||
nameTextbox.blur()
|
||||
e.stopPropagation()
|
||||
}
|
||||
}
|
||||
|
||||
ul.appendChild(Utils.htmlToElement('<li class="menu-separator"></li>'))
|
||||
|
||||
this.appendMenuOptions(ul)
|
||||
|
||||
return menu
|
||||
}
|
||||
|
||||
showAt(left: number, top: number): void {
|
||||
this.options = [
|
||||
{id: 'type', name: this.typeDisplayName(this.property.type), type: 'submenu'},
|
||||
{id: 'delete', name: 'Delete'},
|
||||
]
|
||||
|
||||
super.showAt(left, top)
|
||||
setTimeout(() => {
|
||||
this.nameTextbox.focus()
|
||||
document.execCommand('selectAll', false, null)
|
||||
}, 20)
|
||||
}
|
||||
|
||||
private typeDisplayName(type: PropertyType): string {
|
||||
switch (type) {
|
||||
case 'text': return 'Text'
|
||||
case 'number': return 'Number'
|
||||
case 'select': return 'Select'
|
||||
case 'multiSelect': return 'Multi Select'
|
||||
case 'person': return 'Person'
|
||||
case 'file': return 'File or Media'
|
||||
case 'checkbox': return 'Checkbox'
|
||||
case 'url': return 'URL'
|
||||
case 'email': return 'Email'
|
||||
case 'phone': return 'Phone'
|
||||
case 'createdTime': return 'Created Time'
|
||||
case 'createdBy': return 'Created By'
|
||||
case 'updatedTime': return 'Updated Time'
|
||||
case 'updatedBy': return 'Updated By'
|
||||
default: {
|
||||
Utils.assertFailure(`typeDisplayName, unhandled type: ${type}`)
|
||||
return type
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {PropertyMenu}
|
Loading…
Reference in a new issue