diff --git a/go.mod b/go.mod index 6360c5ae6..b0a8a1bcf 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/qor5/imaging v1.6.4 github.com/qor5/web v1.3.2 github.com/qor5/web/v3 v3.0.12-0.20250322025751-d36834ab80b4 - github.com/qor5/x/v3 v3.0.14-0.20250523074001-acfb06785d4a + github.com/qor5/x/v3 v3.0.14-0.20250529021946-7cf4a1807865 github.com/samber/lo v1.47.0 github.com/shurcooL/sanitized_anchor_name v1.0.0 github.com/spf13/cast v1.6.0 diff --git a/go.sum b/go.sum index 19fee1235..5d72c3237 100644 --- a/go.sum +++ b/go.sum @@ -370,6 +370,8 @@ github.com/qor5/x/v3 v3.0.14-0.20250523072426-119af8528563 h1:mZl3SAXrKzpyNR0xsd github.com/qor5/x/v3 v3.0.14-0.20250523072426-119af8528563/go.mod h1:SjJJvVZ/j2Xcceflqhzoz2Dy59UhTQ339/u/mgUGl5M= github.com/qor5/x/v3 v3.0.14-0.20250523074001-acfb06785d4a h1:5uWdj5tI03z37LQaka3c3TTk95ShkavzLTjZsvvV/cE= github.com/qor5/x/v3 v3.0.14-0.20250523074001-acfb06785d4a/go.mod h1:SjJJvVZ/j2Xcceflqhzoz2Dy59UhTQ339/u/mgUGl5M= +github.com/qor5/x/v3 v3.0.14-0.20250529021946-7cf4a1807865 h1:l8oRD2opecQBvzqTog/+Ce7VE8mjP8Vp5PO1N7lEqYg= +github.com/qor5/x/v3 v3.0.14-0.20250529021946-7cf4a1807865/go.mod h1:SjJJvVZ/j2Xcceflqhzoz2Dy59UhTQ339/u/mgUGl5M= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= diff --git a/pagebuilder/page.go b/pagebuilder/page.go index 7c730982b..273a8716b 100644 --- a/pagebuilder/page.go +++ b/pagebuilder/page.go @@ -56,6 +56,17 @@ func (b *Builder) defaultPageInstall(pb *presets.Builder, pm *presets.ModelBuild } dp := pm.Detailing(detailList...) + dp.WrapPageFunc(func(in web.PageFunc) web.PageFunc { + return func(ctx *web.EventContext) (r web.PageResponse, err error) { + r, err = in(ctx) + if err != nil { + return + } + r.Body = h.Div(r.Body).Class("px-6") + return + } + }) + dp.Field("Title").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent { msgr := i18n.MustGetModuleMessages(ctx.R, I18nPageBuilderKey, Messages_en_US).(*Messages) var ( @@ -71,20 +82,9 @@ func (b *Builder) defaultPageInstall(pb *presets.Builder, pm *presets.ModelBuild Color(ColorPrimary).Size(SizeSmall).Class("px-1 mx-1").Attr("style", "height:20px") } - listingHref := pm.Info().ListingHref() return h.Div( - VBtn("").Size(SizeXSmall).Icon("mdi-arrow-left").Tile(true).Variant(VariantOutlined).Attr("@click", - fmt.Sprintf(` - const last = vars.__history.last(); - if (last && last.url && last.url.startsWith(%q)) { - $event.view.window.history.back(); - return; - } - %s`, listingHref, web.GET().URL(listingHref).PushState(true).Go(), - ), - ), - h.H1("{{vars.pageTitle}}").Class("page-main-title ml-4"), - versionBadge.Class("mt-2 ml-2"), + h.H1("{{vars.pageTitle}}").Class("page-main-title"), + versionBadge.Class("ml-2"), ).Class("d-inline-flex align-center") }) // register modelBuilder diff --git a/presets/detailing.go b/presets/detailing.go index 41e90175f..42f2a218a 100644 --- a/presets/detailing.go +++ b/presets/detailing.go @@ -15,8 +15,9 @@ import ( ) type ( - DetailingStyle string - DetailingLayout string + DetailingStyle string + DetailingLayout string + DetailingBreadcrumbFunc func(ctx *web.EventContext, obj any, id string) (BreadcrumbItemsFunc, error) ) const ( @@ -42,6 +43,7 @@ type DetailingBuilder struct { layouts []DetailingLayout idCurrentActiveProcessor IdCurrentActiveProcessor FieldsBuilder + breadcrumbFunc DetailingBreadcrumbFunc } type pageTitle interface { @@ -99,6 +101,11 @@ func (b *DetailingBuilder) PageFunc(pf web.PageFunc) (r *DetailingBuilder) { return b } +func (b *DetailingBuilder) WrapPageFunc(w func(in web.PageFunc) web.PageFunc) (r *DetailingBuilder) { + b.pageFunc = w(b.pageFunc) + return b +} + func (b *DetailingBuilder) FetchFunc(v FetchFunc) (r *DetailingBuilder) { b.fetcher = v return b @@ -133,10 +140,7 @@ func (b *DetailingBuilder) AfterTitleCompFunc(v ObjectComponentFunc) (r *Detaili } func (b *DetailingBuilder) GetPageFunc() web.PageFunc { - if b.pageFunc != nil { - return b.pageFunc - } - return b.defaultPageFunc + return b.pageFunc } func (b *DetailingBuilder) AppendTabsPanelFunc(v TabComponentFunc) (r *DetailingBuilder) { @@ -253,7 +257,13 @@ func (b *DetailingBuilder) defaultPageFunc(ctx *web.EventContext) (r web.PageRes for i, layout := range b.layouts { layoutClass[i] = string(layout) } - + if b.breadcrumbFunc != nil { + itemFunc, err := b.breadcrumbFunc(ctx, b.mb.Info(), ctx.Param(ParamID)) + if err != nil { + return r, err + } + ctx.WithContextValue(BreadcrumbItemsFuncKey{}, itemFunc) + } r.Body = VContainer().Children( notice, h.Div().Class("d-flex flex-column", strings.Join(layoutClass, ", ")).Children( @@ -454,3 +464,60 @@ func (b *DetailingBuilder) Section(sections ...*SectionBuilder) *DetailingBuilde } return b } + +func (b *DetailingBuilder) defaultBreadcrumbFunc(ctx *web.EventContext, obj any, id string) (BreadcrumbItemsFunc, error) { + var ( + msgr = b.mb.mustGetMessages(ctx.R) + titleComp h.HTMLComponent + title = msgr.DetailingObjectTitle(b.mb.Info().LabelName(ctx, true), getPageTitle(obj, id)) + ) + if b.titleFunc != nil { + style, ok := ctx.ContextValue(ctxKeyDetailingStyle{}).(DetailingStyle) + if !ok { + style = DetailingStylePage + } + xtitle, xtitleComp, err := b.titleFunc(ctx, obj, style, title) + if err != nil { + return nil, err + } + if xtitleComp != nil { + titleComp = xtitleComp + } + if xtitle != "" { + title = xtitle + } + } + if titleComp == nil { + titleComp = h.Text(title) + } + return func(ctx *web.EventContext, disableLast bool) (r []h.HTMLComponent) { + listingHref := b.mb.Info().ListingHref() + r = []h.HTMLComponent{ + VBreadcrumbsItem(h.Text(b.mb.Info().LabelName(ctx, false))). + Href(listingHref). + Attr("@click.stop", web.Plaid().PushState(true).URL(listingHref).Go()), + } + if b.mb.hasDetailing && !b.drawer { + detailingHref := b.mb.Info().DetailingHref(ctx.Param(ParamID)) + r = append(r, VBreadcrumbsItem(titleComp). + Href(detailingHref). + Attr("@click.stop", web.Plaid().PushState(true).URL(detailingHref).Go()). + Disabled(disableLast)) + } + return r + }, nil +} + +func (b *DetailingBuilder) Breadcrumb(f DetailingBreadcrumbFunc) *DetailingBuilder { + b.breadcrumbFunc = f + return b +} + +func (b *DetailingBuilder) GetBreadcrumb() DetailingBreadcrumbFunc { + return b.breadcrumbFunc +} + +func (b *DetailingBuilder) WrapBreadcrumb(w func(DetailingBreadcrumbFunc) DetailingBreadcrumbFunc) *DetailingBuilder { + b.breadcrumbFunc = w(b.breadcrumbFunc) + return b +} diff --git a/presets/model.go b/presets/model.go index e60efc83e..228244366 100644 --- a/presets/model.go +++ b/presets/model.go @@ -172,6 +172,8 @@ func (mb *ModelBuilder) newDetailing() (r *DetailingBuilder) { if mb.p.dataOperator != nil { mb.detailing.FetchFunc(mb.p.dataOperator.Fetch) } + mb.detailing.Breadcrumb(mb.detailing.defaultBreadcrumbFunc) + mb.detailing.PageFunc(mb.detailing.defaultPageFunc) return } diff --git a/presets/presets.go b/presets/presets.go index a715780bc..3d4df338c 100644 --- a/presets/presets.go +++ b/presets/presets.go @@ -69,6 +69,10 @@ type Builder struct { } type AssetFunc func(ctx *web.EventContext) +type ( + BreadcrumbItemsFuncKey struct{} + BreadcrumbItemsFunc func(ctx *web.EventContext, disableLast bool) (r []h.HTMLComponent) +) type extraAsset struct { path string @@ -947,6 +951,10 @@ func (b *Builder) defaultLayout(in web.PageFunc, cfg *LayoutConfig) (out web.Pag innerPageTitleCompo, ok := ctx.ContextValue(CtxPageTitleComponent).(h.HTMLComponent) if !ok { innerPageTitleCompo = VToolbarTitle(innerPr.PageTitle) // Class("text-h6 font-weight-regular"), + breadcrumbFunc, ok := ctx.ContextValue(BreadcrumbItemsFuncKey{}).(BreadcrumbItemsFunc) + if ok { + innerPageTitleCompo = CreateVXBreadcrumbs(ctx, breadcrumbFunc) + } } else { ctx.WithContextValue(CtxPageTitleComponent, nil) } @@ -1377,3 +1385,17 @@ func redirectSlashes(next http.Handler) http.Handler { next.ServeHTTP(w, r) }) } + +func CreateVXBreadcrumbs(ctx *web.EventContext, f BreadcrumbItemsFunc) h.HTMLComponent { + items := f(ctx, true) + + joinedItems := make([]h.HTMLComponent, 0, len(items)*2-1) + for i, item := range items { + joinedItems = append(joinedItems, item) + if i < len(items)-1 { + joinedItems = append(joinedItems, VBreadcrumbsDivider(h.Text("ยป"))) + } + } + + return vuetifyx.VXBreadcrumbs(joinedItems...).Class("pa-0") +} diff --git a/utils/testflow/gentool/go.sum b/utils/testflow/gentool/go.sum index 9559c51b0..53a21c2ef 100644 --- a/utils/testflow/gentool/go.sum +++ b/utils/testflow/gentool/go.sum @@ -22,6 +22,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/qor5/web/v3 v3.0.12-0.20250225073451-8e876be98c21 h1:Ho7ZJ04vT96lqe6g7PJhc9eFEUsEgDGpHyPhqzwGF3E= github.com/qor5/web/v3 v3.0.12-0.20250225073451-8e876be98c21/go.mod h1:32vdHHcZb2JimlcaclW9hLUyimdXjrllZDHTh3rl6d0= +github.com/qor5/web/v3 v3.0.12-0.20250322025751-d36834ab80b4 h1:gv/yjsDE+xa/nKb14a1mABOz2Hv66OHn8xtR3TQsvA8= github.com/qor5/web/v3 v3.0.12-0.20250322025751-d36834ab80b4/go.mod h1:zU8n7tDAwYgq3HWMpe0dgmywBZaZx3ENBfwmjwEwYPo= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=