mirror of
https://github.com/Dvorinka/excalidraw-full.git
synced 2026-06-03 13:52:56 +00:00
Add webUI into binary
To get the most simple deployment of excalidraw, the binary should ship all components (webUI, socket.io, data storage).
This commit is contained in:
+3
-1
@@ -1,2 +1,4 @@
|
|||||||
dist/
|
dist/
|
||||||
excalidraw-backend
|
frontend/
|
||||||
|
!frontend/.keep
|
||||||
|
excalidraw-complete
|
||||||
|
|||||||
@@ -46,6 +46,3 @@ changelog:
|
|||||||
- "^docs:"
|
- "^docs:"
|
||||||
- "^test:"
|
- "^test:"
|
||||||
|
|
||||||
release:
|
|
||||||
disable: true
|
|
||||||
skip_upload: true
|
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
# Exalidraw Backend
|
# Exalidraw Complete
|
||||||
|
|
||||||
Frustrated on how difficult it is to setup excalidraw self-hosted but with data
|
Frustrated on how difficult it is to setup excalidraw self-hosted but with data
|
||||||
storage and collaboration function this represents and attempt to run the
|
storage and collaboration function this represents and attempt to run the
|
||||||
necessary function with a single binary implemented in go.
|
necessary function with a single binary implemented in go. This includes:
|
||||||
|
|
||||||
Apply the patch to the frontend and build excalidraw. Run Excalidraw frontend and
|
- the frontend UI
|
||||||
on the same host run
|
- a in-memory data layer
|
||||||
|
- socket.io implementation for collaboration
|
||||||
|
|
||||||
|
Apply the patch to the frontend and build excalidraw into `frontend`. Run
|
||||||
```bash
|
```bash
|
||||||
go run main.go
|
go run main.go
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Everything will be served under `localhost:3002`
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package memory
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"excalidraw-backend/core"
|
"excalidraw-complete/core"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/oklog/ulid/v2"
|
"github.com/oklog/ulid/v2"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
module excalidraw-backend
|
module excalidraw-complete
|
||||||
|
|
||||||
go 1.21
|
go 1.21
|
||||||
|
|
||||||
|
|||||||
@@ -2,20 +2,24 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"excalidraw-backend/core"
|
"embed"
|
||||||
"excalidraw-backend/documents/memory"
|
_ "embed"
|
||||||
|
"excalidraw-complete/core"
|
||||||
|
"excalidraw-complete/documents/memory"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
"github.com/go-chi/cors"
|
"github.com/go-chi/cors"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
"github.com/oklog/ulid/v2"
|
|
||||||
"github.com/zishang520/engine.io/v2/types"
|
"github.com/zishang520/engine.io/v2/types"
|
||||||
socketio "github.com/zishang520/socket.io/v2/socket"
|
socketio "github.com/zishang520/socket.io/v2/socket"
|
||||||
)
|
)
|
||||||
@@ -35,6 +39,35 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed all:frontend
|
||||||
|
var assets embed.FS
|
||||||
|
|
||||||
|
func Assets() (fs.FS, error) {
|
||||||
|
return fs.Sub(assets, "frontend")
|
||||||
|
}
|
||||||
|
|
||||||
|
type FrontEndHandler struct{}
|
||||||
|
|
||||||
|
func (h FrontEndHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
frontendHandler(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func frontendHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
upath := r.URL.Path
|
||||||
|
if !strings.HasPrefix(upath, "/") {
|
||||||
|
upath = "/" + upath
|
||||||
|
r.URL.Path = upath
|
||||||
|
}
|
||||||
|
upath = path.Clean(upath)
|
||||||
|
|
||||||
|
sub, err := fs.Sub(assets, "frontend")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
http.FileServer(http.FS(sub)).ServeHTTP(w, r)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
opts := socketio.DefaultServerOptions()
|
opts := socketio.DefaultServerOptions()
|
||||||
opts.SetMaxHttpBufferSize(5000000)
|
opts.SetMaxHttpBufferSize(5000000)
|
||||||
@@ -53,24 +86,23 @@ func main() {
|
|||||||
room := socketio.Room(datas[0].(string))
|
room := socketio.Room(datas[0].(string))
|
||||||
fmt.Printf("Socket %v has joined %v\n", me, room)
|
fmt.Printf("Socket %v has joined %v\n", me, room)
|
||||||
socket.Join(room)
|
socket.Join(room)
|
||||||
ioo.In(room).FetchSockets()(func(sockets []*socketio.RemoteSocket, _ error) {
|
ioo.In(room).FetchSockets()(func(usersInRoom []*socketio.RemoteSocket, _ error) {
|
||||||
|
if len(usersInRoom) <= 1 {
|
||||||
if len(sockets) <= 1 {
|
ioo.To(socketio.Room(me)).Emit("first-in-room")
|
||||||
ioo.To(socketio.Room(socket.Id())).Emit("first-in-room")
|
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("emit new user %v in room %v\n", me, room)
|
fmt.Printf("emit new user %v in room %v\n", me, room)
|
||||||
socket.Broadcast().To(room).Emit("new-user", me)
|
socket.Broadcast().To(room).Emit("new-user", me)
|
||||||
}
|
}
|
||||||
|
|
||||||
data := []socketio.SocketId{}
|
// Inform all clients by new users.
|
||||||
for _, osocket := range sockets {
|
newRoomUsers := []socketio.SocketId{}
|
||||||
data = append(data, osocket.Id())
|
for _, user := range usersInRoom {
|
||||||
|
newRoomUsers = append(newRoomUsers, user.Id())
|
||||||
}
|
}
|
||||||
fmt.Printf(" room %v has users %v\n", room, data)
|
fmt.Printf(" room %v has users %v\n", room, newRoomUsers)
|
||||||
|
|
||||||
ioo.In(room).Emit(
|
ioo.In(room).Emit(
|
||||||
"room-user-change",
|
"room-user-change",
|
||||||
data,
|
newRoomUsers,
|
||||||
)
|
)
|
||||||
|
|
||||||
})
|
})
|
||||||
@@ -91,20 +123,22 @@ func main() {
|
|||||||
|
|
||||||
})
|
})
|
||||||
socket.On("disconnecting", func(datas ...any) {
|
socket.On("disconnecting", func(datas ...any) {
|
||||||
for _, oroom := range socket.Rooms().Keys() {
|
for _, currentRoom := range socket.Rooms().Keys() {
|
||||||
ioo.In(oroom).FetchSockets()(func(sockets []*socketio.RemoteSocket, _ error) {
|
ioo.In(currentRoom).FetchSockets()(func(usersInRoom []*socketio.RemoteSocket, _ error) {
|
||||||
otherClients := []socketio.SocketId{}
|
allUsers := []socketio.SocketId{}
|
||||||
fmt.Printf("disconnecting %v from room %v", me, oroom)
|
remainingUsers := []socketio.SocketId{}
|
||||||
for _, osocket := range sockets {
|
fmt.Printf("disconnecting %v from room %v\n", me, currentRoom)
|
||||||
if osocket.Id() != me {
|
for _, userInRoom := range usersInRoom {
|
||||||
otherClients = append(otherClients, osocket.Id())
|
allUsers = append(allUsers, userInRoom.Id())
|
||||||
fmt.Println("other", osocket.Id())
|
if userInRoom.Id() != me {
|
||||||
|
remainingUsers = append(remainingUsers, userInRoom.Id())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(otherClients) > 0 {
|
if len(remainingUsers) > 0 {
|
||||||
ioo.In(oroom).Emit(
|
fmt.Printf("leaving user, room %v has users %v -> %v\n", currentRoom, allUsers, remainingUsers)
|
||||||
|
ioo.In(currentRoom).Emit(
|
||||||
"room-user-change",
|
"room-user-change",
|
||||||
otherClients,
|
remainingUsers,
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -133,11 +167,7 @@ func main() {
|
|||||||
|
|
||||||
documentStore := memory.NewDocumentStore()
|
documentStore := memory.NewDocumentStore()
|
||||||
|
|
||||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
r.Mount("/", FrontEndHandler{})
|
||||||
w.Write([]byte("you are all set"))
|
|
||||||
fmt.Println(ulid.Make())
|
|
||||||
render.Status(r, http.StatusOK)
|
|
||||||
})
|
|
||||||
|
|
||||||
r.Route("/api/v2", func(r chi.Router) {
|
r.Route("/api/v2", func(r chi.Router) {
|
||||||
r.Post("/post/", func(w http.ResponseWriter, r *http.Request) {
|
r.Post("/post/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -171,6 +201,7 @@ func main() {
|
|||||||
|
|
||||||
r.Handle("/socket.io/", ioo.ServeHandler(nil))
|
r.Handle("/socket.io/", ioo.ServeHandler(nil))
|
||||||
go http.ListenAndServe(":3002", r)
|
go http.ListenAndServe(":3002", r)
|
||||||
|
fmt.Println("listen on 3002")
|
||||||
|
|
||||||
exit := make(chan struct{})
|
exit := make(chan struct{})
|
||||||
SignalC := make(chan os.Signal)
|
SignalC := make(chan os.Signal)
|
||||||
|
|||||||
Reference in New Issue
Block a user