Moving into standard react-router
This commit is contained in:
parent
a2c90d3ac6
commit
72cf5e7129
9 changed files with 291 additions and 295 deletions
|
@ -8,7 +8,7 @@ import {
|
|||
} from "react-router-dom";
|
||||
|
||||
import LoginPage from './pages/loginPage';
|
||||
import HomePage from './pages/homePage';
|
||||
import BoardPage from './pages/boardPage';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
|
@ -23,8 +23,8 @@ export default function App() {
|
|||
<Route path="/login">
|
||||
<LoginPage />
|
||||
</Route>
|
||||
<Route path="/">
|
||||
<HomePage />
|
||||
<Route path="/board">
|
||||
<BoardPage />
|
||||
</Route>
|
||||
</Switch>
|
||||
</main>
|
||||
|
|
|
@ -1,250 +0,0 @@
|
|||
import React from "react"
|
||||
import ReactDOM from "react-dom"
|
||||
import { BoardTree } from "./boardTree"
|
||||
import { BoardView } from "./boardView"
|
||||
import { CardTree } from "./cardTree"
|
||||
import { CardDialog } from "./components/cardDialog"
|
||||
import { FilterComponent } from "./components/filterComponent"
|
||||
import { PageHeader } from "./components/pageHeader"
|
||||
import { WorkspaceComponent } from "./components/workspaceComponent"
|
||||
import { FlashMessage } from "./flashMessage"
|
||||
import { Mutator } from "./mutator"
|
||||
import { OctoClient } from "./octoClient"
|
||||
import { OctoListener } from "./octoListener"
|
||||
import { IBlock, IPageController } from "./octoTypes"
|
||||
import { UndoManager } from "./undomanager"
|
||||
import { Utils } from "./utils"
|
||||
import { WorkspaceTree } from "./workspaceTree"
|
||||
|
||||
class BoardPage implements IPageController {
|
||||
boardTitle: HTMLElement
|
||||
mainBoardHeader: HTMLElement
|
||||
mainBoardBody: HTMLElement
|
||||
groupByButton: HTMLElement
|
||||
groupByLabel: HTMLElement
|
||||
|
||||
boardId?: string
|
||||
viewId?: string
|
||||
|
||||
workspaceTree: WorkspaceTree
|
||||
boardTree?: BoardTree
|
||||
view: BoardView
|
||||
|
||||
updateTitleTimeout: number
|
||||
updatePropertyLabelTimeout: number
|
||||
|
||||
shownCardTree: CardTree
|
||||
|
||||
private filterAnchorElement?: HTMLElement
|
||||
private octo = new OctoClient()
|
||||
private boardListener = new OctoListener()
|
||||
private cardListener = new OctoListener()
|
||||
|
||||
constructor() {
|
||||
const queryString = new URLSearchParams(window.location.search)
|
||||
const boardId = queryString.get("id")
|
||||
const viewId = queryString.get("v")
|
||||
|
||||
this.layoutPage()
|
||||
|
||||
this.workspaceTree = new WorkspaceTree(this.octo)
|
||||
|
||||
console.log(`BoardPage. boardId: ${this.boardId}`)
|
||||
if (boardId) {
|
||||
this.attachToBoard(boardId, viewId)
|
||||
} else {
|
||||
this.sync()
|
||||
}
|
||||
|
||||
document.body.addEventListener("keydown", async (e) => {
|
||||
if (e.target !== document.body) { return }
|
||||
|
||||
if (e.keyCode === 90 && !e.shiftKey && (e.ctrlKey || e.metaKey) && !e.altKey) { // Cmd+Z
|
||||
Utils.log(`Undo`)
|
||||
const description = UndoManager.shared.undoDescription
|
||||
await UndoManager.shared.undo()
|
||||
if (description) {
|
||||
FlashMessage.show(`Undo ${description}`)
|
||||
} else {
|
||||
FlashMessage.show(`Undo`)
|
||||
}
|
||||
} else if (e.keyCode === 90 && e.shiftKey && (e.ctrlKey || e.metaKey) && !e.altKey) { // Shift+Cmd+Z
|
||||
Utils.log(`Redo`)
|
||||
const description = UndoManager.shared.redoDescription
|
||||
await UndoManager.shared.redo()
|
||||
if (description) {
|
||||
FlashMessage.show(`Redo ${description}`)
|
||||
} else {
|
||||
FlashMessage.show(`Redo`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.render()
|
||||
}
|
||||
|
||||
private layoutPage() {
|
||||
const root = Utils.getElementById("octo-tasks-app")
|
||||
root.innerText = ""
|
||||
|
||||
const header = root.appendChild(document.createElement("div"))
|
||||
header.id = "header"
|
||||
|
||||
const main = root.appendChild(document.createElement("div"))
|
||||
main.id = "main"
|
||||
|
||||
const overlay = root.appendChild(document.createElement("div"))
|
||||
overlay.id = "overlay"
|
||||
|
||||
const modal = root.appendChild(document.createElement("div"))
|
||||
modal.id = "modal"
|
||||
}
|
||||
|
||||
render() {
|
||||
const { octo, boardTree } = this
|
||||
const { board, activeView } = boardTree || {}
|
||||
const mutator = new Mutator(octo)
|
||||
|
||||
const mainElement = Utils.getElementById("main")
|
||||
|
||||
ReactDOM.render(
|
||||
<PageHeader />,
|
||||
Utils.getElementById("header")
|
||||
)
|
||||
|
||||
if (board) {
|
||||
Utils.setFavicon(board.icon)
|
||||
document.title = `OCTO - ${board.title} | ${activeView.title}`
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<WorkspaceComponent mutator={mutator} workspaceTree={this.workspaceTree} boardTree={this.boardTree} pageController={this} />,
|
||||
mainElement
|
||||
)
|
||||
|
||||
if (boardTree && boardTree.board && this.shownCardTree) {
|
||||
ReactDOM.render(
|
||||
<CardDialog mutator={mutator} boardTree={boardTree} cardTree={this.shownCardTree} onClose={() => { this.showCard(undefined) }}></CardDialog>,
|
||||
Utils.getElementById("overlay")
|
||||
)
|
||||
} else {
|
||||
ReactDOM.render(
|
||||
<div />,
|
||||
Utils.getElementById("overlay")
|
||||
)
|
||||
}
|
||||
|
||||
if (this.filterAnchorElement) {
|
||||
const element = this.filterAnchorElement
|
||||
const bodyRect = document.body.getBoundingClientRect()
|
||||
const rect = element.getBoundingClientRect()
|
||||
// Show at bottom-left of element
|
||||
const maxX = bodyRect.right - 420 - 100
|
||||
const pageX = Math.min(maxX, rect.left - bodyRect.left)
|
||||
const pageY = rect.bottom - bodyRect.top
|
||||
|
||||
ReactDOM.render(
|
||||
<FilterComponent
|
||||
mutator={mutator}
|
||||
boardTree={boardTree}
|
||||
pageX={pageX}
|
||||
pageY={pageY}
|
||||
onClose={() => { this.showFilter(undefined) }}
|
||||
>
|
||||
</FilterComponent>,
|
||||
Utils.getElementById("modal")
|
||||
)
|
||||
} else {
|
||||
ReactDOM.render(<div />, Utils.getElementById("modal"))
|
||||
}
|
||||
}
|
||||
|
||||
private attachToBoard(boardId: string, viewId?: string) {
|
||||
this.boardId = boardId
|
||||
this.viewId = viewId
|
||||
|
||||
this.boardTree = new BoardTree(this.octo, boardId)
|
||||
|
||||
this.boardListener.open(boardId, (blockId: string) => {
|
||||
console.log(`octoListener.onChanged: ${blockId}`)
|
||||
this.sync()
|
||||
})
|
||||
|
||||
this.sync()
|
||||
}
|
||||
|
||||
async sync() {
|
||||
const { workspaceTree, boardTree } = this
|
||||
|
||||
await workspaceTree.sync()
|
||||
if (boardTree) {
|
||||
await boardTree.sync()
|
||||
|
||||
// Default to first view
|
||||
if (!this.viewId) {
|
||||
this.viewId = boardTree.views[0].id
|
||||
}
|
||||
|
||||
boardTree.setActiveView(this.viewId)
|
||||
// TODO: Handle error (viewId not found)
|
||||
this.viewId = boardTree.activeView.id
|
||||
console.log(`sync complete... title: ${boardTree.board.title}`)
|
||||
}
|
||||
|
||||
this.render()
|
||||
}
|
||||
|
||||
// IPageController
|
||||
|
||||
async showCard(card: IBlock) {
|
||||
this.cardListener.close()
|
||||
|
||||
if (card) {
|
||||
const cardTree = new CardTree(this.octo, card.id)
|
||||
await cardTree.sync()
|
||||
this.shownCardTree = cardTree
|
||||
|
||||
this.cardListener = new OctoListener()
|
||||
this.cardListener.open(card.id, async () => {
|
||||
await cardTree.sync()
|
||||
this.render()
|
||||
})
|
||||
} else {
|
||||
this.shownCardTree = undefined
|
||||
}
|
||||
|
||||
this.render()
|
||||
}
|
||||
|
||||
showBoard(boardId: string) {
|
||||
if (this.boardTree?.board?.id === boardId) { return }
|
||||
|
||||
const newUrl = window.location.protocol + "//" + window.location.host + window.location.pathname + `?id=${encodeURIComponent(boardId)}`
|
||||
window.history.pushState({ path: newUrl }, "", newUrl)
|
||||
|
||||
this.attachToBoard(boardId)
|
||||
}
|
||||
|
||||
showView(viewId: string) {
|
||||
this.viewId = viewId
|
||||
this.boardTree.setActiveView(this.viewId)
|
||||
const newUrl = window.location.protocol + "//" + window.location.host + window.location.pathname + `?id=${encodeURIComponent(this.boardId)}&v=${encodeURIComponent(viewId)}`
|
||||
window.history.pushState({ path: newUrl }, "", newUrl)
|
||||
this.render()
|
||||
}
|
||||
|
||||
showFilter(ahchorElement?: HTMLElement) {
|
||||
this.filterAnchorElement = ahchorElement
|
||||
this.render()
|
||||
}
|
||||
|
||||
setSearchText(text?: string) {
|
||||
this.boardTree.setSearchText(text)
|
||||
this.render()
|
||||
}
|
||||
}
|
||||
|
||||
export { BoardPage }
|
||||
|
||||
const _ = new BoardPage()
|
||||
console.log("BoardPage")
|
|
@ -8,7 +8,7 @@ import { CardFilter } from "../cardFilter"
|
|||
import { Constants } from "../constants"
|
||||
import { Menu } from "../menu"
|
||||
import { Mutator } from "../mutator"
|
||||
import { IBlock, IPageController } from "../octoTypes"
|
||||
import { IBlock } from "../octoTypes"
|
||||
import { OctoUtils } from "../octoUtils"
|
||||
import { Utils } from "../utils"
|
||||
import { BoardCard } from "./boardCard"
|
||||
|
@ -19,7 +19,10 @@ import { Editable } from "./editable"
|
|||
type Props = {
|
||||
mutator: Mutator,
|
||||
boardTree?: BoardTree
|
||||
pageController: IPageController
|
||||
showView: (id: string) => void
|
||||
showCard: (card: IBlock) => void
|
||||
showFilter: (el: HTMLElement) => void
|
||||
setSearchText: (text: string) => void
|
||||
}
|
||||
|
||||
type State = {
|
||||
|
@ -44,7 +47,7 @@ class BoardComponent extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { mutator, boardTree, pageController } = this.props
|
||||
const { mutator, boardTree, showView } = this.props
|
||||
|
||||
if (!boardTree || !boardTree.board) {
|
||||
return (
|
||||
|
@ -88,7 +91,7 @@ class BoardComponent extends React.Component<Props, State> {
|
|||
<div className="octo-board">
|
||||
<div className="octo-controls">
|
||||
<Editable style={{ color: "#000000", fontWeight: 600 }} text={activeView.title} placeholderText="Untitled View" onChanged={(text) => { mutator.changeTitle(activeView, text) }} />
|
||||
<div className="octo-button" style={{ color: "#000000", fontWeight: 600 }} onClick={(e) => { OctoUtils.showViewMenu(e, mutator, boardTree, pageController) }}><div className="imageDropdown"></div></div>
|
||||
<div className="octo-button" style={{ color: "#000000", fontWeight: 600 }} onClick={(e) => { OctoUtils.showViewMenu(e, mutator, boardTree, showView) }}><div className="imageDropdown"></div></div>
|
||||
<div className="octo-spacer"></div>
|
||||
<div className="octo-button" onClick={(e) => { this.propertiesClicked(e) }}>Properties</div>
|
||||
<div className="octo-button" id="groupByButton" onClick={(e) => { this.groupByClicked(e) }}>
|
||||
|
@ -223,7 +226,7 @@ class BoardComponent extends React.Component<Props, State> {
|
|||
async showCard(card?: IBlock) {
|
||||
console.log(`showCard: ${card?.title}`)
|
||||
|
||||
await this.props.pageController.showCard(card)
|
||||
await this.props.showCard(card)
|
||||
}
|
||||
|
||||
async addCard(groupByValue?: string) {
|
||||
|
@ -270,8 +273,7 @@ class BoardComponent extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
private filterClicked(e: React.MouseEvent) {
|
||||
const { pageController } = this.props
|
||||
pageController.showFilter(e.target as HTMLElement)
|
||||
this.props.showFilter(e.target as HTMLElement)
|
||||
}
|
||||
|
||||
private async optionsClicked(e: React.MouseEvent) {
|
||||
|
@ -403,13 +405,13 @@ class BoardComponent extends React.Component<Props, State> {
|
|||
if (e.keyCode === 27) { // ESC: Clear search
|
||||
this.searchFieldRef.current.text = ""
|
||||
this.setState({ ...this.state, isSearching: false })
|
||||
this.props.pageController.setSearchText(undefined)
|
||||
this.props.setSearchText(undefined)
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
searchChanged(text?: string) {
|
||||
this.props.pageController.setSearchText(text)
|
||||
this.props.setSearchText(text)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import { WorkspaceTree } from "../workspaceTree"
|
|||
|
||||
type Props = {
|
||||
mutator: Mutator
|
||||
pageController: IPageController
|
||||
showBoard: (id: string) => void
|
||||
workspaceTree: WorkspaceTree,
|
||||
boardTree?: BoardTree
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ class Sidebar extends React.Component<Props> {
|
|||
}
|
||||
|
||||
private showOptions(e: React.MouseEvent, board: Board) {
|
||||
const { mutator, pageController, workspaceTree } = this.props
|
||||
const { mutator, showBoard, workspaceTree } = this.props
|
||||
const { boards } = workspaceTree
|
||||
|
||||
const options: MenuOption[] = []
|
||||
|
@ -64,8 +64,8 @@ class Sidebar extends React.Component<Props> {
|
|||
mutator.deleteBlock(
|
||||
board,
|
||||
"delete block",
|
||||
async () => { pageController.showBoard(nextBoardId!) },
|
||||
async () => { pageController.showBoard(board.id) },
|
||||
async () => { showBoard(nextBoardId!) },
|
||||
async () => { showBoard(board.id) },
|
||||
)
|
||||
break
|
||||
}
|
||||
|
@ -104,20 +104,19 @@ class Sidebar extends React.Component<Props> {
|
|||
}
|
||||
|
||||
private boardClicked(board: Board) {
|
||||
const { pageController } = this.props
|
||||
pageController.showBoard(board.id)
|
||||
this.props.showBoard(board.id)
|
||||
}
|
||||
|
||||
async addBoardClicked() {
|
||||
const { mutator, boardTree, pageController } = this.props
|
||||
const { mutator, boardTree, showBoard } = this.props
|
||||
|
||||
const oldBoardId = boardTree?.board?.id
|
||||
const board = new Board()
|
||||
await mutator.insertBlock(
|
||||
board,
|
||||
"add board",
|
||||
async () => { pageController.showBoard(board.id) },
|
||||
async () => { if (oldBoardId) { pageController.showBoard(oldBoardId) } })
|
||||
async () => { showBoard(board.id) },
|
||||
async () => { if (oldBoardId) { showBoard(oldBoardId) } })
|
||||
|
||||
await mutator.insertBlock(board)
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { BoardTree } from "../boardTree"
|
|||
import { CsvExporter } from "../csvExporter"
|
||||
import { Menu } from "../menu"
|
||||
import { Mutator } from "../mutator"
|
||||
import { IBlock, IPageController } from "../octoTypes"
|
||||
import { IBlock } from "../octoTypes"
|
||||
import { OctoUtils } from "../octoUtils"
|
||||
import { Utils } from "../utils"
|
||||
import Button from "./button"
|
||||
|
@ -17,7 +17,10 @@ import { TableRow } from "./tableRow"
|
|||
type Props = {
|
||||
mutator: Mutator,
|
||||
boardTree?: BoardTree
|
||||
pageController: IPageController
|
||||
showView: (id: string) => void
|
||||
showCard: (card: IBlock) => void
|
||||
showFilter: (el: HTMLElement) => void
|
||||
setSearchText: (text: string) => void
|
||||
}
|
||||
|
||||
type State = {
|
||||
|
@ -43,7 +46,7 @@ class TableComponent extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { mutator, boardTree, pageController } = this.props
|
||||
const { mutator, boardTree, showView } = this.props
|
||||
|
||||
if (!boardTree || !boardTree.board) {
|
||||
return (
|
||||
|
@ -85,7 +88,7 @@ class TableComponent extends React.Component<Props, State> {
|
|||
<div className="octo-table">
|
||||
<div className="octo-controls">
|
||||
<Editable style={{ color: "#000000", fontWeight: 600 }} text={activeView.title} placeholderText="Untitled View" onChanged={(text) => { mutator.changeTitle(activeView, text) }} />
|
||||
<div className="octo-button" style={{ color: "#000000", fontWeight: 600 }} onClick={(e) => { OctoUtils.showViewMenu(e, mutator, boardTree, pageController) }}><div className="imageDropdown"></div></div>
|
||||
<div className="octo-button" style={{ color: "#000000", fontWeight: 600 }} onClick={(e) => { OctoUtils.showViewMenu(e, mutator, boardTree, showView) }}><div className="imageDropdown"></div></div>
|
||||
<div className="octo-spacer"></div>
|
||||
<div className="octo-button" onClick={(e) => { this.propertiesClicked(e) }}>Properties</div>
|
||||
<div className={ hasFilter ? "octo-button active" : "octo-button"} onClick={(e) => { this.filterClicked(e) }}>Filter</div>
|
||||
|
@ -243,8 +246,7 @@ class TableComponent extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
private filterClicked(e: React.MouseEvent) {
|
||||
const { pageController } = this.props
|
||||
pageController.showFilter(e.target as HTMLElement)
|
||||
this.props.showFilter(e.target as HTMLElement)
|
||||
}
|
||||
|
||||
private async optionsClicked(e: React.MouseEvent) {
|
||||
|
@ -348,7 +350,7 @@ class TableComponent extends React.Component<Props, State> {
|
|||
async showCard(card: IBlock) {
|
||||
console.log(`showCard: ${card.title}`)
|
||||
|
||||
await this.props.pageController.showCard(card)
|
||||
await this.props.showCard(card)
|
||||
}
|
||||
|
||||
focusOnCardTitle(cardId: string) {
|
||||
|
@ -396,13 +398,13 @@ class TableComponent extends React.Component<Props, State> {
|
|||
if (e.keyCode === 27) { // ESC: Clear search
|
||||
this.searchFieldRef.current.text = ""
|
||||
this.setState({ ...this.state, isSearching: false })
|
||||
this.props.pageController.setSearchText(undefined)
|
||||
this.props.setSearchText(undefined)
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
searchChanged(text?: string) {
|
||||
this.props.pageController.setSearchText(text)
|
||||
this.props.setSearchText(text)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react"
|
||||
import { BoardTree } from "../boardTree"
|
||||
import { Mutator } from "../mutator"
|
||||
import { IPageController } from "../octoTypes"
|
||||
import { IBlock } from "../octoTypes"
|
||||
import { Utils } from "../utils"
|
||||
import { WorkspaceTree } from "../workspaceTree"
|
||||
import { BoardComponent } from "./boardComponent"
|
||||
|
@ -12,16 +12,20 @@ type Props = {
|
|||
mutator: Mutator,
|
||||
workspaceTree: WorkspaceTree
|
||||
boardTree?: BoardTree
|
||||
pageController: IPageController
|
||||
showBoard: (id: string) => void
|
||||
showView: (id: string) => void
|
||||
showCard: (card: IBlock) => void
|
||||
showFilter: (el: HTMLElement) => void
|
||||
setSearchText: (text: string) => void
|
||||
}
|
||||
|
||||
class WorkspaceComponent extends React.Component<Props> {
|
||||
render() {
|
||||
const { mutator, boardTree, workspaceTree, pageController } = this.props
|
||||
const { mutator, boardTree, workspaceTree, showBoard} = this.props
|
||||
|
||||
const element =
|
||||
<div className="octo-workspace">
|
||||
<Sidebar mutator={mutator} pageController={pageController} workspaceTree={workspaceTree} boardTree={boardTree}></Sidebar>
|
||||
<Sidebar mutator={mutator} showBoard={showBoard} workspaceTree={workspaceTree} boardTree={boardTree}></Sidebar>
|
||||
{this.mainComponent()}
|
||||
</div>
|
||||
|
||||
|
@ -29,7 +33,7 @@ class WorkspaceComponent extends React.Component<Props> {
|
|||
}
|
||||
|
||||
private mainComponent() {
|
||||
const { mutator, boardTree, pageController } = this.props
|
||||
const { mutator, boardTree, showCard, showFilter, setSearchText, showView } = this.props
|
||||
const { activeView } = boardTree || {}
|
||||
|
||||
if (!activeView) {
|
||||
|
@ -38,11 +42,11 @@ class WorkspaceComponent extends React.Component<Props> {
|
|||
|
||||
switch (activeView?.viewType) {
|
||||
case "board": {
|
||||
return <BoardComponent mutator={mutator} boardTree={boardTree} pageController={pageController} />
|
||||
return <BoardComponent mutator={mutator} boardTree={boardTree} showCard={showCard} showFilter={showFilter} setSearchText={setSearchText} showView={showView}/>
|
||||
}
|
||||
|
||||
case "table": {
|
||||
return <TableComponent mutator={mutator} boardTree={boardTree} pageController={pageController} />
|
||||
return <TableComponent mutator={mutator} boardTree={boardTree} showCard={showCard} showFilter={showFilter} setSearchText={setSearchText} showView={showView}/>
|
||||
}
|
||||
|
||||
default: {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { IBlock, IPageController } from "./octoTypes"
|
|||
import { Utils } from "./utils"
|
||||
|
||||
class OctoUtils {
|
||||
static async showViewMenu(e: React.MouseEvent, mutator: Mutator, boardTree: BoardTree, pageController: IPageController) {
|
||||
static async showViewMenu(e: React.MouseEvent, mutator: Mutator, boardTree: BoardTree, showView: (id: string) => void) {
|
||||
const { board } = boardTree
|
||||
|
||||
const options: MenuOption[] = boardTree.views.map(view => ({ id: view.id, name: view.title || "Untitled View" }))
|
||||
|
@ -33,7 +33,7 @@ class OctoUtils {
|
|||
const view = boardTree.activeView
|
||||
const nextView = boardTree.views.find(o => o !== view)
|
||||
await mutator.deleteBlock(view, "delete view")
|
||||
pageController.showView(nextView.id)
|
||||
showView(nextView.id)
|
||||
break
|
||||
}
|
||||
case "__addview-board": {
|
||||
|
@ -48,8 +48,8 @@ class OctoUtils {
|
|||
await mutator.insertBlock(
|
||||
view,
|
||||
"add view",
|
||||
async () => { pageController.showView(view.id) },
|
||||
async () => { pageController.showView(oldViewId) })
|
||||
async () => { showView(view.id) },
|
||||
async () => { showView(oldViewId) })
|
||||
break
|
||||
}
|
||||
case "__addview-table": {
|
||||
|
@ -65,13 +65,13 @@ class OctoUtils {
|
|||
await mutator.insertBlock(
|
||||
view,
|
||||
"add view",
|
||||
async () => { pageController.showView(view.id) },
|
||||
async () => { pageController.showView(oldViewId) })
|
||||
async () => { showView(view.id) },
|
||||
async () => { showView(oldViewId) })
|
||||
break
|
||||
}
|
||||
default: {
|
||||
const view = boardTree.views.find(o => o.id === optionId)
|
||||
pageController.showView(view.id)
|
||||
showView(view.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
239
src/client/pages/boardPage.tsx
Normal file
239
src/client/pages/boardPage.tsx
Normal file
|
@ -0,0 +1,239 @@
|
|||
import React from "react"
|
||||
import ReactDOM from "react-dom"
|
||||
import { BoardTree } from "../boardTree"
|
||||
import { BoardView } from "../boardView"
|
||||
import { CardTree } from "../cardTree"
|
||||
import { CardDialog } from "../components/cardDialog"
|
||||
import { FilterComponent } from "../components/filterComponent"
|
||||
import { PageHeader } from "../components/pageHeader"
|
||||
import { WorkspaceComponent } from "../components/workspaceComponent"
|
||||
import { FlashMessage } from "../flashMessage"
|
||||
import { Mutator } from "../mutator"
|
||||
import { OctoClient } from "../octoClient"
|
||||
import { OctoListener } from "../octoListener"
|
||||
import { IBlock, IPageController } from "../octoTypes"
|
||||
import { UndoManager } from "../undomanager"
|
||||
import { Utils } from "../utils"
|
||||
import { WorkspaceTree } from "../workspaceTree"
|
||||
|
||||
type Props = {
|
||||
}
|
||||
|
||||
type State = {
|
||||
boardId: string
|
||||
viewId: string
|
||||
workspaceTree: WorkspaceTree
|
||||
boardTree?: BoardTree
|
||||
}
|
||||
|
||||
export default class BoardPage extends React.Component<Props, State> {
|
||||
workspaceTree: WorkspaceTree
|
||||
boardTree?: BoardTree
|
||||
view: BoardView
|
||||
|
||||
updateTitleTimeout: number
|
||||
updatePropertyLabelTimeout: number
|
||||
|
||||
shownCardTree: CardTree
|
||||
|
||||
private filterAnchorElement?: HTMLElement
|
||||
private octo = new OctoClient()
|
||||
private boardListener = new OctoListener()
|
||||
private cardListener = new OctoListener()
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
const queryString = new URLSearchParams(window.location.search)
|
||||
const boardId = queryString.get("id")
|
||||
const viewId = queryString.get("v")
|
||||
|
||||
this.state = {
|
||||
boardId,
|
||||
viewId,
|
||||
workspaceTree: new WorkspaceTree(this.octo),
|
||||
}
|
||||
|
||||
console.log(`BoardPage. boardId: ${boardId}`)
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props, prevState: State) {
|
||||
const board = this.state.boardTree.board;
|
||||
const prevBoard = prevState.boardTree.board;
|
||||
|
||||
const activeView = this.state.boardTree.activeView;
|
||||
const prevActiveView = prevState.boardTree.activeView;
|
||||
|
||||
if (board.icon !== prevBoard.icon) {
|
||||
Utils.setFavicon(board.icon)
|
||||
}
|
||||
if (board.title !== prevBoard.title || activeView.title !== prevActiveView.title) {
|
||||
document.title = `OCTO - ${board.title} | ${activeView.title}`
|
||||
}
|
||||
}
|
||||
|
||||
undoRedoHandler = async (e: KeyboardEvent) => {
|
||||
if (e.target !== document) { return }
|
||||
|
||||
if (e.keyCode === 90 && !e.shiftKey && (e.ctrlKey || e.metaKey) && !e.altKey) { // Cmd+Z
|
||||
Utils.log(`Undo`)
|
||||
const description = UndoManager.shared.undoDescription
|
||||
await UndoManager.shared.undo()
|
||||
if (description) {
|
||||
FlashMessage.show(`Undo ${description}`)
|
||||
} else {
|
||||
FlashMessage.show(`Undo`)
|
||||
}
|
||||
} else if (e.keyCode === 90 && e.shiftKey && (e.ctrlKey || e.metaKey) && !e.altKey) { // Shift+Cmd+Z
|
||||
Utils.log(`Redo`)
|
||||
const description = UndoManager.shared.redoDescription
|
||||
await UndoManager.shared.redo()
|
||||
if (description) {
|
||||
FlashMessage.show(`Redo ${description}`)
|
||||
} else {
|
||||
FlashMessage.show(`Redo`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener("keydown", this.undoRedoHandler)
|
||||
if (this.state.boardId) {
|
||||
this.attachToBoard(this.state.boardId, this.state.viewId)
|
||||
} else {
|
||||
this.sync()
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener("keydown", this.undoRedoHandler)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { board, activeView } = this.state.boardTree || {}
|
||||
const mutator = new Mutator(this.octo)
|
||||
|
||||
// TODO Move all this into the root portal component when that is merged
|
||||
if (this.state.boardTree && this.state.boardTree.board && this.shownCardTree) {
|
||||
ReactDOM.render(
|
||||
<CardDialog mutator={mutator} boardTree={this.state.boardTree} cardTree={this.shownCardTree} onClose={() => { this.showCard(undefined) }}></CardDialog>,
|
||||
Utils.getElementById("overlay")
|
||||
)
|
||||
} else {
|
||||
ReactDOM.render(
|
||||
<div />,
|
||||
Utils.getElementById("overlay")
|
||||
)
|
||||
}
|
||||
|
||||
if (this.filterAnchorElement) {
|
||||
const element = this.filterAnchorElement
|
||||
const bodyRect = document.body.getBoundingClientRect()
|
||||
const rect = element.getBoundingClientRect()
|
||||
// Show at bottom-left of element
|
||||
const maxX = bodyRect.right - 420 - 100
|
||||
const pageX = Math.min(maxX, rect.left - bodyRect.left)
|
||||
const pageY = rect.bottom - bodyRect.top
|
||||
|
||||
ReactDOM.render(
|
||||
<FilterComponent
|
||||
mutator={mutator}
|
||||
boardTree={this.state.boardTree}
|
||||
pageX={pageX}
|
||||
pageY={pageY}
|
||||
onClose={() => { this.showFilter(undefined) }}
|
||||
>
|
||||
</FilterComponent>,
|
||||
Utils.getElementById("modal")
|
||||
)
|
||||
} else {
|
||||
ReactDOM.render(<div />, Utils.getElementById("modal"))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='BoardPage'>
|
||||
<WorkspaceComponent mutator={mutator} workspaceTree={this.workspaceTree} boardTree={this.state.boardTree} showView={this.showView} showCard={this.showCard} showBoard={this.showBoard} showFilter={this.showFilter} setSearchText={this.setSearchText} />,
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private attachToBoard(boardId: string, viewId?: string) {
|
||||
const boardTree = new BoardTree(this.octo, boardId)
|
||||
this.setState({
|
||||
boardId,
|
||||
viewId,
|
||||
boardTree,
|
||||
})
|
||||
|
||||
this.boardListener.open(boardId, (blockId: string) => {
|
||||
console.log(`octoListener.onChanged: ${blockId}`)
|
||||
this.sync()
|
||||
})
|
||||
|
||||
this.sync()
|
||||
}
|
||||
|
||||
async sync() {
|
||||
const { viewId, workspaceTree, boardTree } = this.state
|
||||
|
||||
await workspaceTree.sync()
|
||||
if (boardTree) {
|
||||
await boardTree.sync()
|
||||
|
||||
// Default to first view
|
||||
if (!viewId) {
|
||||
this.setState({viewId: boardTree.views[0].id})
|
||||
}
|
||||
|
||||
boardTree.setActiveView(this.state.viewId)
|
||||
// TODO: Handle error (viewId not found)
|
||||
this.setState({
|
||||
viewId: boardTree.activeView.id
|
||||
})
|
||||
console.log(`sync complete... title: ${boardTree.board.title}`)
|
||||
}
|
||||
}
|
||||
|
||||
// IPageController
|
||||
|
||||
async showCard(card: IBlock) {
|
||||
this.cardListener.close()
|
||||
|
||||
if (card) {
|
||||
const cardTree = new CardTree(this.octo, card.id)
|
||||
await cardTree.sync()
|
||||
this.shownCardTree = cardTree
|
||||
|
||||
this.cardListener = new OctoListener()
|
||||
this.cardListener.open(card.id, async () => {
|
||||
await cardTree.sync()
|
||||
this.render()
|
||||
})
|
||||
} else {
|
||||
this.shownCardTree = undefined
|
||||
}
|
||||
}
|
||||
|
||||
showBoard(boardId: string) {
|
||||
if (this.boardTree?.board?.id === boardId) { return }
|
||||
|
||||
const newUrl = window.location.protocol + "//" + window.location.host + window.location.pathname + `?id=${encodeURIComponent(boardId)}`
|
||||
window.history.pushState({ path: newUrl }, "", newUrl)
|
||||
|
||||
this.attachToBoard(boardId)
|
||||
}
|
||||
|
||||
showView(viewId: string) {
|
||||
this.state.boardTree.setActiveView(viewId)
|
||||
this.setState({viewId, boardTree: this.state.boardTree})
|
||||
const newUrl = window.location.protocol + "//" + window.location.host + window.location.pathname + `?id=${encodeURIComponent(this.state.boardId)}&v=${encodeURIComponent(viewId)}`
|
||||
window.history.pushState({ path: newUrl }, "", newUrl)
|
||||
}
|
||||
|
||||
showFilter(ahchorElement?: HTMLElement) {
|
||||
this.filterAnchorElement = ahchorElement
|
||||
}
|
||||
|
||||
setSearchText(text?: string) {
|
||||
this.boardTree.setSearchText(text)
|
||||
}
|
||||
}
|
|
@ -51,7 +51,7 @@ function makeCommonConfig() {
|
|||
inject: true,
|
||||
title: "OCTO",
|
||||
chunks: ["main"],
|
||||
template: "html-template/page.ejs",
|
||||
template: "html-templates/page.ejs",
|
||||
filename: 'index.html'
|
||||
}),
|
||||
],
|
||||
|
|
Loading…
Reference in a new issue