1// ===== checkout_wasm.go =====
   2package main
   3
   4import (
   5	"encoding/json"
   6	"fmt"
   7	"log"
   8	"strconv"
   9	"strings"
  10	"syscall/js"
  11)
  12
  13// set client pk on compile
  14var stripePK string
  15
  16type item struct {
  17	ID     string `json:"id"`
  18	Amount int    `json:"amount"`
  19	Qty    int    `json:"quantity"`
  20}
  21
  22var (
  23	wasmName   string
  24	doc        = js.Global().Get("document")
  25	body       = doc.Call("querySelector", "body")
  26	bodystring = body.Get("innerHTML").String()
  27	cart       []item
  28)
  29
  30func main() {
  31	ready := make(chan struct{})
  32
  33	document := js.Global().Get("document")
  34	readyState := document.Get("readyState").String()
  35	if readyState == "interactive" || readyState == "complete" {
  36		log.Println(wasmName+":", "WASM: DOM already fully loaded")
  37		close(ready)
  38	} else {
  39		cb := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
  40			log.Println(wasmName+":", "WASM: DOM fully loaded and parsed")
  41			close(ready)
  42			return nil
  43		})
  44		defer cb.Release()
  45
  46		document.Call("addEventListener", "DOMContentLoaded", cb)
  47		log.Println(wasmName+":", "WASM: waiting for DOM to load")
  48	}
  49
  50	<-ready
  51
  52	c := make(chan struct{}, 0)
  53	if stripePK == "" {
  54		log.Fatal("Stripe PK not found!")
  55	}
  56	window := js.Global().Get("window")
  57	location := window.Get("location")
  58	pathname := location.Get("pathname").String()
  59
  60	switch pathname {
  61	case "/complete":
  62		completeLogic()
  63	default:
  64		defaultLogic()
  65	}
  66	<-c
  67}
  68
  69func defaultLogic() {
  70	js.Global().Set("addToCart", js.FuncOf(addUnToCart))
  71	js.Global().Set("clearStorage", js.FuncOf(clearAll))
  72	js.Global().Set("emptyCart", js.FuncOf(emptyCart))
  73	js.Global().Set("updateItemQuantity", js.FuncOf(updateItemQuantity))
  74	js.Global().Set("removeFromCart", js.FuncOf(removeFromCart))
  75	js.Global().Set("addShippingInfo", js.FuncOf(addShippingInfo))
  76	js.Global().Set("goToCheckout", js.FuncOf(goToCheckout))
  77	js.Global().Set("cancelCheckout", js.FuncOf(cancelCheckout))
  78	js.Global().Set("callUpdateCartDisplay", js.FuncOf(updateCartDisplayWrapper))
  79
  80	loadCart()
  81	updateCartDisplay()
  82}
  83
  84func updateCartDisplayWrapper(this js.Value, args []js.Value) interface{} {
  85	updateCartDisplay()
  86	return nil
  87}
  88
  89func saveCart() {
  90	cartJSON, err := json.Marshal(cart)
  91	if err != nil {
  92		log.Println(wasmName+":", "Error saving cart:", err)
  93		return
  94	}
  95	js.Global().Get("localStorage").Call("setItem", "cartItems", string(cartJSON))
  96	updateCartDisplay()
  97}
  98
  99func addToCart(this js.Value, args []js.Value) any {
 100	if len(args) < 2 {
 101		return "Error: Missing arguments"
 102	}
 103	var cartItem item
 104	index := -1
 105	id := args[0].String()
 106	qty := args[2].Int()
 107	if qty == 0 {
 108		qty = 1
 109	}
 110	amount := int(args[1].Float()) * qty
 111	for i, _ := range cart {
 112		if strings.Split(cart[i].ID, "|")[0] == strings.Split(id, "|")[0] {
 113			index = i
 114		}
 115	}
 116	if index > -1 {
 117		// update shipping
 118		if strings.Split(cart[index].ID, "|")[0] == "shipping-to" {
 119			cart[index].ID = id
 120			cart[index].Qty = 1
 121			cart[index].Amount = amount
 122		} else {
 123			cart[index].Qty = cart[index].Qty + qty
 124			cart[index].Amount = cart[index].Amount + amount
 125		}
 126	} else {
 127		cartItem = item{
 128			ID:     id,
 129			Amount: amount,
 130			Qty:    qty,
 131		}
 132		cart = append(cart, cartItem)
 133	}
 134	saveCart()
 135	return nil
 136}
 137
 138func addUnToCart(this js.Value, args []js.Value) interface{} {
 139	if len(args) < 2 {
 140		return "Error: Missing arguments"
 141	}
 142	id := args[0].String()
 143	price := args[1].Float()
 144	quantityInput := doc.Call("getElementById", fmt.Sprintf("qty-%s", id))
 145	if !quantityInput.Truthy() {
 146		log.Println(wasmName+":", "Error: Quantity input not found for item", id)
 147		return nil
 148	}
 149	quantity, err := strconv.Atoi(quantityInput.Get("value").String())
 150	if err != nil || quantity < 1 {
 151		quantity = 1
 152	}
 153
 154	addToCart(js.Value{}, []js.Value{
 155		js.ValueOf(id),
 156		js.ValueOf(int(price * 100)),
 157		js.ValueOf(quantity),
 158	})
 159	return nil
 160}
 161
 162func removeFromCart(this js.Value, inputs []js.Value) interface{} {
 163	id := inputs[0].String()
 164	newCart := []item{}
 165	for _, m := range cart {
 166		if m.ID != id {
 167			newCart = append(newCart, m)
 168		}
 169	}
 170	cart = newCart
 171	saveCart()
 172	return nil
 173}
 174
 175func loadCart() {
 176	storedCart := js.Global().Get("localStorage").Call("getItem", "cartItems")
 177	if !storedCart.IsUndefined() && !storedCart.IsNull() {
 178		err := json.Unmarshal([]byte(storedCart.String()), &cart)
 179		if err != nil {
 180			log.Println(`can't unmarshal cart from local storage`)
 181			cart = []item{}
 182		}
 183	}
 184}
 185
 186func emptyCart(this js.Value, inputs []js.Value) interface{} {
 187	js.Global().Get("localStorage").Call("removeItem", "cartItems")
 188	cart = []item{}
 189	updateCartDisplay()
 190	return nil
 191}
 192
 193func clearAll(this js.Value, inputs []js.Value) interface{} {
 194	js.Global().Get("localStorage").Call("clear")
 195	cart = []item{}
 196	updateCartDisplay()
 197	return nil
 198}
 199
 200func updateCartDisplay() {
 201	cartContainer := doc.Call("getElementById", "cart-items")
 202	totalPriceElement := doc.Call("getElementById", "total-price")
 203	table := cartContainer.Call("querySelector", "table")
 204	if table.IsNull() {
 205		table = doc.Call("createElement", "table")
 206		thead := doc.Call("createElement", "thead")
 207		thead.Set("innerHTML", `<tr><th>Item</th><th>Price</th><th>Quantity</th><th>Actions</th></tr>`)
 208		table.Call("appendChild", thead)
 209		tbody := doc.Call("createElement", "tbody")
 210		tbody.Set("id", "cart-tbody")
 211		table.Call("appendChild", tbody)
 212		cartContainer.Call("appendChild", table)
 213	}
 214	tbody := doc.Call("getElementById", "cart-tbody")
 215	tbody.Set("innerHTML", "")
 216
 217	total := 0
 218	hasShipping := false
 219	for _, m := range cart {
 220		total += m.Amount
 221		row := doc.Call("createElement", "tr")
 222
 223		row.Set("innerHTML", fmt.Sprintf(`<td>%s</td><td>$%.2f</td><td>%s</td><td><button onclick='removeFromCart("%s")'>Remove</button></td>`,
 224			func() string {
 225				parts := strings.Split(m.ID, "|")
 226				if len(parts) < 8 {
 227					return m.ID
 228				}
 229				hasShipping = true
 230				return fmt.Sprintf("%s:<br>%s<br>%s<br>%s, %s %s<br>%s<br>%s", parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], parts[6], parts[7])
 231			}(),
 232			float64(m.Amount)/100,
 233			func() string {
 234				if len(strings.Split(m.ID, "|")) == 8 {
 235					return ""
 236				}
 237				return fmt.Sprintf(`<input type='number' value='%d' min='1' onchange='updateItemQuantity("%s", this.value)'>`, m.Qty, m.ID)
 238			}(),
 239			m.ID,
 240		))
 241		tbody.Call("appendChild", row)
 242	}
 243	totalPriceElement.Set("textContent", fmt.Sprintf("Total: $%.2f", float64(total)/100))
 244
 245	checkoutbutton := doc.Call("getElementById", "checkout-button")
 246	if !checkoutbutton.Truthy() {
 247		return
 248	}
 249
 250	if len(cart) > 1 && hasShipping {
 251		checkoutbutton.Call("removeAttribute", "disabled")
 252	} else {
 253		checkoutbutton.Call("setAttribute", "disabled", "true")
 254	}
 255}
 256
 257func updateItemQuantity(this js.Value, args []js.Value) interface{} {
 258	id := args[0].String()
 259	qty, err := strconv.Atoi(args[1].String())
 260	if err != nil {
 261		log.Println(err)
 262	}
 263	for i, _ := range cart {
 264		if cart[i].ID == id {
 265			unitPrice := cart[i].Amount / cart[i].Qty
 266			cart[i].Qty = qty
 267			cart[i].Amount = unitPrice * qty
 268			break
 269		}
 270	}
 271	saveCart()
 272	return nil
 273}
 274
 275func addShippingInfo(this js.Value, args []js.Value) interface{} {
 276	event := args[0]
 277	form := args[1]
 278	event.Call("preventDefault")
 279	getFormValue := func(name string) string {
 280		return form.Call("querySelector", fmt.Sprintf("[name='%s']", name)).Get("value").String()
 281	}
 282	shippingInfo := fmt.Sprintf("shipping-to|%s|%s|%s|%s|%s|%s|%s",
 283		getFormValue("shipping-name"),
 284		getFormValue("shipping-address"),
 285		getFormValue("shipping-city"),
 286		getFormValue("shipping-state"),
 287		getFormValue("shipping-zip"),
 288		getFormValue("shipping-country"),
 289		getFormValue("shipping-phone"),
 290	)
 291	priceStr := getFormValue("shipping-price")
 292	price, err := strconv.ParseFloat(priceStr, 64)
 293	if err != nil {
 294		log.Println(wasmName+":", "Error: Failed to parse shipping price")
 295		price = 0.0
 296	}
 297
 298	addToCart(js.Value{}, []js.Value{
 299		js.ValueOf(shippingInfo),
 300		js.ValueOf(int(price * 100)),
 301		js.ValueOf(1),
 302	})
 303	return false
 304}
 305
 306var (
 307	elements       js.Value
 308	stripeValue    js.Value
 309	stripe         js.Value
 310	checkoutButton = doc.Call("getElementById", "checkout-button")
 311	checkoutDiv    = doc.Call("getElementById", "checkout-container")
 312	checkoutStripe = doc.Call("getElementById", "stripecheckout")
 313)
 314
 315func goToCheckout(this js.Value, args []js.Value) any {
 316	if stripeValue.IsUndefined() {
 317		log.Println(`js.Global().Get("Stripe")`)
 318		stripeValue = js.Global().Get("Stripe")
 319		if stripeValue.IsUndefined() {
 320			log.Println(`Stripe is undefined, attempting to load Stripe.js`)
 321
 322			doc := js.Global().Get("document")
 323			head := doc.Call("querySelector", "head")
 324			script := doc.Call("createElement", "script")
 325			script.Set("src", "https://js.stripe.com/v3/")
 326			script.Set("defer", true)
 327
 328			done := make(chan bool)
 329			script.Call("addEventListener", "load", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
 330				log.Println(wasmName+":", "Stripe.js script has been loaded")
 331				done <- true
 332				return nil
 333			}))
 334
 335			head.Call("appendChild", script)
 336
 337			<-done
 338
 339			stripeValue = js.Global().Get("Stripe")
 340			if stripeValue.IsUndefined() {
 341				log.Println(wasmName+":", "Failed to load Stripe.js")
 342				return nil
 343			}
 344		}
 345	}
 346
 347	log.Println(wasmName+":", "Stripe.js loaded successfully")
 348
 349	if stripe.IsUndefined() {
 350		log.Println(wasmName+":", "Invoking Stripe")
 351		stripe = stripeValue.Invoke(stripePK)
 352		if stripe.IsUndefined() {
 353			log.Println(wasmName+":", "Failed to invoke Stripe")
 354			return nil
 355		}
 356	}
 357
 358	log.Println(wasmName+":", "Stripe initialized")
 359	checkoutStripe = doc.Call("getElementById", "stripecheckout")
 360	if checkoutStripe.IsUndefined() {
 361		log.Println(wasmName+":", "element with ID stripecheckout not found")
 362	}
 363
 364	checkoutStripe.Call("showModal")
 365	log.Println(wasmName+":", "initializePayment()")
 366	initializePayment()
 367
 368	return nil
 369}
 370
 371func cancelCheckout(this js.Value, args []js.Value) any {
 372	log.Println(wasmName+":", "Cancelling checkout ; closing dialog")
 373	checkoutStripe.Call("close")
 374	updateCartDisplay()
 375	return nil
 376}
 377
 378func initializePayment() {
 379	type cItem struct {
 380		ID     string `json:"id"`
 381		Amount int    `json:"amount"`
 382	}
 383	type checkout struct {
 384		Items []cItem `json:"items"`
 385	}
 386	payload := checkout{
 387		Items: func() []cItem {
 388			var items []cItem
 389			for _, it := range cart {
 390				items = append(items, cItem{ID: it.ID + " X " + strconv.Itoa(it.Qty), Amount: it.Amount})
 391			}
 392			return items
 393		}(),
 394	}
 395	payloadJSON, err := json.Marshal(payload)
 396	if err != nil {
 397		log.Println(wasmName+":", "Error marshaling JSON:", err)
 398		return
 399	}
 400	fetchInit := map[string]interface{}{
 401		"method": "POST",
 402		"headers": map[string]interface{}{
 403			"Content-Type": "application/json",
 404		},
 405		"body": string(payloadJSON),
 406	}
 407
 408	log.Println(wasmName+":", "fetch  /create-payment-intent")
 409	js.Global().Call("fetch", "/create-payment-intent", js.ValueOf(fetchInit)).
 410		Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
 411			response := args[0]
 412			log.Println(wasmName+":", "got response from fetch /create-payment-intent")
 413			if !response.Get("ok").Bool() {
 414				log.Println(wasmName+":", "Fetch request failed with status:", response.Get("status").Int())
 415				showMessage("Failed to create payment intent: " + response.Get("status").String())
 416				return nil
 417			}
 418			response.Call("json").Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
 419				clientSecret := args[0].Get("clientSecret").String()
 420				log.Println(wasmName+":", "Client secret received:", clientSecret)
 421				setupStripeElements(clientSecret)
 422				return nil
 423			})).Call("catch", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
 424				log.Println(wasmName+":", "Error parsing JSON response:", args[0])
 425				showMessage("Failed to parse payment intent response.")
 426				return nil
 427			}))
 428			return nil
 429		})).
 430		Call("catch", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
 431			log.Println(wasmName+":", "Error in fetch request:", args[0])
 432			showMessage("Failed to communicate with the server.")
 433			return nil
 434		}))
 435}
 436
 437func setupStripeElements(clientSecret string) {
 438	elements = stripe.Call("elements", map[string]interface{}{
 439		"clientSecret": clientSecret,
 440	})
 441	if elements.IsUndefined() {
 442		log.Println(wasmName+":", "Failed to initialize Stripe Elements")
 443		showMessage("Failed to initialize payment elements.")
 444		return
 445	}
 446	paymentElement := elements.Call("create", "payment", map[string]interface{}{
 447		"layout": "tabs",
 448	})
 449	if paymentElement.IsUndefined() {
 450		log.Println(wasmName+":", "Failed to create payment element")
 451		showMessage("Failed to create payment element.")
 452		return
 453	}
 454	paymentElement.Call("mount", "#payment-element")
 455	submitButton := doc.Call("getElementById", "submit")
 456	submitButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
 457		args[0].Call("preventDefault")
 458		showSpinner(true)
 459		confirmPayment(clientSecret)
 460		return nil
 461	}))
 462}
 463
 464func confirmPayment(clientSecret string) {
 465
 466	windowLocation := js.Global().Get("window").Get("location")
 467	protocol := windowLocation.Get("protocol").String()
 468	hostname := windowLocation.Get("hostname").String()
 469	port := windowLocation.Get("port").String()
 470
 471	baseURL := protocol + "//" + hostname
 472	if port != "" {
 473		baseURL += ":" + port
 474	}
 475	//	path := windowLocation.Get("pathname").String()
 476	//    baseURL += strings.Split(path, "?")[0]
 477	//    log.Println(wasmName+":","return url ", baseURL)
 478
 479	returnURL := baseURL + "/complete"
 480	returnURL += "?payment_intent=" + clientSecret // + "#complete"
 481	log.Println(wasmName+":", "Return URL for payment:", returnURL)
 482
 483	stripe.Call("confirmPayment", map[string]interface{}{
 484		"elements": elements,
 485		"confirmParams": map[string]interface{}{
 486			"return_url": returnURL,
 487		},
 488	}).Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
 489		result := args[0]
 490		if result.Get("error").IsUndefined() {
 491			log.Println(wasmName+":", "Payment successful:", result)
 492			showMessage("Payment successful! Thank you for your order.")
 493		} else {
 494			log.Println(wasmName+":", "Payment error:", result.Get("error").Get("message").String())
 495			showMessage("Payment failed: " + result.Get("error").Get("message").String())
 496		}
 497
 498		showSpinner(false)
 499		return nil
 500	}))
 501}
 502
 503func showMessage(message string) {
 504	messageElement := doc.Call("getElementById", "payment-message")
 505	messageElement.Set("innerText", message)
 506	messageElement.Set("className", "")
 507}
 508
 509func showSpinner(isLoading bool) {
 510	spinner := doc.Call("getElementById", "spinner")
 511	buttonText := doc.Call("getElementById", "button-text")
 512
 513	if isLoading {
 514		spinner.Set("className", "")
 515		buttonText.Set("className", "hidden")
 516	} else {
 517		spinner.Set("className", "hidden")
 518		buttonText.Set("className", "")
 519	}
 520}
 521
 522// /complete
 523
 524func completeLogic() {
 525	initializeStripe()
 526}
 527
 528func initializeStripe() {
 529	if stripeValue.IsUndefined() {
 530		log.Println(`js.Global().Get("Stripe")`)
 531		stripeValue = js.Global().Get("Stripe")
 532		if stripeValue.IsUndefined() {
 533			log.Println(`Stripe is undefined, attempting to load Stripe.js`)
 534
 535			doc := js.Global().Get("document")
 536			head := doc.Call("querySelector", "head")
 537			script := doc.Call("createElement", "script")
 538			script.Set("src", "https://js.stripe.com/v3/")
 539			script.Set("defer", true)
 540
 541			done := make(chan bool)
 542			script.Call("addEventListener", "load", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
 543				log.Println(wasmName+":", "Stripe.js script has been loaded")
 544				done <- true
 545				return nil
 546			}))
 547
 548			head.Call("appendChild", script)
 549
 550			<-done
 551
 552			stripeValue = js.Global().Get("Stripe")
 553			if stripeValue.IsUndefined() {
 554				log.Println(wasmName+":", "Failed to load Stripe.js")
 555				return
 556			}
 557		}
 558	}
 559
 560	log.Println(wasmName+":", "Stripe.js loaded successfully")
 561
 562	if stripe.IsUndefined() {
 563		log.Println(wasmName+":", "Invoking Stripe")
 564		stripe = stripeValue.Invoke(stripePK)
 565		if stripe.IsUndefined() {
 566			log.Println(wasmName+":", "Failed to invoke Stripe")
 567			return
 568		}
 569	}
 570
 571	log.Println(wasmName+":", "Stripe initialized")
 572	checkStatus()
 573}
 574
 575var (
 576	successIcon = `<svg width="16" height="14" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg">
 577		<path fill-rule="evenodd" clip-rule="evenodd" d="M15.4695 0.232963C15.8241 0.561287 15.8454 1.1149 15.5171 1.46949L6.14206 11.5945C5.97228 11.7778 5.73221 11.8799 5.48237 11.8748C5.23253 11.8698 4.99677 11.7582 4.83452 11.5681L0.459523 6.44311C0.145767 6.07557 0.18937 5.52327 0.556912 5.20951C0.924454 4.89575 1.47676 4.93936 1.79051 5.3069L5.52658 9.68343L14.233 0.280522C14.5613 -0.0740672 15.1149 -0.0953599 15.4695 0.232963Z" fill="white"/>
 578	</svg>`
 579
 580	errorIcon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
 581		<path fill-rule="evenodd" clip-rule="evenodd" d="M1.25628 1.25628C1.59799 0.914573 2.15201 0.914573 2.49372 1.25628L8 6.76256L13.5063 1.25628C13.848 0.914573 14.402 0.914573 14.7437 1.25628C15.0854 1.59799 15.0854 2.15201 14.7437 2.49372L9.23744 8L14.7437 13.5063C15.0854 13.848 15.0854 14.402 14.7437 14.7437C14.402 15.0854 13.848 15.0854 13.5063 14.7437L8 9.23744L2.49372 14.7437C2.15201 15.0854 1.59799 15.0854 1.25628 14.7437C0.914573 14.402 0.914573 13.848 1.25628 13.5063L6.76256 8L1.25628 2.49372C0.914573 2.15201 0.914573 1.59799 1.25628 1.25628Z" fill="white"/>
 582	</svg>`
 583
 584	infoIcon = `<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
 585		<path fill-rule="evenodd" clip-rule="evenodd" d="M10 1.5H4C2.61929 1.5 1.5 2.61929 1.5 4V10C1.5 11.3807 2.61929 12.5 4 12.5H10C11.3807 12.5 12.5 11.3807 12.5 10V4C12.5 2.61929 11.3807 1.5 10 1.5ZM4 0C1.79086 0 0 1.79086 0 4V10C0 12.2091 1.79086 14 4 14H10C12.2091 14 14 12.2091 14 10V4C14 1.79086 12.2091 0 10 0H4Z" fill="white"/>
 586		<path fill-rule="evenodd" clip-rule="evenodd" d="M5.25 7C5.25 6.58579 5.58579 6.25 6 6.25H7.25C7.66421 6.25 8 6.58579 8 7V10.5C8 10.9142 7.66421 11.25 7.25 11.25C6.83579 11.25 6.5 10.9142 6.5 10.5V7.75H6C5.58579 7.75 5.25 7.41421 5.25 7Z" fill="white"/>
 587		<path d="M5.75 4C5.75 3.31075 6.31075 2.75 7 2.75C7.68925 2.75 8.25 3.31075 8.25 4C8.25 4.68925 7.68925 5.25 7 5.25C6.31075 5.25 5.75 4.68925 5.75 4Z" fill="white"/>
 588	</svg>`
 589)
 590
 591func setErrorState() {
 592	js.Global().Get("document").Call("querySelector", "#status-icon").Set("style", map[string]interface{}{"backgroundColor": "#DF1B41"})
 593	js.Global().Get("document").Call("querySelector", "#status-icon").Set("innerHTML", errorIcon)
 594	js.Global().Get("document").Call("querySelector", "#status-text").Set("textContent", "Something went wrong, please try again.")
 595	js.Global().Get("document").Call("querySelector", "#details-table").Call("classList").Call("add", "hidden")
 596	js.Global().Get("document").Call("querySelector", "#view-details").Call("classList").Call("add", "hidden")
 597}
 598
 599func checkStatus() {
 600	clientSecret := js.Global().Get("URLSearchParams").New(js.Global().Get("window").Get("location").Get("search")).Call("get", "payment_intent_client_secret").String()
 601
 602	if clientSecret == "" {
 603		setErrorState()
 604		return
 605	}
 606
 607	if stripe.IsUndefined() {
 608		log.Println(wasmName+":", "Stripe is not initialized")
 609		setErrorState()
 610		return
 611	}
 612
 613	stripe.Call("retrievePaymentIntent", clientSecret).Call("then", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
 614		paymentIntent := p[0].Get("paymentIntent")
 615		setPaymentDetails(paymentIntent)
 616		return nil
 617	})).Call("catch", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
 618		setErrorState()
 619		return nil
 620	}))
 621}
 622
 623func getAllLocalStorageData() map[string]interface{} {
 624	localStorage := js.Global().Get("localStorage")
 625	keys := js.Global().Get("Object").Call("keys", localStorage)
 626	data := make(map[string]interface{})
 627
 628	for i := 0; i < keys.Length(); i++ {
 629		key := keys.Index(i).String()
 630		value := localStorage.Call("getItem", key).String()
 631		var parsedValue interface{}
 632		err := json.Unmarshal([]byte(value), &parsedValue)
 633		if err != nil {
 634			parsedValue = value // If not JSON, store raw value
 635		}
 636		data[key] = parsedValue
 637	}
 638	return data
 639}
 640
 641func submitOrder(localStorageData map[string]interface{}, paymentIntentId string) {
 642	orderData := map[string]interface{}{
 643		"localStorageData": localStorageData,
 644		"paymentIntentId":  paymentIntentId,
 645	}
 646
 647	body, err := json.Marshal(orderData)
 648	if err != nil {
 649		log.Println(wasmName+":", "Error marshalling order data:", err)
 650		return
 651	}
 652
 653	fetch := js.Global().Get("fetch")
 654	if fetch.IsUndefined() {
 655		log.Println(wasmName+":", "Fetch API is not available")
 656		return
 657	}
 658
 659	options := map[string]interface{}{
 660		"method": "POST",
 661		"headers": map[string]interface{}{
 662			"Content-Type": "application/json",
 663		},
 664		"body": string(body),
 665	}
 666
 667	fetch.Invoke("/submit-order", js.ValueOf(options)).Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
 668		response := args[0]
 669		response.Call("json").Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
 670			data := args[0]
 671			log.Println(wasmName+":", "Order submitted successfully:", data)
 672			return nil
 673		})).Call("catch", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
 674			err := args[0]
 675			js.Global().Call("alert", fmt.Sprintf("Error submitting order: %s\n Please reload the page.\nReach out to us on Telegram regarding this incident:\nhttps://t.me/magnetosphere", err))
 676			log.Println(wasmName+":", "Error parsing order response:", err)
 677			return nil
 678		}))
 679		return nil
 680	})).Call("catch", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
 681		err := args[0]
 682		js.Global().Call("alert", fmt.Sprintf("Error submitting order: %s\n Please reload the page.\nReach out to us on Telegram regarding this incident:\nhttps://t.me/magnetosphere", err))
 683		log.Println(wasmName+":", "Error submitting order:", err)
 684		return nil
 685	}))
 686}
 687
 688func setPaymentDetails(intent js.Value) {
 689	var statusText, iconColor, icon string
 690	statusText = "Something went wrong, please try again."
 691	iconColor = "#DF1B41"
 692	icon = errorIcon
 693
 694	if !intent.IsUndefined() {
 695		intentStatus := intent.Get("status").String()
 696		intentID := intent.Get("id").String()
 697
 698		allLocalStorageData := getAllLocalStorageData()
 699
 700		switch intentStatus {
 701		case "succeeded":
 702			statusText = "Payment succeeded"
 703			iconColor = "#30B130"
 704			icon = successIcon
 705			if len(allLocalStorageData) > 0 {
 706				submitOrder(allLocalStorageData, intentID)
 707			} else {
 708				log.Println(wasmName+":", "No data found in localStorage; order not submitted.")
 709			}
 710		case "processing":
 711			statusText = "Your payment is processing."
 712			iconColor = "#6D6E78"
 713			icon = infoIcon
 714			if len(allLocalStorageData) > 0 {
 715				submitOrder(allLocalStorageData, intentID)
 716			} else {
 717				log.Println(wasmName+":", "No data found in localStorage; order not submitted.")
 718			}
 719		case "requires_payment_method":
 720			statusText = "Your payment was not successful, please try again."
 721		default:
 722			statusText = "Unknown payment status."
 723		}
 724
 725		// Update the status icon, text, and links
 726		js.Global().Get("document").Call("querySelector", "#status-icon").Set("style", map[string]interface{}{"backgroundColor": iconColor})
 727		js.Global().Get("document").Call("querySelector", "#status-icon").Set("innerHTML", icon)
 728		js.Global().Get("document").Call("querySelector", "#status-text").Set("textContent", statusText)
 729		js.Global().Get("document").Call("querySelector", "#intent-id").Set("textContent", intentID)
 730		js.Global().Get("document").Call("querySelector", "#intent-status").Set("textContent", intentStatus)
 731		js.Global().Get("document").Call("querySelector", "#view-details").Set("href", "https://dashboard.stripe.com/payments/"+intentID)
 732
 733		// Update the "Order Details" link with the paymentIntent ID
 734		orderDetailsLink := js.Global().Get("document").Call("querySelector", "#order-details-link")
 735		orderDetailsLink.Set("href", "/order/"+intentID)
 736		orderDetailsLink.Set("onclick", nil) // Allow default behavior (navigation)
 737
 738	} else {
 739		setErrorState()
 740	}
 741}
 742
 743
 744// ===== stl2.go =====
 745// Package main stl2.go
 746package main
 747
 748import (
 749	"bytes"
 750	"crypto/rand"
 751	"encoding/base64"
 752	"encoding/binary"
 753	"log"
 754	m "math"
 755	r "reflect"
 756	"runtime"
 757	"strconv"
 758	"strings"
 759	"syscall/js"
 760	u "unsafe"
 761
 762	"github.com/go-gl/mathgl/mgl32"
 763	"gitlab.com/russoj88/stl/stl"
 764)
 765
 766var (
 767	wasmName                                                     string
 768	running                                                      = true
 769	done                                                         chan struct{}
 770	stlFileName, originalHTML                                    string
 771	rr                                                           Renderer
 772	existingFooter, body, footer, sXV, sYV, sZV, sZoomV, cEl, gl js.Value
 773	currentZoom                                                  float32 = 3
 774)
 775
 776const gebi = "getElementById"
 777const ael = "addEventListener"
 778const ih = "innerHTML"
 779
 780func main() {
 781	ready := make(chan struct{})
 782
 783	document := js.Global().Get("document")
 784	readyState := document.Get("readyState").String()
 785	if readyState == "interactive" || readyState == "complete" {
 786		log.Println(wasmName+":", "WASM: DOM already fully loaded")
 787		close(ready)
 788	} else {
 789		cb := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
 790			log.Println(wasmName+":", "WASM: DOM fully loaded and parsed")
 791			close(ready)
 792			return nil
 793		})
 794		defer cb.Release()
 795
 796		document.Call(ael, "DOMContentLoaded", cb)
 797		log.Println(wasmName+":", "WASM: waiting for DOM to load")
 798	}
 799
 800	// Wait until the DOM is ready
 801	<-ready
 802
 803	cEl = document.Call(gebi, "gocanvas")
 804	if cEl.IsUndefined() || cEl.IsNull() {
 805		return
 806	}
 807
 808	window := js.Global().Get("window")
 809	location := window.Get("location")
 810	pathname := location.Get("pathname").String()
 811	parts := strings.SplitN(pathname, "/", 3)
 812	if len(parts) > 1 {
 813		parts[1] = "/" + parts[1]
 814	} else {
 815		parts = append(parts, "/")
 816	}
 817	switch parts[1] {
 818	case "/cat":
 819		log.Println(wasmName+":", "nothing to animate ; return")
 820		return
 821	case "/p":
 822		log.Println(wasmName+":", "fetching stereolithograph")
 823		preElement := js.Global().Get("document").Call("getElementById", "package-type")
 824		if preElement.IsUndefined() || preElement.IsNull() {
 825			log.Println(wasmName+":", "Element with ID 'package-type' not found. Exiting.")
 826			return // Exit the WebAssembly here
 827		}
 828
 829		packageText := preElement.Get("textContent").String()
 830		packageType := packageText[len("Package Type: "):]
 831		stlFileName = packageType + ".stl"
 832
 833		// Fetch the STL file
 834		response := js.Global().Call("fetch", "/i/stl/base64/"+stlFileName)
 835		promise := response.Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
 836			resp := args[0]
 837			if !resp.Get("ok").Bool() {
 838				log.Printf("HTTP Error: %s (status: %d)", resp.Get("statusText").String(), resp.Get("status").Int())
 839				log.Println(wasmName+":", "Failed to fetch the STL file "+stlFileName+". Exiting.")
 840				return nil // Exit the WebAssembly here
 841			}
 842			return resp.Call("text")
 843		}))
 844
 845		// Process the fetched data
 846		promise.Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
 847			if len(args) > 0 {
 848				result := args[0].String()
 849				uploadedFile, err1 := parseBase64File(result)
 850				if !err1.IsNull() {
 851					log.Println(wasmName+":", "Error parsing the base64 file:", err1)
 852					return nil
 853				}
 854
 855				stlSolid, err2 := NewSTL(uploadedFile)
 856				if err2 != nil {
 857					log.Println(wasmName+":", "Error creating STL object:", err2)
 858					return nil
 859				}
 860
 861				vert, colors, indices := stlSolid.GetModel()
 862				modelSize := getMaxScalar(vert)
 863				currentZoom := modelSize * 3
 864				rr.SetZoom(currentZoom)
 865				rr.SetModel(colors, vert, indices)
 866			}
 867			return nil
 868		}))
 869
 870		// Ensure this function does not execute until the fetch of the STL file succeeds
 871		promise.Call("catch", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
 872			log.Println(wasmName+":", "An error occurred during the fetch or processing. Exiting.")
 873			return nil
 874		}))
 875		niam()
 876	default:
 877		niam()
 878	}
 879}
 880func niam() {
 881	mgl32.DisableMemoryPooling()
 882
 883	//stlFileName = "TO-247.stl"
 884	tdata := struct {
 885		XRange, ZMin, ZMax string
 886		XStep, ZoomStep    string
 887	}{
 888		XRange: "1", ZMin: "0", ZMax: "50",
 889		XStep: "0.01", ZoomStep: "0.1",
 890	}
 891	if stlFileName != ".stl" && stlFileName != "" {
 892		tdata.ZMin = "10"
 893		tdata.ZMax = "1000"
 894	}
 895
 896	var controlsHTML = `
 897	<datalist id="speeds">
 898	<option>-` + tdata.XRange + `</option><option>0</option><option>` + tdata.XRange + `</option></datalist>
 899	<table class="🌐">
 900	<tr><td><p><button type="button" id="stop">Stop Rendering</button></p></td><td><p>X<input id="X" type="range" min="-` + tdata.XRange + `" max="` + tdata.XRange + `" step="` + tdata.XStep + `" list="speeds"><text id="XV">00.00</text></p></td>
 901	<td><p>Y<input id="Y" type="range" min="-` + tdata.XRange + `" max="` + tdata.XRange + `" step="` + tdata.XStep + `" list="speeds"><text id="YV">00.00</text></p></td>
 902	<td><p>Z<input id="Z" type="range" min="-` + tdata.XRange + `" max="` + tdata.XRange + `" step="` + tdata.XStep + `" list="speeds"><text id="ZV">00.00</text></p></td>
 903	<td><p>Zoom<input id="Zoom" type="range" min="` + tdata.ZMin + `" max="` + tdata.ZMax + `" step="` + tdata.ZoomStep + `" list="speeds"><text id="ZoomV">0000.00</text></p></td>
 904	</table>
 905	`
 906	doc := js.Global().Get("document")
 907	body = doc.Get("body")
 908	existingFooter = doc.Call("getElementsByTagName", "footer").Index(0)
 909	if existingFooter.Truthy() {
 910		originalHTML = existingFooter.Get(ih).String()
 911		footer = doc.Call("createElement", "footer")
 912		footer.Set(ih, originalHTML+controlsHTML)
 913		body.Call("replaceChild", footer, existingFooter)
 914	} else {
 915		footer = doc.Call("createElement", "footer")
 916		footer.Set(ih, controlsHTML)
 917		body.Call("appendChild", footer)
 918	}
 919
 920	cEl = doc.Call(gebi, "gocanvas")
 921	width := doc.Get("body").Get("clientWidth").Int()
 922	height := doc.Get("body").Get("clientHeight").Int()
 923
 924	cEl.Set("width", width)
 925	cEl.Set("height", height)
 926	sXc := js.FuncOf(sCX)
 927	sX := doc.Call(gebi, "X")
 928	sX.Call(ael, "input", sXc)
 929	sXV = doc.Call(gebi, "XV")
 930
 931	sYc := js.FuncOf(sCY)
 932	sY := doc.Call(gebi, "Y")
 933	sY.Call(ael, "input", sYc)
 934	sYV = doc.Call(gebi, "YV")
 935
 936	sZc := js.FuncOf(sCZ)
 937	sZ := doc.Call(gebi, "Z")
 938	sZ.Call(ael, "input", sZc)
 939	sZV = doc.Call(gebi, "ZV")
 940
 941	sZoomc := js.FuncOf(sCZoom)
 942	sZoom := doc.Call(gebi, "Zoom")
 943	sZoom.Call(ael, "input", sZoomc)
 944	sZoomV = doc.Call(gebi, "ZoomV")
 945
 946	sBc := js.FuncOf(stopApplication)
 947	sB := doc.Call(gebi, "stop")
 948	sB.Call(ael, "click", sBc)
 949	defer sBc.Release()
 950	/*
 951	   		if stlFileName != ".stl" && stlFileName != "" {
 952
 953	   	response := js.Global().Call("fetch", "/stl/base64/"+stlFileName)
 954	   	promise := response.Call("then", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
 955	   		if p[0].Get("ok").Bool() {
 956	   			return p[0].Call("text")
 957	   		}
 958	   		return "Error fetching stereolithograph"
 959	   	}))
 960	   	promise.Call("then", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
 961	   		result := p[0].String()
 962	   		uploadedFile, _ := parseBase64File(result) // nolint
 963	   		stlSolid, _ := NewSTL(uploadedFile) // nolint
 964	   		vert, colors, indices := stlSolid.GetModel()
 965	   		modelSize := getMaxScalar(vert)
 966	   		currentZoom := modelSize * 3
 967	   		rr.SetZoom(currentZoom)
 968	   		rr.SetModel(colors, vert, indices)
 969	   		return nil
 970	   	}))
 971	   }
 972	*/
 973	gl = cEl.Call("getContext", "webgl")
 974	if gl.IsUndefined() {
 975		gl = cEl.Call("getContext", "experimental-webgl")
 976	}
 977	if gl.IsUndefined() {
 978		js.Global().Call("alert", "WASM:  browser might not support webgl")
 979		return
 980	}
 981
 982	config := InitialConfig{
 983		W:        width,
 984		H:        height,
 985		X:        0,
 986		Y:        0,
 987		Z:        0,
 988		Vertices: verticesNative,
 989		Indices:  indicesNative,
 990		Colors:   colorsNative,
 991		FSC:      fragShaderCode,
 992		VSC:      vertShaderCode,
 993	}
 994
 995	config.X = cryptoRandFloat32() / 20
 996	config.Y = cryptoRandFloat32() / 20
 997	config.Z = cryptoRandFloat32() / 20
 998	config.Vertices, config.Indices = generateSphereVertices(float32(1.0), 30, 30)
 999	if stlFileName == ".stl" || stlFileName == "" {
1000		config.FSC, config.VSC = fragShaderCode1, vertShaderCode1
1001	}
1002	var jsErr js.Value
1003	rr, jsErr = NewRenderer(gl, config)
1004	if !jsErr.IsNull() {
1005		js.Global().Call("alert", "WASM: Cannot load webgl ")
1006		return
1007	}
1008	rr.SetZoom(currentZoom)
1009	defer rr.Release()
1010
1011	x, y, z := rr.GetSpeed()
1012	sX.Set("value", f32(x, 'f', -1, 64))
1013	if x > 0 {
1014		sXV.Set(ih, "+"+f32(x, 'f', 2, 64))
1015	}
1016	if x == 0 {
1017		sXV.Set(ih, " "+f32(x, 'f', 2, 64))
1018	}
1019	if x < 0 {
1020		sXV.Set(ih, f32(x, 'f', 2, 64))
1021	}
1022	sY.Set("value", f32(y, 'f', -1, 64))
1023	if y > 0 {
1024		sYV.Set(ih, "+"+f32(y, 'f', 2, 64))
1025	}
1026	if y == 0 {
1027		sYV.Set(ih, "0"+f32(y, 'f', 2, 64))
1028	}
1029	if y < 0 {
1030		sYV.Set(ih, f32(y, 'f', 2, 64))
1031	}
1032	sZ.Set("value", f32(z, 'f', -1, 64))
1033	if z > 0 {
1034		sZV.Set(ih, "+"+f32(z, 'f', 2, 64))
1035	}
1036	if z == 0 {
1037		sZV.Set(ih, "0"+f32(z, 'f', 2, 64))
1038	}
1039	if z < 0 {
1040		sZV.Set(ih, f32(z, 'f', 2, 64))
1041	}
1042
1043	var renderFrame js.Func
1044	renderFrame = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
1045		rr.Render(this, args)
1046		js.Global().Call("requestAnimationFrame", renderFrame)
1047		return nil
1048	})
1049	js.Global().Call("requestAnimationFrame", renderFrame)
1050
1051	done = make(chan struct{})
1052
1053	<-done
1054}
1055
1056func parseBase64File(input string) (output []byte, err js.Value) {
1057	searchString := "base64,"
1058	searchLength := len(searchString)
1059	var index = -1
1060	for i := 0; i <= len(input)-searchLength; i++ {
1061		if input[i:i+searchLength] == searchString {
1062			index = i
1063			break
1064		}
1065	}
1066	if index < 0 {
1067		err = js.Global().Get("Error").New("Error opening file")
1068		return
1069	}
1070	sBuffer := input[index+searchLength:]
1071	output, decodeErr := base64.StdEncoding.DecodeString(sBuffer)
1072	if decodeErr != nil {
1073		err = js.Global().Get("Error").New(decodeErr.Error())
1074		return
1075	}
1076	return output, js.Null()
1077}
1078
1079func getMaxScalar(vertices []float32) float32 {
1080	var max float32
1081	for baseIndex := 0; baseIndex < len(vertices); baseIndex += 3 {
1082		testScale := scalar(vertices[baseIndex], vertices[baseIndex], vertices[baseIndex])
1083		if testScale > max {
1084			max = testScale
1085		}
1086	}
1087	return max
1088}
1089
1090func scalar(x float32, y float32, z float32) float32 {
1091	xy := m.Sqrt(float64(x*x + y*y))
1092	return float32(m.Sqrt(xy*xy + float64(z*z)))
1093}
1094
1095func cryptoRandFloat32() float32 {
1096	b := make([]byte, 4)
1097	_, err := rand.Read(b)
1098	if err != nil {
1099		panic("crypto/rand read failed: " + err.Error())
1100	}
1101	u := uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3])
1102	return float32(u) / float32(m.MaxUint32)
1103}
1104
1105func stopApplication(_ js.Value, _ []js.Value) interface{} {
1106	running = false
1107	sZoomV.Set(ih, float32(0))
1108	currentZoom = float32(0)
1109	rr.SetZoom(float32(0))
1110	footer.Set(ih, originalHTML)
1111
1112	if callUpdateCartDisplay := js.Global().Get("callUpdateCartDisplay"); !callUpdateCartDisplay.IsUndefined() && !callUpdateCartDisplay.IsNull() {
1113		callUpdateCartDisplay.Invoke()
1114	} else {
1115		js.Global().Call("console.warn", "callUpdateCartDisplay is undefined or null")
1116	}
1117
1118	js.Global().Call("setTimeout", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
1119		close(done)
1120		//		done <- struct{}{}
1121		return nil
1122	}), 5000)
1123	return nil
1124}
1125
1126func sCX(this js.Value, _ []js.Value) interface{} {
1127	sSpeed := this.Get("value").String()
1128	s, _ := strconv.ParseFloat(sSpeed, 64)
1129	rr.SetX(float32(s))
1130	if s > 0 {
1131		sXV.Set(ih, "+"+f64(s, 'f', 2, 32))
1132	}
1133	if s == 0 {
1134		sXV.Set(ih, "0"+f64(s, 'f', 2, 32))
1135	}
1136	if s < 0 {
1137		sXV.Set(ih, f64(s, 'f', 2, 32))
1138	}
1139	return nil
1140}
1141
1142func sCY(this js.Value, _ []js.Value) interface{} {
1143	sS := this.Get("value").String()
1144	s, _ := strconv.ParseFloat(sS, 64)
1145	rr.SetY(float32(s))
1146	if s > 0 {
1147		sYV.Set(ih, "+"+f64(s, 'f', 2, 32))
1148	}
1149	if s == 0 {
1150		sYV.Set(ih, "0"+f64(s, 'f', 2, 32))
1151	}
1152	if s < 0 {
1153		sYV.Set(ih, f64(s, 'f', 2, 32))
1154	}
1155	return nil
1156}
1157
1158func sCZ(this js.Value, _ []js.Value) interface{} {
1159	sS := this.Get("value").String()
1160	s, _ := strconv.ParseFloat(sS, 64)
1161	rr.SetZ(float32(s))
1162	if s > 0 {
1163		sZV.Set(ih, "+"+f64(s, 'f', 2, 32))
1164	}
1165	if s == 0 {
1166		sZV.Set(ih, "0"+f64(s, 'f', 2, 32))
1167	}
1168	if s < 0 {
1169		sZV.Set(ih, f64(s, 'f', 2, 32))
1170	}
1171	return nil
1172}
1173
1174func sCZoom(this js.Value, _ []js.Value) interface{} {
1175	sS := this.Get("value").String()
1176	s, _ := strconv.ParseFloat(sS, 64)
1177	if s < 10 {
1178		sZoomV.Set(ih, "000"+f64(s, 'f', 2, 32))
1179	} else if s < 100 {
1180		sZoomV.Set(ih, "00"+f64(s, 'f', 2, 32))
1181	} else if s < 1000 {
1182		sZoomV.Set(ih, "0"+f64(s, 'f', 2, 32))
1183	} else {
1184		sZoomV.Set(ih, f64(s, 'f', 2, 32))
1185	}
1186	currentZoom = float32(s)
1187	rr.SetZoom(currentZoom)
1188	return nil
1189}
1190
1191// Model is an interface for a model
1192type Model interface {
1193	GetModel() ([]float32, []float32, []uint16)
1194}
1195
1196func cryptoRandIntn(max int) (int, js.Value) {
1197	if max <= 0 {
1198		return 0, js.Global().Get("Error").New("max must be a positive integer")
1199	}
1200	numBytes := (max + 7) / 8
1201	maxBytes := 1 << (numBytes * 8)
1202	randBytes := make([]byte, numBytes)
1203	randNum := 0
1204	for {
1205		_, err := rand.Read(randBytes)
1206		if err != nil {
1207			return 0, js.Global().Get("Error").New("error generating random number")
1208		}
1209		for _, b := range randBytes {
1210			randNum = (randNum << 8) | int(b)
1211		}
1212		if randNum < maxBytes-maxBytes%max {
1213			break
1214		}
1215	}
1216	return randNum % max, js.Null()
1217}
1218
1219// NewSTL returns a new STL & error
1220func NewSTL(buffer []byte) (o STL, err error) {
1221	bufferReader := bytes.NewReader(buffer)
1222	solid, err := stl.From(bufferReader)
1223	if err != nil {
1224		return
1225	}
1226
1227	// Generate random rotation matrix
1228	rotationMatrix := randomRotationMatrix()
1229
1230	// Generate colors
1231	numColors, _ := cryptoRandIntn(5)
1232	numColors += 2 // Random number between 2 and 6
1233	colors := GenerateGradient(numColors, int(solid.TriangleCount))
1234
1235	var index uint32
1236	for i, triangle := range solid.Triangles {
1237		colorR := colors[i].Red
1238		colorG := colors[i].Green
1239		colorB := colors[i].Blue
1240
1241		// Convert each triangle's vertices to custom Vertex type and apply rotation
1242		v0 := Vertex{X: float32(triangle.Vertices[0].X), Y: float32(triangle.Vertices[0].Y), Z: float32(triangle.Vertices[0].Z)}
1243		v1 := Vertex{X: float32(triangle.Vertices[1].X), Y: float32(triangle.Vertices[1].Y), Z: float32(triangle.Vertices[1].Z)}
1244		v2 := Vertex{X: float32(triangle.Vertices[2].X), Y: float32(triangle.Vertices[2].Y), Z: float32(triangle.Vertices[2].Z)}
1245
1246		// Rotate and add vertices
1247		o.addRotatedVertex(&index, v0, rotationMatrix, colorR, colorG, colorB)
1248		o.addRotatedVertex(&index, v1, rotationMatrix, colorR, colorG, colorB)
1249		o.addRotatedVertex(&index, v2, rotationMatrix, colorR, colorG, colorB)
1250	}
1251
1252	return o, err
1253}
1254
1255// Add a rotated vertex to the STL structure
1256func (s *STL) addRotatedVertex(index *uint32, vertex Vertex, rotation mgl32.Mat4, r, g, b float32) {
1257	// Apply rotation
1258	rotatedVertex := rotation.Mul4x1(mgl32.Vec3{vertex.X, vertex.Y, vertex.Z}.Vec4(1.0))
1259
1260	// Add rotated vertex to the STL struct
1261	s.v = append(s.v, rotatedVertex[0], rotatedVertex[1], rotatedVertex[2])
1262	s.i = append(s.i, *index)
1263	s.c = append(s.c, r, g, b)
1264	(*index)++
1265}
1266
1267// STL is a stereolithograph
1268type STL struct {
1269	v []float32
1270	c []float32
1271	i []uint32
1272}
1273
1274// Define a custom Vertex type for storing the vertex data.
1275type Vertex struct {
1276	X, Y, Z float32
1277}
1278
1279// GetModel gets the model
1280func (s STL) GetModel() ([]float32, []float32, []uint32) {
1281	return s.v, s.c, s.i
1282}
1283
1284// InitialConfig is the initial config
1285type InitialConfig struct {
1286	W        int
1287	H        int
1288	X        float32
1289	Y        float32
1290	Z        float32
1291	Colors   []float32
1292	Vertices []float32
1293	Indices  []uint32
1294	FSC      string
1295	VSC      string
1296}
1297
1298// Renderer is the renderer
1299type Renderer struct {
1300	glContext      js.Value
1301	glTypes        GLTypes
1302	colors         js.Value
1303	v              js.Value
1304	i              js.Value
1305	colorBuffer    js.Value
1306	vertexBuffer   js.Value
1307	indexBuffer    js.Value
1308	numIndices     int
1309	numVertices    int
1310	fragShader     js.Value
1311	vertShader     js.Value
1312	shaderProgram  js.Value
1313	tmark          float32
1314	rX             float32 //rotation X
1315	rY             float32
1316	rZ             float32
1317	movMatrix      mgl32.Mat4
1318	PositionMatrix js.Value
1319	ViewMatrix     js.Value
1320	ModelMatrix    js.Value
1321	height         int
1322	width          int
1323	sX             float32
1324	sY             float32
1325	sZ             float32
1326}
1327
1328// NewRenderer returns a new renderer & error
1329func NewRenderer(gl js.Value, config InitialConfig) (r Renderer, err js.Value) {
1330	// Get some WebGL bindings
1331	r.glContext = gl
1332	err = r.glTypes.New(r.glContext)
1333	r.numIndices = len(config.Indices)
1334	r.numVertices = len(config.Vertices)
1335	r.movMatrix = mgl32.Ident4()
1336	r.width = config.W
1337	r.height = config.H
1338
1339	r.sX = config.X
1340	r.sY = config.Y
1341	r.sZ = config.Z
1342
1343	// Convert buffers to JS TypedArrays
1344	r.UpdateColorBuffer(config.Colors)
1345	r.UpdateVerticesBuffer(config.Vertices)
1346	r.UpdateIndicesBuffer(config.Indices)
1347
1348	r.UpdateFragmentShader(config.FSC)
1349	r.UpdateVertexShader(config.VSC)
1350	r.updateShaderProgram()
1351	r.attachShaderProgram()
1352
1353	r.setContextFlags()
1354
1355	r.createMatrixes()
1356	r.EnableObject()
1357	return
1358}
1359
1360// SetModel sets a new model
1361func (r *Renderer) SetModel(Colors []float32, Vertices []float32, Indices []uint32) {
1362	r.numIndices = len(Indices)
1363	r.UpdateColorBuffer(Colors)
1364	r.UpdateVerticesBuffer(Vertices)
1365	r.UpdateIndicesBuffer(Indices)
1366	r.EnableObject()
1367}
1368
1369// Release releases the renderer
1370func (r *Renderer) Release() {
1371	return
1372}
1373
1374// EnableObject enables the object
1375func (r *Renderer) EnableObject() {
1376	r.glContext.Call("bindBuffer", r.glTypes.ElementArrayBuffer, r.indexBuffer)
1377}
1378
1379// SetX set rotation x axis speed
1380func (r *Renderer) SetX(x float32) {
1381	r.sX = x
1382}
1383
1384// SetY set rotation y axis speed
1385func (r *Renderer) SetY(y float32) {
1386	r.sY = y
1387}
1388
1389// SetZ set rotation z axis speed
1390func (r *Renderer) SetZ(z float32) {
1391	r.sZ = z
1392}
1393
1394// GetSpeed returns the rotation speeds
1395func (r *Renderer) GetSpeed() (x, y, z float32) {
1396	return r.sX, r.sY, r.sZ
1397}
1398
1399// SetSize sets the size of the rendering
1400func (r *Renderer) SetSize(height, width int) {
1401	r.height = height
1402	r.width = width
1403}
1404
1405func (r *Renderer) createMatrixes() {
1406	ratio := float32(r.width) / float32(r.height)
1407	//	fmt.Println("Renderer.createMatrixes")
1408	projMatrix := mgl32.Perspective(mgl32.DegToRad(45.0), ratio, 1, 100000.0)
1409	projMatrixBuffer := (*[16]float32)(u.Pointer(&projMatrix)) // nolint
1410	typedProjMatrixBuffer := S2TA([]float32((*projMatrixBuffer)[:]))
1411	r.glContext.Call("uniformMatrix4fv", r.PositionMatrix, false, typedProjMatrixBuffer)
1412
1413	viewMatrix := mgl32.LookAtV(mgl32.Vec3{3.0, 3.0, 3.0}, mgl32.Vec3{0.0, 0.0, 0.0}, mgl32.Vec3{0.0, 1.0, 0.0})
1414	viewMatrixBuffer := (*[16]float32)(u.Pointer(&viewMatrix)) // nolint
1415	typedViewMatrixBuffer := S2TA([]float32((*viewMatrixBuffer)[:]))
1416	r.glContext.Call("uniformMatrix4fv", r.ViewMatrix, false, typedViewMatrixBuffer)
1417}
1418
1419func (r *Renderer) setContextFlags() {
1420	r.glContext.Call("clearColor", 0.0, 0.0, 0.0, 0.0)    // Color the screen is cleared to
1421	r.glContext.Call("viewport", 0, 0, r.width, r.height) // Viewport size
1422	r.glContext.Call("depthFunc", r.glTypes.LEqual)
1423}
1424
1425// UpdateFragmentShader Updates the Fragment Shader
1426func (r *Renderer) UpdateFragmentShader(shaderCode string) {
1427	r.fragShader = r.glContext.Call("createShader", r.glTypes.FragmentShader)
1428	r.glContext.Call("shaderSource", r.fragShader, shaderCode)
1429	r.glContext.Call("compileShader", r.fragShader)
1430}
1431
1432// UpdateVertexShader updates the vertex shader
1433func (r *Renderer) UpdateVertexShader(shaderCode string) {
1434	r.vertShader = r.glContext.Call("createShader", r.glTypes.VertexShader)
1435	r.glContext.Call("shaderSource", r.vertShader, shaderCode)
1436	r.glContext.Call("compileShader", r.vertShader)
1437}
1438
1439func (r *Renderer) updateShaderProgram() {
1440	if r.fragShader.IsUndefined() || r.vertShader.IsUndefined() {
1441		return
1442	}
1443	r.shaderProgram = r.glContext.Call("createProgram")
1444	r.glContext.Call("attachShader", r.shaderProgram, r.vertShader)
1445	r.glContext.Call("attachShader", r.shaderProgram, r.fragShader)
1446	r.glContext.Call("linkProgram", r.shaderProgram)
1447}
1448
1449const gul = "getUniformLocation"
1450
1451func (r *Renderer) attachShaderProgram() {
1452	r.PositionMatrix = r.glContext.Call(gul, r.shaderProgram, "Pmatrix")
1453	r.ViewMatrix = r.glContext.Call(gul, r.shaderProgram, "Vmatrix")
1454	r.ModelMatrix = r.glContext.Call(gul, r.shaderProgram, "Mmatrix")
1455
1456	r.glContext.Call("bindBuffer", r.glTypes.ArrayBuffer, r.vertexBuffer)
1457	position := r.glContext.Call("getAttribLocation", r.shaderProgram, "position")
1458	r.glContext.Call("vertexAttribPointer", position, 3, r.glTypes.Float, false, 0, 0)
1459	r.glContext.Call("enableVertexAttribArray", position)
1460
1461	r.glContext.Call("bindBuffer", r.glTypes.ArrayBuffer, r.colorBuffer)
1462	color := r.glContext.Call("getAttribLocation", r.shaderProgram, "color")
1463	r.glContext.Call("vertexAttribPointer", color, 3, r.glTypes.Float, false, 0, 0)
1464	r.glContext.Call("enableVertexAttribArray", color)
1465
1466	r.glContext.Call("useProgram", r.shaderProgram)
1467	if stlFileName == ".stl" || stlFileName == "" {
1468
1469		uBaseColor := r.glContext.Call(gul, r.shaderProgram, "uBaseColor")
1470		uTopColor := r.glContext.Call(gul, r.shaderProgram, "uTopColor")
1471		uColor := r.glContext.Call(gul, r.shaderProgram, "uColor")
1472		r.glContext.Call("uniform3f", uBaseColor, 1.0, 0.0, 0.0)
1473		r.glContext.Call("uniform3f", uTopColor, 0.0, 0.0, 1.0)
1474		r.glContext.Call("uniform3f", uColor, 1.0, 1.0, 1.0)
1475	}
1476}
1477
1478// UpdateColorBuffer Updates the ColorBuffer
1479func (r *Renderer) UpdateColorBuffer(buffer []float32) {
1480	r.colors = S2TA(buffer)
1481	if r.colorBuffer.IsUndefined() {
1482		r.colorBuffer = r.glContext.Call("createBuffer")
1483	}
1484	r.glContext.Call("bindBuffer", r.glTypes.ArrayBuffer, r.colorBuffer)
1485	r.glContext.Call("bufferData", r.glTypes.ArrayBuffer, r.colors, r.glTypes.StaticDraw)
1486}
1487
1488// UpdateVerticesBuffer Updates the VerticesBuffer
1489func (r *Renderer) UpdateVerticesBuffer(buffer []float32) {
1490	r.v = S2TA(buffer)
1491	if r.vertexBuffer.IsUndefined() {
1492		r.vertexBuffer = r.glContext.Call("createBuffer")
1493	}
1494	r.glContext.Call("bindBuffer", r.glTypes.ArrayBuffer, r.vertexBuffer)
1495	r.glContext.Call("bufferData", r.glTypes.ArrayBuffer, r.v, r.glTypes.StaticDraw)
1496}
1497
1498// UpdateIndicesBuffer Updates the IndicesBuffer
1499func (r *Renderer) UpdateIndicesBuffer(buffer []uint32) {
1500	r.i = S2TA(buffer)
1501	if r.indexBuffer.IsUndefined() {
1502		r.indexBuffer = r.glContext.Call("createBuffer")
1503	}
1504	r.glContext.Call("bindBuffer", r.glTypes.ElementArrayBuffer, r.indexBuffer)
1505	r.glContext.Call("bufferData", r.glTypes.ElementArrayBuffer, r.i, r.glTypes.StaticDraw)
1506}
1507
1508// Render renders
1509func (r *Renderer) Render(_ js.Value, args []js.Value) interface{} { // nolint
1510	now := float32(args[0].Float())
1511	tdiff := now - r.tmark
1512	r.tmark = now
1513	r.rX = r.rX + r.sX*float32(tdiff)/500
1514	r.rY = r.rY + r.sY*float32(tdiff)/500
1515	r.rZ = r.rZ + r.sZ*float32(tdiff)/500
1516
1517	r.movMatrix = mgl32.HomogRotate3DX(r.rX)
1518	r.movMatrix = r.movMatrix.Mul4(mgl32.HomogRotate3DY(r.rY))
1519	r.movMatrix = r.movMatrix.Mul4(mgl32.HomogRotate3DZ(r.rZ))
1520
1521	modelMatrixBuffer := (*[16]float32)(u.Pointer(&r.movMatrix)) // nolint
1522	typedModelMatrixBuffer := S2TA([]float32((*modelMatrixBuffer)[:]))
1523
1524	r.glContext.Call("uniformMatrix4fv", r.ModelMatrix, false, typedModelMatrixBuffer)
1525
1526	r.glContext.Call("enable", r.glTypes.DepthTest)
1527	r.glContext.Call("clear", r.glTypes.ColorBufferBit)
1528	r.glContext.Call("clear", r.glTypes.DepthBufferBit)
1529	usegltype := r.glTypes.Triangles
1530	if stlFileName == ".stl" || stlFileName == "" {
1531		usegltype = r.glTypes.Line
1532		r.glContext.Call("drawArrays", r.glTypes.LineLoop, 0, r.numVertices/3)
1533	}
1534	r.glContext.Call("drawElements", usegltype, r.numIndices, r.glTypes.UnsignedInt, 0)
1535
1536	return nil
1537}
1538
1539// SetZoom Sets the Zoom
1540func (r *Renderer) SetZoom(currentZoom float32) {
1541	viewMatrix := mgl32.LookAtV(mgl32.Vec3{currentZoom, currentZoom, currentZoom}, mgl32.Vec3{0.0, 0.0, 0.0}, mgl32.Vec3{0.0, 1.0, 0.0})
1542	viewMatrixBuffer := (*[16]float32)(u.Pointer(&viewMatrix)) // nolint
1543	typedViewMatrixBuffer := S2TA([]float32((*viewMatrixBuffer)[:]))
1544	r.glContext.Call("uniformMatrix4fv", r.ViewMatrix, false, typedViewMatrixBuffer)
1545}
1546
1547// NewColorInterpolation generates color interpolation
1548func NewColorInterpolation(a Color, b Color) ColorInterpolation {
1549	return ColorInterpolation{
1550		a,
1551		b,
1552		a.Subtract(b),
1553	}
1554}
1555
1556// ColorInterpolation is interpolated color
1557type ColorInterpolation struct {
1558	startColor Color
1559	endColor   Color
1560	deltaColor Color
1561}
1562
1563// Interpolate interpolates
1564func (c ColorInterpolation) Interpolate(percent float32) Color {
1565	scaled := c.deltaColor.MultiplyFloat(percent)
1566	return c.startColor.Add(scaled)
1567}
1568
1569// Color represents a color
1570type Color struct {
1571	Red   float32
1572	Green float32
1573	Blue  float32
1574}
1575
1576// NewRandomColor returns a New RandomColor
1577func NewRandomColor() Color {
1578	const maxRGB = 255
1579	var r, g, b float64
1580	buf := make([]byte, 3)
1581	rand.Read(buf)
1582	r = float64(buf[0]) / 256
1583	g = float64(buf[1]) / 256
1584	b = float64(buf[2]) / 256
1585	r = r * maxRGB
1586	g = g * maxRGB
1587	b = b * maxRGB
1588	return Color{float32(r), float32(g), float32(b)}
1589}
1590
1591// Subtract Subtracts color
1592func (c Color) Subtract(d Color) Color {
1593	return Color{
1594		c.Red - d.Red,
1595		c.Green - d.Green,
1596		c.Blue - d.Blue,
1597	}
1598}
1599
1600// Add Adds color
1601func (c Color) Add(d Color) Color {
1602	return Color{
1603		c.Red + d.Red,
1604		c.Green + d.Green,
1605		c.Blue + d.Blue,
1606	}
1607}
1608
1609// MultiplyFloat Multiplies Float
1610func (c Color) MultiplyFloat(x float32) Color {
1611	return Color{
1612		c.Red * x,
1613		c.Green * x,
1614		c.Blue * x,
1615	}
1616}
1617
1618// GenerateGradient Generates Gradient
1619func GenerateGradient(numColors int, steps int) []Color {
1620	distribution := distributeColors(numColors, steps)
1621	colors := make([]Color, numColors)
1622	for i := 0; i < numColors; i++ {
1623		colors[i] = NewRandomColor()
1624	}
1625	outputBuffer := make([]Color, 0, steps)
1626	for index := 0; index < numColors; index++ {
1627		if index >= numColors-1 {
1628			size := steps - distribution[index]
1629			interpolation := NewColorInterpolation(colors[index-1], colors[index])
1630			buffer := generateSingleGradient(interpolation, size)
1631			outputBuffer = append(outputBuffer, buffer...)
1632			break
1633		}
1634		currentStep := distribution[index]
1635		nextStep := distribution[index+1]
1636		size := nextStep - currentStep
1637		interpolation := NewColorInterpolation(colors[index], colors[index+1])
1638		buffer := generateSingleGradient(interpolation, size)
1639		outputBuffer = append(outputBuffer, buffer...)
1640	}
1641	return outputBuffer
1642}
1643
1644func distributeColors(numColors int, steps int) []int {
1645	diff := int(m.Ceil(float64(steps) / float64(numColors)))
1646	output := make([]int, numColors)
1647	for i := 0; i < numColors; i++ {
1648		output[i] = diff * i
1649	}
1650	return output
1651}
1652
1653func generateSingleGradient(c ColorInterpolation, numSteps int) []Color {
1654	output := make([]Color, numSteps)
1655	for i := 0; i < numSteps; i++ {
1656		percent := float32(i) / float32(numSteps)
1657		output[i] = c.Interpolate(percent)
1658	}
1659	return output
1660}
1661
1662func f32(f float32, g byte, prec, bitSize int) string {
1663	return strconv.FormatFloat(float64(f), g, prec, bitSize)
1664}
1665func f64(f float64, g byte, prec, bitSize int) string {
1666	return strconv.FormatFloat(f, g, prec, bitSize)
1667}
1668
1669func generateSphereVertices(radius float32, stacks, slices int) ([]float32, []uint32) {
1670	var vertices []float32
1671	var indices []uint32
1672
1673	// Generate random initial rotation
1674	rotationMatrix := randomRotationMatrix()
1675
1676	// Generate sphere vertices
1677	for i := 0; i <= stacks; i++ {
1678		phi := float32(i) * float32(m.Pi) / float32(stacks)
1679		for j := 0; j <= slices; j++ {
1680			theta := float32(j) * 2.0 * float32(m.Pi) / float32(slices)
1681			x := radius * float32(m.Sin(float64(phi))) * float32(m.Cos(float64(theta)))
1682			y := radius * float32(m.Sin(float64(phi))) * float32(m.Sin(float64(theta)))
1683			z := radius * float32(m.Cos(float64(phi)))
1684
1685			// Apply rotation to the vertex
1686			vertex := mgl32.Vec3{z, y, x}
1687			rotatedVertex := rotationMatrix.Mul4x1(vertex.Vec4(1.0))
1688
1689			// Append rotated vertex
1690			vertices = append(vertices, rotatedVertex[0], rotatedVertex[1], rotatedVertex[2])
1691		}
1692	}
1693
1694	// Generate sphere indices
1695	for i := 0; i < stacks; i++ {
1696		for j := 0; j <= slices; j++ {
1697			indices = append(indices, uint32(i*(slices+1)+j), uint32((i+1)*(slices+1)+j))
1698		}
1699	}
1700
1701	return vertices, indices
1702}
1703
1704func randomRotationMatrix() mgl32.Mat4 {
1705	// Generate random rotation angles (in radians) using crypto/rand
1706	rotX := randomFloat32() * 2 * float32(m.Pi)
1707	rotY := randomFloat32() * 2 * float32(m.Pi)
1708	rotZ := randomFloat32() * 2 * float32(m.Pi)
1709
1710	// Create rotation matrices for each axis
1711	rotMatrixX := mgl32.HomogRotate3DX(rotX)
1712	rotMatrixY := mgl32.HomogRotate3DY(rotY)
1713	rotMatrixZ := mgl32.HomogRotate3DZ(rotZ)
1714
1715	// Combine rotations (Z * Y * X)
1716	return rotMatrixZ.Mul4(rotMatrixY).Mul4(rotMatrixX)
1717}
1718
1719func randomFloat32() float32 {
1720	var randomValue uint32
1721	err := binary.Read(rand.Reader, binary.BigEndian, &randomValue)
1722	if err != nil {
1723		log.Fatalf("Failed to read random value: %v", err)
1724	}
1725	return float32(randomValue) / float32(0xFFFFFFFF)
1726}
1727
1728var verticesNative = []float32{
1729	-1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1,
1730	-1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1,
1731	-1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1,
1732	1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1,
1733	-1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1, -1,
1734	-1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1,
1735}
1736var colorsNative = []float32{
1737	5, 3, 7, 5, 3, 7, 5, 3, 7, 5, 3, 7,
1738	1, 1, 3, 1, 1, 3, 1, 1, 3, 1, 1, 3,
1739	0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
1740	1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
1741	1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0,
1742	0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
1743}
1744
1745var indicesNative = []uint32{
1746	0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7,
1747	8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15,
1748	16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23,
1749}
1750
1751const vertShaderCode = `
1752attribute vec3 position;
1753uniform mat4 Pmatrix;
1754uniform mat4 Vmatrix;
1755uniform mat4 Mmatrix;
1756attribute vec3 color;
1757varying vec3 vColor;
1758
1759void main(void) {
1760	gl_Position = Pmatrix*Vmatrix*Mmatrix*vec4(position, 1.);
1761	vColor = color;
1762}
1763`
1764const fragShaderCode = `
1765precision mediump float;
1766varying vec3 vColor;
1767void main(void) {
1768	gl_FragColor = vec4(vColor, 1.);
1769}
1770`
1771const fragShaderCode1 = `
1772precision mediump float;
1773uniform vec3 uBaseColor; // Color value at the base
1774uniform vec3 uTopColor;  // Color value at the top
1775varying vec3 vPosition;  // Interpolated vertex position
1776void main(void) {
1777	float t = (vPosition.y + 1.0) * 0.5; // Normalize the y-coordinate to [0, 1]
1778	vec3 rainbowColor = mix(uBaseColor, uTopColor, t);
1779	gl_FragColor = vec4(rainbowColor, 1.0);
1780}
1781`
1782const vertShaderCode1 = `
1783	attribute vec3 position;
1784	uniform mat4 Pmatrix;
1785	uniform mat4 Vmatrix;
1786	uniform mat4 Mmatrix;
1787	varying vec3 vPosition;  // Pass vertex position to fragment shader
1788	void main(void) {
1789		gl_Position = Pmatrix * Vmatrix * Mmatrix * vec4(position, 1.0);
1790		vPosition = position;  // Pass vertex position to fragment shader
1791	}
1792	`
1793
1794// GLTypes provides WebGL bindings.
1795type GLTypes struct {
1796	StaticDraw         js.Value
1797	ArrayBuffer        js.Value
1798	ElementArrayBuffer js.Value
1799	VertexShader       js.Value
1800	FragmentShader     js.Value
1801	Float              js.Value
1802	DepthTest          js.Value
1803	ColorBufferBit     js.Value
1804	DepthBufferBit     js.Value
1805	Triangles          js.Value
1806	UnsignedShort      js.Value
1807	UnsignedInt        js.Value
1808	LEqual             js.Value
1809	LineLoop           js.Value
1810	Line               js.Value
1811}
1812
1813// New grabs the WebGL bindings from a GL context.
1814func (types *GLTypes) New(gl js.Value) js.Value {
1815	types.StaticDraw = gl.Get("STATIC_DRAW")
1816	types.ArrayBuffer = gl.Get("ARRAY_BUFFER")
1817	types.ElementArrayBuffer = gl.Get("ELEMENT_ARRAY_BUFFER")
1818	types.VertexShader = gl.Get("VERTEX_SHADER")
1819	types.FragmentShader = gl.Get("FRAGMENT_SHADER")
1820	types.Float = gl.Get("FLOAT")
1821	types.DepthTest = gl.Get("DEPTH_TEST")
1822	types.ColorBufferBit = gl.Get("COLOR_BUFFER_BIT")
1823	types.Triangles = gl.Get("TRIANGLES")
1824	types.UnsignedShort = gl.Get("UNSIGNED_SHORT")
1825	types.LEqual = gl.Get("LEQUAL")
1826	types.DepthBufferBit = gl.Get("DEPTH_BUFFER_BIT")
1827	types.LineLoop = gl.Get("LINE_LOOP")
1828	types.Line = gl.Get("LINES")
1829	enabled := gl.Call("getExtension", "OES_element_index_uint")
1830	if !enabled.Truthy() {
1831		return js.Global().Get("Error").New("missing extension: OES_element_index_uint")
1832	}
1833	types.UnsignedInt = gl.Get("UNSIGNED_INT")
1834	return js.Null()
1835}
1836
1837func sliceToByteSlice(s interface{}) []byte {
1838	switch s := s.(type) {
1839	case []int8:
1840		h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
1841		return *(*[]byte)(u.Pointer(h))      // nolint
1842	case []int16:
1843		h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
1844		h.Len *= 2
1845		h.Cap *= 2
1846		return *(*[]byte)(u.Pointer(h)) // nolint
1847	case []int32:
1848		h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
1849		h.Len *= 4
1850		h.Cap *= 4
1851		return *(*[]byte)(u.Pointer(h)) // nolint
1852	case []int64:
1853		h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
1854		h.Len *= 8
1855		h.Cap *= 8
1856		return *(*[]byte)(u.Pointer(h)) // nolint
1857	case []uint8:
1858		return s
1859	case []uint16:
1860		h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
1861		h.Len *= 2
1862		h.Cap *= 2
1863		return *(*[]byte)(u.Pointer(h)) // nolint
1864	case []uint32:
1865		h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
1866		h.Len *= 4
1867		h.Cap *= 4
1868		return *(*[]byte)(u.Pointer(h)) // nolint
1869	case []uint64:
1870		h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
1871		h.Len *= 8
1872		h.Cap *= 8
1873		return *(*[]byte)(u.Pointer(h)) // nolint
1874	case []float32:
1875		h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
1876		h.Len *= 4
1877		h.Cap *= 4
1878		return *(*[]byte)(u.Pointer(h)) // nolint
1879	case []float64:
1880		h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
1881		h.Len *= 8
1882		h.Cap *= 8
1883		return *(*[]byte)(u.Pointer(h)) // nolint
1884	default:
1885		//		panic("jsutil: unexpected value at sliceToBytesSlice: " + r.TypeOf(s).String())
1886		panic("jsutil: unexpected value at sliceToBytesSlice: ")
1887	}
1888}
1889
1890const bo = "byteOffset"
1891const bl = "byteLength"
1892const b = "buffer"
1893const u8a = "Uint8Array"
1894
1895// S2TA converts Slice To TypedArray
1896func S2TA(s interface{}) js.Value {
1897	switch s := s.(type) {
1898	case []int8:
1899		a := js.Global().Get(u8a).New(len(s))
1900		js.CopyBytesToJS(a, sliceToByteSlice(s))
1901		runtime.KeepAlive(s)
1902		buf := a.Get(b)
1903		return js.Global().Get("Int8Array").New(buf, a.Get(bo), a.Get(bl))
1904	case []int16:
1905		a := js.Global().Get(u8a).New(len(s) * 2)
1906		js.CopyBytesToJS(a, sliceToByteSlice(s))
1907		runtime.KeepAlive(s)
1908		buf := a.Get(b)
1909		return js.Global().Get("Int16Array").New(buf, a.Get(bo), a.Get(bl).Int()/2)
1910	case []int32:
1911		a := js.Global().Get(u8a).New(len(s) * 4)
1912		js.CopyBytesToJS(a, sliceToByteSlice(s))
1913		runtime.KeepAlive(s)
1914		buf := a.Get(b)
1915		return js.Global().Get("Int32Array").New(buf, a.Get(bo), a.Get(bl).Int()/4)
1916	case []uint8:
1917		a := js.Global().Get(u8a).New(len(s))
1918		js.CopyBytesToJS(a, s)
1919		runtime.KeepAlive(s)
1920		return a
1921	case []uint16:
1922		a := js.Global().Get(u8a).New(len(s) * 2)
1923		js.CopyBytesToJS(a, sliceToByteSlice(s))
1924		runtime.KeepAlive(s)
1925		buf := a.Get(b)
1926		return js.Global().Get("Uint16Array").New(buf, a.Get(bo), a.Get(bl).Int()/2)
1927	case []uint32:
1928		a := js.Global().Get(u8a).New(len(s) * 4)
1929		js.CopyBytesToJS(a, sliceToByteSlice(s))
1930		runtime.KeepAlive(s)
1931		buf := a.Get(b)
1932		return js.Global().Get("Uint32Array").New(buf, a.Get(bo), a.Get(bl).Int()/4)
1933	case []float32:
1934		a := js.Global().Get(u8a).New(len(s) * 4)
1935		js.CopyBytesToJS(a, sliceToByteSlice(s))
1936		runtime.KeepAlive(s)
1937		buf := a.Get(b)
1938		return js.Global().Get("Float32Array").New(buf, a.Get(bo), a.Get(bl).Int()/4)
1939	case []float64:
1940		a := js.Global().Get(u8a).New(len(s) * 8)
1941		js.CopyBytesToJS(a, sliceToByteSlice(s))
1942		runtime.KeepAlive(s)
1943		buf := a.Get(b)
1944		return js.Global().Get("Float64Array").New(buf, a.Get(bo), a.Get(bl).Int()/8)
1945	default:
1946		//		panic("jsutil: unexpected value at S2TA: " + r.TypeOf(s).String())
1947		panic("jsutil: unexpected value at S2TA: ")
1948	}
1949}
1950
1951