Skip to content

Commit 481e890

Browse files
committed
implement liveview native tutorial in express
1 parent fb4fbaa commit 481e890

File tree

10 files changed

+158
-92
lines changed

10 files changed

+158
-92
lines changed

packages/express/dist/liveviewjs-express.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ interface NodeExpressLiveViewServerOptions {
4343
fileSystemAdaptor?: FileSystemAdaptor;
4444
wrapperTemplate?: LiveViewWrapperTemplate;
4545
onError?: (err: any) => void;
46+
debug?: (msg: string) => void;
4647
}
4748
declare class NodeExpressLiveViewServer implements LiveViewServerAdaptor<RequestHandler, (wsServer: WebSocketServer) => Promise<void>> {
4849
#private;

packages/express/dist/liveviewjs-express.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ class NodeExpressLiveViewServer {
162162
flashAdaptor: this.flashAdapter,
163163
pubSub: this.pubSub,
164164
onError: options === null || options === void 0 ? void 0 : options.onError,
165+
debug: options === null || options === void 0 ? void 0 : options.debug,
165166
}, "f");
166167
}
167168
wsMiddleware() {

packages/express/dist/liveviewjs-express.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ class NodeExpressLiveViewServer {
152152
flashAdaptor: this.flashAdapter,
153153
pubSub: this.pubSub,
154154
onError: options === null || options === void 0 ? void 0 : options.onError,
155+
debug: options === null || options === void 0 ? void 0 : options.debug,
155156
}, "f");
156157
}
157158
wsMiddleware() {

packages/express/src/example/ios.ts

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
11
import express, { NextFunction, Request, Response } from "express";
22
import session, { MemoryStore } from "express-session";
33
import { Server } from "http";
4-
import { LiveViewRouter, SessionFlashAdaptor, SingleProcessPubSub } from "liveviewjs";
5-
import { nanoid } from "nanoid";
6-
import { NodeFileSystemAdatptor } from "src/node/fsAdaptor";
4+
import { LiveViewRouter } from "liveviewjs";
75
import { WebSocketServer } from "ws";
8-
import { NodeJwtSerDe } from "../node/jwtSerDe";
96
import { NodeExpressLiveViewServer } from "../node/server";
10-
import { NodeWsAdaptor } from "../node/wsAdaptor";
117
import { indexHandler } from "./indexHandler";
128
import { iosPageRenderer, iosRootRenderer } from "./iosRenderers";
13-
import { iosLiveView } from "./liveview/ios";
9+
import { catLive } from "./liveview/ios/cat";
10+
import { catListLive } from "./liveview/ios/catList";
1411

1512
// you'd want to set this to some secure, random string in production
1613
const signingSecret = "MY_VERY_SECRET_KEY";
1714

1815
// map request paths to LiveViews
1916
const router: LiveViewRouter = {
20-
"/ios": iosLiveView,
17+
"/cats": catListLive,
18+
"/cats/:cat": catLive,
2119
};
2220

2321
// configure your express app
@@ -59,13 +57,14 @@ app.use((req: Request, res: Response, next: NextFunction) => {
5957
// initialize the LiveViewServer
6058
const liveView = new NodeExpressLiveViewServer(
6159
router,
62-
new NodeJwtSerDe(signingSecret),
63-
new SingleProcessPubSub(),
6460
iosPageRenderer,
65-
{ title: "Express Demo", suffix: " · LiveViewJS" },
66-
new SessionFlashAdaptor(),
67-
new NodeFileSystemAdatptor(),
68-
iosRootRenderer
61+
{ title: "iOS Demo", suffix: " · LiveViewJS" },
62+
{
63+
serDeSigningSecret: signingSecret,
64+
wrapperTemplate: iosRootRenderer,
65+
onError: (err) => console.error(err),
66+
debug: (msg) => console.log(msg),
67+
}
6968
);
7069

7170
// setup the LiveViewJS middleware
@@ -84,20 +83,8 @@ const wsServer = new WebSocketServer({
8483
httpServer.on("request", app);
8584

8685
// initialize the LiveViewJS websocket message router
87-
const liveViewRouter = liveView.wsRouter();
88-
89-
// send websocket requests to the LiveViewJS message router
90-
wsServer.on("connection", (ws) => {
91-
const connectionId = nanoid();
92-
ws.on("message", async (message, isBinary) => {
93-
// pass websocket messages to LiveViewJS
94-
await liveViewRouter.onMessage(connectionId, message, new NodeWsAdaptor(ws), isBinary);
95-
});
96-
ws.on("close", async () => {
97-
// pass websocket close events to LiveViewJS
98-
await liveViewRouter.onClose(connectionId);
99-
});
100-
});
86+
const liveViewWsMiddleware = liveView.wsMiddleware();
87+
liveViewWsMiddleware(wsServer);
10188

10289
// listen for requests
10390
const port = process.env.PORT || 4001;

packages/express/src/example/iosRenderers.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import {
22
html,
3-
LiveViewPageRenderer,
4-
LiveViewRootRenderer,
3+
LiveTitleOptions,
4+
LiveViewHtmlPageTemplate,
55
LiveViewTemplate,
6+
LiveViewWrapperTemplate,
67
live_title_tag,
7-
PageTitleDefaults,
88
safe,
99
SessionData,
1010
SessionFlashAdaptor,
@@ -14,19 +14,19 @@ import {
1414
* Render function for the "root" of the LiveView. Expected that this function will
1515
* embed the LiveView inside and contain the necessary HTML tags to make the LiveView
1616
* work including the client javascript.
17-
* @param pageTitleDefaults the PageTitleDefauls that should be used for the title tag especially if it is a `live_title_tag`
17+
* @param liveTitleOptions the PageTitleDefauls that should be used for the title tag especially if it is a `live_title_tag`
1818
* @param csrfToken the CSRF token value that should be embedded into a <meta/> tag named "csrf-token". LiveViewJS uses this to validate socket requests
1919
* @param liveViewContent the content rendered by the LiveView
2020
* @returns a LiveViewTemplate that can be rendered by the LiveViewJS server
2121
*/
22-
export const iosPageRenderer: LiveViewPageRenderer = (
23-
pageTitleDefaults: PageTitleDefaults,
22+
export const iosPageRenderer: LiveViewHtmlPageTemplate = (
23+
liveTitleOptions: LiveTitleOptions,
2424
csrfToken: string,
2525
liveViewContent: LiveViewTemplate
2626
): LiveViewTemplate => {
27-
const pageTitle = pageTitleDefaults?.title ?? "";
28-
const pageTitlePrefix = pageTitleDefaults?.prefix ?? "";
29-
const pageTitleSuffix = pageTitleDefaults?.suffix ?? "";
27+
const pageTitle = liveTitleOptions?.title ?? "";
28+
const pageTitlePrefix = liveTitleOptions?.prefix ?? "";
29+
const pageTitleSuffix = liveTitleOptions?.suffix ?? "";
3030
return html`
3131
<!DOCTYPE html>
3232
<html lang="en">
@@ -54,7 +54,7 @@ export const iosPageRenderer: LiveViewPageRenderer = (
5454
* @param liveViewContent the content rendered by the LiveView
5555
* @returns a LiveViewTemplate to be embedded in the root template
5656
*/
57-
export const iosRootRenderer: LiveViewRootRenderer = async (
57+
export const iosRootRenderer: LiveViewWrapperTemplate = async (
5858
session: SessionData,
5959
liveViewContent: LiveViewTemplate
6060
): Promise<LiveViewTemplate> => {

packages/express/src/example/liveview/ios.ts

Lines changed: 0 additions & 55 deletions
This file was deleted.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { createLiveView, html } from "liveviewjs";
2+
import { favorites, scores } from "./data";
3+
4+
type CatCtx = {
5+
cat: string;
6+
fav: boolean;
7+
score: number;
8+
};
9+
10+
type CatEvent =
11+
| {
12+
type: "change-score";
13+
value: number;
14+
}
15+
| {
16+
type: "toggle-favorite";
17+
};
18+
19+
// LiveView Native Tutorial -
20+
// https://liveviewnative.github.io/liveview-client-swiftui/tutorials/phoenixliveviewnative/01-initial-list/
21+
export const catLive = createLiveView<CatCtx, CatEvent>({
22+
mount: (socket, _, params) => {
23+
console.log("params", params);
24+
const cat = params.cat as string;
25+
const fav = favorites[cat] ?? false;
26+
const score = scores[cat] ?? 0;
27+
socket.assign({ cat, fav, score });
28+
},
29+
handleEvent: (event, socket) => {
30+
console.log("event", event);
31+
switch (event.type) {
32+
case "change-score":
33+
scores.cat = event.value;
34+
socket.assign({ score: event.value });
35+
break;
36+
case "toggle-favorite":
37+
favorites.cat = !favorites.cat;
38+
socket.assign({ fav: favorites.cat });
39+
break;
40+
}
41+
},
42+
render: (ctx) => {
43+
const { cat, fav, score } = ctx;
44+
return html`
45+
<vstack navigation-title="${cat}" nav-favorite="${fav}">
46+
<asyncimage src="/images/cats/${cat}.jpg" frame='{"width": 300, "height": 300}' />
47+
<cat-rating score="${score}" />
48+
</vstack>
49+
`;
50+
},
51+
});
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { createLiveView, html } from "liveviewjs";
2+
import { cats, favorites } from "./data";
3+
4+
// LiveView Native Tutorial -
5+
// https://liveviewnative.github.io/liveview-client-swiftui/tutorials/phoenixliveviewnative/01-initial-list/
6+
export const catListLive = createLiveView({
7+
mount: (socket) => {
8+
socket.assign({ cats });
9+
},
10+
handleEvent: (event, socket) => {
11+
console.log("event", event);
12+
switch (event.type) {
13+
case "toggle-favorite":
14+
favorites[event.name] = !favorites[event.name];
15+
break;
16+
}
17+
},
18+
render: (_, meta) => {
19+
return html` <list navigation-title="Cats!">
20+
${cats.map((cat) => {
21+
return html`
22+
<navigationlink
23+
id="${cat}"
24+
data-phx-link="redirect"
25+
data-phx-link-state="push"
26+
data-phx-href="${buildHref({ to: { path: "/cats/" + cat } })}">
27+
<hstack>
28+
<asyncimage src="/images/cats/${cat}.jpg" frame-width="100" frame-height="100" />
29+
<text>${cat}</text>
30+
<spacer />
31+
<button phx-click="toggle-favorite" phx-value-name=${cat}>
32+
<image
33+
system-name=${favorites[cat] ? "star.fill" : "star"}
34+
symbol-color=${favorites[cat] ? "#f3c51a" : "#000000"} />
35+
</button>
36+
</hstack>
37+
</navigationlink>
38+
`;
39+
})}
40+
</list>`;
41+
},
42+
});
43+
44+
function buildHref(options: {
45+
to: {
46+
path: string;
47+
params?: Record<string, string>;
48+
};
49+
}) {
50+
const { path, params } = options.to;
51+
const urlParams = new URLSearchParams(params);
52+
if (urlParams.toString().length > 0) {
53+
return `${path}?${urlParams.toString()}`;
54+
} else {
55+
return path;
56+
}
57+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export const cats = [
2+
"Clenil",
3+
"Flippers",
4+
"Jorts",
5+
"Kipper",
6+
"Lemmy",
7+
"Lissy",
8+
"Mikkel",
9+
"Minka",
10+
"Misty",
11+
"Nelly",
12+
"Ninj",
13+
"Pollito",
14+
"Siegfried",
15+
"Truman",
16+
"Washy",
17+
];
18+
19+
export const favorites: Record<string, boolean> = {};
20+
21+
export const scores: Record<string, number> = {};

packages/express/src/node/server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ interface NodeExpressLiveViewServerOptions {
3232
fileSystemAdaptor?: FileSystemAdaptor;
3333
wrapperTemplate?: LiveViewWrapperTemplate;
3434
onError?: (err: any) => void;
35+
debug?: (msg: string) => void;
3536
}
3637

3738
export class NodeExpressLiveViewServer
@@ -69,6 +70,7 @@ export class NodeExpressLiveViewServer
6970
flashAdaptor: this.flashAdapter,
7071
pubSub: this.pubSub,
7172
onError: options?.onError,
73+
debug: options?.debug,
7274
};
7375
}
7476

0 commit comments

Comments
 (0)