summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fig-frontend/client/package-lock.json22
-rw-r--r--fig-frontend/client/package.json4
-rw-r--r--fig-frontend/client/src/assets/blueprint.jpg (renamed from fig-frontend/client/src/blueprint.jpg)bin1308988 -> 1308988 bytes
-rw-r--r--fig-frontend/client/src/components/backdrop.ts41
-rw-r--r--fig-frontend/client/src/components/footer.ts72
-rw-r--r--fig-frontend/client/src/components/gizmo.ts34
-rw-r--r--fig-frontend/client/src/components/globals.d.ts2
-rw-r--r--fig-frontend/client/src/components/header.ts34
-rw-r--r--fig-frontend/client/src/components/login.ts34
-rw-r--r--fig-frontend/client/src/components/window.ts130
-rw-r--r--fig-frontend/client/src/config.ts3
-rw-r--r--fig-frontend/client/src/index.css18
-rw-r--r--fig-frontend/client/src/index.ts121
-rw-r--r--fig-frontend/client/src/state.ts13
-rw-r--r--fig-frontend/fig-frontend.cabal2
-rw-r--r--fig-frontend/src/Fig/Frontend.hs67
-rw-r--r--fig-frontend/src/Fig/Frontend/State.hs41
-rw-r--r--fig-frontend/src/Fig/Frontend/Utils.hs2
18 files changed, 480 insertions, 160 deletions
diff --git a/fig-frontend/client/package-lock.json b/fig-frontend/client/package-lock.json
index 01fb3ce..ab2278b 100644
--- a/fig-frontend/client/package-lock.json
+++ b/fig-frontend/client/package-lock.json
@@ -8,8 +8,10 @@
"name": "fig-frontend",
"version": "1.0.0",
"dependencies": {
+ "@adobe/lit-mobx": "^2.2.1",
"interactjs": "^1.10.26",
- "lit": "^3.0.0"
+ "lit": "^3.0.0",
+ "mobx": "^6.12.0"
},
"devDependencies": {
"esbuild": "^0.17.18",
@@ -18,6 +20,15 @@
"typescript-language-server": "^4.0.0"
}
},
+ "node_modules/@adobe/lit-mobx": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/@adobe/lit-mobx/-/lit-mobx-2.2.1.tgz",
+ "integrity": "sha512-5CCGIjPkzOG5k1pyJSzpQMhj/BSWy3hzykawtxZ4oM/wItqgEitfxypqHlAIDhfMhhp4J4OlFlqCzbnEL6sYzQ==",
+ "peerDependencies": {
+ "lit": "^2.0.0 || ^3.0.0",
+ "mobx": "^5.0.0 || ^6.0.0"
+ }
+ },
"node_modules/@esbuild/android-arm": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
@@ -503,6 +514,15 @@
"yallist": "^2.1.2"
}
},
+ "node_modules/mobx": {
+ "version": "6.12.0",
+ "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.12.0.tgz",
+ "integrity": "sha512-Mn6CN6meXEnMa0a5u6a5+RKrqRedHBhZGd15AWLk9O6uFY4KYHzImdt8JI8WODo1bjTSRnwXhJox+FCUZhCKCQ==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mobx"
+ }
+ },
"node_modules/pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
diff --git a/fig-frontend/client/package.json b/fig-frontend/client/package.json
index 52b6451..2812cd4 100644
--- a/fig-frontend/client/package.json
+++ b/fig-frontend/client/package.json
@@ -5,8 +5,10 @@
"build": "esbuild src/index.ts --loader:.jpg=file --outdir=../../fig-frontend-assets/js --bundle --minify"
},
"dependencies": {
+ "@adobe/lit-mobx": "^2.2.1",
"interactjs": "^1.10.26",
- "lit": "^3.0.0"
+ "lit": "^3.0.0",
+ "mobx": "^6.12.0"
},
"devDependencies": {
"esbuild": "^0.17.18",
diff --git a/fig-frontend/client/src/blueprint.jpg b/fig-frontend/client/src/assets/blueprint.jpg
index 6efe89d..6efe89d 100644
--- a/fig-frontend/client/src/blueprint.jpg
+++ b/fig-frontend/client/src/assets/blueprint.jpg
Binary files differ
diff --git a/fig-frontend/client/src/components/backdrop.ts b/fig-frontend/client/src/components/backdrop.ts
new file mode 100644
index 0000000..d35d205
--- /dev/null
+++ b/fig-frontend/client/src/components/backdrop.ts
@@ -0,0 +1,41 @@
+import { html, css, LitElement, unsafeCSS } from "lit";
+import { customElement } from "lit/decorators.js";
+
+import * as Config from "../config";
+
+import blueprint from "../assets/blueprint.jpg";
+
+@customElement("fig-backdrop")
+export class Backdrop extends LitElement {
+ static styles = css`
+#backdrop {
+ z-index: -2;
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ width: 100vw;
+ height: 100vh;
+ background-image: url("${unsafeCSS(new URL(blueprint, Config.SCRIPT_URL))}");
+}
+#blur {
+ z-index: -1;
+ backdrop-filter: blur(3px);
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ width: 100vw;
+ height: 100vh;
+ background-size: 100px 100px;
+ background-position: -20px -20px;
+ background-image:
+ linear-gradient(to right, lightgrey 1px, transparent 1px),
+ linear-gradient(to bottom, lightgrey 1px, transparent 1px);
+}
+`;
+ render() {
+ return html`
+<div id="backdrop"></div>
+<div id="blur"></div>
+`;
+ }
+}
diff --git a/fig-frontend/client/src/components/footer.ts b/fig-frontend/client/src/components/footer.ts
new file mode 100644
index 0000000..64e35ae
--- /dev/null
+++ b/fig-frontend/client/src/components/footer.ts
@@ -0,0 +1,72 @@
+import { html, css, LitElement } from "lit";
+import { customElement, state } from "lit/decorators.js";
+
+import * as Config from "../config";
+import * as State from "../state";
+import * as Twitch from "../twitch";
+
+@customElement("fig-footer")
+export class Footer extends LitElement {
+ private global = State.global;
+ static styles = css`
+#footer {
+ position: absolute;
+ bottom: 0px;
+ left: 0px;
+ right: 0px;
+ height: 4ex;
+ background: linear-gradient(0deg, rgba(79,39,0,1) 0%, rgba(212,151,113,1) 50%, rgba(213,139,45,1) 100%);
+}
+button {
+ font-family: "Rubik Maps";
+ height: 100%;
+ color: black;
+ border-style: none;
+ background: linear-gradient(0deg, rgba(79,39,0,1) 0%, rgba(212,151,113,1) 50%, rgba(213,139,45,1) 100%);
+ filter: brightness(125%);
+}
+button:hover {
+ filter: brightness(150%);
+}
+button:active {
+ filter: brightness(75%);
+}
+`;
+
+ // twitch login
+ login() {
+ Twitch.startTwitchAuth();
+ }
+ async check() {
+ const resp = await fetch(`${Config.API_URL}/check`);
+ console.log(await resp.text());
+ }
+ button_login() {
+ const token = Twitch.getAuthToken();
+ console.log(token);
+ if (token) {
+ return html`
+<button @click=${this.check}>CHECK</button>
+`;
+ } else {
+ return html`
+<button @click=${this.login}>LOGIN</button>
+`;
+ }
+ }
+
+ // toggle gizmo pane
+ toggle_gizmo() {
+ console.log(this.global.gizmo_active);
+ this.global.toggle_gizmo();
+ }
+
+ render() {
+ return html`
+<div id="footer">
+ ${this.button_login()}
+ <button @click=${this.toggle_gizmo}>GIZMO</button>
+</div>
+`;
+ }
+}
diff --git a/fig-frontend/client/src/components/gizmo.ts b/fig-frontend/client/src/components/gizmo.ts
new file mode 100644
index 0000000..bd1285c
--- /dev/null
+++ b/fig-frontend/client/src/components/gizmo.ts
@@ -0,0 +1,34 @@
+import { MobxLitElement } from "@adobe/lit-mobx";
+import { html, css } from "lit";
+import { customElement } from "lit/decorators.js";
+
+import * as State from "../state";
+
+@customElement("fig-gizmo")
+export class Gizmo extends MobxLitElement {
+ private global = State.global;
+
+ static style = css`
+`;
+
+ static get(id: string): Gizmo | null {
+ const e = document.getElementById(id);
+ if (e instanceof Gizmo) return e;
+ return null;
+ }
+
+ constructor() {
+ super();
+ }
+
+ render() {
+ console.log("render", this.global.gizmo_active);
+ if (this.global.gizmo_active) {
+ return html`
+<fig-window>
+<h1>hi</h1>
+</fig-window>
+`;
+ }
+ }
+}
diff --git a/fig-frontend/client/src/components/globals.d.ts b/fig-frontend/client/src/components/globals.d.ts
new file mode 100644
index 0000000..65e20c0
--- /dev/null
+++ b/fig-frontend/client/src/components/globals.d.ts
@@ -0,0 +1,2 @@
+declare module "*.png";
+declare module "*.jpg";
diff --git a/fig-frontend/client/src/components/header.ts b/fig-frontend/client/src/components/header.ts
new file mode 100644
index 0000000..c4c709f
--- /dev/null
+++ b/fig-frontend/client/src/components/header.ts
@@ -0,0 +1,34 @@
+import { html, css, LitElement } from "lit";
+import { customElement } from "lit/decorators.js";
+
+@customElement("fig-header")
+export class Header extends LitElement {
+ static styles = css`
+#header {
+ position: absolute;
+ text-align: center;
+ border-radius: 100px;
+ border-style: solid;
+ border-color: silver;
+ border-width: 5px;
+ background: linear-gradient(0deg, rgba(89,89,89,1) 35%, rgba(158,158,158,1) 100%);
+ top: 1em;
+ left: 33vw;
+ width: 33vw;
+}
+#header:hover {
+ filter: brightness(150%);
+}
+h1 {
+ color: #b5a642;
+ font-family: "Rubik Maps";
+}
+`;
+ render() {
+ return html`
+<div id="header">
+ <h1>"The JunkYard"</h1>
+</div>
+`;
+ }
+}
diff --git a/fig-frontend/client/src/components/login.ts b/fig-frontend/client/src/components/login.ts
new file mode 100644
index 0000000..195c97f
--- /dev/null
+++ b/fig-frontend/client/src/components/login.ts
@@ -0,0 +1,34 @@
+import { html, css, LitElement } from "lit";
+import { customElement } from "lit/decorators.js";
+
+import * as Config from "../config";
+import * as Twitch from "../twitch";
+
+@customElement("fig-login")
+export class Login extends LitElement {
+ static styles = css`
+ `;
+
+ login() {
+ Twitch.startTwitchAuth();
+ }
+
+ async check() {
+ const resp = await fetch(`${Config.API_URL}/check`);
+ console.log(await resp.text());
+ }
+
+ render() {
+ const token = Twitch.getAuthToken();
+ console.log(token);
+ if (token) {
+ return html`
+<button @click=${this.check}>check token</button>
+`;
+ } else {
+ return html`
+<button @click=${this.login}>login</button>
+`;
+ }
+ }
+}
diff --git a/fig-frontend/client/src/components/window.ts b/fig-frontend/client/src/components/window.ts
new file mode 100644
index 0000000..9a8f93a
--- /dev/null
+++ b/fig-frontend/client/src/components/window.ts
@@ -0,0 +1,130 @@
+import { html, css, LitElement } from "lit";
+import { customElement, property } from "lit/decorators.js";
+
+import interact from "interactjs";
+
+@customElement("fig-window")
+export class Window extends LitElement {
+ static z: number = 0;
+
+ x: number;
+ y: number;
+
+ @property()
+ title: string;
+
+ @property()
+ hidden: boolean;
+
+ static styles = css`
+#windowcontainer {
+ z-index: 0;
+ position: relative;
+ width: 100%;
+ height: 100%;
+ display: grid;
+ grid-template-columns: auto;
+ grid-template-rows: 4ex 1fr;
+ user-select: none;
+}
+#title {
+ font-family: "Rubik Maps";
+ font-size: 150%;
+ color: black;
+ grid-column: 1;
+ grid-row: 1;
+ height: 2.2ex;
+ overflow: hidden;
+ z-index: 2;
+ margin-bottom: -1ex;
+ background: linear-gradient(0deg, rgba(89,89,89,1) 35%, rgba(158,158,158,1) 100%);
+ border-radius: 100px;
+ border-style: solid;
+ border-color: silver;
+ border-width: 5px;
+ text-align: center;
+}
+#window {
+ overflow: hidden;
+ grid-column: 1;
+ grid-row-start: 2;
+ grid-row-end: 2;
+ z-index: 1;
+ background-color: white;
+ border-radius: 5ex;
+ border-style: solid;
+ border-color: black;
+ border-width: 2px;
+ border-top-width: 0px;
+ padding-top: 1ex;
+ padding-left: 2ex;
+ padding-right: 2ex;
+}
+`;
+
+ static get(id: string): Window | null {
+ const e = document.getElementById(id);
+ if (e instanceof Window) return e;
+ return null;
+ }
+
+ constructor() {
+ super();
+ this.x = 0;
+ this.y = 0;
+ this.style.left = `${this.x}px`
+ this.style.top = `${this.y}px`
+ this.style.width = "300px"
+ this.style.height = "300px"
+ interact(this).draggable({
+ allowFrom: "#title",
+ modifiers: [
+ interact.modifiers.restrict({
+ restriction: "body",
+ endOnly: true
+ })
+ ],
+ listeners: {
+ move(event) {
+ event.target.x += event.dx
+ event.target.y += event.dy
+ event.target.style.left = `${event.target.x}px`
+ event.target.style.top = `${event.target.y}px`
+ },
+ }
+ }).resizable({
+ edges: { top: false, bottom: true, left: true, right: true },
+ listeners: {
+ move(event) {
+ event.target.x += event.deltaRect.left
+ event.target.y += event.deltaRect.top
+ event.target.style.left = `${event.target.x}px`
+ event.target.style.top = `${event.target.y}px`
+ event.target.style.width = `${Math.max(event.rect.width, 300)}px`
+ event.target.style.height = `${Math.max(event.rect.height, 300)}px`
+ },
+ },
+ });
+ }
+
+ toggle() {
+ this.hidden = !this.hidden;
+ }
+
+ click() {
+ this.style.zIndex = `${++Window.z}`;
+ }
+
+ render() {
+ if (!this.hidden) {
+ return html`
+<div id="windowcontainer" @pointerdown=${this.click}>
+ <div id="title">${this.title}</slot></div>
+ <div id="window">
+ <slot></slot>
+ </div>
+</div>
+`;
+ }
+ }
+}
diff --git a/fig-frontend/client/src/config.ts b/fig-frontend/client/src/config.ts
index 5498e16..e001107 100644
--- a/fig-frontend/client/src/config.ts
+++ b/fig-frontend/client/src/config.ts
@@ -1,3 +1,4 @@
export const URL = "http://localhost:8000";
-export const API_URL = "http://localhost:8000/api";
+export const API_URL = `${URL}/api/`;
+export const SCRIPT_URL = `${URL}/js/`;
export const CLIENT_ID = "q486jugzn2my4iw6l181o006ugye4j";
diff --git a/fig-frontend/client/src/index.css b/fig-frontend/client/src/index.css
index 891c5fd..b1c8427 100644
--- a/fig-frontend/client/src/index.css
+++ b/fig-frontend/client/src/index.css
@@ -5,26 +5,10 @@ body {
top: 0px;
width: 100vw;
height: 100vh;
+ overflow: hidden;
}
fig-window {
display: block;
position: absolute;
}
-
-fig-header {
- position: absolute;
- text-align: center;
- border-radius: 100px;
- border-style: solid;
- border-color: silver;
- border-width: 5px;
- background: linear-gradient(0deg, rgba(89,89,89,1) 35%, rgba(158,158,158,1) 100%);
- top: 1em;
- left: 33vw;
- width: 33vw;
-}
-
-fig-header:hover {
- filter: brightness(150%);
-}
diff --git a/fig-frontend/client/src/index.ts b/fig-frontend/client/src/index.ts
index 899c7fd..be0b0c5 100644
--- a/fig-frontend/client/src/index.ts
+++ b/fig-frontend/client/src/index.ts
@@ -1,119 +1,10 @@
-import { html, css, LitElement } from "lit";
-import { customElement, property } from "lit/decorators.js";
-
-import interact from "interactjs";
-
-import * as Config from "./config";
-import * as Twitch from "./twitch";
+import "./components/backdrop";
+import "./components/header";
+import "./components/footer";
+import "./components/login";
+import "./components/window";
+import "./components/gizmo";
import "./index.css";
console.log("welcome to \"the junkyard\"");
-
-@customElement("fig-window")
-export class Window extends LitElement {
- x: number;
- y: number;
-
- constructor() {
- super();
- this.x = 0;
- this.y = 0;
- interact(this).draggable({
- listeners: {
- move(event) {
- event.target.x += event.dx
- event.target.y += event.dy
- event.target.style.left = `${event.target.x}px`
- event.target.style.top = `${event.target.y}px`
- },
- }
- });
- }
-
- render() {
- console.log("render");
- return html`
-<slot></slot>
-`;
- }
-}
-
-@customElement("fig-backdrop")
-export class Backdrop extends LitElement {
- static styles = css`
-#backdrop {
- z-index: -2;
- position: absolute;
- left: 0px;
- top: 0px;
- width: 100vw;
- height: 100vh;
- background-image: url("blueprint.jpg");
-}
-#blur {
- z-index: -1;
- backdrop-filter: blur(3px);
- position: absolute;
- left: 0px;
- top: 0px;
- width: 100vw;
- height: 100vh;
- background-size: 100px 100px;
- background-position: -20px -20px;
- background-image:
- linear-gradient(to right, lightgrey 1px, transparent 1px),
- linear-gradient(to bottom, lightgrey 1px, transparent 1px);
-}
-`;
- render() {
- return html`
-<div id="backdrop"></div>
-<div id="blur"></div>
-`;
- }
-}
-
-@customElement("fig-login")
-export class Login extends LitElement {
- static styles = css`
- `;
-
- login() {
- Twitch.startTwitchAuth();
- }
-
- async check() {
- const resp = await fetch(`${Config.API_URL}/check`);
- console.log(await resp.text());
- }
-
- render() {
- const token = Twitch.getAuthToken();
- console.log(token);
- if (token) {
- return html`
-<button @click=${this.check}>check token</button>
-`;
- } else {
- return html`
-<button @click=${this.login}>login</button>
-`;
- }
- }
-}
-
-@customElement("fig-header")
-export class Header extends LitElement {
- static styles = css`
-h1 {
- color: #b5a642;
- font-family: "Rubik Maps";
-}
-`;
- render() {
- return html`
-<h1>Welcome To "The JunkYard"</h1>
-`;
- }
-}
diff --git a/fig-frontend/client/src/state.ts b/fig-frontend/client/src/state.ts
new file mode 100644
index 0000000..19bc5d9
--- /dev/null
+++ b/fig-frontend/client/src/state.ts
@@ -0,0 +1,13 @@
+import { observable, action } from 'mobx';
+
+class Global {
+ @observable
+ public gizmo_active: boolean = true;
+
+ @action
+ public toggle_gizmo() {
+ this.gizmo_active = !this.gizmo_active;
+ }
+}
+
+export const global = new Global();
diff --git a/fig-frontend/fig-frontend.cabal b/fig-frontend/fig-frontend.cabal
index b71911e..aed5cfd 100644
--- a/fig-frontend/fig-frontend.cabal
+++ b/fig-frontend/fig-frontend.cabal
@@ -22,6 +22,7 @@ common deps
, http-client
, http-client-tls
, jose-jwt
+ , lens
, lucid2
, megaparsec
, mtl
@@ -52,6 +53,7 @@ library
Fig.Frontend
Fig.Frontend.Utils
Fig.Frontend.Auth
+ Fig.Frontend.State
executable fig-frontend
import: defaults
diff --git a/fig-frontend/src/Fig/Frontend.hs b/fig-frontend/src/Fig/Frontend.hs
index fa1965a..036420a 100644
--- a/fig-frontend/src/Fig/Frontend.hs
+++ b/fig-frontend/src/Fig/Frontend.hs
@@ -2,41 +2,58 @@ module Fig.Frontend where
import Fig.Prelude
+import Control.Lens (use)
+
import qualified Network.Wai.Middleware.Static as Wai.Static
import qualified Network.Wai.Handler.Warp as Warp
import qualified Web.Twain as Tw
import qualified Lucid as L
+import qualified Lucid.Base as L
import Fig.Frontend.Utils
import Fig.Frontend.Auth
+import Fig.Frontend.State
server :: Config -> IO ()
server cfg = do
log $ "Frontend server running on port " <> tshow cfg.port
- Warp.run cfg.port $ app cfg
-
-app :: Config -> Tw.Application
-app cfg = foldr' @[] ($)
- (Tw.notFound . Tw.send $ Tw.text "not found")
- [ Wai.Static.staticPolicy $ Wai.Static.addBase cfg.assetPath
- , Tw.get "/"
- . Tw.send . Tw.html
- . L.renderBS
- $ L.doctypehtml_ do
- L.head_ do
- L.title_ "The Junkyard"
- L.link_ [L.rel_ "stylesheet", L.href_ "js/index.css"]
- L.link_ [L.rel_ "stylesheet", L.href_ "https://fonts.googleapis.com/css?family=Rubik+Maps"]
- L.link_ [L.rel_ "icon", L.href_ "data:;base64,iVBORw0KGgo="]
- L.script_ [L.type_ "module", L.src_ "js/index.js"] ("" :: L.Html ())
- L.body_ do
- L.term "fig-backdrop" ""
- L.term "fig-header" ""
- L.term "fig-login" ""
- L.term "fig-window" do
- L.h1_ "hello"
- , Tw.get "/api/check" $ authed cfg \auth -> do
- Tw.send $ Tw.json @[Text] [auth.id, auth.name]
- ]
+ Warp.run cfg.port =<< app cfg
+
+window :: Text -> Text -> L.Html () -> L.Html ()
+window id_ title body =
+ L.term "fig-window" [L.id_ id_, L.makeAttributes "title" title] do
+ body
+
+app :: Config -> IO Tw.Application
+app cfg = do
+ st <- stateRef
+ pure $ foldr' @[] ($)
+ (Tw.notFound . Tw.send $ Tw.text "not found")
+ [ Wai.Static.staticPolicy $ Wai.Static.addBase cfg.assetPath
+ , Tw.get "/"
+ . Tw.send . Tw.html
+ . L.renderBS
+ $ L.doctypehtml_ do
+ L.head_ do
+ L.title_ "The Junkyard"
+ L.link_ [L.rel_ "stylesheet", L.href_ "js/index.css"]
+ L.link_ [L.rel_ "stylesheet", L.href_ "https://fonts.googleapis.com/css?family=Rubik+Maps"]
+ L.link_ [L.rel_ "icon", L.href_ "data:;base64,iVBORw0KGgo="]
+ L.script_ [L.type_ "module", L.src_ "js/index.js"] ("" :: L.Html ())
+ L.body_ do
+ L.term "fig-backdrop" ""
+ L.term "fig-header" ""
+ L.term "fig-footer" ""
+ window "test1" "window one" do
+ L.p_ "Deserunt consequatur neque autem. Dicta assumenda autem consequatur sunt animi. Dolor voluptatem rerum aut id ut vel. Labore soluta itaque voluptas dolores repellat. Voluptatem dolor fugit necessitatibus amet et natus velit. Enim nihil voluptate qui quasi architecto. Reprehenderit porro aperiam eaque sequi veritatis in quos. Odio tenetur labore cupiditate nisi. Deserunt aut dolore consequuntur inventore quod veniam commodi. Incidunt qui sequi dolor. Quia adipisci dolores ab. Dolor molestias est earum ea. Possimus ullam repellat qui consequatur dolorem excepturi non. Incidunt magnam quaerat temporibus quisquam. Vero nihil possimus ratione voluptas sunt. Qui quisquam ipsa omnis est totam iure odio. Ab fugiat in id nisi. Dolor sit est soluta eaque ut eveniet. Eveniet rerum fuga doloremque repellendus eligendi sunt asperiores. Tenetur iure vitae ea sapiente et. Aspernatur natus aut rerum magnam occaecati. Veritatis exercitationem necessitatibus assumenda est assumenda eaque molestiae."
+ window "test2" "window two" do
+ L.h1_ "hello"
+ L.term "fig-gizmo" [L.id_ "gizmo1"] ""
+ , Tw.get "/api/check" $ authed cfg \auth -> do
+ Tw.send $ Tw.json @[Text] [auth.id, auth.name]
+ , Tw.put "/api/buffer" do
+ buf <- withState st $ use buffer
+ Tw.send $ Tw.text buf
+ ]
diff --git a/fig-frontend/src/Fig/Frontend/State.hs b/fig-frontend/src/Fig/Frontend/State.hs
new file mode 100644
index 0000000..6053105
--- /dev/null
+++ b/fig-frontend/src/Fig/Frontend/State.hs
@@ -0,0 +1,41 @@
+{-# Language TemplateHaskell #-}
+
+module Fig.Frontend.State where
+
+import Control.Lens.TH (makeLensesFor)
+import Control.Lens ((<>=))
+import Control.Monad.State (runStateT)
+
+import Fig.Prelude
+
+import qualified Data.IORef as IORef
+
+newtype State = State
+ { buffer :: Text
+ }
+makeLensesFor [("buffer", "buffer")] ''State
+
+defaultState :: State
+defaultState = State
+ { buffer = ""
+ }
+
+type StateRef = IORef.IORef State
+
+stateRef :: IO StateRef
+stateRef = IORef.newIORef defaultState
+
+withState ::
+ MonadIO m' =>
+ StateRef ->
+ (forall m. (MonadIO m, MonadState State m) => m a) ->
+ m' a
+withState ref f = do
+ s <- liftIO $ IORef.readIORef ref
+ (res, s') <- liftIO $ runStateT f s
+ liftIO $ IORef.writeIORef ref s'
+ pure res
+
+sayHi :: StateRef -> IO ()
+sayHi ref = withState ref do
+ buffer <>= "hi"
diff --git a/fig-frontend/src/Fig/Frontend/Utils.hs b/fig-frontend/src/Fig/Frontend/Utils.hs
index 1ba1d5f..20234e7 100644
--- a/fig-frontend/src/Fig/Frontend/Utils.hs
+++ b/fig-frontend/src/Fig/Frontend/Utils.hs
@@ -22,6 +22,7 @@ data Config = Config
{ port :: Int
, assetPath :: FilePath
, clientId :: Text
+ , authToken :: Text
} deriving (Show, Eq, Ord)
configCodec :: Toml.TomlCodec Config
@@ -29,6 +30,7 @@ configCodec = do
port <- Toml.int "port" Toml..= (\a -> a.port)
assetPath <- Toml.string "asset_path" Toml..= (\a -> a.assetPath)
clientId <- Toml.text "client_id" Toml..= (\a -> a.clientId)
+ authToken <- Toml.text "auth_token" Toml..= (\a -> a.authToken)
pure $ Config{..}
loadConfig :: FilePath -> IO Config