Merge branch 'main' into add-menu-wrapper

This commit is contained in:
Jesús Espino 2020-10-15 08:52:10 +02:00
commit b49a760e4d
24 changed files with 206 additions and 184 deletions

View file

@ -1,4 +1,5 @@
{
"serverRoot": "http://localhost:8000",
"port": 8000,
"dbtype": "sqlite3",
"dbconfig": "./octo.db",

View file

@ -21,7 +21,7 @@ func readConfigFile() (*Configuration, error) {
viper.SetConfigName("config") // name of config file (without extension)
viper.SetConfigType("json") // REQUIRED if the config file does not have the extension in the name
viper.AddConfigPath(".") // optionally look for config in the working directory
viper.SetDefault("ServerRoot", "http://localhost")
viper.SetDefault("ServerRoot", "http://localhost:8000")
viper.SetDefault("Port", 8000)
viper.SetDefault("DBType", "sqlite3")
viper.SetDefault("DBConfigString", "./octo.db")

View file

@ -48,16 +48,16 @@ func NewSQLStore(dbType, connectionString string) (*SQLStore, error) {
// Block is the basic data unit
type Block struct {
ID string `json:"id"`
ParentID string `json:"parentId"`
Schema int64 `json:"schema"`
Type string `json:"type"`
Title string `json:"title"`
Properties map[string]interface{} `json:"properties"`
Fields map[string]interface{} `json:"fields"`
CreateAt int64 `json:"createAt"`
UpdateAt int64 `json:"updateAt"`
DeleteAt int64 `json:"deleteAt"`
ID string `json:"id"`
ParentID string `json:"parentId"`
Schema int64 `json:"schema"`
Type string `json:"type"`
Title string `json:"title"`
Order int64 `json:"order"`
Fields map[string]interface{} `json:"fields"`
CreateAt int64 `json:"createAt"`
UpdateAt int64 `json:"updateAt"`
DeleteAt int64 `json:"deleteAt"`
}
func (s *SQLStore) createTablesIfNotExists() error {

View file

@ -31,6 +31,9 @@ export default function App() {
<div id="overlay">
</div>
<div id="modal">
</div>
</div>
</Router>
)

View file

@ -7,10 +7,7 @@ class Block implements IBlock {
parentId: string
type: string
title: string
icon?: string
url?: string
order: number
properties: Record<string, string> = {}
fields: Record<string, any> = {}
createAt: number = Date.now()
updateAt: number = 0
@ -37,32 +34,15 @@ class Block implements IBlock {
this.parentId = block.parentId
this.type = block.type
// Shallow copy here. Derived classes must make deep copies of their known properties in their constructors.
this.fields = block.fields ? { ...block.fields } : {}
this.title = block.title
this.icon = block.icon
this.url = block.url
this.order = block.order
this.createAt = block.createAt || now
this.updateAt = block.updateAt || now
this.deleteAt = block.deleteAt || 0
if (block.schema !== 1) {
if (Array.isArray(block.properties)) {
// HACKHACK: Port from old schema
this.properties = {}
for (const property of block.properties) {
if (property.id) {
this.properties[property.id] = property.value
}
}
} else {
this.properties = { ...block.properties || {} }
}
} else {
this.properties = { ...block.properties } // Shallow copy here. Derived classes must make deep copies of their known properties in their constructors.
}
}
}

View file

@ -16,6 +16,9 @@ interface IPropertyTemplate {
}
class Board extends Block {
get icon(): string { return this.fields.icon as string }
set icon(value: string) { this.fields.icon = value }
get cardProperties(): IPropertyTemplate[] { return this.fields.cardProperties as IPropertyTemplate[] }
set cardProperties(value: IPropertyTemplate[]) { this.fields.cardProperties = value }
@ -25,7 +28,7 @@ class Board extends Block {
if (block.fields?.cardProperties) {
// Deep clone of card properties and their options
this.cardProperties = block.fields?.cardProperties.map((o: IPropertyTemplate) => {
this.cardProperties = block.fields.cardProperties.map((o: IPropertyTemplate) => {
return {
id: o.id,
name: o.name,
@ -36,17 +39,6 @@ class Board extends Block {
} else {
this.cardProperties = []
}
if (block.schema !== 1) {
this.cardProperties = block.cardProperties?.map((o: IPropertyTemplate) => {
return {
id: o.id,
name: o.name,
type: o.type,
options: o.options ? o.options.map(option => ({ ...option })) : []
}
}) || []
}
}
}

View file

@ -1,24 +1,25 @@
import { Board, IPropertyOption, IPropertyTemplate } from "./board"
import { BoardView } from "./boardView"
import { Card } from "./card"
import { CardFilter } from "./cardFilter"
import { OctoClient } from "./octoClient"
import { IBlock } from "./octoTypes"
import { Utils } from "./utils"
type Group = { option: IPropertyOption, cards: IBlock[] }
type Group = { option: IPropertyOption, cards: Card[] }
class BoardTree {
board!: Board
views: BoardView[] = []
cards: IBlock[] = []
emptyGroupCards: IBlock[] = []
cards: Card[] = []
emptyGroupCards: Card[] = []
groups: Group[] = []
activeView?: BoardView
groupByProperty?: IPropertyTemplate
private searchText?: string
private allCards: IBlock[] = []
private allCards: Card[] = []
get allBlocks(): IBlock[] {
return [this.board, ...this.views, ...this.allCards]
}
@ -44,7 +45,7 @@ class BoardTree {
this.views = viewBlocks.map(o => new BoardView(o))
const cardBlocks = blocks.filter(block => block.type === "card")
this.allCards = cardBlocks
this.allCards = cardBlocks.map(o => new Card(o))
this.cards = []
this.ensureMinimumSchema()
@ -104,18 +105,25 @@ class BoardTree {
}
applyFilterSortAndGroup() {
Utils.assert(this.allCards !== undefined)
this.cards = this.filterCards(this.allCards)
Utils.assert(this.cards !== undefined)
this.cards = this.searchFilterCards(this.cards)
Utils.assert(this.cards !== undefined)
this.cards = this.sortCards(this.cards)
Utils.assert(this.cards !== undefined)
if (this.activeView.groupById) {
this.setGroupByProperty(this.activeView.groupById)
} else {
Utils.assert(this.activeView.viewType !== "board")
}
Utils.assert(this.cards !== undefined)
}
private searchFilterCards(cards: IBlock[]) {
private searchFilterCards(cards: Card[]): Card[] {
const searchText = this.searchText?.toLocaleLowerCase()
if (!searchText) { return cards.slice() }
@ -166,7 +174,7 @@ class BoardTree {
}
}
private filterCards(cards: IBlock[]): IBlock[] {
private filterCards(cards: Card[]): Card[] {
const { board } = this
const filterGroup = this.activeView?.filter
if (!filterGroup) { return cards.slice() }
@ -174,11 +182,11 @@ class BoardTree {
return CardFilter.applyFilterGroup(filterGroup, board.cardProperties, cards)
}
private sortCards(cards: IBlock[]): IBlock[] {
private sortCards(cards: Card[]): Card[] {
if (!this.activeView) { Utils.assertFailure(); return cards }
const { board } = this
const { sortOptions } = this.activeView
let sortedCards: IBlock[]
let sortedCards: Card[] = []
if (sortOptions.length < 1) {
Utils.log(`Default sort`)
@ -213,7 +221,11 @@ class BoardTree {
} else {
const sortPropertyId = sortOption.propertyId
const template = board.cardProperties.find(o => o.id === sortPropertyId)
Utils.log(`Sort by ${template.name}`)
if (!template) {
Utils.logError(`Missing template for property id: ${sortPropertyId}`)
return cards.slice()
}
Utils.log(`Sort by ${template?.name}`)
sortedCards = cards.sort((a, b) => {
// Always put cards with no titles at the bottom
if (a.title && !b.title) { return -1 }

View file

@ -25,9 +25,9 @@ class BoardView extends Block {
this.type = "view"
this.sortOptions = block.properties?.sortOptions?.map((o: ISortOption) => ({ ...o })) || [] // Deep clone
this.visiblePropertyIds = block.properties?.visiblePropertyIds?.slice() || []
this.filter = new FilterGroup(block.properties?.filter)
this.sortOptions = block.fields?.sortOptions?.map((o: ISortOption) => ({ ...o })) || [] // Deep clone
this.visiblePropertyIds = block.fields?.visiblePropertyIds?.slice() || []
this.filter = new FilterGroup(block.fields?.filter)
// TODO: Remove this fixup code
if (block.schema !== 1) {

18
src/client/card.ts Normal file
View file

@ -0,0 +1,18 @@
import { Block } from "./block"
class Card extends Block {
get icon(): string { return this.fields.icon as string }
set icon(value: string) { this.fields.icon = value }
get properties(): Record<string, string> { return this.fields.properties as Record<string, string> }
set properties(value: Record<string, string>) { this.fields.properties = value }
constructor(block: any = {}) {
super(block)
this.type = "card"
this.properties = { ...(block.fields?.properties || {}) }
}
}
export { Card }

View file

@ -1,15 +1,15 @@
import { IPropertyTemplate } from "./board"
import { Card } from "./card"
import { FilterClause } from "./filterClause"
import { FilterGroup } from "./filterGroup"
import { IBlock } from "./octoTypes"
import { Utils } from "./utils"
class CardFilter {
static applyFilterGroup(filterGroup: FilterGroup, templates: IPropertyTemplate[], cards: IBlock[]): IBlock[] {
static applyFilterGroup(filterGroup: FilterGroup, templates: IPropertyTemplate[], cards: Card[]): Card[] {
return cards.filter(card => this.isFilterGroupMet(filterGroup, templates, card))
}
static isFilterGroupMet(filterGroup: FilterGroup, templates: IPropertyTemplate[], card: IBlock): boolean {
static isFilterGroupMet(filterGroup: FilterGroup, templates: IPropertyTemplate[], card: Card): boolean {
const { filters } = filterGroup
if (filterGroup.filters.length < 1) {
@ -38,7 +38,7 @@ class CardFilter {
}
}
static isClauseMet(filter: FilterClause, templates: IPropertyTemplate[], card: IBlock): boolean {
static isClauseMet(filter: FilterClause, templates: IPropertyTemplate[], card: Card): boolean {
const value = card.properties[filter.propertyId]
switch (filter.condition) {
case "includes": {
@ -55,22 +55,31 @@ class CardFilter {
case "isNotEmpty": {
return !!value
}
default: {
Utils.assertFailure(`Invalid filter condition ${filter.condition}`)
}
}
Utils.assertFailure(`Invalid filter condition ${filter.condition}`)
return true
}
static propertiesThatMeetFilterGroup(filterGroup: FilterGroup, templates: IPropertyTemplate[]): Record<string, any> {
static propertiesThatMeetFilterGroup(filterGroup: FilterGroup, templates: IPropertyTemplate[]): Record<string, string> {
// TODO: Handle filter groups
const filters = filterGroup.filters.filter(o => !FilterGroup.isAnInstanceOf(o))
if (filters.length < 1) { return [] }
if (filters.length < 1) { return {} }
if (filterGroup.operation === "or") {
// Just need to meet the first clause
const property = this.propertyThatMeetsFilterClause(filters[0] as FilterClause, templates)
return [property]
const result: Record<string, string> = {}
result[property.id] = property.value
return result
} else {
return filters.map(filterClause => this.propertyThatMeetsFilterClause(filterClause as FilterClause, templates))
const result: Record<string, string> = {}
filters.forEach(filterClause => {
const p = this.propertyThatMeetsFilterClause(filterClause as FilterClause, templates)
result[p.id] = p.value
})
return result
}
}

View file

@ -1,8 +1,9 @@
import { Card } from "./card"
import { OctoClient } from "./octoClient"
import { IBlock } from "./octoTypes"
class CardTree {
card: IBlock
card: Card
comments: IBlock[]
contents: IBlock[]
isSynched: boolean
@ -18,7 +19,7 @@ class CardTree {
}
private rebuild(blocks: IBlock[]) {
this.card = blocks.find(o => o.id === this.cardId)
this.card = new Card(blocks.find(o => o.id === this.cardId))
this.comments = blocks
.filter(block => block.type === "comment")

View file

@ -1,15 +1,15 @@
import React from "react"
import { Block } from "../block"
import { IPropertyTemplate } from "../board"
import { Card } from "../card"
import { Menu } from "../menu"
import { Mutator } from "../mutator"
import { IBlock } from "../octoTypes"
import { OctoUtils } from "../octoUtils"
import { Utils } from "../utils"
type BoardCardProps = {
mutator: Mutator
card: IBlock
card: Card
visiblePropertyTemplates: IPropertyTemplate[]
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void
onDragStart?: (e: React.DragEvent<HTMLDivElement>) => void

View file

@ -1,17 +1,17 @@
import React from "react"
import { Archiver } from "../archiver"
import { Block } from "../block"
import { BlockIcons } from "../blockIcons"
import { IPropertyOption } from "../board"
import { BoardTree } from "../boardTree"
import { Card } from "../card"
import { CardFilter } from "../cardFilter"
import ViewMenu from "../components/viewMenu"
import MenuWrapper from "../widgets/menuWrapper"
import Menu from "../widgets/menu"
import { Constants } from "../constants"
import { randomEmojiList } from "../emojiList"
import { Menu as OldMenu } from "../menu"
import { Mutator } from "../mutator"
import { IBlock } from "../octoTypes"
import { OctoUtils } from "../octoUtils"
import { Utils } from "../utils"
import { BoardCard } from "./boardCard"
@ -23,7 +23,7 @@ type Props = {
mutator: Mutator,
boardTree?: BoardTree
showView: (id: string) => void
showCard: (card: IBlock) => void
showCard: (card: Card) => void
showFilter: (el: HTMLElement) => void
setSearchText: (text: string) => void
}
@ -34,7 +34,7 @@ type State = {
}
class BoardComponent extends React.Component<Props, State> {
private draggedCard: IBlock
private draggedCard: Card
private draggedHeaderOption: IPropertyOption
private searchFieldRef = React.createRef<Editable>()
@ -232,7 +232,7 @@ class BoardComponent extends React.Component<Props, State> {
)
}
async showCard(card?: IBlock) {
async showCard(card?: Card) {
console.log(`showCard: ${card?.title}`)
await this.props.showCard(card)
@ -242,8 +242,9 @@ class BoardComponent extends React.Component<Props, State> {
const { mutator, boardTree } = this.props
const { activeView, board } = boardTree
const properties = CardFilter.propertiesThatMeetFilterGroup(activeView.filter, board.cardProperties)
const card = new Block({ type: "card", parentId: boardTree.board.id, properties })
const card = new Card()
card.parentId = boardTree.board.id
card.properties = CardFilter.propertiesThatMeetFilterGroup(activeView.filter, board.cardProperties)
if (boardTree.groupByProperty) {
card.properties[boardTree.groupByProperty.id] = groupByValue
}
@ -277,9 +278,11 @@ class BoardComponent extends React.Component<Props, State> {
}
case "testAdd100Cards": {
this.testAddCards(100)
break
}
case "testAdd1000Cards": {
this.testAddCards(1000)
break
}
}
}
@ -290,17 +293,20 @@ class BoardComponent extends React.Component<Props, State> {
const { mutator, boardTree } = this.props
const { board, activeView } = boardTree
const startCount = boardTree?.cards?.length
let optionIndex = 0
for (let i = 0; i < count; i++) {
const properties = CardFilter.propertiesThatMeetFilterGroup(activeView.filter, board.cardProperties)
const card = new Block({ type: "card", parentId: boardTree.board.id, properties })
const card = new Card()
card.parentId = boardTree.board.id
card.properties = CardFilter.propertiesThatMeetFilterGroup(activeView.filter, board.cardProperties)
if (boardTree.groupByProperty && boardTree.groupByProperty.options.length > 0) {
// Cycle through options
const option = boardTree.groupByProperty.options[optionIndex]
optionIndex = (optionIndex + 1) % boardTree.groupByProperty.options.length
card.properties[boardTree.groupByProperty.id] = option.value
card.title = `Test Card ${i + 1}`
card.title = `Test Card ${startCount + i + 1}`
card.icon = BlockIcons.shared.randomIcon()
}
await mutator.insertBlock(card, "test add card")
}
@ -355,6 +361,7 @@ class BoardComponent extends React.Component<Props, State> {
color: "#cccccc"
}
Utils.assert(boardTree.groupByProperty)
await mutator.insertPropertyOption(boardTree, boardTree.groupByProperty, option, "add group")
}

View file

@ -79,7 +79,7 @@ class CardDialog extends React.Component<Props, State> {
}} />
</div>
} else if (block.type === "image") {
const url = block.url
const url = block.fields.url
return <div key={block.id} className="octo-block octo-hover-container">
<div className="octo-block-margin">
<div className="octo-button octo-hovercontrol square octo-hover-item" onClick={(e) => { this.showContentBlockMenu(e, block) }}>

View file

@ -52,7 +52,7 @@ class Switch extends React.Component<Props, State> {
private async onClicked() {
const newIsOn = !this.state.isOn
await this.setState({ isOn: newIsOn })
this.setState({ isOn: newIsOn })
const { onChanged } = this.props

View file

@ -4,13 +4,13 @@ import { Block } from "../block"
import { BlockIcons } from "../blockIcons"
import { IPropertyTemplate } from "../board"
import { BoardTree } from "../boardTree"
import { CsvExporter } from "../csvExporter"
import { Card } from "../card"
import ViewMenu from "../components/viewMenu"
import MenuWrapper from "../widgets/menuWrapper"
import Menu from "../widgets/menu"
import { CsvExporter } from "../csvExporter"
import { Menu as OldMenu } from "../menu"
import { Mutator } from "../mutator"
import { IBlock } from "../octoTypes"
import { OctoUtils } from "../octoUtils"
import { Utils } from "../utils"
import Button from "./button"
@ -21,7 +21,7 @@ type Props = {
mutator: Mutator,
boardTree?: BoardTree
showView: (id: string) => void
showCard: (card: IBlock) => void
showCard: (card: Card) => void
showFilter: (el: HTMLElement) => void
setSearchText: (text: string) => void
}
@ -348,7 +348,7 @@ class TableComponent extends React.Component<Props, State> {
OldMenu.shared.showAtElement(e.target as HTMLElement)
}
async showCard(card: IBlock) {
async showCard(card: Card) {
console.log(`showCard: ${card.title}`)
await this.props.showCard(card)
@ -363,7 +363,8 @@ class TableComponent extends React.Component<Props, State> {
async addCard(show: boolean = false) {
const { mutator, boardTree } = this.props
const card = new Block({ type: "card", parentId: boardTree.board.id })
const card = new Card()
card.parentId = boardTree.board.id
await mutator.insertBlock(
card,
"add card",

View file

@ -1,16 +1,16 @@
import React from "react"
import { BoardTree } from "../boardTree"
import { Card } from "../card"
import { Mutator } from "../mutator"
import { IBlock } from "../octoTypes"
import { OctoUtils } from "../octoUtils"
import { Editable } from "./editable"
type Props = {
mutator: Mutator
boardTree: BoardTree
card: IBlock
card: Card
focusOnMount: boolean
showCard: (card: IBlock) => void
showCard: (card: Card) => void
onKeyDown: (e: React.KeyboardEvent) => void
}

View file

@ -1,7 +1,7 @@
import React from "react"
import { BoardTree } from "../boardTree"
import { Card } from "../card"
import { Mutator } from "../mutator"
import { IBlock } from "../octoTypes"
import { Utils } from "../utils"
import { WorkspaceTree } from "../workspaceTree"
import { BoardComponent } from "./boardComponent"
@ -14,7 +14,7 @@ type Props = {
boardTree?: BoardTree
showBoard: (id: string) => void
showView: (id: string) => void
showCard: (card: IBlock) => void
showCard: (card: Card) => void
showFilter: (el: HTMLElement) => void
setSearchText: (text: string) => void
}

View file

@ -2,6 +2,7 @@ import { Block } from "./block"
import { Board, IPropertyOption, IPropertyTemplate, PropertyType } from "./board"
import { BoardTree } from "./boardTree"
import { BoardView, ISortOption } from "./boardView"
import { Card } from "./card"
import { FilterGroup } from "./filterGroup"
import { OctoClient } from "./octoClient"
import { IBlock } from "./octoTypes"
@ -87,7 +88,7 @@ class Mutator {
)
}
async changeIcon(block: IBlock, icon: string, description: string = "change icon") {
async changeIcon(block: Card | Board, icon: string, description: string = "change icon") {
const { octo, undoManager } = this
const oldValue = block.icon
@ -291,6 +292,8 @@ class Mutator {
const { octo, undoManager } = this
const { board } = boardTree
Utils.assert(board.cardProperties.includes(template))
const oldValue = template.options
const newValue = template.options.slice()
newValue.push(option)
@ -405,18 +408,18 @@ class Mutator {
)
}
async changePropertyValue(block: IBlock, propertyId: string, value?: string, description: string = "change property") {
async changePropertyValue(card: Card, propertyId: string, value?: string, description: string = "change property") {
const { octo, undoManager } = this
const oldValue = block.properties[propertyId]
const oldValue = card.properties[propertyId]
await undoManager.perform(
async () => {
block.properties[propertyId] = value
await octo.updateBlock(block)
card.properties[propertyId] = value
await octo.updateBlock(card)
},
async () => {
block.properties[propertyId] = oldValue
await octo.updateBlock(block)
card.properties[propertyId] = oldValue
await octo.updateBlock(card)
},
description
)
@ -531,7 +534,8 @@ class Mutator {
return undefined
}
const block = new Block({ type: "image", parentId, url, order })
const block = new Block({ type: "image", parentId, order })
block.fields.url = url
await undoManager.perform(
async () => {

View file

@ -57,25 +57,20 @@ class OctoClient {
}
const response = await fetch(this.serverUrl + path)
const blocks = await response.json() as IBlock[]
const blocks = (await response.json() || []) as IBlock[]
this.fixBlocks(blocks)
return blocks
}
fixBlocks(blocks: IBlock[]) {
fixBlocks(blocks: IBlock[]): void {
// TODO
for (const block of blocks) {
if (!block.properties) { block.properties = {} }
if (Array.isArray(block.properties)) {
// PORT from old schema
const properties: Record<string, string> = {}
for (const property of block.properties) {
if (property.id) {
properties[property.id] = property.value
}
}
block.properties = properties
}
if (!block.fields) { block.fields = {} }
const o = block as any
if (o.cardProperties) { block.fields.cardProperties = o.cardProperties; delete o.cardProperties }
if (o.properties) { block.fields.properties = o.properties; delete o.properties }
if (o.icon) { block.fields.icon = o.icon; delete o.icon }
if (o.url) { block.fields.url = o.url; delete o.url }
}
}

View file

@ -1,3 +1,5 @@
import { Card } from "./card"
// A block is the fundamental data type
interface IBlock {
id: string
@ -6,10 +8,7 @@ interface IBlock {
schema: number
type: string
title?: string
url?: string // TODO: Move to properties (_url)
icon?: string
order: number
properties: Record<string, string>
fields: Record<string, any>
createAt: number
@ -19,7 +18,7 @@ interface IBlock {
// These are methods exposed by the top-level page to components
interface IPageController {
showCard(card: IBlock): Promise<void>
showCard(card: Card): Promise<void>
showBoard(boardId: string): void
showView(viewId: string): void
showFilter(anchorElement?: HTMLElement): void

View file

@ -1,9 +1,10 @@
import React from "react"
import { IPropertyTemplate } from "./board"
import { BoardTree } from "./boardTree"
import { BoardView, ISortOption } from "./boardView"
import { ISortOption } from "./boardView"
import { Card } from "./card"
import { Editable } from "./components/editable"
import { Menu, MenuOption } from "./menu"
import { Menu } from "./menu"
import { Mutator } from "./mutator"
import { IBlock } from "./octoTypes"
import { Utils } from "./utils"
@ -25,15 +26,15 @@ class OctoUtils {
return displayValue
}
static propertyValueReadonlyElement(card: IBlock, propertyTemplate: IPropertyTemplate, emptyDisplayValue: string = "Empty"): JSX.Element {
static propertyValueReadonlyElement(card: Card, propertyTemplate: IPropertyTemplate, emptyDisplayValue: string = "Empty"): JSX.Element {
return this.propertyValueElement(undefined, card, propertyTemplate, emptyDisplayValue)
}
static propertyValueEditableElement(mutator: Mutator, card: IBlock, propertyTemplate: IPropertyTemplate, emptyDisplayValue?: string): JSX.Element {
static propertyValueEditableElement(mutator: Mutator, card: Card, propertyTemplate: IPropertyTemplate, emptyDisplayValue?: string): JSX.Element {
return this.propertyValueElement(mutator, card, propertyTemplate, emptyDisplayValue)
}
private static propertyValueElement(mutator: Mutator | undefined, card: IBlock, propertyTemplate: IPropertyTemplate, emptyDisplayValue: string = "Empty"): JSX.Element {
private static propertyValueElement(mutator: Mutator | undefined, card: Card, propertyTemplate: IPropertyTemplate, emptyDisplayValue: string = "Empty"): JSX.Element {
const propertyValue = card.properties[propertyTemplate.id]
const displayValue = OctoUtils.propertyDisplayValue(card, propertyValue, propertyTemplate)
const finalDisplayValue = displayValue || emptyDisplayValue

View file

@ -2,6 +2,7 @@ import React from "react"
import ReactDOM from "react-dom"
import { BoardTree } from "../boardTree"
import { BoardView } from "../boardView"
import { Card } from "../card"
import { CardTree } from "../cardTree"
import { CardDialog } from "../components/cardDialog"
import { FilterComponent } from "../components/filterComponent"
@ -10,7 +11,6 @@ import { FlashMessage } from "../flashMessage"
import { Mutator } from "../mutator"
import { OctoClient } from "../octoClient"
import { OctoListener } from "../octoListener"
import { IBlock } from "../octoTypes"
import { UndoManager } from "../undomanager"
import { Utils } from "../utils"
import { WorkspaceTree } from "../workspaceTree"
@ -24,6 +24,7 @@ type State = {
workspaceTree: WorkspaceTree
boardTree?: BoardTree
shownCardTree?: CardTree
filterAnchorElement?: HTMLElement
}
export default class BoardPage extends React.Component<Props, State> {
@ -32,7 +33,6 @@ export default class BoardPage extends React.Component<Props, State> {
updateTitleTimeout: number
updatePropertyLabelTimeout: number
private filterAnchorElement?: HTMLElement
private octo = new OctoClient()
private boardListener = new OctoListener()
private cardListener = new OctoListener()
@ -126,8 +126,8 @@ export default class BoardPage extends React.Component<Props, State> {
}
}
if (this.filterAnchorElement) {
const element = this.filterAnchorElement
if (this.state.filterAnchorElement) {
const element = this.state.filterAnchorElement
const bodyRect = document.body.getBoundingClientRect()
const rect = element.getBoundingClientRect()
// Show at bottom-left of element
@ -210,7 +210,7 @@ export default class BoardPage extends React.Component<Props, State> {
// IPageController
async showCard(card: IBlock) {
async showCard(card: Card) {
this.cardListener.close()
if (card) {
@ -246,11 +246,12 @@ export default class BoardPage extends React.Component<Props, State> {
window.history.pushState({ path: newUrl }, "", newUrl)
}
showFilter(ahchorElement?: HTMLElement) {
this.filterAnchorElement = ahchorElement
showFilter(anchorElement?: HTMLElement) {
this.setState({...this.state, filterAnchorElement: anchorElement})
}
setSearchText(text?: string) {
this.state.boardTree?.setSearchText(text)
this.setState({...this.state, boardTree: this.state.boardTree})
}
}

View file

@ -1,76 +1,74 @@
import React from 'react';
import { IBlock } from "../octoTypes"
import React from 'react'
import { Archiver } from "../archiver"
import { Board } from "../board"
import Button from '../components/button'
import { Mutator } from "../mutator"
import { OctoClient } from "../octoClient"
import { IBlock } from "../octoTypes"
import { UndoManager } from "../undomanager"
import { Utils } from "../utils"
import Button from '../components/button';
type Props = {};
type Props = {}
type State = {
boards: IBlock[];
};
boards: IBlock[]
}
export default class HomePage extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
boards: [],
}
}
constructor(props: Props) {
super(props)
this.state = {
boards: [],
}
}
componentDidMount() {
this.loadBoards();
}
componentDidMount() {
this.loadBoards()
}
loadBoards = async () => {
const octo = new OctoClient()
loadBoards = async () => {
const octo = new OctoClient()
const boards = await octo.getBlocks(null, "board")
this.setState({boards});
}
this.setState({ boards })
}
importClicked = async () => {
const octo = new OctoClient()
const mutator = new Mutator(octo, UndoManager.shared)
Archiver.importFullArchive(mutator, () => {
this.loadBoards()
})
}
importClicked = async () => {
const octo = new OctoClient()
const mutator = new Mutator(octo, UndoManager.shared)
Archiver.importFullArchive(mutator, () => {
this.loadBoards()
})
}
exportClicked = async () => {
const octo = new OctoClient()
const mutator = new Mutator(octo, UndoManager.shared)
Archiver.exportFullArchive(mutator)
}
exportClicked = async () => {
const octo = new OctoClient()
const mutator = new Mutator(octo, UndoManager.shared)
Archiver.exportFullArchive(mutator)
}
addClicked = async () => {
const octo = new OctoClient()
addClicked = async () => {
const octo = new OctoClient()
const board = new Board()
await octo.insertBlock(board)
}
public render(): React.ReactNode {
return (
<div>
<Button onClick={this.addClicked}>+ Add Board</Button>
<br />
<Button onClick={this.addClicked}>Import Archive</Button>
<br />
<Button onClick={this.addClicked}>Export Archive</Button>
{this.state.boards.map((board) => (
<p>
<a href={`/board/${board.id}`}>
{board.icon && <span>{board.icon}</span>}
<span>{board.title}</span>
<span>{Utils.displayDate(new Date(board.updateAt))}</span>
</a>
</p>
))}
</div>
);
}
render(): React.ReactNode {
return (
<div>
<Button onClick={this.addClicked}>+ Add Board</Button>
<br />
<Button onClick={this.addClicked}>Import Archive</Button>
<br />
<Button onClick={this.addClicked}>Export Archive</Button>
{this.state.boards.map((board) => (
<p>
<a href={`/board/${board.id}`}>
<span>{board.title}</span>
<span>{Utils.displayDate(new Date(board.updateAt))}</span>
</a>
</p>
))}
</div>
)
}
}