github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/common/widget.go (about)

     1  package common
     2  
     3  import (
     4  	"database/sql"
     5  	"encoding/json"
     6  	"strconv"
     7  	"strings"
     8  	"sync/atomic"
     9  
    10  	qgen "github.com/Azareal/Gosora/query_gen"
    11  )
    12  
    13  type WidgetStmts struct {
    14  	//getList *sql.Stmt
    15  	getDockList *sql.Stmt
    16  	delete      *sql.Stmt
    17  	create      *sql.Stmt
    18  	update      *sql.Stmt
    19  
    20  	//qgen.SimpleModel
    21  }
    22  
    23  var widgetStmts WidgetStmts
    24  
    25  func init() {
    26  	DbInits.Add(func(acc *qgen.Accumulator) error {
    27  		w := "widgets"
    28  		widgetStmts = WidgetStmts{
    29  			//getList: acc.Select(w).Columns("wid,position,side,type,active,location,data").Orderby("position ASC").Prepare(),
    30  			getDockList: acc.Select(w).Columns("wid,position,type,active,location,data").Where("side=?").Orderby("position ASC").Prepare(),
    31  			//model: acc.SimpleModel(w,"position,type,active,location,data","wid"),
    32  			delete: acc.Delete(w).Where("wid=?").Prepare(),
    33  			create: acc.Insert(w).Columns("position,side,type,active,location,data").Fields("?,?,?,?,?,?").Prepare(),
    34  			update: acc.Update(w).Set("position=?,side=?,type=?,active=?,location=?,data=?").Where("wid=?").Prepare(),
    35  		}
    36  		return acc.FirstError()
    37  	})
    38  }
    39  
    40  // TODO: Shrink this struct for common uses in the templates? Would that really make things go faster?
    41  type Widget struct {
    42  	ID       int
    43  	Enabled  bool
    44  	Location string // Coming Soon: overview, topics, topic / topic_view, forums, forum, global
    45  	Position int
    46  	RawBody  string
    47  	Body     string
    48  	Side     string
    49  	Type     string
    50  
    51  	Literal      bool
    52  	TickMask     atomic.Value
    53  	InitFunc     func(w *Widget, sched *WidgetScheduler) error
    54  	ShutdownFunc func(w *Widget) error
    55  	BuildFunc    func(w *Widget, hvars interface{}) (string, error)
    56  	TickFunc     func(w *Widget) error
    57  }
    58  
    59  func (w *Widget) Delete() error {
    60  	_, err := widgetStmts.delete.Exec(w.ID)
    61  	if err != nil {
    62  		return err
    63  	}
    64  
    65  	// Reload the dock
    66  	// TODO: Better synchronisation
    67  	Widgets.delete(w.ID)
    68  	widgets, err := getDockWidgets(w.Side)
    69  	if err != nil {
    70  		return err
    71  	}
    72  	setDock(w.Side, widgets)
    73  	return nil
    74  }
    75  
    76  func (w *Widget) Copy() (ow *Widget) {
    77  	ow = &Widget{}
    78  	*ow = *w
    79  	return ow
    80  }
    81  
    82  // TODO: Test this
    83  // TODO: Add support for zone:id. Perhaps, carry a ZoneID property around in *Header? It might allow some weirdness like frontend[5] which matches any zone with an ID of 5 but it would be a tad faster than verifying each zone, although it might be problematic if users end up relying on this behaviour for areas which don't pass IDs to the widgets system but *probably* should
    84  // TODO: Add a selector which also matches topics inside a specific forum?
    85  func (w *Widget) Allowed(zone string, zoneid int) bool {
    86  	for _, loc := range strings.Split(w.Location, "|") {
    87  		if len(loc) == 0 {
    88  			continue
    89  		}
    90  		sloc := strings.Split(":", loc)
    91  		if len(sloc) > 1 {
    92  			iloc, _ := strconv.Atoi(sloc[1])
    93  			if zoneid != 0 && iloc != zoneid {
    94  				continue
    95  			}
    96  		}
    97  		if loc == "global" || loc == zone {
    98  			return true
    99  		} else if loc[0] == '!' {
   100  			loc = loc[1:]
   101  			if loc != "global" && loc != zone {
   102  				return true
   103  			}
   104  		}
   105  	}
   106  	return false
   107  }
   108  
   109  // TODO: Refactor
   110  func (w *Widget) Build(hvars interface{}) (string, error) {
   111  	if w.Literal {
   112  		return w.Body, nil
   113  	}
   114  	if w.BuildFunc != nil {
   115  		return w.BuildFunc(w, hvars)
   116  	}
   117  	header := hvars.(*Header)
   118  	err := header.Theme.RunTmpl(w.Body, hvars, header.Writer)
   119  	return "", err
   120  }
   121  
   122  type WidgetEdit struct {
   123  	*Widget
   124  	Data map[string]string
   125  }
   126  
   127  func (w *WidgetEdit) Create() (int, error) {
   128  	data, err := json.Marshal(w.Data)
   129  	if err != nil {
   130  		return 0, err
   131  	}
   132  	res, err := widgetStmts.create.Exec(w.Position, w.Side, w.Type, w.Enabled, w.Location, data)
   133  	if err != nil {
   134  		return 0, err
   135  	}
   136  
   137  	// Reload the dock
   138  	widgets, err := getDockWidgets(w.Side)
   139  	if err != nil {
   140  		return 0, err
   141  	}
   142  	setDock(w.Side, widgets)
   143  
   144  	wid64, err := res.LastInsertId()
   145  	return int(wid64), err
   146  }
   147  
   148  func (w *WidgetEdit) Commit() error {
   149  	data, err := json.Marshal(w.Data)
   150  	if err != nil {
   151  		return err
   152  	}
   153  	_, err = widgetStmts.update.Exec(w.Position, w.Side, w.Type, w.Enabled, w.Location, data, w.ID)
   154  	if err != nil {
   155  		return err
   156  	}
   157  
   158  	// Reload the dock
   159  	widgets, err := getDockWidgets(w.Side)
   160  	if err != nil {
   161  		return err
   162  	}
   163  	setDock(w.Side, widgets)
   164  	return nil
   165  }