From f45924c07333de6f84d706fc15b398bffb1daac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Thu, 15 Oct 2020 19:46:32 +0200 Subject: [PATCH 1/4] Splitting cardDialog in to dialog, cardDialog and cardDetail --- src/client/components/cardDetail.tsx | 406 ++++++++++++++++++++++++ src/client/components/cardDialog.tsx | 441 ++------------------------- src/client/components/dialog.tsx | 57 ++++ 3 files changed, 480 insertions(+), 424 deletions(-) create mode 100644 src/client/components/cardDetail.tsx create mode 100644 src/client/components/dialog.tsx diff --git a/src/client/components/cardDetail.tsx b/src/client/components/cardDetail.tsx new file mode 100644 index 000000000..d508e3f12 --- /dev/null +++ b/src/client/components/cardDetail.tsx @@ -0,0 +1,406 @@ +import React from "react" +import { Block } from "../block" +import { BlockIcons } from "../blockIcons" +import { BoardTree } from "../boardTree" +import { CardTree } from "../cardTree" +import Menu from "../widgets/menu" +import Dialog from "./dialog" +import mutator from "../mutator" +import {Menu as OldMenu, MenuOption} from "../menu" +import { IBlock } from "../octoTypes" +import { OctoUtils } from "../octoUtils" +import { PropertyMenu } from "../propertyMenu" +import { Utils } from "../utils" +import Button from "./button" +import { Editable } from "./editable" +import { MarkdownEditor } from "./markdownEditor" + +type Props = { + boardTree: BoardTree + cardTree: CardTree +} + +type State = { + isHoverOnCover: boolean +} + +export default class CardDetail extends React.Component { + private titleRef = React.createRef() + private keydownHandler: any + + constructor(props: Props) { + super(props) + this.state = { isHoverOnCover: false } + } + + componentDidMount() { + this.titleRef.current.focus() + } + + render() { + const { boardTree, cardTree } = this.props + const { board } = boardTree + const { card, comments } = cardTree + + const newCommentPlaceholderText = "Add a comment..." + + const backgroundRef = React.createRef() + const newCommentRef = React.createRef() + const sendCommentButtonRef = React.createRef() + let contentElements + if (cardTree.contents.length > 0) { + contentElements = +
+ {cardTree.contents.map(block => { + if (block.type === "text") { + const cardText = block.title + return
+
+
{ this.showContentBlockMenu(e, block) }}> +
+
+
+ { + Utils.log(`change text ${block.id}, ${text}`) + mutator.changeTitle(block, text, "edit card text") + }} /> +
+ } else if (block.type === "image") { + const url = block.fields.url + return
+
+
{ this.showContentBlockMenu(e, block) }}> +
+
+
+ {block.title} +
+ } + + return
+ })} +
+ } else { + contentElements =
+
+
+ { + const order = cardTree.contents.length * 1000 + const block = new Block({ type: "text", parentId: card.id, title: text, order }) + mutator.insertBlock(block, "add card text") + }} /> +
+
+ } + + const icon = card.icon + + // TODO: Replace this placeholder + const username = "John Smith" + const userImageUrl = `data:image/svg+xml,` + + const menu = ( + + { + await mutator.deleteBlock(card, "delete card") + this.close() + }}/> + + ) + + return ( + <> +
+ {icon ? +
{ this.iconClicked(e) }}>{icon}
+ : undefined + } +
{ this.setState({ ...this.state, isHoverOnCover: true }) }} + onMouseLeave={() => { this.setState({ ...this.state, isHoverOnCover: false }) }} + > + +
+ + { mutator.changeTitle(card, text) }} /> + + {/* Property list */} + +
+ {board.cardProperties.map(propertyTemplate => { + return ( +
+
{ + 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}
+ {OctoUtils.propertyValueEditableElement(card, propertyTemplate)} +
+ ) + })} + +
{ + // TODO: Show UI + await mutator.insertPropertyTemplate(boardTree) + }}>+ Add a property
+
+ + {/* Comments */} + +
+
+ {comments.map(comment => { + const optionsButtonRef = React.createRef() + const showCommentMenu = (e: React.MouseEvent, activeComment: IBlock) => { + OldMenu.shared.options = [ + { id: "delete", name: "Delete" } + ] + OldMenu.shared.onMenuClicked = (id) => { + switch (id) { + case "delete": { + mutator.deleteBlock(activeComment) + break + } + } + } + OldMenu.shared.showAtElement(e.target as HTMLElement) + } + + return
{ optionsButtonRef.current.style.display = null }} onMouseLeave={() => { optionsButtonRef.current.style.display = "none" }}> +
+ +
{username}
+
{(new Date(comment.createAt)).toLocaleTimeString()}
+
{ showCommentMenu(e, comment) }}>...
+
+
{comment.title}
+
+ })} + + {/* New comment */} + +
+ + { return }} + onFocus={() => { + sendCommentButtonRef.current.style.display = null + }} + onBlur={() => { + if (!newCommentRef.current.text) { + sendCommentButtonRef.current.style.display = "none" + } + }} + onKeyDown={(e) => { + if (e.keyCode === 13 && !(e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey) { + sendCommentButtonRef.current.click() + } + }} + > + +
{ + const text = newCommentRef.current.text + console.log(`Send comment: ${newCommentRef.current.text}`) + this.sendComment(text) + newCommentRef.current.text = undefined + newCommentRef.current.blur() + }} + >Send
+
+
+ +
+ +
+ + {/* Content blocks */} + +
+ {contentElements} +
+ +
+
+
{ + OldMenu.shared.options = [ + { id: "text", name: "Text" }, + { id: "image", name: "Image" }, + ] + OldMenu.shared.onMenuClicked = async (optionId: string, type?: string) => { + switch (optionId) { + case "text": + const order = cardTree.contents.length * 1000 + const block = new Block({ type: "text", parentId: card.id, order }) + await mutator.insertBlock(block, "add text") + break + case "image": + Utils.selectLocalFile( + (file) => { + mutator.createImageBlock(card.id, file, cardTree.contents.length * 1000) + }, + ".jpg,.jpeg,.png") + break + } + } + OldMenu.shared.showAtElement(e.target as HTMLElement) + }} + >Add content
+
+
+ + ) + } + + async sendComment(text: string) { + const { cardTree } = this.props + const { card } = cardTree + + Utils.assertValue(card) + + const block = new Block({ type: "comment", parentId: card.id, title: text }) + await mutator.insertBlock(block, "add comment") + } + + private showContentBlockMenu(e: React.MouseEvent, block: IBlock) { + const { cardTree } = this.props + const { card } = cardTree + const index = cardTree.contents.indexOf(block) + + const options: MenuOption[] = [] + if (index > 0) { + options.push({ id: "moveUp", name: "Move up" }) + } + if (index < cardTree.contents.length - 1) { + options.push({ id: "moveDown", name: "Move down" }) + } + + options.push( + { id: "insertAbove", name: "Insert above", type: "submenu" }, + { id: "delete", name: "Delete" } + ) + + OldMenu.shared.options = options + OldMenu.shared.subMenuOptions.set("insertAbove", [ + { id: "text", name: "Text" }, + { id: "image", name: "Image" }, + ]) + OldMenu.shared.onMenuClicked = (optionId: string, type?: string) => { + switch (optionId) { + case "moveUp": { + if (index < 1) { Utils.logError(`Unexpected index ${index}`); return } + const previousBlock = cardTree.contents[index - 1] + const newOrder = OctoUtils.getOrderBefore(previousBlock, cardTree.contents) + Utils.log(`moveUp ${newOrder}`) + mutator.changeOrder(block, newOrder, "move up") + break + } + case "moveDown": { + if (index >= cardTree.contents.length - 1) { Utils.logError(`Unexpected index ${index}`); return } + const nextBlock = cardTree.contents[index + 1] + const newOrder = OctoUtils.getOrderAfter(nextBlock, cardTree.contents) + Utils.log(`moveDown ${newOrder}`) + mutator.changeOrder(block, newOrder, "move down") + break + } + case "insertAbove-text": { + const newBlock = new Block({ type: "text", parentId: card.id }) + // TODO: Handle need to reorder all blocks + newBlock.order = OctoUtils.getOrderBefore(block, cardTree.contents) + Utils.log(`insert block ${block.id}, order: ${block.order}`) + mutator.insertBlock(newBlock, "insert card text") + break + } + case "insertAbove-image": { + Utils.selectLocalFile( + (file) => { + mutator.createImageBlock(card.id, file, OctoUtils.getOrderBefore(block, cardTree.contents)) + }, + ".jpg,.jpeg,.png") + + break + } + case "delete": { + mutator.deleteBlock(block) + break + } + } + } + OldMenu.shared.showAtElement(e.target as HTMLElement) + } + + private iconClicked(e: React.MouseEvent) { + const { cardTree } = this.props + const { card } = cardTree + + OldMenu.shared.options = [ + { id: "random", name: "Random" }, + { id: "remove", name: "Remove Icon" }, + ] + OldMenu.shared.onMenuClicked = (optionId: string, type?: string) => { + switch (optionId) { + case "remove": + mutator.changeIcon(card, undefined, "remove icon") + break + case "random": + const newIcon = BlockIcons.shared.randomIcon() + mutator.changeIcon(card, newIcon) + break + } + } + OldMenu.shared.showAtElement(e.target as HTMLElement) + } + + close() { + OldMenu.shared.hide() + PropertyMenu.shared.hide() + } +} diff --git a/src/client/components/cardDialog.tsx b/src/client/components/cardDialog.tsx index 81d42415e..3a94ca724 100644 --- a/src/client/components/cardDialog.tsx +++ b/src/client/components/cardDialog.tsx @@ -1,17 +1,10 @@ import React from "react" -import { Block } from "../block" -import { BlockIcons } from "../blockIcons" import { BoardTree } from "../boardTree" import { CardTree } from "../cardTree" -import { Menu, MenuOption } from "../menu" +import Menu from "../widgets/menu" +import Dialog from "./dialog" +import CardDetail from "./cardDetail" import mutator from "../mutator" -import { IBlock } from "../octoTypes" -import { OctoUtils } from "../octoUtils" -import { PropertyMenu } from "../propertyMenu" -import { Utils } from "../utils" -import Button from "./button" -import { Editable } from "./editable" -import { MarkdownEditor } from "./markdownEditor" type Props = { boardTree: BoardTree @@ -19,421 +12,21 @@ type Props = { onClose: () => void } -type State = { - isHoverOnCover: boolean -} - -class CardDialog extends React.Component { - private titleRef = React.createRef() - private keydownHandler: any - - constructor(props: Props) { - super(props) - this.state = { isHoverOnCover: false } - } - - componentDidMount() { - this.titleRef.current.focus() - this.keydownHandler = (e: KeyboardEvent) => { - if (e.target !== document.body) { return } - - if (e.keyCode === 27) { - this.close() - e.stopPropagation() - } - } - document.addEventListener("keydown", this.keydownHandler) - } - - componentWillUnmount() { - document.removeEventListener("keydown", this.keydownHandler) - } - +class CardDialog extends React.Component { render() { - const { boardTree, cardTree } = this.props - const { board } = boardTree - const { card, comments } = cardTree - - const newCommentPlaceholderText = "Add a comment..." - - const backgroundRef = React.createRef() - const newCommentRef = React.createRef() - const sendCommentButtonRef = React.createRef() - let contentElements - if (cardTree.contents.length > 0) { - contentElements = -
- {cardTree.contents.map(block => { - if (block.type === "text") { - const cardText = block.title - return
-
-
{ this.showContentBlockMenu(e, block) }}> -
-
-
- { - Utils.log(`change text ${block.id}, ${text}`) - mutator.changeTitle(block, text, "edit card text") - }} /> -
- } else if (block.type === "image") { - const url = block.fields.url - return
-
-
{ this.showContentBlockMenu(e, block) }}> -
-
-
- {block.title} -
- } - - return
- })} -
- - } else { - contentElements =
-
-
- { - const order = cardTree.contents.length * 1000 - const block = new Block({ type: "text", parentId: card.id, title: text, order }) - mutator.insertBlock(block, "add card text") - }} /> -
-
- } - - const icon = card.icon - - // TODO: Replace this placeholder - const username = "John Smith" - const userImageUrl = `data:image/svg+xml,` - - const element = -
{ - if (e.target === backgroundRef.current) { this.close() } - }}> -
-
-
- -
-
- {icon ? -
{ this.iconClicked(e) }}>{icon}
- : undefined - } -
{ this.setState({ ...this.state, isHoverOnCover: true }) }} - onMouseLeave={() => { this.setState({ ...this.state, isHoverOnCover: false }) }} - > - -
- - { mutator.changeTitle(card, text) }} /> - - {/* Property list */} - -
- {board.cardProperties.map(propertyTemplate => { - return ( -
-
{ - 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}
- {OctoUtils.propertyValueEditableElement(card, propertyTemplate)} -
- ) - })} - -
{ - // TODO: Show UI - await mutator.insertPropertyTemplate(boardTree) - }}>+ Add a property
-
- - {/* Comments */} - -
-
- {comments.map(comment => { - const optionsButtonRef = React.createRef() - const showCommentMenu = (e: React.MouseEvent, activeComment: IBlock) => { - Menu.shared.options = [ - { id: "delete", name: "Delete" } - ] - Menu.shared.onMenuClicked = (id) => { - switch (id) { - case "delete": { - mutator.deleteBlock(activeComment) - break - } - } - } - Menu.shared.showAtElement(e.target as HTMLElement) - } - - return
{ optionsButtonRef.current.style.display = null }} onMouseLeave={() => { optionsButtonRef.current.style.display = "none" }}> -
- -
{username}
-
{(new Date(comment.createAt)).toLocaleTimeString()}
-
{ showCommentMenu(e, comment) }}>...
-
-
{comment.title}
-
- })} - - {/* New comment */} - -
- - { return }} - onFocus={() => { - sendCommentButtonRef.current.style.display = null - }} - onBlur={() => { - if (!newCommentRef.current.text) { - sendCommentButtonRef.current.style.display = "none" - } - }} - onKeyDown={(e) => { - if (e.keyCode === 13 && !(e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey) { - sendCommentButtonRef.current.click() - } - }} - > - -
{ - const text = newCommentRef.current.text - console.log(`Send comment: ${newCommentRef.current.text}`) - this.sendComment(text) - newCommentRef.current.text = undefined - newCommentRef.current.blur() - }} - >Send
-
-
- -
-
- - {/* Content blocks */} - -
- {contentElements} -
- -
-
-
{ - Menu.shared.options = [ - { id: "text", name: "Text" }, - { id: "image", name: "Image" }, - ] - Menu.shared.onMenuClicked = async (optionId: string, type?: string) => { - switch (optionId) { - case "text": - const order = cardTree.contents.length * 1000 - const block = new Block({ type: "text", parentId: card.id, order }) - await mutator.insertBlock(block, "add text") - break - case "image": - Utils.selectLocalFile( - (file) => { - mutator.createImageBlock(card.id, file, cardTree.contents.length * 1000) - }, - ".jpg,.jpeg,.png") - break - } - } - Menu.shared.showAtElement(e.target as HTMLElement) - }} - >Add content
-
-
- -
-
- - return element - } - - async sendComment(text: string) { - const { cardTree } = this.props - const { card } = cardTree - - Utils.assertValue(card) - - const block = new Block({ type: "comment", parentId: card.id, title: text }) - await mutator.insertBlock(block, "add comment") - } - - private showContentBlockMenu(e: React.MouseEvent, block: IBlock) { - const { cardTree } = this.props - const { card } = cardTree - const index = cardTree.contents.indexOf(block) - - const options: MenuOption[] = [] - if (index > 0) { - options.push({ id: "moveUp", name: "Move up" }) - } - if (index < cardTree.contents.length - 1) { - options.push({ id: "moveDown", name: "Move down" }) - } - - options.push( - { id: "insertAbove", name: "Insert above", type: "submenu" }, - { id: "delete", name: "Delete" } - ) - - Menu.shared.options = options - Menu.shared.subMenuOptions.set("insertAbove", [ - { id: "text", name: "Text" }, - { id: "image", name: "Image" }, - ]) - Menu.shared.onMenuClicked = (optionId: string, type?: string) => { - switch (optionId) { - case "moveUp": { - if (index < 1) { Utils.logError(`Unexpected index ${index}`); return } - const previousBlock = cardTree.contents[index - 1] - const newOrder = OctoUtils.getOrderBefore(previousBlock, cardTree.contents) - Utils.log(`moveUp ${newOrder}`) - mutator.changeOrder(block, newOrder, "move up") - break - } - case "moveDown": { - if (index >= cardTree.contents.length - 1) { Utils.logError(`Unexpected index ${index}`); return } - const nextBlock = cardTree.contents[index + 1] - const newOrder = OctoUtils.getOrderAfter(nextBlock, cardTree.contents) - Utils.log(`moveDown ${newOrder}`) - mutator.changeOrder(block, newOrder, "move down") - break - } - case "insertAbove-text": { - const newBlock = new Block({ type: "text", parentId: card.id }) - // TODO: Handle need to reorder all blocks - newBlock.order = OctoUtils.getOrderBefore(block, cardTree.contents) - Utils.log(`insert block ${block.id}, order: ${block.order}`) - mutator.insertBlock(newBlock, "insert card text") - break - } - case "insertAbove-image": { - Utils.selectLocalFile( - (file) => { - mutator.createImageBlock(card.id, file, OctoUtils.getOrderBefore(block, cardTree.contents)) - }, - ".jpg,.jpeg,.png") - - break - } - case "delete": { - mutator.deleteBlock(block) - break - } - } - } - Menu.shared.showAtElement(e.target as HTMLElement) - } - - private iconClicked(e: React.MouseEvent) { - const { cardTree } = this.props - const { card } = cardTree - - Menu.shared.options = [ - { id: "random", name: "Random" }, - { id: "remove", name: "Remove Icon" }, - ] - Menu.shared.onMenuClicked = (optionId: string, type?: string) => { - switch (optionId) { - case "remove": - mutator.changeIcon(card, undefined, "remove icon") - break - case "random": - const newIcon = BlockIcons.shared.randomIcon() - mutator.changeIcon(card, newIcon) - break - } - } - Menu.shared.showAtElement(e.target as HTMLElement) - } - - close() { - Menu.shared.hide() - PropertyMenu.shared.hide() - - this.props.onClose() + const menu = ( + + { + await mutator.deleteBlock(this.props.cardTree.card, "delete card") + this.props.onClose() + }}/> + + ) + return ( + + + + ) } } diff --git a/src/client/components/dialog.tsx b/src/client/components/dialog.tsx new file mode 100644 index 000000000..ac917cbd5 --- /dev/null +++ b/src/client/components/dialog.tsx @@ -0,0 +1,57 @@ +import React from "react" +import Menu from "../widgets/menu" +import MenuWrapper from "../widgets/menuWrapper" +import mutator from "../mutator" +import Button from "./button" + +type Props = { + children: React.ReactNode + toolsMenu: React.ReactNode + onClose: () => void +} + +export default class Dialog extends React.Component { + keydownHandler = (e: KeyboardEvent) => { + if (e.target !== document.body) { return } + + if (e.keyCode === 27) { + this.close() + e.stopPropagation() + } + } + + componentDidMount() { + document.addEventListener("keydown", this.keydownHandler) + } + + componentWillUnmount() { + document.removeEventListener("keydown", this.keydownHandler) + } + + render() { + const {toolsMenu} = this.props + + return ( +
{ if (e.target === e.currentTarget) { this.close() } }} + > +
+ {toolsMenu && +
+
+ + + {toolsMenu} + +
} + {this.props.children} +
+
+ ) + } + + close() { + this.props.onClose() + } +} From 60fe66dd2d61e321110b7329313c6a0c665e0c12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Thu, 15 Oct 2020 22:43:16 +0200 Subject: [PATCH 2/4] Fixing some details --- src/client/components/cardDetail.tsx | 31 ++++++++++++++++++++-------- src/client/components/cardDialog.tsx | 8 +++---- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/client/components/cardDetail.tsx b/src/client/components/cardDetail.tsx index a2ca4121b..d406bedbc 100644 --- a/src/client/components/cardDetail.tsx +++ b/src/client/components/cardDetail.tsx @@ -1,5 +1,6 @@ import React from "react" import { Block } from "../block" +import { Card } from "../card" import { BlockIcons } from "../blockIcons" import { BoardTree } from "../boardTree" import { CardTree } from "../cardTree" @@ -14,19 +15,22 @@ import { Utils } from "../utils" import Button from "./button" import { Editable } from "./editable" import { MarkdownEditor } from "./markdownEditor" +import { OctoListener } from "../octoListener" type Props = { boardTree: BoardTree - cardTree: CardTree + card: Card } type State = { isHoverOnCover: boolean + cardTree?: CardTree } export default class CardDetail extends React.Component { private titleRef = React.createRef() private keydownHandler: any + private cardListener: OctoListener constructor(props: Props) { super(props) @@ -35,12 +39,23 @@ export default class CardDetail extends React.Component { componentDidMount() { this.titleRef.current.focus() + this.cardListener = new OctoListener() + this.cardListener.open(this.props.card.id, async () => { + await cardTree.sync() + this.setState({ cardTree }) + }) + const cardTree = new CardTree(this.props.card.id) + cardTree.sync().then(() => { + this.setState({cardTree}) + }) + } render() { - const { boardTree, cardTree } = this.props + const { boardTree, card } = this.props + const { cardTree } = this.state const { board } = boardTree - const { card, comments } = cardTree + const { comments } = cardTree const newCommentPlaceholderText = "Add a comment..." @@ -291,8 +306,7 @@ export default class CardDetail extends React.Component { } async sendComment(text: string) { - const { cardTree } = this.props - const { card } = cardTree + const { card } = this.props Utils.assertValue(card) @@ -301,8 +315,8 @@ export default class CardDetail extends React.Component { } private showContentBlockMenu(e: React.MouseEvent, block: IBlock) { - const { cardTree } = this.props - const { card } = cardTree + const { cardTree } = this.state + const { card } = this.props const index = cardTree.contents.indexOf(block) const options: MenuOption[] = [] @@ -368,8 +382,7 @@ export default class CardDetail extends React.Component { } private iconClicked(e: React.MouseEvent) { - const { cardTree } = this.props - const { card } = cardTree + const { card } = this.props OldMenu.shared.options = [ { id: "random", name: "Random" }, diff --git a/src/client/components/cardDialog.tsx b/src/client/components/cardDialog.tsx index 3a94ca724..ef81587f5 100644 --- a/src/client/components/cardDialog.tsx +++ b/src/client/components/cardDialog.tsx @@ -1,6 +1,6 @@ import React from "react" import { BoardTree } from "../boardTree" -import { CardTree } from "../cardTree" +import { Card } from "../card" import Menu from "../widgets/menu" import Dialog from "./dialog" import CardDetail from "./cardDetail" @@ -8,7 +8,7 @@ import mutator from "../mutator" type Props = { boardTree: BoardTree - cardTree: CardTree + card: Card onClose: () => void } @@ -17,14 +17,14 @@ class CardDialog extends React.Component { const menu = ( { - await mutator.deleteBlock(this.props.cardTree.card, "delete card") + await mutator.deleteBlock(this.props.card, "delete card") this.props.onClose() }}/> ) return ( - + ) } From 67932238bd16c7dea9e7556529f99e1c1bb71a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Thu, 15 Oct 2020 22:46:20 +0200 Subject: [PATCH 3/4] Small fixes and reformating --- src/client/components/cardDetail.tsx | 353 ++++++++++++++------------- 1 file changed, 180 insertions(+), 173 deletions(-) diff --git a/src/client/components/cardDetail.tsx b/src/client/components/cardDetail.tsx index d406bedbc..203b801e3 100644 --- a/src/client/components/cardDetail.tsx +++ b/src/client/components/cardDetail.tsx @@ -38,7 +38,6 @@ export default class CardDetail extends React.Component { } componentDidMount() { - this.titleRef.current.focus() this.cardListener = new OctoListener() this.cardListener.open(this.props.card.id, async () => { await cardTree.sync() @@ -47,14 +46,22 @@ export default class CardDetail extends React.Component { const cardTree = new CardTree(this.props.card.id) cardTree.sync().then(() => { this.setState({cardTree}) + setTimeout(() => { + if (this.titleRef.current) { + this.titleRef.current.focus() + } + }, 0); }) } render() { const { boardTree, card } = this.props - const { cardTree } = this.state + const { cardTree } = this.state const { board } = boardTree + if (!cardTree) { + return null + } const { comments } = cardTree const newCommentPlaceholderText = "Add a comment..." @@ -117,192 +124,192 @@ export default class CardDetail extends React.Component { const username = "John Smith" const userImageUrl = `data:image/svg+xml,` - return ( - <> -
- {icon ? -
{ this.iconClicked(e) }}>{icon}
- : undefined - } -
{ this.setState({ ...this.state, isHoverOnCover: true }) }} - onMouseLeave={() => { this.setState({ ...this.state, isHoverOnCover: false }) }} - > - -
+ return ( + <> +
+ {icon ? +
{ this.iconClicked(e) }}>{icon}
+ : undefined + } +
{ this.setState({ ...this.state, isHoverOnCover: true }) }} + onMouseLeave={() => { this.setState({ ...this.state, isHoverOnCover: false }) }} + > + +
- { mutator.changeTitle(card, text) }} /> + { mutator.changeTitle(card, text) }} /> - {/* Property list */} + {/* Property list */} -
- {board.cardProperties.map(propertyTemplate => { - return ( -
-
{ - const menu = PropertyMenu.shared - menu.property = propertyTemplate - menu.onNameChanged = (propertyName) => { - Utils.log(`menu.onNameChanged`) - mutator.renameProperty(board, propertyTemplate.id, propertyName) - } +
+ {board.cardProperties.map(propertyTemplate => { + return ( +
+
{ + 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}
- {OctoUtils.propertyValueEditableElement(card, propertyTemplate)} -
- ) - })} + 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}
+ {OctoUtils.propertyValueEditableElement(card, propertyTemplate)} +
+ ) + })} -
{ - // TODO: Show UI - await mutator.insertPropertyTemplate(boardTree) - }}>+ Add a property
-
+
{ + // TODO: Show UI + await mutator.insertPropertyTemplate(boardTree) + }}>+ Add a property
+
- {/* Comments */} + {/* Comments */} -
-
- {comments.map(comment => { - const optionsButtonRef = React.createRef() - const showCommentMenu = (e: React.MouseEvent, activeComment: IBlock) => { - OldMenu.shared.options = [ - { id: "delete", name: "Delete" } - ] - OldMenu.shared.onMenuClicked = (id) => { - switch (id) { - case "delete": { - mutator.deleteBlock(activeComment) - break - } - } - } - OldMenu.shared.showAtElement(e.target as HTMLElement) - } +
+
+ {comments.map(comment => { + const optionsButtonRef = React.createRef() + const showCommentMenu = (e: React.MouseEvent, activeComment: IBlock) => { + OldMenu.shared.options = [ + { id: "delete", name: "Delete" } + ] + OldMenu.shared.onMenuClicked = (id) => { + switch (id) { + case "delete": { + mutator.deleteBlock(activeComment) + break + } + } + } + OldMenu.shared.showAtElement(e.target as HTMLElement) + } - return
{ optionsButtonRef.current.style.display = null }} onMouseLeave={() => { optionsButtonRef.current.style.display = "none" }}> -
- -
{username}
-
{(new Date(comment.createAt)).toLocaleTimeString()}
-
{ showCommentMenu(e, comment) }}>...
-
-
{comment.title}
-
- })} + return
{ optionsButtonRef.current.style.display = null }} onMouseLeave={() => { optionsButtonRef.current.style.display = "none" }}> +
+ +
{username}
+
{(new Date(comment.createAt)).toLocaleTimeString()}
+
{ showCommentMenu(e, comment) }}>...
+
+
{comment.title}
+
+ })} - {/* New comment */} + {/* New comment */} -
- - { return }} - onFocus={() => { - sendCommentButtonRef.current.style.display = null - }} - onBlur={() => { - if (!newCommentRef.current.text) { - sendCommentButtonRef.current.style.display = "none" - } - }} - onKeyDown={(e) => { - if (e.keyCode === 13 && !(e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey) { - sendCommentButtonRef.current.click() - } - }} - > +
+ + { return }} + onFocus={() => { + sendCommentButtonRef.current.style.display = null + }} + onBlur={() => { + if (!newCommentRef.current.text) { + sendCommentButtonRef.current.style.display = "none" + } + }} + onKeyDown={(e) => { + if (e.keyCode === 13 && !(e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey) { + sendCommentButtonRef.current.click() + } + }} + > -
{ - const text = newCommentRef.current.text - console.log(`Send comment: ${newCommentRef.current.text}`) - this.sendComment(text) - newCommentRef.current.text = undefined - newCommentRef.current.blur() - }} - >Send
-
-
+
{ + const text = newCommentRef.current.text + console.log(`Send comment: ${newCommentRef.current.text}`) + this.sendComment(text) + newCommentRef.current.text = undefined + newCommentRef.current.blur() + }} + >Send
+
+
-
-
+
+
- {/* Content blocks */} + {/* Content blocks */} -
- {contentElements} -
+
+ {contentElements} +
-
-
-
{ - OldMenu.shared.options = [ - { id: "text", name: "Text" }, - { id: "image", name: "Image" }, - ] - OldMenu.shared.onMenuClicked = async (optionId: string, type?: string) => { - switch (optionId) { - case "text": - const order = cardTree.contents.length * 1000 - const block = new Block({ type: "text", parentId: card.id, order }) - await mutator.insertBlock(block, "add text") - break - case "image": - Utils.selectLocalFile( - (file) => { - mutator.createImageBlock(card.id, file, cardTree.contents.length * 1000) - }, - ".jpg,.jpeg,.png") - break - } - } - OldMenu.shared.showAtElement(e.target as HTMLElement) - }} - >Add content
-
-
- - ) +
+
+
{ + OldMenu.shared.options = [ + { id: "text", name: "Text" }, + { id: "image", name: "Image" }, + ] + OldMenu.shared.onMenuClicked = async (optionId: string, type?: string) => { + switch (optionId) { + case "text": + const order = cardTree.contents.length * 1000 + const block = new Block({ type: "text", parentId: card.id, order }) + await mutator.insertBlock(block, "add text") + break + case "image": + Utils.selectLocalFile( + (file) => { + mutator.createImageBlock(card.id, file, cardTree.contents.length * 1000) + }, + ".jpg,.jpeg,.png") + break + } + } + OldMenu.shared.showAtElement(e.target as HTMLElement) + }} + >Add content
+
+
+ + ) } async sendComment(text: string) { From 43faeaab356b756d3487bcfd3d1dfc8ef34ef88b Mon Sep 17 00:00:00 2001 From: Chen-I Lim Date: Sun, 18 Oct 2020 19:35:24 -0700 Subject: [PATCH 4/4] cleanup merge --- src/client/components/cardDetail.tsx | 19 +++++++++---------- src/client/components/dialog.tsx | 4 +--- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/client/components/cardDetail.tsx b/src/client/components/cardDetail.tsx index 203b801e3..17f5cb657 100644 --- a/src/client/components/cardDetail.tsx +++ b/src/client/components/cardDetail.tsx @@ -1,21 +1,20 @@ import React from "react" -import { Block } from "../block" -import { Card } from "../card" import { BlockIcons } from "../blockIcons" +import { Block } from "../blocks/block" +import { Card } from "../blocks/card" +import { TextBlock } from "../blocks/textBlock" import { BoardTree } from "../boardTree" import { CardTree } from "../cardTree" -import Menu from "../widgets/menu" -import Dialog from "./dialog" +import { Menu as OldMenu, MenuOption } from "../menu" import mutator from "../mutator" -import {Menu as OldMenu, MenuOption} from "../menu" -import { IBlock } from "../octoTypes" +import { OctoListener } from "../octoListener" +import { IBlock, IOrderedBlock } from "../octoTypes" import { OctoUtils } from "../octoUtils" import { PropertyMenu } from "../propertyMenu" import { Utils } from "../utils" import Button from "./button" import { Editable } from "./editable" import { MarkdownEditor } from "./markdownEditor" -import { OctoListener } from "../octoListener" type Props = { boardTree: BoardTree @@ -50,7 +49,7 @@ export default class CardDetail extends React.Component { if (this.titleRef.current) { this.titleRef.current.focus() } - }, 0); + }, 0) }) } @@ -321,7 +320,7 @@ export default class CardDetail extends React.Component { await mutator.insertBlock(block, "add comment") } - private showContentBlockMenu(e: React.MouseEvent, block: IBlock) { + private showContentBlockMenu(e: React.MouseEvent, block: IOrderedBlock) { const { cardTree } = this.state const { card } = this.props const index = cardTree.contents.indexOf(block) @@ -363,7 +362,7 @@ export default class CardDetail extends React.Component { break } case "insertAbove-text": { - const newBlock = new Block({ type: "text", parentId: card.id }) + const newBlock = new TextBlock({ parentId: card.id }) // TODO: Handle need to reorder all blocks newBlock.order = OctoUtils.getOrderBefore(block, cardTree.contents) Utils.log(`insert block ${block.id}, order: ${block.order}`) diff --git a/src/client/components/dialog.tsx b/src/client/components/dialog.tsx index ac917cbd5..139836b24 100644 --- a/src/client/components/dialog.tsx +++ b/src/client/components/dialog.tsx @@ -1,7 +1,5 @@ import React from "react" -import Menu from "../widgets/menu" import MenuWrapper from "../widgets/menuWrapper" -import mutator from "../mutator" import Button from "./button" type Props = { @@ -41,7 +39,7 @@ export default class Dialog extends React.Component {
- + {toolsMenu}
}