github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/common/widgets.go (about) 1 /* Copyright Azareal 2017 - 2020 */ 2 package common 3 4 import ( 5 "bytes" 6 "encoding/json" 7 "fmt" 8 "html/template" 9 "strings" 10 "sync" 11 "sync/atomic" 12 13 min "github.com/Azareal/Gosora/common/templates" 14 "github.com/Azareal/Gosora/uutils" 15 "github.com/pkg/errors" 16 ) 17 18 // TODO: Clean this file up 19 var Docks WidgetDocks 20 var widgetUpdateMutex sync.RWMutex 21 22 type WidgetDock struct { 23 Items []*Widget 24 Scheduler *WidgetScheduler 25 } 26 27 type WidgetDocks struct { 28 LeftOfNav []*Widget 29 RightOfNav []*Widget 30 LeftSidebar WidgetDock 31 RightSidebar WidgetDock 32 //PanelLeft []Menus 33 Footer WidgetDock 34 } 35 36 type WidgetMenu struct { 37 Name string 38 MenuList []WidgetMenuItem 39 } 40 41 type WidgetMenuItem struct { 42 Text string 43 Location string 44 Compact bool 45 } 46 47 type NameTextPair struct { 48 Name string 49 Text template.HTML 50 } 51 52 func preparseWidget(w *Widget, wdata string) (e error) { 53 prebuildWidget := func(name string, data interface{}) (string, error) { 54 var b bytes.Buffer 55 e := DefaultTemplates.ExecuteTemplate(&b, name+".html", data) 56 content := b.String() 57 if Config.MinifyTemplates { 58 content = min.Minify(content) 59 } 60 return content, e 61 } 62 63 sbytes := []byte(wdata) 64 w.Literal = true 65 // TODO: Split these hard-coded items out of this file and into the files for the individual widget types 66 switch w.Type { 67 case "simple", "about": 68 var tmp NameTextPair 69 e = json.Unmarshal(sbytes, &tmp) 70 if e != nil { 71 return e 72 } 73 w.Body, e = prebuildWidget("widget_"+w.Type, tmp) 74 case "search_and_filter": 75 w.Literal = false 76 w.BuildFunc = widgetSearchAndFilter 77 case "wol": 78 w.Literal = false 79 w.InitFunc = wolInit 80 w.BuildFunc = wolRender 81 w.TickFunc = wolTick 82 case "wol_context": 83 w.Literal = false 84 w.BuildFunc = wolContextRender 85 default: 86 w.Body = wdata 87 } 88 89 // TODO: Test this 90 // TODO: Should we toss this through a proper parser rather than crudely replacing it? 91 rep := func(from, to string) { 92 w.Location = strings.Replace(w.Location, from, to, -1) 93 } 94 rep(" ", "") 95 rep("frontend", "!panel") 96 rep("!!", "") 97 98 // Skip blank zones 99 locs := strings.Split(w.Location, "|") 100 if len(locs) > 0 { 101 w.Location = "" 102 for _, loc := range locs { 103 if loc == "" { 104 continue 105 } 106 w.Location += loc + "|" 107 } 108 w.Location = w.Location[:len(w.Location)-1] 109 } 110 111 return e 112 } 113 114 func GetDockList() []string { 115 return []string{ 116 "leftOfNav", 117 "rightOfNav", 118 "rightSidebar", 119 "footer", 120 } 121 } 122 123 func GetDock(dock string) []*Widget { 124 switch dock { 125 case "leftOfNav": 126 return Docks.LeftOfNav 127 case "rightOfNav": 128 return Docks.RightOfNav 129 case "rightSidebar": 130 return Docks.RightSidebar.Items 131 case "footer": 132 return Docks.Footer.Items 133 } 134 return nil 135 } 136 137 func HasDock(dock string) bool { 138 switch dock { 139 case "leftOfNav", "rightOfNav", "rightSidebar", "footer": 140 return true 141 } 142 return false 143 } 144 145 // TODO: Find a more optimimal way of doing this... 146 func HasWidgets(dock string, h *Header) bool { 147 if !h.Theme.HasDock(dock) { 148 return false 149 } 150 151 // Let themes forcibly override this slot 152 sbody := h.Theme.BuildDock(dock) 153 if sbody != "" { 154 return true 155 } 156 157 var widgets []*Widget 158 switch dock { 159 case "leftOfNav": 160 widgets = Docks.LeftOfNav 161 case "rightOfNav": 162 widgets = Docks.RightOfNav 163 case "rightSidebar": 164 widgets = Docks.RightSidebar.Items 165 case "footer": 166 widgets = Docks.Footer.Items 167 } 168 169 wcount := 0 170 for _, widget := range widgets { 171 if !widget.Enabled { 172 continue 173 } 174 if widget.Allowed(h.Zone, h.ZoneID) { 175 wcount++ 176 } 177 } 178 return wcount > 0 179 } 180 181 func BuildWidget(dock string, h *Header) (sbody string) { 182 if !h.Theme.HasDock(dock) { 183 return "" 184 } 185 // Let themes forcibly override this slot 186 sbody = h.Theme.BuildDock(dock) 187 if sbody != "" { 188 return sbody 189 } 190 191 var widgets []*Widget 192 switch dock { 193 case "leftOfNav": 194 widgets = Docks.LeftOfNav 195 case "rightOfNav": 196 widgets = Docks.RightOfNav 197 case "topMenu": 198 // 1 = id for the default menu 199 mhold, e := Menus.Get(1) 200 if e == nil { 201 e := mhold.Build(h.Writer, h.CurrentUser, h.Path) 202 if e != nil { 203 LogError(e) 204 } 205 } 206 return "" 207 case "rightSidebar": 208 widgets = Docks.RightSidebar.Items 209 case "footer": 210 widgets = Docks.Footer.Items 211 } 212 213 for _, widget := range widgets { 214 if !widget.Enabled { 215 continue 216 } 217 if widget.Allowed(h.Zone, h.ZoneID) { 218 item, e := widget.Build(h) 219 if e != nil { 220 LogError(e) 221 } 222 sbody += item 223 } 224 } 225 return sbody 226 } 227 228 var DockToID = map[string]int{ 229 "leftOfNav": 0, 230 "rightOfNav": 1, 231 "topMenu": 2, 232 "rightSidebar": 3, 233 "footer": 4, 234 } 235 236 func BuildWidget2(dock int, h *Header) (sbody string) { 237 if !h.Theme.HasDockByID(dock) { 238 return "" 239 } 240 // Let themes forcibly override this slot 241 sbody = h.Theme.BuildDockByID(dock) 242 if sbody != "" { 243 return sbody 244 } 245 246 var widgets []*Widget 247 switch dock { 248 case 0: 249 widgets = Docks.LeftOfNav 250 case 1: 251 widgets = Docks.RightOfNav 252 case 2: 253 // 1 = id for the default menu 254 mhold, e := Menus.Get(1) 255 if e == nil { 256 e := mhold.Build(h.Writer, h.CurrentUser, h.Path) 257 if e != nil { 258 LogError(e) 259 } 260 } 261 return "" 262 case 3: 263 widgets = Docks.RightSidebar.Items 264 case 4: 265 widgets = Docks.Footer.Items 266 } 267 268 for _, w := range widgets { 269 if !w.Enabled { 270 continue 271 } 272 if w.Allowed(h.Zone, h.ZoneID) { 273 item, e := w.Build(h) 274 if e != nil { 275 LogError(e) 276 } 277 sbody += item 278 } 279 } 280 return sbody 281 } 282 283 func BuildWidget3(dock int, h *Header) { 284 if !h.Theme.HasDockByID(dock) { 285 return 286 } 287 // Let themes forcibly override this slot 288 if sbody := h.Theme.BuildDockByID(dock); sbody != "" { 289 h.Writer.Write(uutils.StringToBytes(sbody)) 290 return 291 } 292 293 var widgets []*Widget 294 switch dock { 295 case 0: 296 widgets = Docks.LeftOfNav 297 case 1: 298 widgets = Docks.RightOfNav 299 case 2: 300 // 1 = id for the default menu 301 mhold, err := Menus.Get(1) 302 if err == nil { 303 err := mhold.Build(h.Writer, h.CurrentUser, h.Path) 304 if err != nil { 305 LogError(err) 306 } 307 } 308 return 309 case 3: 310 widgets = Docks.RightSidebar.Items 311 case 4: 312 widgets = Docks.Footer.Items 313 } 314 315 for _, w := range widgets { 316 if !w.Enabled { 317 continue 318 } 319 if w.Allowed(h.Zone, h.ZoneID) { 320 item, e := w.Build(h) 321 if e != nil { 322 LogError(e) 323 } 324 if item != "" { 325 h.Writer.Write(uutils.StringToBytes(item)) 326 } 327 } 328 } 329 } 330 331 // TODO: Find a more optimimal way of doing this... 332 func HasWidgets2(dock int, h *Header) bool { 333 if !h.Theme.HasDockByID(dock) { 334 return false 335 } 336 337 // Let themes forcibly override this slot 338 // TODO: Optimise this bit 339 sbody := h.Theme.BuildDockByID(dock) 340 if sbody != "" { 341 return true 342 } 343 344 var widgets []*Widget 345 switch dock { 346 case 0: 347 widgets = Docks.LeftOfNav 348 case 1: 349 widgets = Docks.RightOfNav 350 case 3: 351 widgets = Docks.RightSidebar.Items 352 case 4: 353 widgets = Docks.Footer.Items 354 } 355 356 wcount := 0 357 for _, widget := range widgets { 358 if !widget.Enabled { 359 continue 360 } 361 if widget.Allowed(h.Zone, h.ZoneID) { 362 wcount++ 363 } 364 } 365 return wcount > 0 366 } 367 368 func getDockWidgets(dock string) (widgets []*Widget, e error) { 369 rows, e := widgetStmts.getDockList.Query(dock) 370 if e != nil { 371 return nil, e 372 } 373 defer rows.Close() 374 375 for rows.Next() { 376 w := &Widget{Position: 0, Side: dock} 377 e = rows.Scan(&w.ID, &w.Position, &w.Type, &w.Enabled, &w.Location, &w.RawBody) 378 if e != nil { 379 return nil, e 380 } 381 e = preparseWidget(w, w.RawBody) 382 if e != nil { 383 return nil, e 384 } 385 Widgets.set(w) 386 widgets = append(widgets, w) 387 } 388 return widgets, rows.Err() 389 } 390 391 // TODO: Make a store for this? 392 func InitWidgets() (fi error) { 393 // TODO: Let themes set default values for widget docks, and let them lock in particular places with their stuff, e.g. leftOfNav and rightOfNav 394 f := func(name string) { 395 if fi != nil { 396 return 397 } 398 dock, e := getDockWidgets(name) 399 if e != nil { 400 fi = e 401 return 402 } 403 setDock(name, dock) 404 } 405 406 f("leftOfNav") 407 f("rightOfNav") 408 f("leftSidebar") 409 f("rightSidebar") 410 f("footer") 411 if fi != nil { 412 return fi 413 } 414 415 Tasks.Sec.Add(Docks.LeftSidebar.Scheduler.Tick) 416 Tasks.Sec.Add(Docks.RightSidebar.Scheduler.Tick) 417 Tasks.Sec.Add(Docks.Footer.Scheduler.Tick) 418 419 return nil 420 } 421 422 func releaseWidgets(ws []*Widget) { 423 for _, w := range ws { 424 if w.ShutdownFunc != nil { 425 w.ShutdownFunc(w) 426 } 427 } 428 } 429 430 // TODO: Use atomics 431 func setDock(dock string, widgets []*Widget) { 432 dockHandle := func(dockWidgets []*Widget) { 433 DebugLog(dock, widgets) 434 releaseWidgets(dockWidgets) 435 } 436 dockHandle2 := func(dockWidgets WidgetDock) WidgetDock { 437 dockHandle(dockWidgets.Items) 438 if dockWidgets.Scheduler == nil { 439 dockWidgets.Scheduler = &WidgetScheduler{} 440 } 441 for _, widget := range widgets { 442 if widget.InitFunc != nil { 443 widget.InitFunc(widget, dockWidgets.Scheduler) 444 } 445 } 446 dockWidgets.Scheduler.Store() 447 return WidgetDock{widgets, dockWidgets.Scheduler} 448 } 449 widgetUpdateMutex.Lock() 450 defer widgetUpdateMutex.Unlock() 451 switch dock { 452 case "leftOfNav": 453 dockHandle(Docks.LeftOfNav) 454 Docks.LeftOfNav = widgets 455 case "rightOfNav": 456 dockHandle(Docks.RightOfNav) 457 Docks.RightOfNav = widgets 458 case "leftSidebar": 459 Docks.LeftSidebar = dockHandle2(Docks.LeftSidebar) 460 case "rightSidebar": 461 Docks.RightSidebar = dockHandle2(Docks.RightSidebar) 462 case "footer": 463 Docks.Footer = dockHandle2(Docks.Footer) 464 default: 465 fmt.Printf("bad dock '%s'\n", dock) 466 return 467 } 468 } 469 470 type WidgetScheduler struct { 471 widgets []*Widget 472 store atomic.Value 473 } 474 475 func (s *WidgetScheduler) Add(w *Widget) { 476 s.widgets = append(s.widgets, w) 477 } 478 479 func (s *WidgetScheduler) Store() { 480 s.store.Store(s.widgets) 481 } 482 483 func (s *WidgetScheduler) Tick() error { 484 widgets := s.store.Load().([]*Widget) 485 for _, widget := range widgets { 486 if widget.TickFunc == nil { 487 continue 488 } 489 e := widget.TickFunc(widget) 490 if e != nil { 491 return errors.WithStack(e) 492 } 493 } 494 return nil 495 }