Skip to content

Commit

Permalink
Merge pull request #7 from floodfx/abstract_base_class
Browse files Browse the repository at this point in the history
Abstract base class
  • Loading branch information
floodfx authored Jan 26, 2022
2 parents 4d20c2f + 121ce05 commit 89af722
Show file tree
Hide file tree
Showing 11 changed files with 449 additions and 468 deletions.
90 changes: 33 additions & 57 deletions src/examples/autocomplete/component.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import html from "../../server/templates";
import { LiveViewComponent, LiveViewContext, LiveViewExternalEventListener, LiveViewInternalEventListener } from "../../server/types";
import { PhxSocket } from "../../server/socket/types";
import { sendInternalMessage } from "../../server/socket/message_router";
import { BaseLiveViewComponent, LiveViewExternalEventListener, LiveViewInternalEventListener, LiveViewSocket } from "../../server/types";
import { WebSocket } from "ws";
import { searchByCity, searchByZip, Store } from "../live-search/data";
import { listCities, suggest } from "./data";

import { suggest } from "./data";

export interface AutocompleteContext {
zip: string;
Expand All @@ -17,25 +14,20 @@ export interface AutocompleteContext {

const idToWs = new Map<string, WebSocket>();

export class AutocompleteLiveViewComponent implements
LiveViewComponent<AutocompleteContext>,
export class AutocompleteLiveViewComponent extends BaseLiveViewComponent<AutocompleteContext, unknown> implements
LiveViewExternalEventListener<AutocompleteContext, "zip-search", Pick<AutocompleteContext, "zip">>,
LiveViewExternalEventListener<AutocompleteContext, "suggest-city", Pick<AutocompleteContext, "city">>,
LiveViewInternalEventListener<AutocompleteContext, { type: "run_zip_search", zip: string }>,
LiveViewInternalEventListener<AutocompleteContext, { type: "run_city_search", city: string }>
{

mount(params: any, session: any, socket: PhxSocket) {
if (socket.connected) {
// TODO handle disconnect
idToWs.set(socket.id, socket.ws!);
}
mount(params: any, session: any, socket: LiveViewSocket<AutocompleteContext>) {
const zip = "";
const city = "";
const stores: Store[] = [];
const matches: string[] = [];
const loading = false
return { data: { zip, city, stores, matches, loading } };
return { zip, city, stores, matches, loading };
};

renderStoreStatus(store: Store) {
Expand Down Expand Up @@ -78,125 +70,109 @@ export class AutocompleteLiveViewComponent implements



render(context: LiveViewContext<AutocompleteContext>) {
render(context: AutocompleteContext) {
return html`
<h1>Find a Store</h1>
<div id="search">
<form phx-submit="zip-search">
<input type="text" name="zip" value="${context.data.zip}"
<input type="text" name="zip" value="${context.zip}"
placeholder="Zip Code"
autofocus autocomplete="off"
${context.data.loading ? "readonly" : ""} />
${context.loading ? "readonly" : ""} />
<button type="submit">
📫🔎
</button>
</form>
<form phx-submit="city-search" phx-change="suggest-city">
<input type="text" name="city" value="${context.data.city}"
<input type="text" name="city" value="${context.city}"
placeholder="City"
autocomplete="off"
list="matches"
phx-debounce="1000"
${context.data.loading ? "readonly" : ""} />
${context.loading ? "readonly" : ""} />
<button type="submit">
🏙🔎
</button>
</form>
<datalist id="matches">
${context.data.matches.map(match => html`<option value="${match}">${match}</option>`)}
${context.matches.map(match => html`<option value="${match}">${match}</option>`)}
</datalist>
${context.data.loading ? this.renderLoading() : ""}
${context.loading ? this.renderLoading() : ""}
<div class="stores">
<ul>
${context.data.stores.map(store => this.renderStore(store))}
${context.stores.map(store => this.renderStore(store))}
</ul>
</div>
</div>
`
};

handleEvent(event: "zip-search" | "suggest-city", params: { zip: string } | { city: string }, socket: PhxSocket) {
console.log("event:", event, params, socket);
handleEvent(event: "zip-search" | "suggest-city", params: { zip: string } | { city: string }, socket: LiveViewSocket<AutocompleteContext>) {
// console.log("event:", event, params, socket);
if (event === "zip-search") {

// @ts-ignore TODO better params types for different events
const { zip } = params;
// wait a second to send the message
setTimeout(() => {
sendInternalMessage(socket, this, { type: "run_zip_search", zip });
socket.sendInternal({ type: "run_zip_search", zip });
}, 1000);
return { data: { zip, city: "", stores: [], matches: [], loading: true } };
return { zip, city: "", stores: [], matches: [], loading: true };
}
else if (event === "suggest-city") {

// @ts-ignore TODO better params types for different events
const { city } = params;
const matches = suggest(city);
console.log("matches:", matches);
return { data: { zip: "", city, stores: [], matches, loading: false } };
return { zip: "", city, stores: [], matches, loading: false };
}
else if (event === "city-search") {
// @ts-ignore TODO better params types for different events
const { city } = params;
// wait a second to send the message
setTimeout(() => {
sendInternalMessage(socket, this, { type: "run_city_search", city });
socket.sendInternal({ type: "run_city_search", city });
}, 1000);
return { data: { zip: "", city, stores: [], matches: [], loading: true } };
return { zip: "", city, stores: [], matches: [], loading: true };
}
else {
return { data: { zip: "", city: "", stores: [], matches: [], loading: false } };
return { zip: "", city: "", stores: [], matches: [], loading: false };
}
}

// handleEvent(event: "suggest-city", params: {zip: string}, socket: PhxSocket) {
// console.log("event:", event, params, socket);
// const { zip } = params;
// // wait a second to send the message
// setTimeout(() => {
// sendInternalMessage(socket, this, {type: "run_zip_search", zip });
// }, 1000);

// return { data: { zip, city: "", stores:[], matches:[], loading: true } };
// }

handleInfo(event: { type: "run_zip_search", zip: string } | { type: "run_city_search", city: string }, socket: PhxSocket) {
// lookup websocekt by id
socket.ws = idToWs.get(socket.id);
handleInfo(event: { type: "run_zip_search", zip: string } | { type: "run_city_search", city: string }, socket: LiveViewSocket<AutocompleteContext>) {

let stores: Store[] = [];
switch (event.type) {
case "run_zip_search":
const { zip } = event;
stores = searchByZip(zip);
return {
data: {
zip,
city: "",
stores,
matches: [],
loading: false
}
zip,
city: "",
stores,
matches: [],
loading: false
}
case "run_city_search":
const { city } = event;
stores = searchByCity(city);
return {
data: {
zip: "",
city,
stores,
matches: [],
loading: false
}
zip: "",
city,
stores,
matches: [],
loading: false
}

}
}

Expand Down
28 changes: 10 additions & 18 deletions src/examples/license_liveview.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,24 @@
import html from "../server/templates";
import { LiveViewComponent, LiveViewContext, LiveViewExternalEventListener, LiveViewInternalEventListener } from "../server/types";
import { PhxSocket } from "../server/socket/types";
import { BaseLiveViewComponent, LiveViewExternalEventListener, LiveViewSocket } from "../server/types";
import { numberToCurrency } from "./utils";
import { LightContext } from "./light_liveview";

export interface LicenseContext {
seats: number;
amount: number;
}

const _db: { [key: string]: LicenseContext } = {};

export class LicenseLiveViewComponent implements
LiveViewComponent<LicenseContext>,
export class LicenseLiveViewComponent extends BaseLiveViewComponent<LicenseContext, unknown> implements
LiveViewExternalEventListener<LicenseContext, "update", Pick<LicenseContext, "seats">>
{


mount(params: any, session: any, socket: PhxSocket) {
mount(params: any, session: any, socket: LiveViewSocket<LicenseContext>) {
// store this somewhere durable
const seats = 2;
const amount = calculateLicenseAmount(seats);
const ctx: LicenseContext = { seats, amount };
_db[socket.id] = ctx;
return { data: ctx };
return { seats, amount };
};

render(context: LiveViewContext<LicenseContext>) {
render(context: LicenseContext) {
return html`
<h1>Team License</h1>
<div id="license">
Expand All @@ -36,29 +28,29 @@ export class LicenseLiveViewComponent implements
🪑
<span>
Your license is currently for
<strong>${context.data.seats}</strong> seats.
<strong>${context.seats}</strong> seats.
</span>
</div>
<form phx-change="update">
<input type="range" min="1" max="10"
name="seats" value="${context.data.seats}" />
name="seats" value="${context.seats}" />
</form>
<div class="amount">
${numberToCurrency(context.data.amount)}
${numberToCurrency(context.amount)}
</div>
</div>
</div>
</div>
`
};

handleEvent(event: "update", params: { seats: number }, socket: PhxSocket) {
handleEvent(event: "update", params: { seats: number }, socket: LiveViewSocket<LicenseContext>) {
// console.log("event:", event, params, socket);
const { seats } = params;
const amount = calculateLicenseAmount(seats);
return { data: { seats, amount } };
return { seats, amount };
}

}
Expand Down
30 changes: 11 additions & 19 deletions src/examples/light_liveview.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,29 @@
import html from "../server/templates";
import { LiveViewComponent, LiveViewContext, LiveViewExternalEventListener, LiveViewInternalEventListener } from "../server/types";
import { PhxSocket } from "../server/socket/types";
import { BaseLiveViewComponent, LiveViewComponent, LiveViewExternalEventListener, LiveViewSocket } from "../server/types";

export interface LightContext {
brightness: number;
}

export type LightEvent = "on" | "off" | "up" | "down";

const _db: { [key: string]: LightContext } = {};

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


mount(params: any, session: any, socket: PhxSocket) {
// store this somewhere durable
const ctx: LightContext = { brightness: 10 };
_db[socket.id] = ctx;
return { data: ctx };
mount(params: any, session: any, socket: LiveViewSocket<LightContext>) {
return { brightness: 10 };
};

render(context: LiveViewContext<LightContext>) {
render(context: LightContext) {
return html`
<div id="light">
<h1>Front Porch Light</h1>
<div class="meter">
<span style="width: ${context.data.brightness} %>%">
${context.data.brightness}%
<span style="width: ${context.brightness} %>%">
${context.brightness}%
</span>
</div>
Expand All @@ -52,9 +46,8 @@ export class LightLiveViewComponent implements
`
};

handleEvent(event: LightEvent, params: any, socket: PhxSocket) {
const ctx = _db[socket.id];
console.log("event:", event, socket, ctx);
handleEvent(event: LightEvent, params: any, socket: LiveViewSocket<LightContext>) {
const ctx: LightContext = { brightness: socket.context.brightness };
switch (event) {
case 'off':
ctx.brightness = 0;
Expand All @@ -69,8 +62,7 @@ export class LightLiveViewComponent implements
ctx.brightness = Math.max(ctx.brightness - 10, 0);
break;
}
_db[socket.id] = ctx;
return { data: ctx };
return ctx;
}

}
Loading

0 comments on commit 89af722

Please sign in to comment.