/* Copyright Azareal 2017 - 2020 */ package common import ( "bytes" "encoding/json" "fmt" "html/template" "strings" "sync" "sync/atomic" min "github.com/Azareal/Gosora/common/templates" "github.com/pkg/errors" ) // TODO: Clean this file up var Docks WidgetDocks var widgetUpdateMutex sync.RWMutex type WidgetDock struct { Items []*Widget Scheduler *WidgetScheduler } type WidgetDocks struct { LeftOfNav []*Widget RightOfNav []*Widget LeftSidebar WidgetDock RightSidebar WidgetDock //PanelLeft []Menus Footer WidgetDock } type WidgetMenu struct { Name string MenuList []WidgetMenuItem } type WidgetMenuItem struct { Text string Location string Compact bool } type NameTextPair struct { Name string Text template.HTML } func preparseWidget(w *Widget, wdata string) (err error) { prebuildWidget := func(name string, data interface{}) (string, error) { var b bytes.Buffer err := DefaultTemplates.ExecuteTemplate(&b, name+".html", data) content := string(b.Bytes()) if Config.MinifyTemplates { content = min.Minify(content) } return content, err } sbytes := []byte(wdata) w.Literal = true // TODO: Split these hard-coded items out of this file and into the files for the individual widget types switch w.Type { case "simple", "about": var tmp NameTextPair err = json.Unmarshal(sbytes, &tmp) if err != nil { return err } w.Body, err = prebuildWidget("widget_"+w.Type, tmp) case "search_and_filter": w.Literal = false w.BuildFunc = widgetSearchAndFilter case "wol": w.Literal = false w.InitFunc = wolInit w.BuildFunc = wolRender w.TickFunc = wolTick case "wol_context": w.Literal = false w.BuildFunc = wolContextRender default: w.Body = wdata } // TODO: Test this // TODO: Should we toss this through a proper parser rather than crudely replacing it? w.Location = strings.Replace(w.Location, " ", "", -1) w.Location = strings.Replace(w.Location, "frontend", "!panel", -1) w.Location = strings.Replace(w.Location, "!!", "", -1) // Skip blank zones locs := strings.Split(w.Location, "|") if len(locs) > 0 { w.Location = "" for _, loc := range locs { if loc == "" { continue } w.Location += loc + "|" } w.Location = w.Location[:len(w.Location)-1] } return err } func GetDockList() []string { return []string{ "leftOfNav", "rightOfNav", "rightSidebar", "footer", } } func GetDock(dock string) []*Widget { switch dock { case "leftOfNav": return Docks.LeftOfNav case "rightOfNav": return Docks.RightOfNav case "rightSidebar": return Docks.RightSidebar.Items case "footer": return Docks.Footer.Items } return nil } func HasDock(dock string) bool { switch dock { case "leftOfNav", "rightOfNav", "rightSidebar", "footer": return true } return false } // TODO: Find a more optimimal way of doing this... func HasWidgets(dock string, h *Header) bool { if !h.Theme.HasDock(dock) { return false } // Let themes forcibly override this slot sbody := h.Theme.BuildDock(dock) if sbody != "" { return true } var widgets []*Widget switch dock { case "leftOfNav": widgets = Docks.LeftOfNav case "rightOfNav": widgets = Docks.RightOfNav case "rightSidebar": widgets = Docks.RightSidebar.Items case "footer": widgets = Docks.Footer.Items } wcount := 0 for _, widget := range widgets { if !widget.Enabled { continue } if widget.Allowed(h.Zone, h.ZoneID) { wcount++ } } return wcount > 0 } func BuildWidget(dock string, h *Header) (sbody string) { if !h.Theme.HasDock(dock) { return "" } // Let themes forcibly override this slot sbody = h.Theme.BuildDock(dock) if sbody != "" { return sbody } var widgets []*Widget switch dock { case "leftOfNav": widgets = Docks.LeftOfNav case "rightOfNav": widgets = Docks.RightOfNav case "topMenu": // 1 = id for the default menu mhold, err := Menus.Get(1) if err == nil { err := mhold.Build(h.Writer, h.CurrentUser, h.Path) if err != nil { LogError(err) } } return "" case "rightSidebar": widgets = Docks.RightSidebar.Items case "footer": widgets = Docks.Footer.Items } for _, widget := range widgets { if !widget.Enabled { continue } if widget.Allowed(h.Zone, h.ZoneID) { item, err := widget.Build(h) if err != nil { LogError(err) } sbody += item } } return sbody } var DockToID = map[string]int{ "leftOfNav": 0, "rightOfNav": 1, "topMenu": 2, "rightSidebar": 3, "footer": 4, } func BuildWidget2(dock int, h *Header) (sbody string) { if !h.Theme.HasDockByID(dock) { return "" } // Let themes forcibly override this slot sbody = h.Theme.BuildDockByID(dock) if sbody != "" { return sbody } var widgets []*Widget switch dock { case 0: widgets = Docks.LeftOfNav case 1: widgets = Docks.RightOfNav case 2: // 1 = id for the default menu mhold, err := Menus.Get(1) if err == nil { err := mhold.Build(h.Writer, h.CurrentUser, h.Path) if err != nil { LogError(err) } } return "" case 3: widgets = Docks.RightSidebar.Items case 4: widgets = Docks.Footer.Items } for _, widget := range widgets { if !widget.Enabled { continue } if widget.Allowed(h.Zone, h.ZoneID) { item, err := widget.Build(h) if err != nil { LogError(err) } sbody += item } } return sbody } func getDockWidgets(dock string) (widgets []*Widget, err error) { rows, err := widgetStmts.getDockList.Query(dock) if err != nil { return nil, err } defer rows.Close() for rows.Next() { w := &Widget{Position: 0, Side: dock} err = rows.Scan(&w.ID, &w.Position, &w.Type, &w.Enabled, &w.Location, &w.RawBody) if err != nil { return nil, err } err = preparseWidget(w, w.RawBody) if err != nil { return nil, err } Widgets.set(w) widgets = append(widgets, w) } return widgets, rows.Err() } // TODO: Make a store for this? func InitWidgets() (fi error) { // TODO: Let themes set default values for widget docks, and let them lock in particular places with their stuff, e.g. leftOfNav and rightOfNav f := func(name string) { if fi != nil { return } dock, err := getDockWidgets(name) if err != nil { fi = err return } setDock(name, dock) } f("leftOfNav") f("rightOfNav") f("leftSidebar") f("rightSidebar") f("footer") if fi != nil { return fi } AddScheduledSecondTask(Docks.LeftSidebar.Scheduler.Tick) AddScheduledSecondTask(Docks.RightSidebar.Scheduler.Tick) AddScheduledSecondTask(Docks.Footer.Scheduler.Tick) return nil } func releaseWidgets(ws []*Widget) { for _, w := range ws { if w.ShutdownFunc != nil { w.ShutdownFunc(w) } } } // TODO: Use atomics func setDock(dock string, widgets []*Widget) { dockHandle := func(dockWidgets []*Widget) { DebugLog(dock, widgets) releaseWidgets(dockWidgets) } dockHandle2 := func(dockWidgets WidgetDock) WidgetDock { dockHandle(dockWidgets.Items) if dockWidgets.Scheduler == nil { dockWidgets.Scheduler = &WidgetScheduler{} } for _, widget := range widgets { if widget.InitFunc != nil { widget.InitFunc(widget, dockWidgets.Scheduler) } } dockWidgets.Scheduler.Store() return WidgetDock{widgets, dockWidgets.Scheduler} } widgetUpdateMutex.Lock() defer widgetUpdateMutex.Unlock() switch dock { case "leftOfNav": dockHandle(Docks.LeftOfNav) Docks.LeftOfNav = widgets case "rightOfNav": dockHandle(Docks.RightOfNav) Docks.RightOfNav = widgets case "leftSidebar": Docks.LeftSidebar = dockHandle2(Docks.LeftSidebar) case "rightSidebar": Docks.RightSidebar = dockHandle2(Docks.RightSidebar) case "footer": Docks.Footer = dockHandle2(Docks.Footer) default: fmt.Printf("bad dock '%s'\n", dock) return } } type WidgetScheduler struct { widgets []*Widget store atomic.Value } func (s *WidgetScheduler) Add(w *Widget) { s.widgets = append(s.widgets, w) } func (s *WidgetScheduler) Store() { s.store.Store(s.widgets) } func (s *WidgetScheduler) Tick() error { widgets := s.store.Load().([]*Widget) for _, widget := range widgets { if widget.TickFunc == nil { continue } err := widget.TickFunc(widget) if err != nil { return errors.WithStack(err) } } return nil }