Merge branch 'main' into add-menu-wrapper
This commit is contained in:
commit
b49a760e4d
24 changed files with 206 additions and 184 deletions
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"serverRoot": "http://localhost:8000",
|
||||
"port": 8000,
|
||||
"dbtype": "sqlite3",
|
||||
"dbconfig": "./octo.db",
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -31,6 +31,9 @@ export default function App() {
|
|||
|
||||
<div id="overlay">
|
||||
</div>
|
||||
|
||||
<div id="modal">
|
||||
</div>
|
||||
</div>
|
||||
</Router>
|
||||
)
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 })) : []
|
||||
}
|
||||
}) || []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
18
src/client/card.ts
Normal 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 }
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
@ -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) }}>
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue