diff --git a/src/client/components/boardComponent.tsx b/src/client/components/boardComponent.tsx index a2ead7735..9ea23b6c1 100644 --- a/src/client/components/boardComponent.tsx +++ b/src/client/components/boardComponent.tsx @@ -6,16 +6,87 @@ import { IPropertyOption } from "../board" import { BoardTree } from "../boardTree" import { CardFilter } from "../cardFilter" import { Constants } from "../constants" -import { Menu } from "../menu" +import Menu from "../widgets/menu" +import { Menu as OldMenu } from "../menu" import { Mutator } from "../mutator" import { IBlock, IPageController } from "../octoTypes" import { OctoUtils } from "../octoUtils" import { Utils } from "../utils" import { BoardCard } from "./boardCard" +import { Board } from "../board" +import { BoardView } from "../boardView" import { BoardColumn } from "./boardColumn" import { Button } from "./button" import { Editable } from "./editable" +type ViewMenuProps = { + mutator: Mutator, + boardTree?: BoardTree + pageController: IPageController, + board: Board, + onClose: () => void, +} +function ViewMenu({board, onClose, boardTree, mutator, pageController}: ViewMenuProps) { + const handleDeleteView = async (id: string) => { + Utils.log(`deleteView`) + const view = boardTree.activeView + const nextView = boardTree.views.find(o => o !== view) + await mutator.deleteBlock(view, "delete view") + pageController.showView(nextView.id) + } + + const handleViewClick = (id: string) => { + Utils.log(`view ` + id) + const view = boardTree.views.find(o => o.id === id) + pageController.showView(view.id) + } + + const handleAddViewBoard = async (id: string) => { + Utils.log(`addview-board`) + const view = new BoardView() + view.title = "Board View" + view.viewType = "board" + view.parentId = board.id + + const oldViewId = boardTree.activeView.id + + await mutator.insertBlock( + view, + "add view", + async () => { pageController.showView(view.id) }, + async () => { pageController.showView(oldViewId) }) + } + + const handleAddViewTable = async (id: string) => { + Utils.log(`addview-table`) + const view = new BoardView() + view.title = "Table View" + view.viewType = "table" + view.parentId = board.id + view.visiblePropertyIds = board.cardProperties.map(o => o.id) + + const oldViewId = boardTree.activeView.id + + await mutator.insertBlock( + view, + "add view", + async () => { pageController.showView(view.id) }, + async () => { pageController.showView(oldViewId) }) + } + + return ( + + {boardTree.views.map((view) => ())} + + {boardTree.views.length > 1 && } + + + + + + ); +} + type Props = { mutator: Mutator, boardTree?: BoardTree @@ -25,6 +96,7 @@ type Props = { type State = { isHoverOnCover: boolean isSearching: boolean + viewMenu: boolean } class BoardComponent extends React.Component { @@ -34,7 +106,7 @@ class BoardComponent extends React.Component { constructor(props: Props) { super(props) - this.state = { isHoverOnCover: false, isSearching: !!this.props.boardTree?.getSearchText() } + this.state = { isHoverOnCover: false, isSearching: !!this.props.boardTree?.getSearchText(), viewMenu: false} } componentDidUpdate(prevPros: Props, prevState: State) { @@ -88,7 +160,21 @@ class BoardComponent extends React.Component {
{ mutator.changeTitle(activeView, text) }} /> -
{ OctoUtils.showViewMenu(e, mutator, boardTree, pageController) }}>
+
this.setState({viewMenu: true})} + > + {this.state.viewMenu && + this.setState({viewMenu:false})} + mutator={mutator} + boardTree={boardTree} + pageController={pageController} + />} +
+
{ this.propertiesClicked(e) }}>Properties
{ this.groupByClicked(e) }}> @@ -202,11 +288,11 @@ class BoardComponent extends React.Component { const { mutator, boardTree } = this.props const { board } = boardTree - Menu.shared.options = [ + OldMenu.shared.options = [ { id: "random", name: "Random" }, { id: "remove", name: "Remove Icon" }, ] - Menu.shared.onMenuClicked = (optionId: string, type?: string) => { + OldMenu.shared.onMenuClicked = (optionId: string, type?: string) => { switch (optionId) { case "remove": mutator.changeIcon(board, undefined, "remove icon") @@ -217,7 +303,7 @@ class BoardComponent extends React.Component { break } } - Menu.shared.showAtElement(e.target as HTMLElement) + OldMenu.shared.showAtElement(e.target as HTMLElement) } async showCard(card?: IBlock) { @@ -247,12 +333,12 @@ class BoardComponent extends React.Component { async valueOptionClicked(e: React.MouseEvent, option: IPropertyOption) { const { mutator, boardTree } = this.props - Menu.shared.options = [ + OldMenu.shared.options = [ { id: "delete", name: "Delete" }, { id: "", name: "", type: "separator" }, ...Constants.menuColors ] - Menu.shared.onMenuClicked = async (optionId: string, type?: string) => { + OldMenu.shared.onMenuClicked = async (optionId: string, type?: string) => { switch (optionId) { case "delete": console.log(`Delete property value: ${option.value}`) @@ -266,7 +352,7 @@ class BoardComponent extends React.Component { } } } - Menu.shared.showAtElement(e.target as HTMLElement) + OldMenu.shared.showAtElement(e.target as HTMLElement) } private filterClicked(e: React.MouseEvent) { @@ -277,13 +363,13 @@ class BoardComponent extends React.Component { private async optionsClicked(e: React.MouseEvent) { const { boardTree } = this.props - Menu.shared.options = [ + OldMenu.shared.options = [ { id: "exportBoardArchive", name: "Export board archive" }, { id: "testAdd100Cards", name: "TEST: Add 100 cards" }, { id: "testAdd1000Cards", name: "TEST: Add 1,000 cards" }, ] - Menu.shared.onMenuClicked = async (id: string) => { + OldMenu.shared.onMenuClicked = async (id: string) => { switch (id) { case "exportBoardArchive": { Archiver.exportBoardTree(boardTree) @@ -297,7 +383,7 @@ class BoardComponent extends React.Component { } } } - Menu.shared.showAtElement(e.target as HTMLElement) + OldMenu.shared.showAtElement(e.target as HTMLElement) } private async testAddCards(count: number) { @@ -325,12 +411,12 @@ class BoardComponent extends React.Component { const { activeView } = boardTree const selectProperties = boardTree.board.cardProperties - Menu.shared.options = selectProperties.map((o) => { + OldMenu.shared.options = selectProperties.map((o) => { const isVisible = activeView.visiblePropertyIds.includes(o.id) return { id: o.id, name: o.name, type: "switch", isOn: isVisible } }) - Menu.shared.onMenuToggled = async (id: string, isOn: boolean) => { + OldMenu.shared.onMenuToggled = async (id: string, isOn: boolean) => { const property = selectProperties.find(o => o.id === id) Utils.assertValue(property) Utils.log(`Toggle property ${property.name} ${isOn}`) @@ -343,20 +429,20 @@ class BoardComponent extends React.Component { } await mutator.changeViewVisibleProperties(activeView, newVisiblePropertyIds) } - Menu.shared.showAtElement(e.target as HTMLElement) + OldMenu.shared.showAtElement(e.target as HTMLElement) } private async groupByClicked(e: React.MouseEvent) { const { mutator, boardTree } = this.props const selectProperties = boardTree.board.cardProperties.filter(o => o.type === "select") - Menu.shared.options = selectProperties.map((o) => { return { id: o.id, name: o.name } }) - Menu.shared.onMenuClicked = async (command: string) => { + OldMenu.shared.options = selectProperties.map((o) => { return { id: o.id, name: o.name } }) + OldMenu.shared.onMenuClicked = async (command: string) => { if (boardTree.activeView.groupById === command) { return } await mutator.changeViewGroupById(boardTree.activeView, command) } - Menu.shared.showAtElement(e.target as HTMLElement) + OldMenu.shared.showAtElement(e.target as HTMLElement) } async addGroupClicked() { diff --git a/src/client/widgets/menu.tsx b/src/client/widgets/menu.tsx new file mode 100644 index 000000000..78de42a8c --- /dev/null +++ b/src/client/widgets/menu.tsx @@ -0,0 +1,159 @@ +import React from 'react'; + +type MenuOptionProps = { + id: string, + name: string, + onClick?: (id: string) => void, +} + +function SeparatorOption() { + return (
) +} + +type SubMenuOptionProps = MenuOptionProps & { +} + +type SubMenuState = { + isOpen: boolean; +} + +class SubMenuOption extends React.Component { + state = { + isOpen: false + } + + handleMouseEnter = () => { + this.setState({isOpen: true}); + } + + close = () => { + this.setState({isOpen: false}); + } + + render() { + return ( +
+
{this.props.name}
+
+ {this.state.isOpen && + + {this.props.children} + + } +
+ ) + } +} + +type ColorOptionProps = MenuOptionProps & { + icon?: "checked" | "sortUp" | "sortDown" | undefined, +} + +class ColorOption extends React.Component { + render() { + const {name, icon} = this.props; + return ( +
+
{name}
+ {icon &&
} +
+
+ ) + } +} + +type SwitchOptionProps = MenuOptionProps & { + isOn: boolean, + icon?: "checked" | "sortUp" | "sortDown" | undefined, +} + +class SwitchOption extends React.Component { + handleOnClick = () => { + this.props.onClick(this.props.id) + } + render() { + const {name, icon, isOn} = this.props; + return ( +
+
{name}
+ {icon &&
} +
+
+
+
+ ); + } +} + +type TextOptionProps = MenuOptionProps & { + icon?: "checked" | "sortUp" | "sortDown" | undefined, +} +class TextOption extends React.Component { + handleOnClick = () => { + this.props.onClick(this.props.id) + } + + render() { + const {name, icon} = this.props; + return ( +
+
{name}
+ {icon &&
} +
+ ); + } +} + +type MenuProps = { + children: React.ReactNode + onClose: () => void +} + +export default class Menu extends React.Component { + static Color = ColorOption + static SubMenu = SubMenuOption + static Switch = SwitchOption + static Separator = SeparatorOption + static Text = TextOption + + onBodyClick = (e: MouseEvent) => { + this.props.onClose() + } + + onBodyKeyDown = (e: KeyboardEvent) => { + // Ignore keydown events on other elements + if (e.target !== document.body) { return } + if (e.keyCode === 27) { + // ESC + this.props.onClose() + e.stopPropagation() + } + } + + componentDidMount() { + document.addEventListener("click", this.onBodyClick) + document.addEventListener("keydown", this.onBodyKeyDown) + } + + componentWillUnmount() { + document.removeEventListener("click", this.onBodyClick) + document.removeEventListener("keydown", this.onBodyKeyDown) + } + + render() { + return ( +
+
+ {this.props.children} +
+
+ ) + } +}