package pagebuilder
import (
"database/sql"
"encoding/json"
"fmt"
"net/url"
"os"
"path"
"strconv"
"strings"
"github.com/iancoleman/strcase"
"github.com/jinzhu/inflection"
"github.com/qor5/admin/l10n"
"github.com/qor5/admin/presets"
"github.com/qor5/admin/presets/actions"
"github.com/qor5/admin/publish"
. "github.com/qor5/ui/vuetify"
vx "github.com/qor5/ui/vuetifyx"
"github.com/qor5/web"
"github.com/qor5/x/i18n"
"github.com/sunfmin/reflectutils"
h "github.com/theplant/htmlgo"
"goji.io/pat"
"gorm.io/gorm"
)
const (
AddContainerDialogEvent = "page_builder_AddContainerDialogEvent"
AddContainerEvent = "page_builder_AddContainerEvent"
DeleteContainerConfirmationEvent = "page_builder_DeleteContainerConfirmationEvent"
DeleteContainerEvent = "page_builder_DeleteContainerEvent"
MoveContainerEvent = "page_builder_MoveContainerEvent"
ToggleContainerVisibilityEvent = "page_builder_ToggleContainerVisibilityEvent"
MarkAsSharedContainerEvent = "page_builder_MarkAsSharedContainerEvent"
RenameContainerDialogEvent = "page_builder_RenameContainerDialogEvent"
RenameContainerEvent = "page_builder_RenameContainerEvent"
paramPageID = "pageID"
paramPageVersion = "pageVersion"
paramLocale = "locale"
paramContainerID = "containerID"
paramMoveResult = "moveResult"
paramContainerName = "containerName"
paramSharedContainer = "sharedContainer"
paramModelID = "modelID"
DevicePhone = "phone"
DeviceTablet = "tablet"
DeviceComputer = "computer"
)
func (b *Builder) Preview(ctx *web.EventContext) (r web.PageResponse, err error) {
isTpl := ctx.R.FormValue("tpl") != ""
id := ctx.R.FormValue("id")
version := ctx.R.FormValue("version")
locale := ctx.R.FormValue("locale")
var p *Page
r.Body, p, err = b.renderPageOrTemplate(ctx, isTpl, id, version, locale, false)
if err != nil {
return
}
r.PageTitle = p.Title
return
}
const editorPreviewContentPortal = "editorPreviewContentPortal"
func (b *Builder) Editor(ctx *web.EventContext) (r web.PageResponse, err error) {
isTpl := ctx.R.FormValue("tpl") != ""
id := pat.Param(ctx.R, "id")
version := ctx.R.FormValue("version")
locale := ctx.R.Form.Get("locale")
isLocalizable := ctx.R.Form.Has("locale")
var body h.HTMLComponent
var containerList h.HTMLComponent
var device string
var p *Page
var previewHref string
deviceQueries := url.Values{}
if isTpl {
previewHref = fmt.Sprintf("/preview?id=%s&tpl=1", id)
deviceQueries.Add("tpl", "1")
if isLocalizable && l10nON {
previewHref = fmt.Sprintf("/preview?id=%s&tpl=1&locale=%s", id, locale)
deviceQueries.Add("locale", locale)
}
} else {
previewHref = fmt.Sprintf("/preview?id=%s&version=%s", id, version)
deviceQueries.Add("version", version)
if isLocalizable && l10nON {
previewHref = fmt.Sprintf("/preview?id=%s&version=%s&locale=%s", id, version, locale)
deviceQueries.Add("locale", locale)
}
}
body, p, err = b.renderPageOrTemplate(ctx, isTpl, id, version, locale, true)
if err != nil {
return
}
r.PageTitle = fmt.Sprintf("Editor for %s: %s", id, p.Title)
device, _ = b.getDevice(ctx)
containerList, err = b.renderContainersList(ctx, p.ID, p.GetVersion(), p.GetLocale(), p.GetStatus() != publish.StatusDraft)
if err != nil {
return
}
msgr := i18n.MustGetModuleMessages(ctx.R, I18nPageBuilderKey, Messages_en_US).(*Messages)
r.Body = h.Components(
VAppBar(
VSpacer(),
VBtn("").Icon(true).Children(
VIcon("phone_iphone"),
).Attr("@click", web.Plaid().Queries(deviceQueries).Query("device", "phone").PushState(true).Go()).
Class("mr-10").InputValue(device == "phone"),
VBtn("").Icon(true).Children(
VIcon("tablet_mac"),
).Attr("@click", web.Plaid().Queries(deviceQueries).Query("device", "tablet").PushState(true).Go()).
Class("mr-10").InputValue(device == "tablet"),
VBtn("").Icon(true).Children(
VIcon("laptop_mac"),
).Attr("@click", web.Plaid().Queries(deviceQueries).Query("device", "laptop").PushState(true).Go()).
InputValue(device == "laptop"),
VSpacer(),
VBtn(msgr.Preview).Text(true).Href(b.prefix+previewHref).Target("_blank"),
VAppBarNavIcon().On("click.stop", "vars.navDrawer = !vars.navDrawer"),
).Dark(true).
Color("primary").
App(true),
VMain(
VContainer(web.Portal(body).Name(editorPreviewContentPortal)).
Class("mt-6").
Fluid(true),
VNavigationDrawer(containerList).
App(true).
Right(true).
Fixed(true).
Value(true).
Width(420).
Attr("v-model", "vars.navDrawer").
Attr(web.InitContextVars, `{navDrawer: null}`),
),
)
return
}
func (b *Builder) getDevice(ctx *web.EventContext) (device string, style string) {
device = ctx.R.FormValue("device")
if len(device) == 0 {
device = b.defaultDevice
}
switch device {
case DevicePhone:
style = "width: 414px;"
case DeviceTablet:
style = "width: 768px;"
// case Device_Computer:
// style = "width: 1264px;"
}
return
}
const FreeStyleKey = "FreeStyle"
func (b *Builder) renderPageOrTemplate(ctx *web.EventContext, isTpl bool, pageOrTemplateID string, version, locale string, isEditor bool) (r h.HTMLComponent, p *Page, err error) {
if isTpl {
tpl := &Template{}
err = b.db.First(tpl, "id = ? and locale_code = ?", pageOrTemplateID, locale).Error
if err != nil {
return
}
p = tpl.Page()
version = p.Version.Version
} else {
err = b.db.First(&p, "id = ? and version = ? and locale_code = ?", pageOrTemplateID, version, locale).Error
if err != nil {
return
}
}
var isReadonly bool
if p.GetStatus() != publish.StatusDraft && isEditor {
isReadonly = true
}
var comps []h.HTMLComponent
comps, err = b.renderContainers(ctx, p.ID, p.GetVersion(), p.GetLocale(), isEditor, isReadonly)
if err != nil {
return
}
r = h.Components(comps...)
if b.pageLayoutFunc != nil {
input := &PageLayoutInput{
IsEditor: isEditor,
IsPreview: !isEditor,
Page: p,
}
if isEditor {
input.EditorCss = append(input.EditorCss, h.RawHTML(``))
input.EditorCss = append(input.EditorCss, h.Style(`
.wrapper-shadow {
position:absolute;
width: 100%;
height: 100%;
z-index:9999;
background: rgba(81, 193, 226, 0.25);
opacity: 0;
top: 0;
left: 0;
}
.wrapper-shadow button{
position:absolute;
top: 0;
right: 0;
line-height: 1;
font-size: 0;
border: 2px outset #767676;
cursor: pointer;
}
.wrapper-shadow.hover {
cursor: pointer;
opacity: 1;
}`))
input.FreeStyleBottomJs = []string{`
function scrolltoCurrentContainer(event) {
const current = document.querySelector("div[data-container-id='"+event.data+"']");
if (!current) {
return;
}
const hover = document.querySelector(".wrapper-shadow.hover")
if (hover) {
hover.classList.remove('hover');
}
window.parent.scroll({top: current.offsetTop, behavior: "smooth"});
current.querySelector(".wrapper-shadow").classList.add('hover');
}
document.querySelectorAll('.wrapper-shadow').forEach(shadow => {
shadow.addEventListener('mouseover', event => {
document.querySelectorAll(".wrapper-shadow.hover").forEach(item => {
item.classList.remove('hover');
})
shadow.classList.add('hover');
})
})
window.addEventListener("message", scrolltoCurrentContainer, false);
`}
}
if f := ctx.R.Context().Value(FreeStyleKey); f != nil {
pl, ok := f.(*PageLayoutInput)
if ok {
input.FreeStyleCss = append(input.FreeStyleCss, pl.FreeStyleCss...)
input.FreeStyleTopJs = append(input.FreeStyleTopJs, pl.FreeStyleTopJs...)
input.FreeStyleBottomJs = append(input.FreeStyleBottomJs, pl.FreeStyleBottomJs...)
}
}
if isEditor {
// use newCtx to avoid inserting page head to head outside of iframe
newCtx := &web.EventContext{
R: ctx.R,
Injector: &web.PageInjector{},
}
r = b.pageLayoutFunc(h.Components(comps...), input, newCtx)
newCtx.Injector.HeadHTMLComponent("style", b.pageStyle, true)
r = h.HTMLComponents{
h.RawHTML("\n"),
h.Tag("html").Children(
h.Head(
newCtx.Injector.GetHeadHTMLComponent(),
),
h.Body(
h.Div(
r,
).Id("app").Attr("v-cloak", true),
newCtx.Injector.GetTailHTMLComponent(),
).Class("front"),
).AttrIf("lang", newCtx.Injector.GetHTMLLang(), newCtx.Injector.GetHTMLLang() != ""),
}
_, width := b.getDevice(ctx)
iframeHeightName := "_iframeHeight"
iframeHeightCookie, _ := ctx.R.Cookie(iframeHeightName)
iframeValue := "1000px"
if iframeHeightCookie != nil {
iframeValue = iframeHeightCookie.Value
}
r = h.Div(
h.RawHTML(fmt.Sprintf(`
`,
strings.ReplaceAll(
h.MustString(r, ctx.R.Context()),
"\"",
"""),
iframeHeightName,
iframeValue,
)),
).Class("page-builder-container mx-auto").Attr("style", width)
} else {
r = b.pageLayoutFunc(h.Components(comps...), input, ctx)
ctx.Injector.HeadHTMLComponent("style", b.pageStyle, true)
}
}
return
}
func (b *Builder) renderContainers(ctx *web.EventContext, pageID uint, pageVersion, locale string, isEditor bool, isReadonly bool) (r []h.HTMLComponent, err error) {
var cons []*Container
err = b.db.Order("display_order ASC").Find(&cons, "page_id = ? AND page_version = ? AND locale_code = ?", pageID, pageVersion, locale).Error
if err != nil {
return
}
cbs := b.getContainerBuilders(cons)
device, _ := b.getDevice(ctx)
for _, ec := range cbs {
if ec.container.Hidden {
continue
}
obj := ec.builder.NewModel()
err = b.db.FirstOrCreate(obj, "id = ?", ec.container.ModelID).Error
if err != nil {
return
}
input := RenderInput{
IsEditor: isEditor,
IsReadonly: isReadonly,
Device: device,
}
pure := ec.builder.renderFunc(obj, &input, ctx)
r = append(r, pure)
}
return
}
type ContainerSorterItem struct {
Index int `json:"index"`
Label string `json:"label"`
ModelName string `json:"model_name"`
ModelID string `json:"model_id"`
DisplayName string `json:"display_name"`
ContainerID string `json:"container_id"`
URL string `json:"url"`
Shared bool `json:"shared"`
VisibilityIcon string `json:"visibility_icon"`
ParamID string `json:"param_id"`
}
type ContainerSorter struct {
Items []ContainerSorterItem `json:"items"`
}
func (b *Builder) renderContainersList(ctx *web.EventContext, pageID uint, pageVersion, locale string, isReadonly bool) (r h.HTMLComponent, err error) {
var cons []*Container
err = b.db.Order("display_order ASC").Find(&cons, "page_id = ? AND page_version = ? AND locale_code = ?", pageID, pageVersion, locale).Error
if err != nil {
return
}
var sorterData ContainerSorter
for i, c := range cons {
vicon := "visibility"
if c.Hidden {
vicon = "visibility_off"
}
var displayName = i18n.T(ctx.R, presets.ModelsI18nModuleKey, c.DisplayName)
sorterData.Items = append(sorterData.Items,
ContainerSorterItem{
Index: i,
Label: displayName,
ModelName: inflection.Plural(strcase.ToKebab(c.ModelName)),
ModelID: strconv.Itoa(int(c.ModelID)),
DisplayName: displayName,
ContainerID: strconv.Itoa(int(c.ID)),
URL: b.ContainerByName(c.ModelName).mb.Info().ListingHref(),
Shared: c.Shared,
VisibilityIcon: vicon,
ParamID: c.PrimarySlug(),
},
)
}
msgr := i18n.MustGetModuleMessages(ctx.R, I18nPageBuilderKey, Messages_en_US).(*Messages)
r = web.Scope(
VSheet(
VCard(
h.Tag("vx-draggable").
Attr("v-model", "locals.items", "handle", ".handle", "animation", "300").
Attr("@end", web.Plaid().
URL(fmt.Sprintf("%s/editors", b.prefix)).
EventFunc(MoveContainerEvent).
FieldValue(paramMoveResult, web.Var("JSON.stringify(locals.items)")).
Go()).Children(
// VList(
h.Div(
VListItem(
h.If(!isReadonly,
VListItemIcon(VBtn("").Icon(true).Children(VIcon("drag_indicator"))).Class("handle my-2 ml-1 mr-1"),
).Else(
VListItemIcon().Class("my-2 ml-1 mr-1"),
),
VListItemContent(
VListItemTitle(h.Text("{{item.label}}")).Attr(":style", "[item.shared ? {'color':'green'}:{}]"),
),
h.If(!isReadonly,
VListItemIcon(VBtn("").Icon(true).Children(VIcon("edit").Small(true))).Attr("@click",
web.Plaid().
URL(web.Var("item.url")).
EventFunc(actions.Edit).
Query(presets.ParamOverlay, actions.Drawer).
Query(presets.ParamID, web.Var("item.model_id")).
Go(),
).Class("my-2"),
VListItemIcon(VBtn("").Icon(true).Children(VIcon("{{item.visibility_icon}}").Small(true))).Attr("@click",
web.Plaid().
URL(web.Var("item.url")).
EventFunc(ToggleContainerVisibilityEvent).
Query(paramContainerID, web.Var("item.param_id")).
Go(),
).Class("my-2"),
),
h.If(!isReadonly,
VMenu(
web.Slot(
VBtn("").Children(
VIcon("more_horiz"),
).Attr("v-on", "on").Text(true).Fab(true).Small(true),
).Name("activator").Scope("{ on }"),
VList(
VListItem(
VListItemIcon(VIcon("edit_note")).Class("pl-0 mr-2"),
VListItemTitle(h.Text("Rename")),
).Attr("@click",
web.Plaid().
URL(web.Var("item.url")).
EventFunc(RenameContainerDialogEvent).
Query(paramContainerID, web.Var("item.param_id")).
Query(paramContainerName, web.Var("item.display_name")).
Go(),
),
VListItem(
VListItemIcon(VIcon("delete")).Class("pl-0 mr-2"),
VListItemTitle(h.Text("Delete")),
).Attr("@click", web.Plaid().
URL(web.Var("item.url")).
EventFunc(DeleteContainerConfirmationEvent).
Query(paramContainerID, web.Var("item.param_id")).
Query(paramContainerName, web.Var("item.display_name")).
Go(),
),
VListItem(
VListItemIcon(VIcon("share")).Class("pl-1 mr-2"),
VListItemTitle(h.Text("Mark As Shared Container")),
).Attr("@click",
web.Plaid().
URL(web.Var("item.url")).
EventFunc(MarkAsSharedContainerEvent).
Query(paramContainerID, web.Var("item.param_id")).
Go(),
).Attr("v-if", "!item.shared"),
).Dense(true),
).Left(true),
),
).Class("pl-0").Attr("@click", fmt.Sprintf(`document.querySelector("iframe").contentWindow.postMessage(%s+"_"+%s,"*");`, web.Var("item.model_name"), web.Var("item.model_id"))),
VDivider().Attr("v-if", "index < locals.items.length "),
).Attr("v-for", "(item, index) in locals.items", ":key", "item.index"),
h.If(!isReadonly,
VListItem(
VListItemIcon(VIcon("add").Color("primary")).Class("ma-4"),
VListItemTitle(VBtn(msgr.AddContainers).Color("primary").Text(true)),
).Attr("@click",
web.Plaid().
URL(fmt.Sprintf("%s/editors/%d?version=%s&locale=%s", b.prefix, pageID, pageVersion, locale)).
EventFunc(AddContainerDialogEvent).
Query(paramPageID, pageID).
Query(paramPageVersion, pageVersion).
Query(paramLocale, locale).
Go(),
),
),
// ).Class("py-0"),
),
).Outlined(true),
).Class("pa-4 pt-2"),
).Init(h.JSONString(sorterData)).VSlot("{ locals }")
return
}
func (b *Builder) AddContainer(ctx *web.EventContext) (r web.EventResponse, err error) {
pageID := ctx.QueryAsInt(paramPageID)
pageVersion := ctx.R.FormValue(paramPageVersion)
locale := ctx.R.FormValue(paramLocale)
containerName := ctx.R.FormValue(paramContainerName)
sharedContainer := ctx.R.FormValue(paramSharedContainer)
modelID := ctx.QueryAsInt(paramModelID)
var newModelID uint
if sharedContainer == "true" {
err = b.AddSharedContainerToPage(pageID, pageVersion, locale, containerName, uint(modelID))
r.PushState = web.Location(url.Values{})
} else {
newModelID, err = b.AddContainerToPage(pageID, pageVersion, locale, containerName)
r.VarsScript = web.Plaid().
URL(b.ContainerByName(containerName).mb.Info().ListingHref()).
EventFunc(actions.Edit).
Query(presets.ParamOverlay, actions.Drawer).
Query(presets.ParamID, fmt.Sprint(newModelID)).
Go()
}
return
}
func (b *Builder) MoveContainer(ctx *web.EventContext) (r web.EventResponse, err error) {
moveResult := ctx.R.FormValue(paramMoveResult)
var result []ContainerSorterItem
err = json.Unmarshal([]byte(moveResult), &result)
if err != nil {
return
}
err = b.db.Transaction(func(tx *gorm.DB) (inerr error) {
for i, r := range result {
if inerr = tx.Model(&Container{}).Where("id = ?", r.ContainerID).Update("display_order", i+1).Error; inerr != nil {
return
}
}
return
})
r.PushState = web.Location(url.Values{})
return
}
func (b *Builder) ToggleContainerVisibility(ctx *web.EventContext) (r web.EventResponse, err error) {
var container Container
paramID := ctx.R.FormValue(paramContainerID)
cs := container.PrimaryColumnValuesBySlug(paramID)
containerID := cs["id"]
locale := cs["locale_code"]
err = b.db.Exec("UPDATE page_builder_containers SET hidden = NOT(coalesce(hidden,FALSE)) WHERE id = ? AND locale_code = ?", containerID, locale).Error
r.PushState = web.Location(url.Values{})
return
}
func (b *Builder) DeleteContainerConfirmation(ctx *web.EventContext) (r web.EventResponse, err error) {
paramID := ctx.R.FormValue(paramContainerID)
containerName := ctx.R.FormValue(paramContainerName)
r.UpdatePortals = append(r.UpdatePortals, &web.PortalUpdate{
Name: presets.DeleteConfirmPortalName,
Body: VDialog(
VCard(
VCardTitle(h.Text(fmt.Sprintf("Are you sure you want to delete %s?", containerName))),
VCardActions(
VSpacer(),
VBtn("Cancel").
Depressed(true).
Class("ml-2").
On("click", "vars.deleteConfirmation = false"),
VBtn("Delete").
Color("primary").
Depressed(true).
Dark(true).
Attr("@click", web.Plaid().
URL(fmt.Sprintf("%s/editors", b.prefix)).
EventFunc(DeleteContainerEvent).
Query(paramContainerID, paramID).
Go()),
),
),
).MaxWidth("600px").
Attr("v-model", "vars.deleteConfirmation").
Attr(web.InitContextVars, `{deleteConfirmation: false}`),
})
r.VarsScript = "setTimeout(function(){ vars.deleteConfirmation = true }, 100)"
return
}
func (b *Builder) DeleteContainer(ctx *web.EventContext) (r web.EventResponse, err error) {
var container Container
paramID := ctx.R.FormValue(paramContainerID)
cs := container.PrimaryColumnValuesBySlug(paramID)
containerID := cs["id"]
locale := cs["locale_code"]
err = b.db.Delete(&Container{}, "id = ? AND locale_code = ?", containerID, locale).Error
if err != nil {
return
}
r.PushState = web.Location(url.Values{})
return
}
func (b *Builder) AddContainerToPage(pageID int, pageVersion, locale, containerName string) (modelID uint, err error) {
model := b.ContainerByName(containerName).NewModel()
var dc DemoContainer
b.db.Where("model_name = ? AND locale_code = ?", containerName, locale).First(&dc)
if dc.ID != 0 && dc.ModelID != 0 {
b.db.Where("id = ?", dc.ModelID).First(model)
reflectutils.Set(model, "ID", uint(0))
}
err = b.db.Create(model).Error
if err != nil {
return
}
var maxOrder sql.NullFloat64
err = b.db.Model(&Container{}).Select("MAX(display_order)").Where("page_id = ? and page_version = ? and locale_code = ?", pageID, pageVersion, locale).Scan(&maxOrder).Error
if err != nil {
return
}
modelID = reflectutils.MustGet(model, "ID").(uint)
err = b.db.Create(&Container{
PageID: uint(pageID),
PageVersion: pageVersion,
ModelName: containerName,
DisplayName: containerName,
ModelID: modelID,
DisplayOrder: maxOrder.Float64 + 1,
Locale: l10n.Locale{
LocaleCode: locale,
},
}).Error
if err != nil {
return
}
return
}
func (b *Builder) AddSharedContainerToPage(pageID int, pageVersion, locale, containerName string, modelID uint) (err error) {
var c Container
err = b.db.First(&c, "model_name = ? AND model_id = ? AND shared = true", containerName, modelID).Error
if err != nil {
return
}
var maxOrder sql.NullFloat64
err = b.db.Model(&Container{}).Select("MAX(display_order)").Where("page_id = ? and page_version = ? and locale_code = ?", pageID, pageVersion, locale).Scan(&maxOrder).Error
if err != nil {
return
}
err = b.db.Create(&Container{
PageID: uint(pageID),
PageVersion: pageVersion,
ModelName: containerName,
DisplayName: c.DisplayName,
ModelID: modelID,
Shared: true,
DisplayOrder: maxOrder.Float64 + 1,
Locale: l10n.Locale{
LocaleCode: locale,
},
}).Error
if err != nil {
return
}
return
}
func (b *Builder) copyContainersToNewPageVersion(db *gorm.DB, pageID int, locale, oldPageVersion, newPageVersion string) (err error) {
return b.copyContainersToAnotherPage(db, pageID, oldPageVersion, locale, pageID, newPageVersion, locale)
}
func (b *Builder) copyContainersToAnotherPage(db *gorm.DB, pageID int, pageVersion, locale string, toPageID int, toPageVersion, toPageLocale string) (err error) {
var cons []*Container
err = db.Order("display_order ASC").Find(&cons, "page_id = ? AND page_version = ? AND locale_code = ?", pageID, pageVersion, locale).Error
if err != nil {
return
}
for _, c := range cons {
newModelID := c.ModelID
if !c.Shared {
model := b.ContainerByName(c.ModelName).NewModel()
if err = db.First(model, "id = ?", c.ModelID).Error; err != nil {
return
}
if err = reflectutils.Set(model, "ID", uint(0)); err != nil {
return
}
if err = db.Create(model).Error; err != nil {
return
}
newModelID = reflectutils.MustGet(model, "ID").(uint)
}
if err = db.Create(&Container{
PageID: uint(toPageID),
PageVersion: toPageVersion,
ModelName: c.ModelName,
DisplayName: c.DisplayName,
ModelID: newModelID,
DisplayOrder: c.DisplayOrder,
Shared: c.Shared,
Locale: l10n.Locale{
LocaleCode: toPageLocale,
},
}).Error; err != nil {
return
}
}
return
}
func (b *Builder) localizeContainersToAnotherPage(db *gorm.DB, pageID int, pageVersion, locale string, toPageID int, toPageVersion, toPageLocale string) (err error) {
var cons []*Container
err = db.Order("display_order ASC").Find(&cons, "page_id = ? AND page_version = ? AND locale_code = ?", pageID, pageVersion, locale).Error
if err != nil {
return
}
for _, c := range cons {
newModelID := c.ModelID
newDisplayName := c.DisplayName
if !c.Shared {
model := b.ContainerByName(c.ModelName).NewModel()
if err = db.First(model, "id = ?", c.ModelID).Error; err != nil {
return
}
if err = reflectutils.Set(model, "ID", uint(0)); err != nil {
return
}
if err = db.Create(model).Error; err != nil {
return
}
newModelID = reflectutils.MustGet(model, "ID").(uint)
} else {
var count int64
var sharedCon Container
if err = db.Where("model_name = ? AND localize_from_model_id = ? AND locale_code = ? AND shared = ?", c.ModelName, c.ModelID, toPageLocale, true).First(&sharedCon).Count(&count).Error; err != nil && err != gorm.ErrRecordNotFound {
return
}
if count == 0 {
model := b.ContainerByName(c.ModelName).NewModel()
if err = db.First(model, "id = ?", c.ModelID).Error; err != nil {
return
}
if err = reflectutils.Set(model, "ID", uint(0)); err != nil {
return
}
if err = db.Create(model).Error; err != nil {
return
}
newModelID = reflectutils.MustGet(model, "ID").(uint)
} else {
newModelID = sharedCon.ModelID
newDisplayName = sharedCon.DisplayName
}
}
var newCon Container
err = db.Order("display_order ASC").Find(&newCon, "id = ? AND locale_code = ?", c.ID, toPageLocale).Error
if err != nil {
return
}
newCon.ID = c.ID
newCon.PageID = uint(toPageID)
newCon.PageVersion = toPageVersion
newCon.ModelName = c.ModelName
newCon.DisplayName = newDisplayName
newCon.ModelID = newModelID
newCon.DisplayOrder = c.DisplayOrder
newCon.Shared = c.Shared
newCon.LocaleCode = toPageLocale
newCon.LocalizeFromModelID = c.ModelID
if err = db.Save(&newCon).Error; err != nil {
return
}
}
return
}
func (b *Builder) createModelAfterLocalizeDemoContainer(db *gorm.DB, c *DemoContainer) (err error) {
model := b.ContainerByName(c.ModelName).NewModel()
if err = db.First(model, "id = ?", c.ModelID).Error; err != nil {
return
}
if err = reflectutils.Set(model, "ID", uint(0)); err != nil {
return
}
if err = db.Create(model).Error; err != nil {
return
}
c.ModelID = reflectutils.MustGet(model, "ID").(uint)
return
}
func (b *Builder) MarkAsSharedContainer(ctx *web.EventContext) (r web.EventResponse, err error) {
var container Container
paramID := ctx.R.FormValue(paramContainerID)
cs := container.PrimaryColumnValuesBySlug(paramID)
containerID := cs["id"]
locale := cs["locale_code"]
err = b.db.Model(&Container{}).Where("id = ? AND locale_code = ?", containerID, locale).Update("shared", true).Error
if err != nil {
return
}
r.PushState = web.Location(url.Values{})
return
}
func (b *Builder) RenameContainer(ctx *web.EventContext) (r web.EventResponse, err error) {
var container Container
paramID := ctx.R.FormValue(paramContainerID)
cs := container.PrimaryColumnValuesBySlug(paramID)
containerID := cs["id"]
locale := cs["locale_code"]
name := ctx.R.FormValue("DisplayName")
var c Container
err = b.db.First(&c, "id = ? AND locale_code = ? ", containerID, locale).Error
if err != nil {
return
}
if c.Shared {
err = b.db.Model(&Container{}).Where("model_name = ? AND model_id = ? AND locale_code = ?", c.ModelName, c.ModelID, locale).Update("display_name", name).Error
if err != nil {
return
}
} else {
err = b.db.Model(&Container{}).Where("id = ? AND locale_code = ?", containerID, locale).Update("display_name", name).Error
if err != nil {
return
}
}
r.PushState = web.Location(url.Values{})
return
}
func (b *Builder) RenameContainerDialog(ctx *web.EventContext) (r web.EventResponse, err error) {
paramID := ctx.R.FormValue(paramContainerID)
name := ctx.R.FormValue(paramContainerName)
okAction := web.Plaid().
URL(fmt.Sprintf("%s/editors", b.prefix)).
EventFunc(RenameContainerEvent).Query(paramContainerID, paramID).Go()
portalName := dialogPortalName
if ctx.R.FormValue("portal") == "presets" {
portalName = presets.DialogPortalName
}
r.UpdatePortals = append(r.UpdatePortals, &web.PortalUpdate{
Name: portalName,
Body: web.Scope(
VDialog(
VCard(
VCardTitle(h.Text("Rename")),
VCardText(
VTextField().FieldName("DisplayName").Value(name),
),
VCardActions(
VSpacer(),
VBtn("Cancel").
Depressed(true).
Class("ml-2").
On("click", "locals.renameDialog = false"),
VBtn("OK").
Color("primary").
Depressed(true).
Dark(true).
Attr("@click", okAction),
),
),
).MaxWidth("400px").
Attr("v-model", "locals.renameDialog"),
).Init("{renameDialog:true}").VSlot("{locals}"),
})
return
}
func (b *Builder) AddContainerDialog(ctx *web.EventContext) (r web.EventResponse, err error) {
pageID := ctx.QueryAsInt(paramPageID)
pageVersion := ctx.R.FormValue(paramPageVersion)
locale := ctx.R.FormValue(paramLocale)
// okAction := web.Plaid().EventFunc(RenameContainerEvent).Query(paramContainerID, containerID).Go()
msgr := i18n.MustGetModuleMessages(ctx.R, I18nPageBuilderKey, Messages_en_US).(*Messages)
var containers []h.HTMLComponent
for _, builder := range b.containerBuilders {
cover := builder.cover
if cover == "" {
cover = path.Join(b.prefix, b.imagesPrefix, strings.ReplaceAll(builder.name, " ", "")+".png")
}
containers = append(containers,
VCol(
VCard(
VImg().Src(cover).Height(200),
VCardActions(
VCardTitle(h.Text(i18n.T(ctx.R, presets.ModelsI18nModuleKey, builder.name))),
VSpacer(),
VBtn(msgr.Select).
Text(true).
Color("primary").Attr("@click",
"locals.addContainerDialog = false;"+web.Plaid().
URL(fmt.Sprintf("%s/editors/%d?version=%s&locale=%s", b.prefix, pageID, pageVersion, locale)).
EventFunc(AddContainerEvent).
Query(paramPageID, pageID).
Query(paramPageVersion, pageVersion).
Query(paramLocale, locale).
Query(paramContainerName, builder.name).
Go(),
),
),
),
).Cols(4),
)
}
var cons []*Container
err = b.db.Select("display_name,model_name,model_id").Where("shared = true AND locale_code = ?", locale).Group("display_name,model_name,model_id").Find(&cons).Error
if err != nil {
return
}
var sharedContainers []h.HTMLComponent
for _, sharedC := range cons {
c := b.ContainerByName(sharedC.ModelName)
cover := c.cover
if cover == "" {
cover = path.Join(b.prefix, b.imagesPrefix, strings.ReplaceAll(c.name, " ", "")+".png")
}
sharedContainers = append(sharedContainers,
VCol(
VCard(
VImg().Src(cover).Height(200),
VCardActions(
VCardTitle(h.Text(i18n.T(ctx.R, presets.ModelsI18nModuleKey, sharedC.DisplayName))),
VSpacer(),
VBtn(msgr.Select).
Text(true).
Color("primary").Attr("@click",
"locals.addContainerDialog = false;"+web.Plaid().
URL(fmt.Sprintf("%s/editors/%d?version=%s&locale=%s", b.prefix, pageID, pageVersion, locale)).
EventFunc(AddContainerEvent).
Query(paramPageID, pageID).
Query(paramPageVersion, pageVersion).
Query(paramLocale, locale).
Query(paramContainerName, sharedC.ModelName).
Query(paramModelID, sharedC.ModelID).
Query(paramSharedContainer, "true").
Go(),
),
),
),
).Cols(4),
)
}
r.UpdatePortals = append(r.UpdatePortals, &web.PortalUpdate{
Name: dialogPortalName,
Body: web.Scope(
VDialog(
VTabs(
VTab(h.Text(msgr.New)),
VTabItem(
VSheet(
VContainer(
VRow(
containers...,
),
),
),
).Attr("style", "overflow-y: scroll; overflow-x: hidden; height: 610px;"),
VTab(h.Text(msgr.Shared)),
VTabItem(
VSheet(
VContainer(
VRow(
sharedContainers...,
),
),
),
).Attr("style", "overflow-y: scroll; overflow-x: hidden; height: 610px;"),
),
).Width("1200px").Attr("v-model", "locals.addContainerDialog"),
).Init("{addContainerDialog:true}").VSlot("{locals}"),
})
return
}
type editorContainer struct {
builder *ContainerBuilder
container *Container
}
func (b *Builder) getContainerBuilders(cs []*Container) (r []*editorContainer) {
for _, c := range cs {
for _, cb := range b.containerBuilders {
if cb.name == c.ModelName {
r = append(r, &editorContainer{
builder: cb,
container: c,
})
}
}
}
return
}
const (
dialogPortalName = "pagebuilder_DialogPortalName"
)
func (b *Builder) pageEditorLayout(in web.PageFunc, config *presets.LayoutConfig) (out web.PageFunc) {
return func(ctx *web.EventContext) (pr web.PageResponse, err error) {
ctx.Injector.HeadHTML(strings.Replace(`
`, "{{prefix}}", b.prefix, -1))
b.ps.InjectExtraAssets(ctx)
if len(os.Getenv("DEV_PRESETS")) > 0 {
ctx.Injector.TailHTML(`
`)
} else {
ctx.Injector.TailHTML(strings.Replace(`
`, "{{prefix}}", b.prefix, -1))
}
var innerPr web.PageResponse
innerPr, err = in(ctx)
if err != nil {
panic(err)
}
action := web.POST().
EventFunc(actions.Edit).
URL(web.Var("\""+b.prefix+"/\"+arr[0]")).
Query(presets.ParamOverlay, actions.Drawer).
Query(presets.ParamID, web.Var("arr[1]")).
// Query(presets.ParamOverlayAfterUpdateScript,
// web.Var(
// h.JSONString(web.POST().
// PushState(web.Location(url.Values{})).
// MergeQuery(true).
// ThenScript(`setTimeout(function(){ window.scroll({left: __scrollLeft__, top: __scrollTop__, behavior: "auto"}) }, 50)`).
// Go())+".replace(\"__scrollLeft__\", scrollLeft).replace(\"__scrollTop__\", scrollTop)",
// ),
// ).
Go()
pr.PageTitle = fmt.Sprintf("%s - %s", innerPr.PageTitle, "Page Builder")
pr.Body = VApp(
web.Portal().Name(presets.RightDrawerPortalName),
web.Portal().Name(presets.DialogPortalName),
web.Portal().Name(presets.DeleteConfirmPortalName),
web.Portal().Name(dialogPortalName),
h.Script(`
(function(){
let scrollLeft = 0;
let scrollTop = 0;
function pause(duration) {
return new Promise(res => setTimeout(res, duration));
}
function backoff(retries, fn, delay = 100) {
fn().catch(err => retries > 1
? pause(delay).then(() => backoff(retries - 1, fn, delay * 2))
: Promise.reject(err));
}
function restoreScroll() {
window.scroll({left: scrollLeft, top: scrollTop, behavior: "auto"});
if (window.scrollX == scrollLeft && window.scrollY == scrollTop) {
return Promise.resolve();
}
return Promise.reject();
}
window.addEventListener('fetchStart', (event) => {
scrollLeft = window.scrollX;
scrollTop = window.scrollY;
});
window.addEventListener('fetchEnd', (event) => {
backoff(5, restoreScroll, 100);
});
})()
`),
vx.VXMessageListener().ListenFunc(fmt.Sprintf(`
function(e){
if (!e.data.split) {
return
}
let arr = e.data.split("_");
if (arr.length != 2) {
console.log(arr);
return
}
%s
}`, action)),
innerPr.Body.(h.HTMLComponent),
).Id("vt-app").Attr(web.InitContextVars, `{presetsRightDrawer: false, presetsDialog: false, dialogPortalName: false}`)
return
}
}