diff --git a/webapp/src/components/cardDetail.tsx b/webapp/src/components/cardDetail.tsx index d1a6268f6..8aabfdcab 100644 --- a/webapp/src/components/cardDetail.tsx +++ b/webapp/src/components/cardDetail.tsx @@ -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 { key={propertyTemplate.id} className='octo-propertyrow' > -
{ - 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}
+ +
{propertyTemplate.name}
+ +
{OctoUtils.propertyValueEditableElement(card, propertyTemplate)} ) @@ -276,10 +245,6 @@ class CardDetail extends React.Component { ) } - - close() { - PropertyMenu.shared.hide() - } } export default injectIntl(CardDetail) diff --git a/webapp/src/components/propertyMenu.tsx b/webapp/src/components/propertyMenu.tsx new file mode 100644 index 000000000..e3096668d --- /dev/null +++ b/webapp/src/components/propertyMenu.tsx @@ -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 { + private nameTextbox = React.createRef() + + 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 ( + + 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() + } + }} + /> + + mutator.changePropertyType(board, property, 'text')} + /> + mutator.changePropertyType(board, property, 'number')} + /> + mutator.changePropertyType(board, property, 'select')} + /> + mutator.changePropertyType(board, property, 'createdTime')} + /> + mutator.changePropertyType(board, property, 'updatedTime')} + /> + + mutator.deleteProperty(boardTree, property.id)} + /> + + ) + } +} diff --git a/webapp/src/propertyMenu.ts b/webapp/src/propertyMenu.ts deleted file mode 100644 index 2baefed23..000000000 --- a/webapp/src/propertyMenu.ts +++ /dev/null @@ -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('') - - const ul = menu.appendChild(Utils.htmlToElement('')) - - const nameTextbox = ul.appendChild(Utils.htmlToElement('')) - 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('')) - - 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}