|
|
|
|
|
|
|
|
|
package template |
|
|
|
import ( |
|
"bytes" |
|
"errors" |
|
"html/template" |
|
"path" |
|
"plugin" |
|
"strconv" |
|
"strings" |
|
"sync" |
|
|
|
"github.com/GoAdminGroup/go-admin/context" |
|
c "github.com/GoAdminGroup/go-admin/modules/config" |
|
errors2 "github.com/GoAdminGroup/go-admin/modules/errors" |
|
"github.com/GoAdminGroup/go-admin/modules/language" |
|
"github.com/GoAdminGroup/go-admin/modules/logger" |
|
"github.com/GoAdminGroup/go-admin/modules/menu" |
|
"github.com/GoAdminGroup/go-admin/modules/system" |
|
"github.com/GoAdminGroup/go-admin/modules/utils" |
|
"github.com/GoAdminGroup/go-admin/plugins/admin/models" |
|
"github.com/GoAdminGroup/go-admin/template/login" |
|
"github.com/GoAdminGroup/go-admin/template/types" |
|
"golang.org/x/text/cases" |
|
textLang "golang.org/x/text/language" |
|
) |
|
|
|
|
|
|
|
type Template interface { |
|
Name() string |
|
|
|
|
|
|
|
|
|
Col() types.ColAttribute |
|
Row() types.RowAttribute |
|
|
|
|
|
Form() types.FormAttribute |
|
Table() types.TableAttribute |
|
DataTable() types.DataTableAttribute |
|
|
|
TreeView() types.TreeViewAttribute |
|
Tree() types.TreeAttribute |
|
Tabs() types.TabsAttribute |
|
Alert() types.AlertAttribute |
|
Link() types.LinkAttribute |
|
|
|
Paginator() types.PaginatorAttribute |
|
Popup() types.PopupAttribute |
|
Box() types.BoxAttribute |
|
|
|
Label() types.LabelAttribute |
|
Image() types.ImgAttribute |
|
|
|
Button() types.ButtonAttribute |
|
|
|
|
|
GetTmplList() map[string]string |
|
GetAssetList() []string |
|
GetAssetImportHTML(exceptComponents ...string) template.HTML |
|
GetAsset(string) ([]byte, error) |
|
GetTemplate(bool) (*template.Template, string) |
|
GetVersion() string |
|
GetRequirements() []string |
|
GetHeadHTML() template.HTML |
|
GetFootJS() template.HTML |
|
Get404HTML() template.HTML |
|
Get500HTML() template.HTML |
|
Get403HTML() template.HTML |
|
} |
|
|
|
type PageType uint8 |
|
|
|
const ( |
|
NormalPage PageType = iota |
|
Missing404Page |
|
Error500Page |
|
NoPermission403Page |
|
) |
|
|
|
func GetPageTypeFromPageError(err errors2.PageError) PageType { |
|
if err == nil { |
|
return NormalPage |
|
} else if err == errors2.PageError403 { |
|
return NoPermission403Page |
|
} else if err == errors2.PageError404 { |
|
return Missing404Page |
|
} else { |
|
return Error500Page |
|
} |
|
} |
|
|
|
const ( |
|
CompCol = "col" |
|
CompRow = "row" |
|
CompForm = "form" |
|
CompTable = "table" |
|
CompDataTable = "datatable" |
|
CompTree = "tree" |
|
CompTreeView = "treeview" |
|
CompTabs = "tabs" |
|
CompAlert = "alert" |
|
CompLink = "link" |
|
CompPaginator = "paginator" |
|
CompPopup = "popup" |
|
CompBox = "box" |
|
CompLabel = "label" |
|
CompImage = "image" |
|
CompButton = "button" |
|
) |
|
|
|
func HTML(s string) template.HTML { |
|
return template.HTML(s) |
|
} |
|
|
|
func CSS(s string) template.CSS { |
|
return template.CSS(s) |
|
} |
|
|
|
func JS(s string) template.JS { |
|
return template.JS(s) |
|
} |
|
|
|
|
|
var templateMap = make(map[string]Template) |
|
|
|
|
|
|
|
func Get(ctx *context.Context, theme string) Template { |
|
if ctx != nil { |
|
queryTheme := ctx.Theme() |
|
if queryTheme != "" { |
|
if temp, ok := templateMap[queryTheme]; ok { |
|
return temp |
|
} |
|
} |
|
} |
|
if temp, ok := templateMap[theme]; ok { |
|
return temp |
|
} |
|
panic("wrong theme name") |
|
} |
|
|
|
|
|
|
|
func Default(ctx ...*context.Context) Template { |
|
if len(ctx) > 0 && ctx[0] != nil { |
|
queryTheme := ctx[0].Theme() |
|
if queryTheme != "" { |
|
if temp, ok := templateMap[queryTheme]; ok { |
|
return temp |
|
} |
|
} |
|
} |
|
if temp, ok := templateMap[c.GetTheme()]; ok { |
|
return temp |
|
} |
|
panic("wrong theme name") |
|
} |
|
|
|
var ( |
|
templateMu sync.Mutex |
|
compMu sync.Mutex |
|
) |
|
|
|
|
|
|
|
|
|
func Add(name string, temp Template) { |
|
templateMu.Lock() |
|
defer templateMu.Unlock() |
|
if temp == nil { |
|
panic("template is nil") |
|
} |
|
if _, dup := templateMap[name]; dup { |
|
panic("add template twice " + name) |
|
} |
|
templateMap[name] = temp |
|
} |
|
|
|
|
|
|
|
|
|
func CheckRequirements() (bool, bool) { |
|
if !CheckThemeRequirements() { |
|
return false, true |
|
} |
|
|
|
if !utils.InArray(DefaultThemeNames, Default().Name()) { |
|
return true, true |
|
} |
|
return true, VersionCompare(Default().GetVersion(), system.RequireThemeVersion()[Default().Name()]) |
|
} |
|
|
|
func CheckThemeRequirements() bool { |
|
return VersionCompare(system.Version(), Default().GetRequirements()) |
|
} |
|
|
|
func VersionCompare(toCompare string, versions []string) bool { |
|
for _, v := range versions { |
|
if v == toCompare || utils.CompareVersion(v, toCompare) { |
|
return true |
|
} |
|
} |
|
return false |
|
} |
|
|
|
func GetPageContentFromPageType(ctx *context.Context, title, desc, msg string, pt PageType) (template.HTML, template.HTML, template.HTML) { |
|
if c.GetDebug() { |
|
return template.HTML(title), template.HTML(desc), Default(ctx).Alert().SetTitle(errors2.MsgWithIcon).Warning(msg) |
|
} |
|
|
|
if pt == Missing404Page { |
|
if c.GetCustom404HTML() != template.HTML("") { |
|
return "", "", c.GetCustom404HTML() |
|
} else { |
|
return "", "", Default(ctx).Get404HTML() |
|
} |
|
} else if pt == NoPermission403Page { |
|
if c.GetCustom404HTML() != template.HTML("") { |
|
return "", "", c.GetCustom403HTML() |
|
} else { |
|
return "", "", Default(ctx).Get403HTML() |
|
} |
|
} else { |
|
if c.GetCustom500HTML() != template.HTML("") { |
|
return "", "", c.GetCustom500HTML() |
|
} else { |
|
return "", "", Default(ctx).Get500HTML() |
|
} |
|
} |
|
} |
|
|
|
var DefaultThemeNames = []string{"sword", "adminlte"} |
|
|
|
func Themes() []string { |
|
names := make([]string, len(templateMap)) |
|
i := 0 |
|
for k := range templateMap { |
|
names[i] = k |
|
i++ |
|
} |
|
return names |
|
} |
|
|
|
func AddFromPlugin(name string, mod string) { |
|
|
|
plug, err := plugin.Open(mod) |
|
if err != nil { |
|
logger.Error("AddFromPlugin err", err) |
|
panic(err) |
|
} |
|
|
|
tempPlugin, err := plug.Lookup(cases.Title(textLang.Und).String(name)) |
|
if err != nil { |
|
logger.Error("AddFromPlugin err", err) |
|
panic(err) |
|
} |
|
|
|
var temp Template |
|
temp, ok := tempPlugin.(Template) |
|
if !ok { |
|
logger.Error("AddFromPlugin err: unexpected type from module symbol") |
|
panic(errors.New("AddFromPlugin err: unexpected type from module symbol")) |
|
} |
|
|
|
Add(name, temp) |
|
} |
|
|
|
|
|
type Component interface { |
|
|
|
GetTemplate() (*template.Template, string) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
GetAssetList() []string |
|
|
|
|
|
|
|
|
|
|
|
GetAsset(string) ([]byte, error) |
|
|
|
GetContent() template.HTML |
|
|
|
IsAPage() bool |
|
|
|
GetName() string |
|
|
|
GetJS() template.JS |
|
GetCSS() template.CSS |
|
GetCallbacks() types.Callbacks |
|
} |
|
|
|
var compMap = map[string]Component{ |
|
"login": login.GetLoginComponent(), |
|
} |
|
|
|
|
|
|
|
func GetComp(name string) Component { |
|
if comp, ok := compMap[name]; ok { |
|
return comp |
|
} |
|
panic("wrong component name") |
|
} |
|
|
|
func GetComponentAsset() []string { |
|
assets := make([]string, 0) |
|
for _, comp := range compMap { |
|
assets = append(assets, comp.GetAssetList()...) |
|
} |
|
return assets |
|
} |
|
|
|
func GetComponentAssetWithinPage() []string { |
|
assets := make([]string, 0) |
|
for _, comp := range compMap { |
|
if !comp.IsAPage() { |
|
assets = append(assets, comp.GetAssetList()...) |
|
} |
|
} |
|
return assets |
|
} |
|
|
|
func GetComponentAssetImportHTML(ctx *context.Context) (res template.HTML) { |
|
res = Default(ctx).GetAssetImportHTML(c.GetExcludeThemeComponents()...) |
|
assets := GetComponentAssetWithinPage() |
|
for i := 0; i < len(assets); i++ { |
|
res += getHTMLFromAssetUrl(assets[i]) |
|
} |
|
return |
|
} |
|
|
|
func getHTMLFromAssetUrl(s string) template.HTML { |
|
switch path.Ext(s) { |
|
case ".css": |
|
return template.HTML(`<link rel="stylesheet" href="` + c.GetAssetUrl() + c.Url("/assets"+s) + `">`) |
|
case ".js": |
|
return template.HTML(`<script src="` + c.GetAssetUrl() + c.Url("/assets"+s) + `"></script>`) |
|
default: |
|
return "" |
|
} |
|
} |
|
|
|
func GetAsset(path string) ([]byte, error) { |
|
for _, comp := range compMap { |
|
res, err := comp.GetAsset(path) |
|
if err == nil { |
|
return res, nil |
|
} |
|
} |
|
return nil, errors.New(path + " not found") |
|
} |
|
|
|
|
|
|
|
|
|
func AddComp(comp Component) { |
|
compMu.Lock() |
|
defer compMu.Unlock() |
|
if comp == nil { |
|
panic("component is nil") |
|
} |
|
if _, dup := compMap[comp.GetName()]; dup { |
|
panic("add component twice " + comp.GetName()) |
|
} |
|
compMap[comp.GetName()] = comp |
|
} |
|
|
|
|
|
func AddLoginComp(comp Component) { |
|
compMu.Lock() |
|
defer compMu.Unlock() |
|
compMap["login"] = comp |
|
} |
|
|
|
|
|
|
|
|
|
func SetComp(name string, comp Component) { |
|
compMu.Lock() |
|
defer compMu.Unlock() |
|
if comp == nil { |
|
panic("component is nil") |
|
} |
|
if _, dup := compMap[name]; dup { |
|
compMap[name] = comp |
|
} |
|
} |
|
|
|
type ExecuteParam struct { |
|
User models.UserModel |
|
Tmpl *template.Template |
|
TmplName string |
|
IsPjax bool |
|
Panel types.Panel |
|
Logo template.HTML |
|
Config *c.Config |
|
Menu *menu.Menu |
|
Animation bool |
|
Buttons types.Buttons |
|
NoCompress bool |
|
Iframe bool |
|
} |
|
|
|
func updateNavAndLogoJS(logo template.HTML) template.JS { |
|
if logo == template.HTML("") { |
|
return "" |
|
} |
|
return `$(function () { |
|
$(".logo-lg").html("` + template.JS(logo) + `"); |
|
});` |
|
} |
|
|
|
func updateNavJS(isPjax bool) template.JS { |
|
if !isPjax { |
|
return "" |
|
} |
|
return `$(function () { |
|
let lis = $(".user-menu .dropdown-menu li"); |
|
for (i = 0; i < lis.length - 2; i++) { |
|
$(lis[i]).remove(); |
|
} |
|
$(".user-menu .dropdown-menu").prepend($("#navbar-nav-custom").html()); |
|
});` |
|
} |
|
|
|
type ExecuteOptions struct { |
|
Animation bool |
|
NoCompress bool |
|
HideSideBar bool |
|
HideHeader bool |
|
UpdateMenu bool |
|
NavDropDownButton []*types.NavDropDownItemButton |
|
} |
|
|
|
func GetExecuteOptions(options []ExecuteOptions) ExecuteOptions { |
|
if len(options) == 0 { |
|
return ExecuteOptions{Animation: true} |
|
} |
|
return options[0] |
|
} |
|
|
|
func Execute(ctx *context.Context, param *ExecuteParam) *bytes.Buffer { |
|
|
|
buf := new(bytes.Buffer) |
|
err := param.Tmpl.ExecuteTemplate(buf, param.TmplName, |
|
types.NewPage(ctx, &types.NewPageParam{ |
|
User: param.User, |
|
Menu: param.Menu, |
|
Assets: GetComponentAssetImportHTML(ctx), |
|
Buttons: param.Buttons, |
|
Iframe: param.Iframe, |
|
UpdateMenu: param.IsPjax, |
|
Panel: param.Panel. |
|
GetContent(append([]bool{param.Config.IsProductionEnvironment() && !param.NoCompress}, |
|
param.Animation)...).AddJS(param.Menu.GetUpdateJS(param.IsPjax)). |
|
AddJS(updateNavAndLogoJS(param.Logo)).AddJS(updateNavJS(param.IsPjax)), |
|
TmplHeadHTML: Default(ctx).GetHeadHTML(), |
|
TmplFootJS: Default(ctx).GetFootJS(), |
|
Logo: param.Logo, |
|
})) |
|
if err != nil { |
|
logger.Error("template execute error", err) |
|
} |
|
return buf |
|
} |
|
|
|
func WarningPanel(ctx *context.Context, msg string, pts ...PageType) types.Panel { |
|
pt := Error500Page |
|
if len(pts) > 0 { |
|
pt = pts[0] |
|
} |
|
pageTitle, description, content := GetPageContentFromPageType(ctx, msg, msg, msg, pt) |
|
return types.Panel{ |
|
Content: content, |
|
Description: description, |
|
Title: pageTitle, |
|
} |
|
} |
|
|
|
func WarningPanelWithDescAndTitle(ctx *context.Context, msg, desc, title string, pts ...PageType) types.Panel { |
|
pt := Error500Page |
|
if len(pts) > 0 { |
|
pt = pts[0] |
|
} |
|
pageTitle, description, content := GetPageContentFromPageType(ctx, msg, desc, title, pt) |
|
return types.Panel{ |
|
Content: content, |
|
Description: description, |
|
Title: pageTitle, |
|
} |
|
} |
|
|
|
var DefaultFuncMap = template.FuncMap{ |
|
"lang": language.Get, |
|
"langHtml": language.GetFromHtml, |
|
"link": func(cdnUrl, prefixUrl, assetsUrl string) string { |
|
if cdnUrl == "" { |
|
return prefixUrl + assetsUrl |
|
} |
|
return cdnUrl + assetsUrl |
|
}, |
|
"isLinkUrl": func(s string) bool { |
|
return (len(s) > 7 && s[:7] == "http://") || (len(s) > 8 && s[:8] == "https://") |
|
}, |
|
"render": func(s, old, repl template.HTML) template.HTML { |
|
return template.HTML(strings.ReplaceAll(string(s), string(old), string(repl))) |
|
}, |
|
"renderJS": func(s template.JS, old, repl template.HTML) template.JS { |
|
return template.JS(strings.ReplaceAll(string(s), string(old), string(repl))) |
|
}, |
|
"divide": func(a, b int) int { |
|
return a / b |
|
}, |
|
"renderRowDataHTML": func(id, content template.HTML, value ...map[string]types.InfoItem) template.HTML { |
|
return template.HTML(types.ParseTableDataTmplWithID(id, string(content), value...)) |
|
}, |
|
"renderRowDataJS": func(id template.HTML, content template.JS, value ...map[string]types.InfoItem) template.JS { |
|
return template.JS(types.ParseTableDataTmplWithID(id, string(content), value...)) |
|
}, |
|
"attr": func(s template.HTML) template.HTMLAttr { |
|
return template.HTMLAttr(s) |
|
}, |
|
"js": func(s interface{}) template.JS { |
|
if ss, ok := s.(string); ok { |
|
return template.JS(ss) |
|
} |
|
if ss, ok := s.(template.HTML); ok { |
|
return template.JS(ss) |
|
} |
|
return "" |
|
}, |
|
"changeValue": func(f types.FormField, index int) types.FormField { |
|
if len(f.ValueArr) > 0 { |
|
f.Value = template.HTML(f.ValueArr[index]) |
|
} |
|
if len(f.OptionsArr) > 0 { |
|
f.Options = f.OptionsArr[index] |
|
} |
|
if f.FormType.IsSelect() { |
|
f.FieldClass += "_" + strconv.Itoa(index) |
|
} |
|
return f |
|
}, |
|
} |
|
|
|
type BaseComponent struct { |
|
Name string |
|
HTMLData string |
|
CSS template.CSS |
|
JS template.JS |
|
Callbacks types.Callbacks |
|
} |
|
|
|
func (b *BaseComponent) IsAPage() bool { return false } |
|
func (b *BaseComponent) GetName() string { return b.Name } |
|
func (b *BaseComponent) GetAssetList() []string { return make([]string, 0) } |
|
func (b *BaseComponent) GetAsset(name string) ([]byte, error) { return nil, nil } |
|
func (b *BaseComponent) GetJS() template.JS { return b.JS } |
|
func (b *BaseComponent) GetCSS() template.CSS { return b.CSS } |
|
func (b *BaseComponent) GetCallbacks() types.Callbacks { return b.Callbacks } |
|
func (b *BaseComponent) BindActionTo(ctx *context.Context, action types.Action, id string) { |
|
action.SetBtnId(id) |
|
b.JS += action.Js() |
|
b.HTMLData += string(action.ExtContent(ctx)) |
|
b.Callbacks = append(b.Callbacks, action.GetCallbacks()) |
|
} |
|
func (b *BaseComponent) GetContentWithData(obj interface{}) template.HTML { |
|
buffer := new(bytes.Buffer) |
|
tmpl, defineName := b.GetTemplate() |
|
err := tmpl.ExecuteTemplate(buffer, defineName, obj) |
|
if err != nil { |
|
logger.Error(b.Name+" GetContent error:", err) |
|
} |
|
return template.HTML(buffer.String()) |
|
} |
|
|
|
func (b *BaseComponent) GetTemplate() (*template.Template, string) { |
|
tmpl, err := template.New(b.Name). |
|
Funcs(DefaultFuncMap). |
|
Parse(b.HTMLData) |
|
|
|
if err != nil { |
|
logger.Error(b.Name+" GetTemplate Error: ", err) |
|
} |
|
|
|
return tmpl, b.Name |
|
} |
|
|