mirror of
https://github.com/siyuan-note/siyuan.git
synced 2025-12-16 22:50:13 +01:00
♻️ Refactor av rendering
This commit is contained in:
parent
3f9c7db2d3
commit
5bd101e31b
2 changed files with 450 additions and 397 deletions
450
kernel/model/attribute_view_render.go
Normal file
450
kernel/model/attribute_view_render.go
Normal file
|
|
@ -0,0 +1,450 @@
|
|||
// SiYuan - Refactor your thinking
|
||||
// Copyright (c) 2020-present, b3log.org
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
"github.com/88250/lute/ast"
|
||||
"github.com/siyuan-note/dejavu/entity"
|
||||
"github.com/siyuan-note/filelock"
|
||||
"github.com/siyuan-note/logging"
|
||||
"github.com/siyuan-note/siyuan/kernel/av"
|
||||
"github.com/siyuan-note/siyuan/kernel/sql"
|
||||
"github.com/siyuan-note/siyuan/kernel/util"
|
||||
)
|
||||
|
||||
func RenderRepoSnapshotAttributeView(indexID, avID string) (viewable av.Viewable, attrView *av.AttributeView, err error) {
|
||||
repo, err := newRepository()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
index, err := repo.GetIndex(indexID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
files, err := repo.GetFiles(index)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var avFile *entity.File
|
||||
for _, f := range files {
|
||||
if "/storage/av/"+avID+".json" == f.Path {
|
||||
avFile = f
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if nil == avFile {
|
||||
attrView = av.NewAttributeView(avID)
|
||||
} else {
|
||||
data, readErr := repo.OpenFile(avFile)
|
||||
if nil != readErr {
|
||||
logging.LogErrorf("read attribute view [%s] failed: %s", avID, readErr)
|
||||
return
|
||||
}
|
||||
|
||||
attrView = &av.AttributeView{}
|
||||
if err = gulu.JSON.UnmarshalJSON(data, attrView); err != nil {
|
||||
logging.LogErrorf("unmarshal attribute view [%s] failed: %s", avID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
viewable, err = renderAttributeView(attrView, "", "", "", 1, -1, nil)
|
||||
return
|
||||
}
|
||||
|
||||
func RenderHistoryAttributeView(avID, created string) (viewable av.Viewable, attrView *av.AttributeView, err error) {
|
||||
createdUnix, parseErr := strconv.ParseInt(created, 10, 64)
|
||||
if nil != parseErr {
|
||||
logging.LogErrorf("parse created [%s] failed: %s", created, parseErr)
|
||||
return
|
||||
}
|
||||
|
||||
dirPrefix := time.Unix(createdUnix, 0).Format("2006-01-02-150405")
|
||||
globPath := filepath.Join(util.HistoryDir, dirPrefix+"*")
|
||||
matches, err := filepath.Glob(globPath)
|
||||
if err != nil {
|
||||
logging.LogErrorf("glob [%s] failed: %s", globPath, err)
|
||||
return
|
||||
}
|
||||
if 1 > len(matches) {
|
||||
return
|
||||
}
|
||||
|
||||
historyDir := matches[0]
|
||||
avJSONPath := filepath.Join(historyDir, "storage", "av", avID+".json")
|
||||
if !gulu.File.IsExist(avJSONPath) {
|
||||
avJSONPath = filepath.Join(util.DataDir, "storage", "av", avID+".json")
|
||||
}
|
||||
if !gulu.File.IsExist(avJSONPath) {
|
||||
attrView = av.NewAttributeView(avID)
|
||||
} else {
|
||||
data, readErr := os.ReadFile(avJSONPath)
|
||||
if nil != readErr {
|
||||
logging.LogErrorf("read attribute view [%s] failed: %s", avID, readErr)
|
||||
return
|
||||
}
|
||||
|
||||
attrView = &av.AttributeView{}
|
||||
if err = gulu.JSON.UnmarshalJSON(data, attrView); err != nil {
|
||||
logging.LogErrorf("unmarshal attribute view [%s] failed: %s", avID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
viewable, err = renderAttributeView(attrView, "", "", "", 1, -1, nil)
|
||||
return
|
||||
}
|
||||
|
||||
func RenderAttributeView(blockID, avID, viewID, query string, page, pageSize int, groupPaging map[string]interface{}) (viewable av.Viewable, attrView *av.AttributeView, err error) {
|
||||
waitForSyncingStorages()
|
||||
|
||||
if avJSONPath := av.GetAttributeViewDataPath(avID); !filelock.IsExist(avJSONPath) {
|
||||
attrView = av.NewAttributeView(avID)
|
||||
if err = av.SaveAttributeView(attrView); err != nil {
|
||||
logging.LogErrorf("save attribute view [%s] failed: %s", avID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
attrView, err = av.ParseAttributeView(avID)
|
||||
if err != nil {
|
||||
logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
|
||||
return
|
||||
}
|
||||
|
||||
viewable, err = renderAttributeView(attrView, blockID, viewID, query, page, pageSize, groupPaging)
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
groupValueDefault = "_@default@_" // 默认分组值(值为空的默认分组)
|
||||
groupValueNotInRange = "_@notInRange@_" // 不再范围内的分组值(只有数字类型的分组才可能是该值)
|
||||
groupValueLast30Days, groupValueLast7Days = "_@last30Days@_", "_@last7Days@_"
|
||||
groupValueYesterday, groupValueToday, groupValueTomorrow = "_@yesterday@_", "_@today@_", "_@tomorrow@_"
|
||||
groupValueNext7Days, groupValueNext30Days = "_@next7Days@_", "_@next30Days@_"
|
||||
)
|
||||
|
||||
func renderAttributeView(attrView *av.AttributeView, blockID, viewID, query string, page, pageSize int, groupPaging map[string]interface{}) (viewable av.Viewable, err error) {
|
||||
// 获取待渲染的视图
|
||||
view, err := getRenderAttributeViewView(attrView, blockID, viewID)
|
||||
if nil != err {
|
||||
return
|
||||
}
|
||||
|
||||
// 做一些数据兼容和订正处理
|
||||
checkAttrView(attrView, view)
|
||||
upgradeAttributeViewSpec(attrView)
|
||||
|
||||
// 渲染视图
|
||||
viewable = sql.RenderView(attrView, view, query)
|
||||
err = renderViewableInstance(viewable, view, attrView, page, pageSize)
|
||||
if nil != err {
|
||||
return
|
||||
}
|
||||
|
||||
// 渲染分组视图
|
||||
err = renderAttributeViewGroups(viewable, attrView, view, query, page, pageSize, groupPaging)
|
||||
return
|
||||
}
|
||||
|
||||
func renderAttributeViewGroups(viewable av.Viewable, attrView *av.AttributeView, view *av.View, query string, page, pageSize int, groupPaging map[string]interface{}) (err error) {
|
||||
// 当前日期可能会变,所以如果是按日期分组则需要重新生成分组
|
||||
if isGroupByDate(view) {
|
||||
createdDate := time.UnixMilli(view.GroupCreated).Format("2006-01-02")
|
||||
if time.Now().Format("2006-01-02") != createdDate {
|
||||
regenAttrViewViewGroups(attrView, "force")
|
||||
av.SaveAttributeView(attrView)
|
||||
}
|
||||
}
|
||||
|
||||
groupKey := view.GetGroupKey(attrView)
|
||||
if nil == groupKey {
|
||||
return
|
||||
}
|
||||
|
||||
fixDev := false
|
||||
// 如果存在分组的话渲染分组视图
|
||||
for _, groupView := range view.Groups {
|
||||
if "" == groupView.GetGroupValue() && !fixDev {
|
||||
// TODO 分组上线后删除,预计 2025 年 9 月后可以删除
|
||||
regenAttrViewViewGroups(attrView, "force")
|
||||
av.SaveAttributeView(attrView)
|
||||
fixDev = true
|
||||
}
|
||||
|
||||
switch groupView.GetGroupValue() {
|
||||
case groupValueDefault:
|
||||
groupView.Name = fmt.Sprintf(Conf.language(264), groupKey.Name)
|
||||
case groupValueNotInRange:
|
||||
groupView.Name = Conf.language(265)
|
||||
case groupValueLast30Days:
|
||||
groupView.Name = fmt.Sprintf(Conf.language(259), 30)
|
||||
case groupValueLast7Days:
|
||||
groupView.Name = fmt.Sprintf(Conf.language(259), 7)
|
||||
case groupValueYesterday:
|
||||
groupView.Name = Conf.language(260)
|
||||
case groupValueToday:
|
||||
groupView.Name = Conf.language(261)
|
||||
case groupValueTomorrow:
|
||||
groupView.Name = Conf.language(262)
|
||||
case groupValueNext7Days:
|
||||
groupView.Name = fmt.Sprintf(Conf.language(263), 7)
|
||||
case groupValueNext30Days:
|
||||
groupView.Name = fmt.Sprintf(Conf.language(263), 30)
|
||||
default:
|
||||
groupView.Name = groupView.GetGroupValue()
|
||||
}
|
||||
}
|
||||
|
||||
todayStart := time.Now()
|
||||
todayStart = time.Date(todayStart.Year(), todayStart.Month(), todayStart.Day(), 0, 0, 0, 0, time.Local)
|
||||
sortGroupViews(todayStart, view)
|
||||
|
||||
var groups []av.Viewable
|
||||
for _, groupView := range view.Groups {
|
||||
groupViewable := sql.RenderGroupView(attrView, view, groupView, query)
|
||||
|
||||
groupPage, groupPageSize := page, pageSize
|
||||
if nil != groupPaging {
|
||||
if paging := groupPaging[groupView.ID]; nil != paging {
|
||||
pagingMap := paging.(map[string]interface{})
|
||||
if nil != pagingMap["page"] {
|
||||
groupPage = int(pagingMap["page"].(float64))
|
||||
}
|
||||
if nil != pagingMap["pageSize"] {
|
||||
groupPageSize = int(pagingMap["pageSize"].(float64))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = renderViewableInstance(groupViewable, view, attrView, groupPage, groupPageSize)
|
||||
if nil != err {
|
||||
return
|
||||
}
|
||||
groups = append(groups, groupViewable)
|
||||
|
||||
// 将分组视图的分组字段清空,减少冗余(字段信息可以在总的视图 view 对象上获取到)
|
||||
switch groupView.LayoutType {
|
||||
case av.LayoutTypeTable:
|
||||
groupView.Table.Columns = nil
|
||||
case av.LayoutTypeGallery:
|
||||
groupView.Gallery.CardFields = nil
|
||||
}
|
||||
}
|
||||
viewable.SetGroups(groups)
|
||||
|
||||
// 将总的视图上的项目清空,减少冗余
|
||||
viewable.(av.Collection).SetItems(nil)
|
||||
return
|
||||
}
|
||||
|
||||
func sortGroupViews(todayStart time.Time, view *av.View) {
|
||||
if av.GroupOrderMan == view.Group.Order {
|
||||
sort.Slice(view.Groups, func(i, j int) bool { return view.Groups[i].GroupSort < view.Groups[j].GroupSort })
|
||||
return
|
||||
}
|
||||
|
||||
if av.GroupMethodDateRelative == view.Group.Method {
|
||||
var relativeDateGroups []*av.View
|
||||
var last30Days, last7Days, yesterday, today, tomorrow, next7Days, next30Days, defaultGroup *av.View
|
||||
for _, groupView := range view.Groups {
|
||||
_, err := time.Parse("2006-01", groupView.GetGroupValue())
|
||||
if nil == err { // 如果能解析出来说明是 30 天之前或 30 天之后的分组形式
|
||||
relativeDateGroups = append(relativeDateGroups, groupView)
|
||||
} else { // 否则是相对日期分组形式
|
||||
switch groupView.GetGroupValue() {
|
||||
case groupValueLast30Days:
|
||||
last30Days = groupView
|
||||
case groupValueLast7Days:
|
||||
last7Days = groupView
|
||||
case groupValueYesterday:
|
||||
yesterday = groupView
|
||||
case groupValueToday:
|
||||
today = groupView
|
||||
case groupValueTomorrow:
|
||||
tomorrow = groupView
|
||||
case groupValueNext7Days:
|
||||
next7Days = groupView
|
||||
case groupValueNext30Days:
|
||||
next30Days = groupView
|
||||
case groupValueDefault:
|
||||
defaultGroup = groupView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.SliceStable(relativeDateGroups, func(i, j int) bool {
|
||||
return relativeDateGroups[i].GetGroupValue() < relativeDateGroups[j].GetGroupValue()
|
||||
})
|
||||
|
||||
var lastNext30Days []*av.View
|
||||
if nil != next30Days {
|
||||
lastNext30Days = append(lastNext30Days, next30Days)
|
||||
}
|
||||
if nil != next7Days {
|
||||
lastNext30Days = append(lastNext30Days, next7Days)
|
||||
}
|
||||
if nil != tomorrow {
|
||||
lastNext30Days = append(lastNext30Days, tomorrow)
|
||||
}
|
||||
if nil != today {
|
||||
lastNext30Days = append(lastNext30Days, today)
|
||||
}
|
||||
if nil != yesterday {
|
||||
lastNext30Days = append(lastNext30Days, yesterday)
|
||||
}
|
||||
|
||||
if nil != last7Days {
|
||||
lastNext30Days = append(lastNext30Days, last7Days)
|
||||
}
|
||||
if nil != last30Days {
|
||||
lastNext30Days = append(lastNext30Days, last30Days)
|
||||
}
|
||||
|
||||
startIdx := -1
|
||||
thisMonth := todayStart.Format("2006-01")
|
||||
for i, monthGroup := range relativeDateGroups {
|
||||
if monthGroup.GetGroupValue() < thisMonth {
|
||||
startIdx = i + 1
|
||||
}
|
||||
}
|
||||
if -1 == startIdx {
|
||||
startIdx = 0
|
||||
}
|
||||
for _, g := range lastNext30Days {
|
||||
relativeDateGroups = util.InsertElem(relativeDateGroups, startIdx, g)
|
||||
}
|
||||
if nil != defaultGroup {
|
||||
relativeDateGroups = append([]*av.View{defaultGroup}, relativeDateGroups...)
|
||||
}
|
||||
|
||||
if av.GroupOrderDesc == view.Group.Order {
|
||||
slices.Reverse(relativeDateGroups)
|
||||
}
|
||||
|
||||
view.Groups = relativeDateGroups
|
||||
} else {
|
||||
sort.SliceStable(view.Groups, func(i, j int) bool {
|
||||
iVal, jVal := view.Groups[i].GetGroupValue(), view.Groups[j].GetGroupValue()
|
||||
if av.GroupOrderAsc == view.Group.Order {
|
||||
return util.NaturalCompare(iVal, jVal)
|
||||
}
|
||||
return util.NaturalCompare(jVal, iVal)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func isGroupByDate(view *av.View) bool {
|
||||
if nil == view.Group {
|
||||
return false
|
||||
}
|
||||
return av.GroupMethodDateDay == view.Group.Method || av.GroupMethodDateWeek == view.Group.Method || av.GroupMethodDateMonth == view.Group.Method || av.GroupMethodDateYear == view.Group.Method || av.GroupMethodDateRelative == view.Group.Method
|
||||
}
|
||||
|
||||
func renderViewableInstance(viewable av.Viewable, view *av.View, attrView *av.AttributeView, page, pageSize int) (err error) {
|
||||
if nil == viewable {
|
||||
err = av.ErrViewNotFound
|
||||
logging.LogErrorf("render attribute view [%s] failed", attrView.ID)
|
||||
return
|
||||
}
|
||||
|
||||
av.Filter(viewable, attrView)
|
||||
av.Sort(viewable, attrView)
|
||||
av.Calc(viewable, attrView)
|
||||
|
||||
// 分页
|
||||
switch viewable.GetType() {
|
||||
case av.LayoutTypeTable:
|
||||
table := viewable.(*av.Table)
|
||||
table.RowCount = len(table.Rows)
|
||||
table.PageSize = view.PageSize
|
||||
if 1 > pageSize {
|
||||
pageSize = table.PageSize
|
||||
}
|
||||
start := (page - 1) * pageSize
|
||||
end := start + pageSize
|
||||
if len(table.Rows) < end {
|
||||
end = len(table.Rows)
|
||||
}
|
||||
table.Rows = table.Rows[start:end]
|
||||
case av.LayoutTypeGallery:
|
||||
gallery := viewable.(*av.Gallery)
|
||||
gallery.CardCount = len(gallery.Cards)
|
||||
gallery.PageSize = view.PageSize
|
||||
if 1 > pageSize {
|
||||
pageSize = gallery.PageSize
|
||||
}
|
||||
start := (page - 1) * pageSize
|
||||
end := start + pageSize
|
||||
if len(gallery.Cards) < end {
|
||||
end = len(gallery.Cards)
|
||||
}
|
||||
gallery.Cards = gallery.Cards[start:end]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getRenderAttributeViewView(attrView *av.AttributeView, viewID, blockID string) (ret *av.View, err error) {
|
||||
if 1 > len(attrView.Views) {
|
||||
view, _, _ := av.NewTableViewWithBlockKey(ast.NewNodeID())
|
||||
attrView.Views = append(attrView.Views, view)
|
||||
attrView.ViewID = view.ID
|
||||
if err = av.SaveAttributeView(attrView); err != nil {
|
||||
logging.LogErrorf("save attribute view [%s] failed: %s", attrView.ID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if "" == viewID && "" != blockID {
|
||||
node, _, getErr := getNodeByBlockID(nil, blockID)
|
||||
if nil != getErr {
|
||||
logging.LogWarnf("get node by block ID [%s] failed: %s", blockID, getErr)
|
||||
} else {
|
||||
viewID = node.IALAttr(av.NodeAttrView)
|
||||
}
|
||||
}
|
||||
|
||||
if "" != viewID {
|
||||
ret, _ = attrView.GetCurrentView(viewID)
|
||||
if nil != ret && ret.ID != attrView.ViewID {
|
||||
attrView.ViewID = ret.ID
|
||||
if err = av.SaveAttributeView(attrView); err != nil {
|
||||
logging.LogErrorf("save attribute view [%s] failed: %s", attrView.ID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ret = attrView.GetView(attrView.ViewID)
|
||||
}
|
||||
|
||||
if nil == ret {
|
||||
ret = attrView.Views[0]
|
||||
}
|
||||
return
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue