Skip to content

Commit

Permalink
refactored a bit; supporting phx-change for forms
Browse files Browse the repository at this point in the history
  • Loading branch information
floodfx committed Jan 22, 2022
1 parent 9c1842b commit 5d25d62
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 46 deletions.
32 changes: 9 additions & 23 deletions src/server/live/license_liveview.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import escapeHtml from "../liveview/templates";
import { LiveViewComponent, LiveViewContext, LiveViewExternalEventListener } from "../liveview/types";
import { LightContext } from "./light_liveview";

export interface LicenseContext {
seats: number;
Expand All @@ -9,9 +10,8 @@ export interface LicenseContext {
const _db: { [key: string]: LicenseContext } = {};

export class LicenseLiveViewComponent implements
LiveViewComponent<LicenseContext>
// LiveViewExternalEventListener<LicenseContext, "on">,
// LiveViewExternalEventListener<LicenseContext, "off">
LiveViewComponent<LicenseContext>,
LiveViewExternalEventListener<LicenseContext, "update", Pick<LicenseContext, "seats">>
{


Expand Down Expand Up @@ -52,26 +52,12 @@ export class LicenseLiveViewComponent implements
`
};

// handleEvent(event: PocEvent, params: any, socket: any) {
// const ctx = _db[socket.id];
// console.log("event:", event, socket, ctx);
// switch (event) {
// case 'off':
// ctx.brightness = 0;
// break;
// case 'on':
// ctx.brightness = 100;
// break;
// case 'up':
// ctx.brightness = Math.min(ctx.brightness + 10, 100);
// break;
// case 'down':
// ctx.brightness = Math.max(ctx.brightness - 10, 0);
// break;
// }
// _db[socket.id] = ctx;
// return { data: ctx };
// }
handleEvent(event: "update", params: {seats: number}, socket: any) {
console.log("event:", event, params, socket);
const { seats } = params;
const amount = calculateLicenseAmount(seats);
return { data: { seats, amount} };
}

}

Expand Down
4 changes: 2 additions & 2 deletions src/server/live/light_liveview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ const _db: { [key: string]: LightContext } = {};

export class LightLiveViewComponent implements
LiveViewComponent<LightContext>,
LiveViewExternalEventListener<LightContext, "on">,
LiveViewExternalEventListener<LightContext, "off"> {
LiveViewExternalEventListener<LightContext, "on", any>,
LiveViewExternalEventListener<LightContext, "off", any> {


mount(params: any, session: any, socket: any) {
Expand Down
4 changes: 2 additions & 2 deletions src/server/liveview/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export interface LiveViewComponent<T> {

}

export interface LiveViewExternalEventListener<T, E extends string> {
handleEvent: (event: Lowercase<E>, params: any, socket: any) => LiveViewContext<T>;
export interface LiveViewExternalEventListener<T, E extends string, P> {
handleEvent: (event: Lowercase<E>, params: P, socket: any) => LiveViewContext<T>;
}

export interface LiveViewRouter {
Expand Down
37 changes: 28 additions & 9 deletions src/server/socket/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ export enum PhxSocketProtocolNames {
payload
}

export type PhxSocketProtocol<Payload> = [
export type PhxIncomingMessage<Payload> = [
joinRef: string | null, // number
messageRef: string, // number
topic: "phoenix" | string,
event: "phx_join" | "event" | "heartbeat",
payload: Payload
]

export type PhxOutgoingMessage<Payload> = [
joinRef: string | null, // number
messageRef: string, // number
topic: "phoenix" | string,
Expand All @@ -21,7 +29,8 @@ export interface PhxJoinPayload {
url: string
}

export type PhxJoin = PhxSocketProtocol<PhxJoinPayload>;
export type PhxJoinIncoming = PhxIncomingMessage<PhxJoinPayload>;
export type PhxHeartbeatIncoming = PhxIncomingMessage<{}>;

export type Dynamics = { [key: number]: string | Dynamics }

Expand All @@ -35,20 +44,30 @@ export interface PhxReplyPayload {
status: "ok"
}

export type PhxReply = PhxSocketProtocol<PhxReplyPayload>;
export type PhxReply = PhxOutgoingMessage<PhxReplyPayload>;

export interface PhxEventPayload<T> {
type: string,
export interface PhxEventPayload<Type extends string,Value> {
type: Type,
event: string,
value: T
value: Value
}

export type PhxEvent<T> = PhxSocketProtocol<PhxEventPayload<T>>
export interface PhxEventUploads {
uploads: { [key: string]: unknown }
}

//{type: "click", event: "down", value: {value: ""}}
export type PhxClickEvent = PhxEvent<{ value: { value: string } }>
export type PhxClickPayload = PhxEventPayload<"click",{ value: { value: string } }>;

//{"type":"form","event":"update","value":"seats=3&_target=seats","uploads":{}}
export type PhxFormPayload = PhxEventPayload<"form",{ value: string }> & PhxEventUploads;


export type PhxClickEvent = PhxIncomingMessage<PhxClickPayload>
export type PhxFormEvent = PhxIncomingMessage<PhxFormPayload>


export const newHeartbeatReply = (incoming: PhxSocketProtocol<{}>): PhxReply => {
export const newHeartbeatReply = (incoming: PhxIncomingMessage<{}>): PhxReply => {
return [
null,
incoming[PhxSocketProtocolNames.messageRef],
Expand Down
94 changes: 84 additions & 10 deletions src/server/socket/websocket_server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { PhxJoin, PhxEvent, PhxReply, PhxSocketProtocolNames, RenderedNode, PhxSocketProtocol, newHeartbeatReply } from './types';
import { PhxReply, PhxSocketProtocolNames, RenderedNode, PhxOutgoingMessage, newHeartbeatReply, PhxJoinIncoming, PhxHeartbeatIncoming, PhxClickEvent, PhxFormEvent, PhxIncomingMessage, PhxClickPayload, PhxFormPayload } from './types';
import ws from 'ws';
import { router } from '../live/router';
import qs from 'querystring';
import { URLSearchParams } from 'url';

const wsServer = new ws.Server({
port: 3003,
Expand All @@ -18,24 +20,36 @@ wsServer.on('connection', socket => {
// console.log("message", stringMsg);

// parse string to JSON
const rawPhxMessage = JSON.parse(stringMsg);
const rawPhxMessage: PhxIncomingMessage<unknown> = JSON.parse(stringMsg);
// console.log("rawPhxMessage", rawPhxMessage);

// rawPhxMessage must be an array with 5 elements
if (typeof rawPhxMessage === 'object' && Array.isArray(rawPhxMessage) && rawPhxMessage.length === 5) {
const [joinRef, messageRef, topic, event, payload] = rawPhxMessage;
switch (event) {
case "phx_join":
onPhxJoin(socket, rawPhxMessage as PhxJoin);
onPhxJoin(socket, rawPhxMessage as PhxJoinIncoming);
break;
case "heartbeat":
onHeartbeat(socket, rawPhxMessage as PhxEvent<any>);
onHeartbeat(socket, rawPhxMessage as PhxHeartbeatIncoming);
break;
case "event":
onPhxEvent(socket, rawPhxMessage as PhxEvent<unknown>);
// map based on event type
const { type } = payload as PhxClickPayload | PhxFormPayload
console
switch (type) {
case "click":
onPhxClickEvent(socket, rawPhxMessage as PhxClickEvent);
break;
case "form":
onPhxFormEvent(socket, rawPhxMessage as PhxFormEvent);
break;
default:
console.error("unhandeded event type", type);
}
break;
default:
console.error("unhandeded event", event);
console.error("unhandeded protocol event", event);
}
}
else {
Expand All @@ -48,7 +62,7 @@ wsServer.on('connection', socket => {
});


function onPhxJoin(socket: any, message: PhxJoin) {
function onPhxJoin(socket: any, message: PhxJoinIncoming) {
// console.log("phx_join", message);

// use url to route join request to component
Expand Down Expand Up @@ -105,7 +119,7 @@ function onPhxJoin(socket: any, message: PhxJoin) {
});
}

function onPhxEvent(socket: any, message: PhxEvent<unknown>) {
function onPhxFormEvent(socket: any, message: PhxFormEvent) {
// update context
// rerun render
// send back dynamics if they changed
Expand All @@ -128,9 +142,69 @@ function onPhxEvent(socket: any, message: PhxEvent<unknown>) {
return;
}

const { type, event: payloadEvent, value } = payload;
const params = new URLSearchParams(value);
// TODO update types to have optional handleEvent???
// @ts-ignore
const ctx = component.handleEvent(payload.event, payload.value, { id: topic });
const ctx = component.handleEvent(payload.event, Object.fromEntries(params), { id: topic });

const view = component.render(ctx);

// map array of dynamics to object with indiceies as keys
const dynamics = view.dynamics.reduce((acc: { [key: number]: string }, cur: string, index: number) => {
acc[index] = cur;
return acc;
}, {} as { [key: string]: string })

const reply: PhxReply = [
message[0],
message[1],
message[2],
"phx_reply",
{
response: {
diff: {
...dynamics
}
},
status: "ok"
}
]
// console.log("sending phx_reply", reply);
socket.send(JSON.stringify(reply), { binary: false }, (err: any) => {
if (err) {
console.error("error", err)
}
});
}

function onPhxClickEvent(socket: any, message: PhxClickEvent) {
// update context
// rerun render
// send back dynamics if they changed
// console.log('socket:', socket);
console.log('event:', message);

const [joinRef, messageRef, topic, event, payload] = message;

// route using topic to lookup path
const path = topicToPath[topic];
const component = router[path];
if (!component) {
console.error("no mapping found topic", topic);
return;
}

// check if component has event handler
if(!(component as any).handleEvent) {
console.warn("no event handler for component", component);
return;
}

const { type, event: payloadEvent, value } = payload;
// TODO update types to have optional handleEvent???
// @ts-ignore
const ctx = component.handleEvent(payload.event, value.value, { id: topic });

const view = component.render(ctx);

Expand Down Expand Up @@ -168,7 +242,7 @@ function onPhxEvent(socket: any, message: PhxEvent<unknown>) {
// }
// }

function onHeartbeat(socket: any, message: PhxEvent<any>) {
function onHeartbeat(socket: any, message: PhxHeartbeatIncoming) {
// console.log("heartbeat", message);

const hbReply = newHeartbeatReply(message);
Expand Down

0 comments on commit 5d25d62

Please sign in to comment.