minewebgen - internal/js/utils.go
1 package main
2
3 import (
4 "errors"
5 "image/color"
6 "io"
7 "sort"
8 "strconv"
9 "time"
10
11 "vimagination.zapto.org/byteio"
12 "vimagination.zapto.org/gopherjs/files"
13 "vimagination.zapto.org/gopherjs/mutation"
14 "vimagination.zapto.org/gopherjs/overlay"
15 "vimagination.zapto.org/gopherjs/progress"
16 "vimagination.zapto.org/gopherjs/xdom"
17 "vimagination.zapto.org/gopherjs/xform"
18 "vimagination.zapto.org/gopherjs/xjs"
19 "vimagination.zapto.org/minewebgen/internal/data"
20 "github.com/gopherjs/gopherjs/js"
21 "github.com/gopherjs/websocket"
22 "honnef.co/go/js/dom"
23 )
24
25 func ReadError(r *byteio.StickyLittleEndianReader) error {
26 s := data.ReadString(r)
27 if r.Err != nil {
28 return r.Err
29 }
30 return errors.New(s)
31 }
32
33 func transferFile(typeName, method string, typeID uint8, o *overlay.Overlay) dom.Node {
34 name := xform.InputText("name", "")
35 url := xform.InputRadio("url", "switch", true)
36 upload := xform.InputRadio("upload", "switch", false)
37 fileI := xform.InputUpload("")
38 urlI := xform.InputURL("", "")
39 s := xform.InputSubmit(method)
40
41 name.Required = true
42
43 typeFunc := func(dom.Event) {
44 if url.Checked {
45 urlI.Style().RemoveProperty("display")
46 fileI.Style().SetProperty("display", "none", "")
47 urlI.Required = true
48 fileI.Required = false
49 fileI.SetID("")
50 urlI.SetID("file")
51 } else {
52 fileI.Style().RemoveProperty("display")
53 urlI.Style().SetProperty("display", "none", "")
54 fileI.Required = true
55 urlI.Required = false
56 urlI.SetID("")
57 fileI.SetID("file")
58 }
59 }
60
61 typeFunc(nil)
62
63 url.AddEventListener("change", false, typeFunc)
64 upload.AddEventListener("change", false, typeFunc)
65
66 f := xjs.AppendChildren(xdom.Form(), xjs.AppendChildren(xdom.Fieldset(),
67 xjs.SetInnerText(xdom.Legend(), method+" "+typeName),
68 xform.Label(typeName+" Name", "name"),
69 name,
70 xdom.Br(),
71
72 xform.Label("URL", "url"),
73 url,
74 xdom.Br(),
75
76 xform.Label("Upload", "upload"),
77 upload,
78 xdom.Br(),
79
80 xform.Label("File", "file"),
81
82 fileI,
83 urlI,
84
85 xdom.Br(),
86 s,
87 ))
88
89 s.AddEventListener("click", false, func(e dom.Event) {
90 if name.Value == "" {
91 return
92 }
93 if url.Checked {
94 if urlI.Value == "" {
95 return
96 }
97 } else if len(fileI.Files()) != 1 {
98 return
99
100 }
101 s.Disabled = true
102 name.Disabled = true
103 url.Disabled = true
104 upload.Disabled = true
105 fileI.Disabled = true
106 urlI.Disabled = true
107 e.PreventDefault()
108 go func() {
109 d := xdom.Div()
110 uo := overlay.New(d)
111 uo.OnClose(func() {
112 o.Close()
113 })
114 xjs.Body().AppendChild(uo)
115 status := xdom.Div()
116 d.AppendChild(xjs.SetInnerText(status, "Transferring..."))
117 conn, err := websocket.Dial("ws://" + js.Global.Get("location").Get("host").String() + "/transfer")
118 if err != nil {
119 xjs.SetInnerText(status, err.Error())
120 return
121 }
122 defer conn.Close()
123 w := byteio.StickyLittleEndianWriter{Writer: conn}
124 r := byteio.StickyLittleEndianReader{Reader: conn}
125
126 pb := progress.New(color.RGBA{255, 0, 0, 0}, color.RGBA{0, 0, 255, 0}, 400, 50)
127 d.AppendChild(pb)
128
129 if url.Checked {
130 w.WriteUint8(typeID << 1)
131 data.WriteString(&w, urlI.Value)
132 length := int(r.ReadInt32())
133 total := 0
134 for total < length {
135 switch v := r.ReadUint8(); v {
136 case 1:
137 i := int(r.ReadInt32())
138 total += i
139 pb.Percent(100 * total / length)
140 default:
141 xjs.SetInnerText(status, ReadError(&r).Error())
142 return
143 }
144 }
145 } else {
146 f := files.NewFileReader(files.NewFile(fileI.Files()[0]))
147 l := f.Len()
148 if l == 0 {
149 xjs.SetInnerText(status, "Zero-length file")
150 return
151 }
152 w.WriteUint8(typeID<<1 | 1)
153 w.WriteInt32(int32(l))
154 io.Copy(&w, pb.Reader(f, l))
155 }
156
157 d.RemoveChild(pb)
158 xjs.SetInnerText(status, "Checking File")
159
160 data.WriteString(&w, name.Value)
161
162 var ctx *dom.CanvasRenderingContext2D
163
164 for {
165 switch v := r.ReadUint8(); v {
166 case 0:
167 if r.Err != nil {
168 xjs.SetInnerText(status, r.Err.Error())
169 } else {
170 xjs.SetInnerText(status, ReadError(&r).Error())
171 }
172 return
173 case 1:
174 files := make([]xform.Option, r.ReadInt16())
175 for i := range files {
176 files[i] = xform.Option{
177 Value: strconv.Itoa(i),
178 Label: data.ReadString(&r),
179 }
180 }
181 j := xform.SelectBox("files", files...)
182 sel := xjs.SetInnerText(xdom.Button(), "Select")
183 fo := overlay.New(xjs.AppendChildren(xdom.Div(), xjs.AppendChildren(xdom.Fieldset(),
184 xjs.SetInnerText(xdom.Legend(), "Please select the "+typeName+" file"),
185 xform.Label("File", "files"),
186 j,
187 xdom.Br(),
188 sel,
189 )))
190 c := make(chan int16, 0)
191 done := false
192 fo.OnClose(func() {
193 if !done {
194 done = true
195 c <- -1
196 }
197 })
198 sel.AddEventListener("click", false, func(dom.Event) {
199 if !done {
200 done = true
201 v, err := strconv.Atoi(j.Value)
202 if err != nil {
203 v = -1
204 }
205 c <- int16(v)
206 fo.Close()
207 }
208 })
209 xjs.Body().AppendChild(fo)
210 w.WriteInt16(<-c)
211 close(c)
212 case 2:
213 w := r.ReadInt32()
214 h := r.ReadInt32()
215 canvas := xdom.Canvas()
216 canvas.Width = int(w) * 8
217 canvas.Height = int(h) * 8
218 d.AppendChild(canvas)
219 ctx = canvas.GetContext2d()
220 ctx.Scale(8, 8)
221 case 3:
222 xjs.SetInnerText(status, data.ReadString(&r))
223 case 4:
224 x := r.ReadInt32()
225 y := r.ReadInt32()
226 red := r.ReadUint8()
227 green := r.ReadUint8()
228 blue := r.ReadUint8()
229 alpha := r.ReadUint8()
230 ctx.FillStyle = "rgba(" + strconv.Itoa(int(red)) + ", " + strconv.Itoa(int(green)) + ", " + strconv.Itoa(int(blue)) + ", " + strconv.FormatFloat(float64(alpha)/255, 'f', -1, 32) + ")"
231 ctx.FillRect(int(x), int(y), 1, 1)
232 case 255:
233 uo.Close()
234 return
235 }
236 }
237
238 }()
239 })
240 return f
241 }
242
243 func editProperties(c dom.Element, name string, id int, rpcGet func(int) (map[string]string, error), rpcSet func(id int, properties map[string]string) error) {
244 sp, err := rpcGet(id)
245 if err != nil {
246 c.AppendChild(xjs.SetInnerText(xdom.Div(), "Failed to get properties: "+err.Error()))
247 return
248 }
249 props := make(PropertyList, 0, len(sp))
250 for k, v := range sp {
251 props = append(props, [2]string{k, v})
252 }
253 sort.Sort(props)
254 propE := make([][2]*dom.HTMLSpanElement, len(props))
255 df := xjs.DocumentFragment()
256
257 toggleFunc := func(k, v *dom.HTMLSpanElement, toggle *dom.HTMLInputElement) func(dom.Event) {
258 return func(dom.Event) {
259 if toggle.Value == "-" {
260 k.SetContentEditable("false")
261 v.SetContentEditable("false")
262 k.Style().SetProperty("background-color", "#888", "")
263 v.Style().SetProperty("background-color", "#888", "")
264 toggle.Value = "+"
265 } else {
266 k.SetContentEditable("true")
267 v.SetContentEditable("true")
268 k.Style().RemoveProperty("background-color")
269 v.Style().RemoveProperty("background-color")
270 toggle.Value = "-"
271 }
272 }
273 }
274
275 for i, prop := range props {
276 k := xform.InputSizeable("", prop[0])
277 v := xform.InputSizeable("", prop[1])
278 toggle := xform.InputButton("", "-")
279 toggle.AddEventListener("click", false, toggleFunc(k, v, toggle))
280 propE[i][0] = k
281 propE[i][1] = v
282 xjs.AppendChildren(df,
283 toggle,
284 k,
285 xjs.SetInnerText(xdom.Span(), "="),
286 v,
287 xdom.Br(),
288 )
289 }
290
291 add := xform.InputButton("", "Add")
292 submit := xform.InputButton("", "Save")
293 fs := xjs.AppendChildren(xdom.Fieldset(),
294 xjs.SetInnerText(xdom.Legend(), name+" Properties"),
295 df,
296 add,
297 submit,
298 )
299
300 add.AddEventListener("click", false, func(dom.Event) {
301 k := xform.InputSizeable("", "")
302 v := xform.InputSizeable("", "")
303 toggle := xform.InputButton("", "-")
304 toggle.AddEventListener("click", false, toggleFunc(k, v, toggle))
305 propE = append(propE, [2]*dom.HTMLSpanElement{k, v})
306 fs.InsertBefore(toggle, add)
307 fs.InsertBefore(k, add)
308 fs.InsertBefore(xjs.SetInnerText(xdom.Span(), "="), add)
309 fs.InsertBefore(v, add)
310 fs.InsertBefore(xdom.Br(), add)
311 })
312
313 submit.AddEventListener("click", false, func(dom.Event) {
314 submit.Disabled = true
315 props := make(map[string]string, len(propE))
316 for _, spans := range propE {
317 if spans[0].IsContentEditable() {
318 props[spans[0].TextContent()] = spans[1].TextContent()
319 }
320 }
321 go func() {
322 err := rpcSet(id, props)
323 if err != nil {
324 xjs.Alert("Error setting "+name+" properties: %s", err)
325 return
326 }
327 span := xdom.Span()
328 span.Style().Set("color", "#f00")
329 fs.AppendChild(xjs.SetInnerText(span, "Saved!"))
330 time.Sleep(5 * time.Second)
331 fs.RemoveChild(span)
332 submit.Disabled = false
333 }()
334 })
335
336 xjs.AppendChildren(c, xjs.AppendChildren(xdom.Form(), fs))
337 }
338
339 func misc(mType string, id int, o *overlay.Overlay, deleteFunc func(int) error) func(dom.Element) {
340 return func(c dom.Element) {
341 download := xdom.A()
342 download.Href = "http://" + js.Global.Get("location").Get("host").String() + "/download/" + mType + "/" + strconv.Itoa(id) + ".zip"
343 download.Target = "_blank"
344 del := xdom.Button()
345 del.AddEventListener("click", false, func(dom.Event) {
346 del.Disabled = true
347 if dom.GetWindow().Confirm("Are you sure?") {
348 go func() {
349 err := deleteFunc(id)
350 if err != nil {
351 del.Disabled = false
352 xjs.Alert("Error while deleting %s: %s", mType, err)
353 } else {
354 o.Close()
355 }
356 }()
357 }
358 })
359 xjs.AppendChildren(c,
360 xjs.AppendChildren(xdom.Fieldset(),
361 xjs.SetInnerText(xdom.Legend(), "Download"),
362 xjs.SetInnerText(xdom.Div(), "Click the following link to download the "+mType+" as a zip file."),
363 xjs.SetInnerText(download, download.Href),
364 ),
365 xjs.AppendChildren(xdom.Fieldset(),
366 xjs.SetInnerText(xdom.Legend(), "Delete"),
367 xjs.SetInnerText(xdom.Div(), "The following button will permanently delete the "+mType+" (this cannot be undone)."),
368 xjs.SetInnerText(del, "Delete "+mType),
369 ),
370 )
371 }
372 }
373
374 func registerUpdateStopper(c dom.Element, updateStop chan struct{}) {
375 if c.ParentNode() != nil {
376 mutation.New(func(rs []*mutation.Record, o *mutation.Observer) {
377 if len(rs) > 0 {
378 for _, r := range rs {
379 for _, n := range r.RemovedNodes() {
380 if c.IsEqualNode(n) {
381 o.Disconnect()
382 close(updateStop)
383 return
384 }
385 }
386 }
387 }
388 }).Observe(c.ParentNode(), mutation.ObserverInit{ChildList: true})
389 }
390 }
391
392 func updateSleep(forceUpdate, updateStop <-chan struct{}) bool {
393 t := time.NewTicker(time.Second * 30)
394 defer t.Stop()
395 select {
396 case <-updateStop:
397 return false
398 case <-forceUpdate:
399 case <-t.C:
400 }
401 return true
402 }