-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,8 @@ | ||
{ | ||
"routes": [{ "src": "/(.*)", "dest": "/api" }] | ||
"routes": [{ "src": "/(.*)", "dest": "/api" }], | ||
"functions": { | ||
"api/index.go": { | ||
"maxDuration": "30" | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,7 @@ templ Wrapper(title string, className ...string) { | |
/> | ||
<script src="https://unpkg.com/[email protected]"></script> | ||
<script src="https://unpkg.com/[email protected]/dist/ext/class-tools.js"></script> | ||
<script src="https://unpkg.com/[email protected]/dist/ext/sse.js"></script> | ||
<link | ||
rel="stylesheet" | ||
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css" | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package chatroom | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"slices" | ||
) | ||
|
||
const EndEvent = "end" | ||
const ChatEvent = "chat" | ||
|
||
type Chat struct { | ||
Message string | ||
} | ||
|
||
type Chatroom struct { | ||
source chan Chat | ||
listeners []chan Chat | ||
addListener chan chan Chat | ||
removeListener chan (<-chan Chat) | ||
} | ||
|
||
func New(ctx context.Context) *Chatroom { | ||
c := &Chatroom{ | ||
source: make(chan Chat), | ||
listeners: []chan Chat{}, | ||
addListener: make(chan chan Chat), | ||
removeListener: make(chan (<-chan Chat)), | ||
} | ||
|
||
go c.run(ctx) | ||
|
||
return c | ||
} | ||
|
||
func (c *Chatroom) Join() <-chan Chat { | ||
newListener := make(chan Chat) | ||
c.addListener <- newListener | ||
return newListener | ||
} | ||
|
||
func (c *Chatroom) Leave(channel <-chan Chat) { | ||
fmt.Println("removing listener") | ||
c.removeListener <- channel | ||
} | ||
|
||
func (c *Chatroom) Send(message string) { | ||
c.source <- Chat{Message: message} | ||
} | ||
|
||
func (c *Chatroom) run(ctx context.Context) { | ||
defer func() { | ||
for _, listener := range c.listeners { | ||
if listener != nil { | ||
close(listener) | ||
} | ||
} | ||
}() | ||
|
||
for { | ||
select { | ||
case <-ctx.Done(): | ||
fmt.Println("closing chatroom") | ||
return | ||
case newListener := <-c.addListener: | ||
c.listeners = append(c.listeners, newListener) | ||
case toRemove := <-c.removeListener: | ||
c.listeners = slices.DeleteFunc(c.listeners, func(l chan Chat) bool { | ||
if l == toRemove { | ||
fmt.Println("removed listener") | ||
return true | ||
} | ||
return false | ||
}) | ||
case chat, ok := <-c.source: | ||
if !ok { | ||
return | ||
} | ||
for _, listener := range c.listeners { | ||
listener <- chat | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package extempl | ||
|
||
import ( | ||
"embed" | ||
|
||
"github.com/will-wow/typed-htmx-go/examples/web/layout/templ/layout" | ||
"github.com/will-wow/typed-htmx-go/examples/web/exprint" | ||
"github.com/will-wow/typed-htmx-go/htmx/ext/sse" | ||
"github.com/will-wow/typed-htmx-go/htmx" | ||
"github.com/will-wow/typed-htmx-go/htmx/swap" | ||
"github.com/will-wow/typed-htmx-go/examples/web/sse_ex/chatroom" | ||
"github.com/will-wow/typed-htmx-go/htmx/on" | ||
) | ||
|
||
var hx = htmx.NewTempl() | ||
|
||
//go:embed sse.templ | ||
var fs embed.FS | ||
var ex = exprint.New(fs, "//", "") | ||
|
||
templ Page() { | ||
@layout.Wrapper("Server-Side Events") { | ||
<h1>Server-Side Events</h1> | ||
<p> | ||
A demo of Server-Side Events using htmx and typed-htmx-go. | ||
</p> | ||
<p> | ||
When you click the button below, that fetches a new element that uses the sse extension to start a live feed. | ||
</p> | ||
<pre> | ||
<code class="language-go"> | ||
{ ex.PrintOrErr("sse.templ", "entry") } | ||
</code> | ||
</pre> | ||
<p> | ||
The new elements uses the sse.Connect attribute to connect to a server-side event stream, and allows you to POST messages to the stream. | ||
</p> | ||
<pre> | ||
<code class="language-go"> | ||
{ ex.PrintOrErr("sse.templ", "chatroom") } | ||
</code> | ||
</pre> | ||
<p> | ||
When you or another user posts a message, it will be sent to the server and broadcast to all connected clients as a simple div. | ||
</p> | ||
<pre> | ||
<code class="language-go"> | ||
{ ex.PrintOrErr("sse.templ", "message") } | ||
</code> | ||
</pre> | ||
<p> | ||
After 25 seconds, the server will send the EndEvent, that closes removes the sse connection by replacing the sse.Connect element with the initial button using <code>hx.Swap(swap.OuterHTML)</code>. | ||
</p> | ||
<h2>Demo</h2> | ||
@Entry() | ||
} | ||
} | ||
|
||
templ Entry() { | ||
//ex:start:entry | ||
<button | ||
{ hx.Get("/examples/templ/sse/chatroom/")... } | ||
{ hx.Target(htmx.TargetThis)... } | ||
{ hx.Swap(swap.OuterHTML)... } | ||
> | ||
Enter Chat | ||
</button> | ||
//ex:end:entry | ||
} | ||
|
||
templ Chatroom() { | ||
//ex:start:chatroom | ||
<div | ||
{ hx.Ext(sse.Extension)... } | ||
{ sse.Connect(hx, "/examples/templ/sse/chatroom/feed/")... } | ||
{ sse.Swap(hx, chatroom.EndEvent)... } | ||
{ hx.Swap(swap.OuterHTML)... } | ||
> | ||
<form | ||
id="form" | ||
method="POST" | ||
action="/examples/templ/sse/chatroom/post/" | ||
{ hx.On(on.AfterRequest, "this.querySelector('#message').value = ''")... } | ||
> | ||
<label> | ||
Send a message | ||
<input | ||
id="message" | ||
type="text" | ||
name="message" | ||
/> | ||
</label> | ||
<button type="submit">Send</button> | ||
</form> | ||
<div | ||
{ sse.Swap(hx, chatroom.ChatEvent)... } | ||
{ hx.Swap(swap.BeforeEnd)... } | ||
></div> | ||
</div> | ||
//ex:end:chatroom | ||
} | ||
|
||
//ex:start:message | ||
templ ChatMessage(msg string) { | ||
<div>{ msg }</div> | ||
} | ||
|
||
//ex:end:message |