🎨 Add Relation and Rollup column to database table view https://github.com/siyuan-note/siyuan/issues/9888

This commit is contained in:
Daniel 2023-12-15 20:05:14 +08:00
parent 0547f27839
commit fca3bf6855
No known key found for this signature in database
GPG key ID: 86211BA83DF03017
5 changed files with 215 additions and 5 deletions

View file

@ -66,6 +66,8 @@ const (
KeyTypeCreated KeyType = "created"
KeyTypeUpdated KeyType = "updated"
KeyTypeCheckbox KeyType = "checkbox"
KeyTypeRelation KeyType = "relation"
KeyTypeRollup KeyType = "rollup"
)
// Key 描述了属性视图属性列的基础结构。
@ -77,9 +79,23 @@ type Key struct {
// 以下是某些列类型的特有属性
Options []*KeySelectOption `json:"options,omitempty"` // 选项列表
NumberFormat NumberFormat `json:"numberFormat"` // 列数字格式化
Template string `json:"template"` // 模板内容
// 单选/多选列
Options []*KeySelectOption `json:"options,omitempty"` // 选项列表
// 数字列
NumberFormat NumberFormat `json:"numberFormat"` // 列数字格式化
// 模板列
Template string `json:"template"` // 模板内容
// 关联列
RelationAvID string `json:"relationAvID"` // 关联的属性视图 ID
RelationKeyID string `json:"relationKeyID"` // 关联列 ID
IsBiRelation bool `json:"isBiRelation"` // 是否双向关联
BackRelationKeyID string `json:"backRelationKeyID"` // 双向关联时回链关联列的 ID
// 汇总列
RollupKeyID string `json:"rollupKeyID"` // 汇总列 ID
}
func NewKey(id, name, icon string, keyType KeyType) *Key {

View file

@ -186,6 +186,10 @@ func (value *Value) Compare(other *Value) int {
}
return 0
}
case KeyTypeRelation:
// TODO: relation compare
case KeyTypeRollup:
// TODO: rollup compare
}
return 0
}
@ -567,6 +571,29 @@ func (value *Value) CompareOperator(other *Value, operator FilterOperator) bool
return !value.Checkbox.Checked
}
}
if nil != value.Relation && nil != other.Relation {
switch operator {
case FilterOperatorContains:
if "" == strings.TrimSpace(other.Relation.Content) {
return true
}
return strings.Contains(value.Relation.Content, other.Relation.Content)
case FilterOperatorDoesNotContain:
if "" == strings.TrimSpace(other.Relation.Content) {
return true
}
return !strings.Contains(value.Relation.Content, other.Relation.Content)
case FilterOperatorIsEmpty:
return "" == strings.TrimSpace(value.Relation.Content)
case FilterOperatorIsNotEmpty:
return "" != strings.TrimSpace(value.Relation.Content)
}
}
if nil != value.Rollup && nil != other.Rollup {
// TODO: rollup filter
}
return false
}
@ -760,6 +787,10 @@ func (table *Table) CalcCols() {
table.calcColUpdated(col, i)
case KeyTypeCheckbox:
table.calcColCheckbox(col, i)
case KeyTypeRelation:
table.calcColRelation(col, i)
case KeyTypeRollup:
table.calcColRollup(col, i)
}
}
}
@ -1912,3 +1943,133 @@ func (table *Table) calcColCheckbox(col *TableColumn, colIndex int) {
}
}
}
func (table *Table) calcColRelation(col *TableColumn, colIndex int) {
switch col.Calc.Operator {
case CalcOperatorCountAll:
col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(len(table.Rows)), NumberFormatNone)}
case CalcOperatorCountValues:
countValues := 0
for _, row := range table.Rows {
if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Relation {
countValues++
}
}
col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countValues), NumberFormatNone)}
case CalcOperatorCountUniqueValues:
countUniqueValues := 0
uniqueValues := map[string]bool{}
for _, row := range table.Rows {
if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Relation {
for _, id := range row.Cells[colIndex].Value.Relation.BlockIDs {
if !uniqueValues[id] {
uniqueValues[id] = true
countUniqueValues++
}
}
}
}
col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countUniqueValues), NumberFormatNone)}
case CalcOperatorCountEmpty:
countEmpty := 0
for _, row := range table.Rows {
if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.Relation || 0 == len(row.Cells[colIndex].Value.Relation.BlockIDs) {
countEmpty++
}
}
col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countEmpty), NumberFormatNone)}
case CalcOperatorCountNotEmpty:
countNotEmpty := 0
for _, row := range table.Rows {
if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Relation && 0 < len(row.Cells[colIndex].Value.Relation.BlockIDs) {
countNotEmpty++
}
}
col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty), NumberFormatNone)}
case CalcOperatorPercentEmpty:
countEmpty := 0
for _, row := range table.Rows {
if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.Relation || 0 == len(row.Cells[colIndex].Value.Relation.BlockIDs) {
countEmpty++
}
}
if 0 < len(table.Rows) {
col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
}
case CalcOperatorPercentNotEmpty:
countNotEmpty := 0
for _, row := range table.Rows {
if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Relation && 0 < len(row.Cells[colIndex].Value.Relation.BlockIDs) {
countNotEmpty++
}
}
if 0 < len(table.Rows) {
col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
}
}
}
func (table *Table) calcColRollup(col *TableColumn, colIndex int) {
switch col.Calc.Operator {
case CalcOperatorCountAll:
col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(len(table.Rows)), NumberFormatNone)}
case CalcOperatorCountValues:
countValues := 0
for _, row := range table.Rows {
if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Rollup {
countValues++
}
}
col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countValues), NumberFormatNone)}
case CalcOperatorCountUniqueValues:
countUniqueValues := 0
uniqueValues := map[string]bool{}
for _, row := range table.Rows {
if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Rollup {
for _, content := range row.Cells[colIndex].Value.Rollup.Contents {
if !uniqueValues[content] {
uniqueValues[content] = true
countUniqueValues++
}
}
}
}
col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countUniqueValues), NumberFormatNone)}
case CalcOperatorCountEmpty:
countEmpty := 0
for _, row := range table.Rows {
if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.Rollup || 0 == len(row.Cells[colIndex].Value.Rollup.Contents) {
countEmpty++
}
}
col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countEmpty), NumberFormatNone)}
case CalcOperatorCountNotEmpty:
countNotEmpty := 0
for _, row := range table.Rows {
if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Rollup && 0 < len(row.Cells[colIndex].Value.Rollup.Contents) {
countNotEmpty++
}
}
col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty), NumberFormatNone)}
case CalcOperatorPercentEmpty:
countEmpty := 0
for _, row := range table.Rows {
if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.Rollup || 0 == len(row.Cells[colIndex].Value.Rollup.Contents) {
countEmpty++
}
}
if 0 < len(table.Rows) {
col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
}
case CalcOperatorPercentNotEmpty:
countNotEmpty := 0
for _, row := range table.Rows {
if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Rollup && 0 < len(row.Cells[colIndex].Value.Rollup.Contents) {
countNotEmpty++
}
}
if 0 < len(table.Rows) {
col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)}
}
}
}

View file

@ -50,6 +50,8 @@ type Value struct {
Created *ValueCreated `json:"created,omitempty"`
Updated *ValueUpdated `json:"updated,omitempty"`
Checkbox *ValueCheckbox `json:"checkbox,omitempty"`
Relation *ValueRelation `json:"relation,omitempty"`
Rollup *ValueRollup `json:"rollup,omitempty"`
}
func (value *Value) String() string {
@ -135,6 +137,16 @@ func (value *Value) String() string {
return "√"
}
return ""
case KeyTypeRelation:
if nil == value.Relation {
return ""
}
return value.Relation.Content
case KeyTypeRollup:
if nil == value.Rollup {
return ""
}
return strings.Join(value.Rollup.Contents, " ")
default:
return ""
}
@ -433,3 +445,12 @@ func NewFormattedValueUpdated(content, content2 int64, format UpdatedFormat) (re
type ValueCheckbox struct {
Checked bool `json:"checked"`
}
type ValueRelation struct {
Content string `json:"content"`
BlockIDs []string `json:"blockIDs"`
}
type ValueRollup struct {
Contents []string `json:"contents"`
}

View file

@ -1490,7 +1490,9 @@ func addAttributeViewColumn(operation *Operation) (err error) {
keyType := av.KeyType(operation.Typ)
switch keyType {
case av.KeyTypeText, av.KeyTypeNumber, av.KeyTypeDate, av.KeyTypeSelect, av.KeyTypeMSelect, av.KeyTypeURL, av.KeyTypeEmail, av.KeyTypePhone, av.KeyTypeMAsset, av.KeyTypeTemplate, av.KeyTypeCreated, av.KeyTypeUpdated, av.KeyTypeCheckbox:
case av.KeyTypeText, av.KeyTypeNumber, av.KeyTypeDate, av.KeyTypeSelect, av.KeyTypeMSelect, av.KeyTypeURL, av.KeyTypeEmail,
av.KeyTypePhone, av.KeyTypeMAsset, av.KeyTypeTemplate, av.KeyTypeCreated, av.KeyTypeUpdated, av.KeyTypeCheckbox,
av.KeyTypeRelation, av.KeyTypeRollup:
var icon string
if nil != operation.Data {
icon = operation.Data.(string)
@ -1584,7 +1586,9 @@ func updateAttributeViewColumn(operation *Operation) (err error) {
colType := av.KeyType(operation.Typ)
switch colType {
case av.KeyTypeBlock, av.KeyTypeText, av.KeyTypeNumber, av.KeyTypeDate, av.KeyTypeSelect, av.KeyTypeMSelect, av.KeyTypeURL, av.KeyTypeEmail, av.KeyTypePhone, av.KeyTypeMAsset, av.KeyTypeTemplate, av.KeyTypeCreated, av.KeyTypeUpdated, av.KeyTypeCheckbox:
case av.KeyTypeBlock, av.KeyTypeText, av.KeyTypeNumber, av.KeyTypeDate, av.KeyTypeSelect, av.KeyTypeMSelect, av.KeyTypeURL, av.KeyTypeEmail,
av.KeyTypePhone, av.KeyTypeMAsset, av.KeyTypeTemplate, av.KeyTypeCreated, av.KeyTypeUpdated, av.KeyTypeCheckbox,
av.KeyTypeRelation, av.KeyTypeRollup:
for _, keyValues := range attrView.KeyValues {
if keyValues.Key.ID == operation.ID {
keyValues.Key.Name = strings.TrimSpace(operation.Name)

View file

@ -812,6 +812,14 @@ func FillAttributeViewTableCellNilValue(tableCell *av.TableCell, rowID, colID st
if nil == tableCell.Value.Checkbox {
tableCell.Value.Checkbox = &av.ValueCheckbox{}
}
case av.KeyTypeRelation:
if nil == tableCell.Value.Relation {
tableCell.Value.Relation = &av.ValueRelation{}
}
case av.KeyTypeRollup:
if nil == tableCell.Value.Rollup {
tableCell.Value.Rollup = &av.ValueRollup{}
}
}
}