mirror of
https://github.com/siyuan-note/siyuan.git
synced 2025-12-17 07:00:12 +01:00
♻️ Refactor av rendering
This commit is contained in:
parent
3f9c7db2d3
commit
5bd101e31b
2 changed files with 450 additions and 397 deletions
|
|
@ -32,7 +32,6 @@ import (
|
||||||
"github.com/88250/lute/ast"
|
"github.com/88250/lute/ast"
|
||||||
"github.com/88250/lute/parse"
|
"github.com/88250/lute/parse"
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
"github.com/siyuan-note/dejavu/entity"
|
|
||||||
"github.com/siyuan-note/filelock"
|
"github.com/siyuan-note/filelock"
|
||||||
"github.com/siyuan-note/logging"
|
"github.com/siyuan-note/logging"
|
||||||
"github.com/siyuan-note/siyuan/kernel/av"
|
"github.com/siyuan-note/siyuan/kernel/av"
|
||||||
|
|
@ -1553,352 +1552,6 @@ func GetBlockAttributeViewKeys(blockID string) (ret []*BlockAttributeViewKeys) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var view *av.View
|
|
||||||
if "" != viewID {
|
|
||||||
view, _ = attrView.GetCurrentView(viewID)
|
|
||||||
if nil != view && view.ID != attrView.ViewID {
|
|
||||||
attrView.ViewID = view.ID
|
|
||||||
if err = av.SaveAttributeView(attrView); err != nil {
|
|
||||||
logging.LogErrorf("save attribute view [%s] failed: %s", attrView.ID, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
view = attrView.GetView(attrView.ViewID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if nil == view {
|
|
||||||
view = attrView.Views[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 做一些数据兼容和订正处理
|
|
||||||
checkAttrView(attrView, view)
|
|
||||||
upgradeAttributeViewSpec(attrView)
|
|
||||||
|
|
||||||
viewable = sql.RenderView(attrView, view, query)
|
|
||||||
err = renderViewableInstance(viewable, view, attrView, page, pageSize)
|
|
||||||
if nil != err {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 当前日期可能会变,所以如果是按日期分组则需要重新生成分组
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fixDev := false
|
|
||||||
// 如果存在分组的话渲染分组视图
|
|
||||||
if groupKey := view.GetGroupKey(attrView); nil != groupKey {
|
|
||||||
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 genAttrViewViewGroups(view *av.View, attrView *av.AttributeView) {
|
func genAttrViewViewGroups(view *av.View, attrView *av.AttributeView) {
|
||||||
if nil == view.Group {
|
if nil == view.Group {
|
||||||
return
|
return
|
||||||
|
|
@ -2117,56 +1770,6 @@ func setAttrViewGroupStates(view *av.View, groupStates map[string]*GroupState) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 GetCurrentAttributeViewImages(avID, viewID, query string) (ret []string, err error) {
|
func GetCurrentAttributeViewImages(avID, viewID, query string) (ret []string, err error) {
|
||||||
var attrView *av.AttributeView
|
var attrView *av.AttributeView
|
||||||
attrView, err = av.ParseAttributeView(avID)
|
attrView, err = av.ParseAttributeView(avID)
|
||||||
|
|
|
||||||
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