hot reload, search refactor begin

This commit is contained in:
Simon Martens
2025-02-25 19:23:00 +01:00
parent f35c738cee
commit 4d65b71563
24 changed files with 706 additions and 202 deletions

View File

@@ -5,20 +5,64 @@ import (
"html/template"
"io"
"io/fs"
"log"
"net/http"
"strings"
"sync"
"github.com/Theodor-Springmann-Stiftung/musenalm/helpers/functions"
"github.com/pocketbase/pocketbase/core"
"golang.org/x/net/websocket"
)
const (
ASSETS_URL_PREFIX = "/assets"
RELOAD_TEMPLATE = `
<script type="module">
(function () {
let relto = -1;
const scheme = location.protocol === "https:" ? "wss" : "ws";
// Hardcode port 9000 here:
const url = scheme + "://" + location.hostname + ":9000/pb/reload";
function connect() {
const socket = new WebSocket(url);
socket.addEventListener("open", function () {
console.log("Reload socket connected (port 9000).");
});
socket.addEventListener("message", function (evt) {
if (evt.data === "reload") {
console.log("Received reload signal. Reloading...");
if (relto !== -1) clearTimeout(relto);
relto = setTimeout(() => location.reload(), 0);
}
});
socket.addEventListener("close", function () {
console.log("Reload socket closed. Reconnecting in 3 seconds...");
setTimeout(connect, 3000);
});
socket.addEventListener("error", function (err) {
console.error("Reload socket error:", err);
// We'll let onclose handle reconnection.
});
}
// Initiate the first connection attempt.
connect();
})();
</script>
`
)
type Engine struct {
regmu *sync.Mutex
regmu *sync.Mutex
debug bool
ws *WsServer
onceWS sync.Once
// NOTE: LayoutRegistry and TemplateRegistry have their own syncronization & cache and do not require a mutex here
LayoutRegistry *LayoutRegistry
@@ -44,6 +88,26 @@ func NewEngine(layouts, templates *fs.FS) *Engine {
return &e
}
func (e *Engine) Debug() {
e.debug = true
e.onceWS.Do(func() {
e.ws = NewWsServer()
go e.startWsServerOnPort9000()
})
}
func (e *Engine) startWsServerOnPort9000() {
// We'll create a basic default mux here and mount /pb/reload
mux := http.NewServeMux()
mux.Handle("/pb/reload", websocket.Handler(e.ws.Handler))
log.Println("[Engine Debug] Starting separate WebSocket server on :9000 for live reload...")
if err := http.ListenAndServe(":9000", mux); err != nil {
log.Println("[Engine Debug] WebSocket server error:", err)
}
}
func (e *Engine) funcs() error {
e.mu.Lock()
e.mu.Unlock()
@@ -113,6 +177,12 @@ func (e *Engine) Reload() {
e.Load()
}
func (e *Engine) Refresh() {
if e.debug && e.ws != nil {
e.ws.BroadcastReload()
}
}
// INFO: fn is a function that returns either one value or two values, the second one being an error
func (e *Engine) AddFunc(name string, fn interface{}) {
e.mu.Lock()
@@ -234,7 +304,15 @@ func (e *Engine) Response200(request *core.RequestEvent, path string, ld map[str
return e.Response500(request, err, ld)
}
return request.HTML(http.StatusOK, builder.String())
tstring := builder.String()
if e.debug {
idx := strings.LastIndex(tstring, "</body>")
if idx != -1 {
tstring = tstring[:idx] + RELOAD_TEMPLATE + tstring[idx:]
}
}
return request.HTML(http.StatusOK, tstring)
}
func requestData(request *core.RequestEvent) map[string]interface{} {

57
templating/ws.go Normal file
View File

@@ -0,0 +1,57 @@
package templating
import (
"log"
"sync"
"golang.org/x/net/websocket"
)
// WsServer manages all active WebSocket connections so we can broadcast.
type WsServer struct {
mu sync.Mutex
conns map[*websocket.Conn]bool
}
// NewWsServer creates a WsServer.
func NewWsServer() *WsServer {
return &WsServer{
conns: make(map[*websocket.Conn]bool),
}
}
// Handler is invoked for each new WebSocket connection.
func (s *WsServer) Handler(conn *websocket.Conn) {
s.mu.Lock()
s.conns[conn] = true
s.mu.Unlock()
log.Println("[WsServer] Connected:", conn.RemoteAddr())
// Read in a loop until an error (client disconnect).
var msg string
for {
if err := websocket.Message.Receive(conn, &msg); err != nil {
log.Println("[WsServer] Disconnected:", conn.RemoteAddr())
s.mu.Lock()
delete(s.conns, conn)
s.mu.Unlock()
conn.Close()
return
}
}
}
// BroadcastReload sends a "reload" message to all connected clients.
func (s *WsServer) BroadcastReload() {
s.mu.Lock()
defer s.mu.Unlock()
for conn := range s.conns {
err := websocket.Message.Send(conn, "reload")
if err != nil {
log.Println("[WsServer] Broadcast error:", err)
conn.Close()
delete(s.conns, conn)
}
}
}