diff --git a/kernel/av/av.go b/kernel/av/av.go index 71431bca7..56fa399c9 100644 --- a/kernel/av/av.go +++ b/kernel/av/av.go @@ -192,13 +192,14 @@ type View struct { Gallery *LayoutGallery `json:"gallery,omitempty"` // 卡片布局 ItemIDs []string `json:"itemIds,omitempty"` // 项目 ID 列表,用于维护所有项目 - Groups []*View `json:"groups,omitempty"` // 分组视图列表 - GroupItemIDs []string `json:"groupItemIds,omitempty"` // 分组项目 ID 列表,用于维护分组中的所有项目 - GroupCalc *GroupCalc `json:"groupCalc,omitempty"` // 分组计算规则 - GroupName string `json:"groupName,omitempty"` // 分组名称 - GroupFolded bool `json:"groupFolded,omitempty"` // 分组是否折叠 - GroupHidden bool `json:"groupHidden,omitempty"` // 分组是否隐藏 - GroupDefault bool `json:"groupDefault,omitempty"` // 是否为默认分组 + Groups []*View `json:"groups,omitempty"` // 分组视图列表 + GroupItemIDs []string `json:"groupItemIds,omitempty"` // 分组项目 ID 列表,用于维护分组中的所有项目 + GroupCalc *GroupCalc `json:"groupCalc,omitempty"` // 分组计算规则 + GroupName string `json:"groupName,omitempty"` // 分组名称 + GroupFolded bool `json:"groupFolded,omitempty"` // 分组是否折叠 + GroupHidden bool `json:"groupHidden,omitempty"` // 分组是否隐藏 + GroupHideEmpty bool `json:"groupHideEmpty,omitempty"` // 分组是否隐藏空分组 + GroupDefault bool `json:"groupDefault,omitempty"` // 是否为默认分组 } // GroupCalc 描述了分组计算规则和结果的结构。 diff --git a/kernel/av/av_fix.go b/kernel/av/av_fix.go index c85bc5cbe..f6b334f50 100644 --- a/kernel/av/av_fix.go +++ b/kernel/av/av_fix.go @@ -221,7 +221,7 @@ func upgradeSpec1(av *AttributeView) { } } - // 补全过滤器 Value + // 补全过滤规则 Value for _, view := range av.Views { if nil != view.Table { for _, f := range view.Table.Filters { diff --git a/kernel/av/calc.go b/kernel/av/calc.go index 95a81113d..69b93bfc4 100644 --- a/kernel/av/calc.go +++ b/kernel/av/calc.go @@ -55,52 +55,82 @@ const ( CalcOperatorPercentUnchecked CalcOperator = "Percent unchecked" ) -func Calc(viewable Viewable) { +func Calc(viewable Viewable, attrView *AttributeView) { collection := viewable.(Collection) + + // 字段计算 for i, field := range collection.GetFields() { calc := field.GetCalc() if nil == calc || CalcOperatorNone == calc.Operator { continue } - switch field.GetType() { - case KeyTypeBlock: - CalcFieldBlock(collection, field, i) - case KeyTypeText: - CalcFieldText(collection, field, i) - case KeyTypeNumber: - CalcFieldNumber(collection, field, i) - case KeyTypeDate: - CalcFieldDate(collection, field, i) - case KeyTypeSelect: - CalcFieldSelect(collection, field, i) - case KeyTypeMSelect: - CalcFieldMSelect(collection, field, i) - case KeyTypeURL: - CalcFieldURL(collection, field, i) - case KeyTypeEmail: - CalcFieldEmail(collection, field, i) - case KeyTypePhone: - CalcFieldPhone(collection, field, i) - case KeyTypeMAsset: - CalcFieldMAsset(collection, field, i) - case KeyTypeTemplate: - CalcFieldTemplate(collection, field, i) - case KeyTypeCreated: - CalcFieldCreated(collection, field, i) - case KeyTypeUpdated: - CalcFieldUpdated(collection, field, i) - case KeyTypeCheckbox: - CalcFieldCheckbox(collection, field, i) - case KeyTypeRelation: - CalcFieldRelation(collection, field, i) - case KeyTypeRollup: - CalcFieldRollup(collection, field, i) + calcField(collection, field, i) + } + + // 分组计算 + if groupCalc := viewable.GetGroupCalc(); nil != groupCalc { + if groupCalcKey, _ := attrView.GetKey(groupCalc.Field); nil != groupCalcKey { + if field, fieldIndex := collection.GetField(groupCalcKey.ID); nil != field { + var calcResult *GroupCalc + + if calc := field.GetCalc(); nil != calc && field.GetID() == groupCalcKey.ID { + // 直接使用字段计算结果 + calcResult = &GroupCalc{Field: groupCalcKey.ID, FieldCalc: calc} + } + + if nil == calcResult { + // 在字段上设置计算规则,使用字段结算结果作为分组计算结果,最后再清除字段上的计算规则 + field.SetCalc(groupCalc.FieldCalc) + calcField(collection, field, fieldIndex) + calcResult = &GroupCalc{Field: groupCalcKey.ID, FieldCalc: field.GetCalc()} + field.SetCalc(nil) + } + + viewable.SetGroupCalc(calcResult) + } } } } -func CalcFieldTemplate(collection Collection, field Field, fieldIndex int) { +func calcField(collection Collection, field Field, fieldIndex int) { + switch field.GetType() { + case KeyTypeBlock: + calcFieldBlock(collection, field, fieldIndex) + case KeyTypeText: + calcFieldText(collection, field, fieldIndex) + case KeyTypeNumber: + calcFieldNumber(collection, field, fieldIndex) + case KeyTypeDate: + calcFieldDate(collection, field, fieldIndex) + case KeyTypeSelect: + calcFieldSelect(collection, field, fieldIndex) + case KeyTypeMSelect: + calcFieldMSelect(collection, field, fieldIndex) + case KeyTypeURL: + calcFieldURL(collection, field, fieldIndex) + case KeyTypeEmail: + calcFieldEmail(collection, field, fieldIndex) + case KeyTypePhone: + calcFieldPhone(collection, field, fieldIndex) + case KeyTypeMAsset: + calcFieldMAsset(collection, field, fieldIndex) + case KeyTypeTemplate: + calcFieldTemplate(collection, field, fieldIndex) + case KeyTypeCreated: + calcFieldCreated(collection, field, fieldIndex) + case KeyTypeUpdated: + calcFieldUpdated(collection, field, fieldIndex) + case KeyTypeCheckbox: + calcFieldCheckbox(collection, field, fieldIndex) + case KeyTypeRelation: + calcFieldRelation(collection, field, fieldIndex) + case KeyTypeRollup: + calcFieldRollup(collection, field, fieldIndex) + } +} + +func calcFieldTemplate(collection Collection, field Field, fieldIndex int) { calc := field.GetCalc() switch calc.Operator { case CalcOperatorCountAll: @@ -272,7 +302,7 @@ func CalcFieldTemplate(collection Collection, field Field, fieldIndex int) { } } -func CalcFieldMAsset(collection Collection, field Field, fieldIndex int) { +func calcFieldMAsset(collection Collection, field Field, fieldIndex int) { calc := field.GetCalc() switch calc.Operator { case CalcOperatorCountAll: @@ -361,7 +391,7 @@ func CalcFieldMAsset(collection Collection, field Field, fieldIndex int) { } } -func CalcFieldMSelect(collection Collection, field Field, fieldIndex int) { +func calcFieldMSelect(collection Collection, field Field, fieldIndex int) { calc := field.GetCalc() switch calc.Operator { case CalcOperatorCountAll: @@ -450,7 +480,7 @@ func CalcFieldMSelect(collection Collection, field Field, fieldIndex int) { } } -func CalcFieldSelect(collection Collection, field Field, fieldIndex int) { +func calcFieldSelect(collection Collection, field Field, fieldIndex int) { calc := field.GetCalc() switch calc.Operator { case CalcOperatorCountAll: @@ -535,7 +565,7 @@ func CalcFieldSelect(collection Collection, field Field, fieldIndex int) { } } -func CalcFieldDate(collection Collection, field Field, fieldIndex int) { +func calcFieldDate(collection Collection, field Field, fieldIndex int) { calc := field.GetCalc() switch calc.Operator { case CalcOperatorCountAll: @@ -674,7 +704,7 @@ func CalcFieldDate(collection Collection, field Field, fieldIndex int) { } } -func CalcFieldNumber(collection Collection, field Field, fieldIndex int) { +func calcFieldNumber(collection Collection, field Field, fieldIndex int) { calc := field.GetCalc() switch calc.Operator { case CalcOperatorCountAll: @@ -840,7 +870,7 @@ func CalcFieldNumber(collection Collection, field Field, fieldIndex int) { } } -func CalcFieldText(collection Collection, field Field, fieldIndex int) { +func calcFieldText(collection Collection, field Field, fieldIndex int) { calc := field.GetCalc() switch calc.Operator { case CalcOperatorCountAll: @@ -925,7 +955,7 @@ func CalcFieldText(collection Collection, field Field, fieldIndex int) { } } -func CalcFieldURL(collection Collection, field Field, fieldIndex int) { +func calcFieldURL(collection Collection, field Field, fieldIndex int) { calc := field.GetCalc() switch calc.Operator { case CalcOperatorCountAll: @@ -1010,7 +1040,7 @@ func CalcFieldURL(collection Collection, field Field, fieldIndex int) { } } -func CalcFieldEmail(collection Collection, field Field, fieldIndex int) { +func calcFieldEmail(collection Collection, field Field, fieldIndex int) { calc := field.GetCalc() switch calc.Operator { case CalcOperatorCountAll: @@ -1095,7 +1125,7 @@ func CalcFieldEmail(collection Collection, field Field, fieldIndex int) { } } -func CalcFieldPhone(collection Collection, field Field, fieldIndex int) { +func calcFieldPhone(collection Collection, field Field, fieldIndex int) { calc := field.GetCalc() switch calc.Operator { case CalcOperatorCountAll: @@ -1180,7 +1210,7 @@ func CalcFieldPhone(collection Collection, field Field, fieldIndex int) { } } -func CalcFieldBlock(collection Collection, field Field, fieldIndex int) { +func calcFieldBlock(collection Collection, field Field, fieldIndex int) { calc := field.GetCalc() switch calc.Operator { case CalcOperatorCountAll: @@ -1265,7 +1295,7 @@ func CalcFieldBlock(collection Collection, field Field, fieldIndex int) { } } -func CalcFieldCreated(collection Collection, field Field, fieldIndex int) { +func calcFieldCreated(collection Collection, field Field, fieldIndex int) { calc := field.GetCalc() switch calc.Operator { case CalcOperatorCountAll: @@ -1393,7 +1423,7 @@ func CalcFieldCreated(collection Collection, field Field, fieldIndex int) { } } -func CalcFieldUpdated(collection Collection, field Field, fieldIndex int) { +func calcFieldUpdated(collection Collection, field Field, fieldIndex int) { calc := field.GetCalc() switch calc.Operator { case CalcOperatorCountAll: @@ -1521,7 +1551,7 @@ func CalcFieldUpdated(collection Collection, field Field, fieldIndex int) { } } -func CalcFieldCheckbox(collection Collection, field Field, fieldIndex int) { +func calcFieldCheckbox(collection Collection, field Field, fieldIndex int) { calc := field.GetCalc() switch calc.Operator { case CalcOperatorCountAll: @@ -1569,7 +1599,7 @@ func CalcFieldCheckbox(collection Collection, field Field, fieldIndex int) { } } -func CalcFieldRelation(collection Collection, field Field, fieldIndex int) { +func calcFieldRelation(collection Collection, field Field, fieldIndex int) { calc := field.GetCalc() switch calc.Operator { case CalcOperatorCountAll: @@ -1658,7 +1688,7 @@ func CalcFieldRelation(collection Collection, field Field, fieldIndex int) { } } -func CalcFieldRollup(collection Collection, field Field, fieldIndex int) { +func calcFieldRollup(collection Collection, field Field, fieldIndex int) { calc := field.GetCalc() switch calc.Operator { case CalcOperatorCountAll: diff --git a/kernel/av/filter.go b/kernel/av/filter.go index 207593823..c55b4b68b 100644 --- a/kernel/av/filter.go +++ b/kernel/av/filter.go @@ -24,7 +24,7 @@ import ( "github.com/siyuan-note/siyuan/kernel/util" ) -// ViewFilter 描述了视图过滤器的结构。 +// ViewFilter 描述了视图过滤规则的结构。 type ViewFilter struct { Column string `json:"column"` // 列(字段)ID Operator FilterOperator `json:"operator"` // 过滤操作符 @@ -134,7 +134,7 @@ func (value *Value) Filter(filter *ViewFilter, attrView *AttributeView, rowID st } if nil != filter.Value && value.Type != filter.Value.Type { - // 由于字段类型被用户编辑过导致和过滤器值类型不匹配,该情况下不过滤 + // 由于字段类型被用户编辑过导致和过滤规则值类型不匹配,该情况下不过滤 return true } diff --git a/kernel/av/group.go b/kernel/av/group.go index 5bb10f24e..37685b8bb 100644 --- a/kernel/av/group.go +++ b/kernel/av/group.go @@ -16,6 +16,7 @@ package av +// ViewGroup 描述了视图分组规则的结构。 type ViewGroup struct { Field string `json:"field"` // 分组字段 ID Method GroupMethod `json:"method"` // 分组方式 diff --git a/kernel/av/layout.go b/kernel/av/layout.go index 1e16819fb..c81524167 100644 --- a/kernel/av/layout.go +++ b/kernel/av/layout.go @@ -198,7 +198,7 @@ type Collection interface { GetFields() []Field // GetField 返回指定 ID 的字段。 - GetField(id string) (ret Field) + GetField(id string) (ret Field, fieldIndex int) // GetSorts 返回集合的排序规则。 GetSorts() []*ViewSort diff --git a/kernel/av/layout_gallery.go b/kernel/av/layout_gallery.go index c83411f04..817984383 100644 --- a/kernel/av/layout_gallery.go +++ b/kernel/av/layout_gallery.go @@ -173,13 +173,13 @@ func (gallery *Gallery) GetFields() (ret []Field) { return ret } -func (gallery *Gallery) GetField(id string) Field { - for _, field := range gallery.Fields { +func (gallery *Gallery) GetField(id string) (ret Field, fieldIndex int) { + for i, field := range gallery.Fields { if field.ID == id { - return field + return field, i } } - return nil + return nil, -1 } func (gallery *Gallery) GetType() LayoutType { diff --git a/kernel/av/layout_table.go b/kernel/av/layout_table.go index afb9bc82b..8ce64a0e1 100644 --- a/kernel/av/layout_table.go +++ b/kernel/av/layout_table.go @@ -149,13 +149,13 @@ func (table *Table) GetFields() (ret []Field) { return ret } -func (table *Table) GetField(id string) Field { +func (table *Table) GetField(id string) (ret Field, fieldIndex int) { for _, column := range table.Columns { if column.ID == id { - return column + return column, fieldIndex } } - return nil + return nil, -1 } func (*Table) GetType() LayoutType { diff --git a/kernel/model/attribute_view.go b/kernel/model/attribute_view.go index 807d8158d..55d4f10eb 100644 --- a/kernel/model/attribute_view.go +++ b/kernel/model/attribute_view.go @@ -44,6 +44,75 @@ import ( "github.com/xrash/smetrics" ) +func (tx *Transaction) doSetGroupHideEmpty(operation *Operation) (ret *TxErr) { + if err := SetGroupHideEmpty(operation.AvID, operation.BlockID, operation.Data.(bool)); nil != err { + return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()} + } + return +} + +func SetGroupHideEmpty(avID, blockID string, hidden bool) (err error) { + attrView, err := av.ParseAttributeView(avID) + if err != nil { + return err + } + + view, err := getAttrViewViewByBlockID(attrView, blockID) + if err != nil { + return err + } + + if nil == view.Group { + return + } + + view.GroupHideEmpty = hidden + + err = av.SaveAttributeView(attrView) + if err != nil { + logging.LogErrorf("save attribute view [%s] failed: %s", avID, err) + return err + } + return nil +} + +func (tx *Transaction) doHideAttrViewGroup(operation *Operation) (ret *TxErr) { + if err := HideAttributeViewGroup(operation.AvID, operation.BlockID, operation.ID, operation.Data.(bool)); nil != err { + return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()} + } + return +} + +func HideAttributeViewGroup(avID, blockID, groupID string, hidden bool) (err error) { + attrView, err := av.ParseAttributeView(avID) + if err != nil { + return err + } + + view, err := getAttrViewViewByBlockID(attrView, blockID) + if err != nil { + return err + } + + if nil == view.Group { + return + } + + for _, group := range view.Groups { + if group.ID == groupID { + group.GroupHidden = hidden + break + } + } + + err = av.SaveAttributeView(attrView) + if err != nil { + logging.LogErrorf("save attribute view [%s] failed: %s", avID, err) + return err + } + return nil +} + func (tx *Transaction) doSetAttrViewGroup(operation *Operation) (ret *TxErr) { data, err := gulu.JSON.MarshalJSON(operation.Data) if nil != err { @@ -62,7 +131,7 @@ func (tx *Transaction) doSetAttrViewGroup(operation *Operation) (ret *TxErr) { return } -func SetAttributeViewGroup(avID, blockID string, group *av.ViewGroup) error { +func SetAttributeViewGroup(avID, blockID string, group *av.ViewGroup) (err error) { attrView, err := av.ParseAttributeView(avID) if err != nil { return err @@ -78,29 +147,31 @@ func SetAttributeViewGroup(avID, blockID string, group *av.ViewGroup) error { // TODO Database grouping by field https://github.com/siyuan-note/siyuan/issues/10964 // 生成分组数据 - switch view.LayoutType { - case av.LayoutTypeTable: - table := sql.RenderAttributeViewTable(attrView, view, "") - groupRows := map[string][]*av.TableRow{} - for _, row := range table.Rows { - value := row.GetValue(group.Field) - switch group.Method { - case av.GroupMethodValue: - strVal := value.String(false) - groupRows[strVal] = append(groupRows[strVal], row) - } + groupItems := map[string][]av.Item{} + viewable := sql.RenderView(attrView, view, "") + collection := viewable.(av.Collection) + for _, item := range collection.GetItems() { + value := item.GetValue(group.Field) + switch group.Method { + case av.GroupMethodValue: + strVal := value.String(false) + groupItems[strVal] = append(groupItems[strVal], item) } - - for _, rows := range groupRows { - v := av.NewTableView() + } + for _, items := range groupItems { + var v *av.View + switch view.LayoutType { + case av.LayoutTypeTable: + v = av.NewTableView() v.Table = av.NewLayoutTable() - for _, row := range rows { - v.GroupItemIDs = append(v.GroupItemIDs, row.ID) - } - view.Groups = append(view.Groups, v) + case av.LayoutTypeGallery: + v = av.NewGalleryView() + v.Gallery = av.NewLayoutGallery() } - case av.LayoutTypeGallery: - + for _, item := range items { + v.GroupItemIDs = append(v.GroupItemIDs, item.GetID()) + } + view.Groups = append(view.Groups, v) } err = av.SaveAttributeView(attrView) @@ -1282,7 +1353,7 @@ func renderAttributeView(attrView *av.AttributeView, viewID, query string, page, checkAttrView(attrView, view) upgradeAttributeViewSpec(attrView) - viewable = sql.RenderView(view, attrView, query) + viewable = sql.RenderView(attrView, view, query) err = renderViewableInstance(viewable, view, attrView, page, pageSize) if nil != err { return @@ -1298,7 +1369,7 @@ func renderAttributeView(attrView *av.AttributeView, viewID, query string, page, groupView.Gallery.CardFields = view.Gallery.CardFields } - groupViewable := sql.RenderView(groupView, attrView, query) + groupViewable := sql.RenderView(attrView, groupView, query) err = renderViewableInstance(groupViewable, view, attrView, page, pageSize) if nil != err { return @@ -1318,72 +1389,7 @@ func renderViewableInstance(viewable av.Viewable, view *av.View, attrView *av.At av.Filter(viewable, attrView) av.Sort(viewable, attrView) - av.Calc(viewable) - - if groupCalc := viewable.GetGroupCalc(); nil != groupCalc { - if groupCalcKey, _ := attrView.GetKey(groupCalc.Field); nil != groupCalcKey { - collection := viewable.(av.Collection) - var calcResult *av.GroupCalc - field := collection.GetField(groupCalcKey.ID) - if nil != field { - if calc := field.GetCalc(); nil != calc && field.GetID() == groupCalcKey.ID { - // 直接使用字段计算结果 - calcResult = &av.GroupCalc{Field: groupCalcKey.ID, FieldCalc: calc} - } - - if nil == calcResult { - for i, f := range collection.GetFields() { - if f.GetID() != groupCalcKey.ID { - continue - } - - field.SetCalc(groupCalc.FieldCalc) - - switch field.GetType() { - case av.KeyTypeBlock: - av.CalcFieldBlock(collection, field, i) - case av.KeyTypeText: - av.CalcFieldText(collection, field, i) - case av.KeyTypeNumber: - av.CalcFieldNumber(collection, field, i) - case av.KeyTypeDate: - av.CalcFieldDate(collection, field, i) - case av.KeyTypeSelect: - av.CalcFieldSelect(collection, field, i) - case av.KeyTypeMSelect: - av.CalcFieldMSelect(collection, field, i) - case av.KeyTypeURL: - av.CalcFieldURL(collection, field, i) - case av.KeyTypeEmail: - av.CalcFieldEmail(collection, field, i) - case av.KeyTypePhone: - av.CalcFieldPhone(collection, field, i) - case av.KeyTypeMAsset: - av.CalcFieldMAsset(collection, field, i) - case av.KeyTypeTemplate: - av.CalcFieldTemplate(collection, field, i) - case av.KeyTypeCreated: - av.CalcFieldCreated(collection, field, i) - case av.KeyTypeUpdated: - av.CalcFieldUpdated(collection, field, i) - case av.KeyTypeCheckbox: - av.CalcFieldCheckbox(collection, field, i) - case av.KeyTypeRelation: - av.CalcFieldRelation(collection, field, i) - case av.KeyTypeRollup: - av.CalcFieldRollup(collection, field, i) - } - break - } - - calcResult = &av.GroupCalc{Field: groupCalcKey.ID, FieldCalc: field.GetCalc()} - field.SetCalc(nil) - } - } - - viewable.SetGroupCalc(calcResult) - } - } + av.Calc(viewable, attrView) // 分页 switch viewable.GetType() { @@ -2544,7 +2550,7 @@ func addAttributeViewBlock(now int64, avID, blockID, previousBlockID, addingBloc view, _ := getAttrViewViewByBlockID(attrView, blockID) if nil != view && 0 < len(view.Filters) && !ignoreFillFilter { - viewable := sql.RenderView(view, attrView, "") + viewable := sql.RenderView(attrView, view, "") av.Filter(viewable, attrView) av.Sort(viewable, attrView) diff --git a/kernel/model/transaction.go b/kernel/model/transaction.go index ad9d3acf4..adaebd97d 100644 --- a/kernel/model/transaction.go +++ b/kernel/model/transaction.go @@ -299,6 +299,10 @@ func performTx(tx *Transaction) (ret *TxErr) { ret = tx.doSetAttrViewCardAspectRatio(op) case "setAttrViewGroup": ret = tx.doSetAttrViewGroup(op) + case "hideAttrViewGroup": + ret = tx.doHideAttrViewGroup(op) + case "setGroupHideEmpty": + ret = tx.doSetGroupHideEmpty(op) } if nil != ret { diff --git a/kernel/sql/av.go b/kernel/sql/av.go index 7e946d2b0..9ad4f8aa5 100644 --- a/kernel/sql/av.go +++ b/kernel/sql/av.go @@ -32,7 +32,7 @@ import ( "github.com/siyuan-note/siyuan/kernel/util" ) -func RenderView(view *av.View, attrView *av.AttributeView, query string) (ret av.Viewable) { +func RenderView(attrView *av.AttributeView, view *av.View, query string) (ret av.Viewable) { switch view.LayoutType { case av.LayoutTypeTable: ret = RenderAttributeViewTable(attrView, view, query)