diff options
Diffstat (limited to 'fig-frontend-client')
| -rw-r--r-- | fig-frontend-client/.gitignore | 2 | ||||
| -rw-r--r-- | fig-frontend-client/.psc-ide-port | 1 | ||||
| -rw-r--r-- | fig-frontend-client/Makefile | 35 | ||||
| -rw-r--r-- | fig-frontend-client/assets/icon.png | bin | 0 -> 983 bytes | |||
| -rw-r--r-- | fig-frontend-client/assets/mrblue.png | bin | 0 -> 771 bytes | |||
| -rw-r--r-- | fig-frontend-client/config-deploy.m4 | 4 | ||||
| -rw-r--r-- | fig-frontend-client/config-test.m4 | 4 | ||||
| -rwxr-xr-x | fig-frontend-client/deploy.sh | 3 | ||||
| -rw-r--r-- | fig-frontend-client/extension/background.js | 23 | ||||
| -rw-r--r-- | fig-frontend-client/extension/manifest.dhall | 30 | ||||
| -rw-r--r-- | fig-frontend-client/index-template.html (renamed from fig-frontend-client/index.html) | 4 | ||||
| -rw-r--r-- | fig-frontend-client/main.js | 266 | ||||
| -rw-r--r-- | fig-frontend-client/src/Config.js | 1 | ||||
| -rw-r--r-- | fig-frontend-client/src/Config.purs | 3 | ||||
| -rw-r--r-- | fig-frontend-client/src/Main.purs | 164 |
15 files changed, 301 insertions, 239 deletions
diff --git a/fig-frontend-client/.gitignore b/fig-frontend-client/.gitignore new file mode 100644 index 0000000..b74c9c8 --- /dev/null +++ b/fig-frontend-client/.gitignore @@ -0,0 +1,2 @@ +dist/* +main.js
\ No newline at end of file diff --git a/fig-frontend-client/.psc-ide-port b/fig-frontend-client/.psc-ide-port deleted file mode 100644 index 48a9dfe..0000000 --- a/fig-frontend-client/.psc-ide-port +++ /dev/null @@ -1 +0,0 @@ -15776
\ No newline at end of file diff --git a/fig-frontend-client/Makefile b/fig-frontend-client/Makefile new file mode 100644 index 0000000..872bec7 --- /dev/null +++ b/fig-frontend-client/Makefile @@ -0,0 +1,35 @@ +.PHONY: all deploy extension + +all: dist/test/index.html dist/test/assets dist/test/main.js dist/test/main.css + +deploy: dist/deploy/index.html dist/deploy/assets dist/deploy/main.js dist/deploy/main.css + rsync -av dist/deploy/ "pub.colonq.computer:~/public_html/" + +dist: + mkdir -p dist/test + mkdir -p dist/deploy + +main.js: $(shell find src) + purs-nix bundle + +dist/%/assets: $(shell find assets) dist + rm -rf $@ + mkdir -p $@ + cp -r assets/* $@ + +dist/%/main.js: main.js dist + cp $< $@ + +dist/%/main.css: main.css dist + cp $< $@ + +dist/%/index.html: config-%.m4 dist index-template.html + sh -c "m4 $< >$@" + +extension: dist/extension/assets dist/extension/manifest.json dist/extension/background.js dist/extension/main.js dist/extension/main.css + +dist/extension/manifest.json: extension/manifest.dhall + dhall-to-json <$< >$@ + +dist/extension/%: extension/% + cp $< $@ diff --git a/fig-frontend-client/assets/icon.png b/fig-frontend-client/assets/icon.png Binary files differnew file mode 100644 index 0000000..0431e3f --- /dev/null +++ b/fig-frontend-client/assets/icon.png diff --git a/fig-frontend-client/assets/mrblue.png b/fig-frontend-client/assets/mrblue.png Binary files differnew file mode 100644 index 0000000..8cf2a0b --- /dev/null +++ b/fig-frontend-client/assets/mrblue.png diff --git a/fig-frontend-client/config-deploy.m4 b/fig-frontend-client/config-deploy.m4 new file mode 100644 index 0000000..504bb3e --- /dev/null +++ b/fig-frontend-client/config-deploy.m4 @@ -0,0 +1,4 @@ +define(`CONFIG_SUBST', ` +globalThis.apiServer = "https://api.colonq.computer/api"; +') +include(`index-template.html') diff --git a/fig-frontend-client/config-test.m4 b/fig-frontend-client/config-test.m4 new file mode 100644 index 0000000..f1e6c16 --- /dev/null +++ b/fig-frontend-client/config-test.m4 @@ -0,0 +1,4 @@ +define(`CONFIG_SUBST', ` +globalThis.apiServer = "http://localhost:8000/api"; +') +include(`index-template.html') diff --git a/fig-frontend-client/deploy.sh b/fig-frontend-client/deploy.sh deleted file mode 100755 index f68b833..0000000 --- a/fig-frontend-client/deploy.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -purs-nix bundle -scp -r main.css main.js index.html assets/ pub.colonq.computer:~/public_html diff --git a/fig-frontend-client/extension/background.js b/fig-frontend-client/extension/background.js new file mode 100644 index 0000000..a0f4c91 --- /dev/null +++ b/fig-frontend-client/extension/background.js @@ -0,0 +1,23 @@ +/* Retrieve any previously set cookie and send to content script */ + +function getActiveTab() { + return browser.tabs.query({active: true, currentWindow: true}); +} + +function cookieUpdate() { + getActiveTab().then((tabs) => { + // get any previously set cookie for the current tab + let gettingCookies = browser.cookies.get({ + url: tabs[0].url, + name: "name" + }); + gettingCookies.then((cookie) => { + browser.tabs.sendMessage(tabs[0].id, cookie.value); + }); + }); +} + +// update when the tab is updated +browser.tabs.onUpdated.addListener(cookieUpdate); +// update when the tab is activated +browser.tabs.onActivated.addListener(cookieUpdate); diff --git a/fig-frontend-client/extension/manifest.dhall b/fig-frontend-client/extension/manifest.dhall new file mode 100644 index 0000000..87c48f6 --- /dev/null +++ b/fig-frontend-client/extension/manifest.dhall @@ -0,0 +1,30 @@ +{ manifest_version = 2 +, name = "computerspotting" +, version = "1.0" +, description = "spot the computer" +, icons = + [ { mapKey = "48", mapValue = "assets/mrgreen.png" } + ] +, web_accessible_resources = + [ "mrgreen.png" + , "mrblue.png" + ] +, permissions = + [ "webRequest" + , "webRequestBlocking" + , "tabs" + , "cookies" + , "*://*.twitch.tv/*" + , "*://api.colonq.computer/*" + ] +, background = + { scripts = ["background.js"] + } +, content_scripts = + [ { matches = ["*://*.twitch.tv/*"] + , js = ["main.js"] + , css = ["main.css"] + , run_at = "document_end" + } + ] +}
\ No newline at end of file diff --git a/fig-frontend-client/index.html b/fig-frontend-client/index-template.html index ef70156..1ab53d8 100644 --- a/fig-frontend-client/index.html +++ b/fig-frontend-client/index-template.html @@ -6,9 +6,13 @@ <title>LCOLONQ</title> <link rel="icon" href="./assets/mrgreen.png"> <link rel="stylesheet" type="text/css" href="./main.css"> + <script type="module"> +CONFIG_SUBST + </script> <script type="module" src="./main.js"></script> </head> <body> + <!-- RINGBEARER --> <div id="lcolonq-title"> LCOLONQ <div id="lcolonq-subtitle">hi</div> diff --git a/fig-frontend-client/main.js b/fig-frontend-client/main.js index 5cf9335..70827fc 100644 --- a/fig-frontend-client/main.js +++ b/fig-frontend-client/main.js @@ -1,3 +1,6 @@ +// output/Config/foreign.js +var apiServer = globalThis.apiServer; + // output/Control.Semigroupoid/index.js var semigroupoidFn = { compose: function(f) { @@ -1465,50 +1468,6 @@ var unionWith = function(dictOrd) { var union = function(dictOrd) { return unionWith(dictOrd)($$const); }; -var member = function(dictOrd) { - var compare2 = compare(dictOrd); - return function(k) { - var go2 = function($copy_v) { - var $tco_done = false; - var $tco_result; - function $tco_loop(v) { - if (v instanceof Leaf) { - $tco_done = true; - return false; - } - ; - if (v instanceof Node) { - var v1 = compare2(k)(v.value2); - if (v1 instanceof LT) { - $copy_v = v.value4; - return; - } - ; - if (v1 instanceof GT) { - $copy_v = v.value5; - return; - } - ; - if (v1 instanceof EQ) { - $tco_done = true; - return true; - } - ; - throw new Error("Failed pattern match at Data.Map.Internal (line 457, column 7 - line 460, column 19): " + [v1.constructor.name]); - } - ; - throw new Error("Failed pattern match at Data.Map.Internal (line 454, column 8 - line 460, column 19): " + [v.constructor.name]); - } - ; - while (!$tco_done) { - $tco_result = $tco_loop($copy_v); - } - ; - return $tco_result; - }; - return go2; - }; -}; var lookup = function(dictOrd) { var compare2 = compare(dictOrd); return function(k) { @@ -1798,20 +1757,6 @@ function setCanvasHeight(canvas) { }; }; } -function setFillStyle(ctx) { - return function(style) { - return function() { - ctx.fillStyle = style; - }; - }; -} -function fillRect(ctx) { - return function(r) { - return function() { - ctx.fillRect(r.x, r.y, r.width, r.height); - }; - }; -} function clearRect(ctx) { return function(r) { return function() { @@ -1826,17 +1771,6 @@ function setFont(ctx) { }; }; } -function fillText(ctx) { - return function(text5) { - return function(x) { - return function(y) { - return function() { - ctx.fillText(text5, x, y); - }; - }; - }; - }; -} // output/Graphics.Canvas/index.js var applySecond2 = /* @__PURE__ */ applySecond(applyEffect); @@ -1991,16 +1925,15 @@ var map1 = /* @__PURE__ */ map(functorArray); var union2 = /* @__PURE__ */ union(ordRecord2); var append3 = /* @__PURE__ */ append(semigroupArray); var lookup2 = /* @__PURE__ */ lookup(ordRecord2); -var member2 = /* @__PURE__ */ member(ordRecord2); -var $$for2 = /* @__PURE__ */ $$for(applicativeEffect)(traversableArray); +var when2 = /* @__PURE__ */ when(applicativeEffect); var discard22 = /* @__PURE__ */ discard2(/* @__PURE__ */ bindStateT(monadEffect)); var monadStateStateT2 = /* @__PURE__ */ monadStateStateT(monadEffect); var monadEffectState2 = /* @__PURE__ */ monadEffectState(monadEffectEffect); var applicativeStateT2 = /* @__PURE__ */ applicativeStateT(monadEffect); var pure1 = /* @__PURE__ */ pure(applicativeStateT2); -var when2 = /* @__PURE__ */ when(applicativeStateT2); +var when1 = /* @__PURE__ */ when(applicativeStateT2); var void1 = /* @__PURE__ */ $$void(/* @__PURE__ */ functorStateT(functorEffect)); -var for1 = /* @__PURE__ */ $$for(applicativeStateT2)(traversableArray); +var $$for2 = /* @__PURE__ */ $$for(applicativeStateT2)(traversableArray); var GfxWhiteout = /* @__PURE__ */ function() { function GfxWhiteout2() { } @@ -2046,36 +1979,37 @@ var EventMouseMove = /* @__PURE__ */ function() { }(); var tick = function(dictMonadState) { return modify_(dictMonadState)(function(st) { - var $109 = {}; - for (var $110 in st) { - if ({}.hasOwnProperty.call(st, $110)) { - $109[$110] = st[$110]; + var $110 = {}; + for (var $111 in st) { + if ({}.hasOwnProperty.call(st, $111)) { + $110[$111] = st[$111]; } ; } ; - $109.tick = st.tick + 1 | 0; - $109.transitions = function() { + $110.tick = st.tick + 1 | 0; + $110.redraw = false; + $110.transitions = function() { var v1 = uncons(st.transitions); if (v1 instanceof Nothing) { return st.transitions; } ; if (v1 instanceof Just) { - var $105 = isEmpty(v1.value0.head.cells); - if ($105) { + var $106 = isEmpty(v1.value0.head.cells); + if ($106) { return v1.value0.tail; } ; return st.transitions; } ; - throw new Error("Failed pattern match at Main (line 217, column 19 - line 219, column 79): " + [v1.constructor.name]); + throw new Error("Failed pattern match at Main (line 239, column 19 - line 241, column 79): " + [v1.constructor.name]); }(); - $109.inverse = filter2(function(v1) { + $110.inverse = filter2(function(v1) { return st.tick <= v1; })(st.inverse); - return $109; + return $110; }); }; var tick1 = /* @__PURE__ */ tick(monadStateStateT2); @@ -2092,7 +2026,7 @@ var pickRandom = function(cells2) { return new Just(v.value0); } ; - throw new Error("Failed pattern match at Main (line 135, column 3 - line 137, column 28): " + [v.constructor.name]); + throw new Error("Failed pattern match at Main (line 139, column 3 - line 141, column 28): " + [v.constructor.name]); }; }; var pickCell = function(dictMonadState) { @@ -2117,36 +2051,37 @@ var pickCell = function(dictMonadState) { ; if (v1 instanceof Just) { return put2(function() { - var $119 = {}; - for (var $120 in st) { - if ({}.hasOwnProperty.call(st, $120)) { - $119[$120] = st[$120]; + var $120 = {}; + for (var $121 in st) { + if ({}.hasOwnProperty.call(st, $121)) { + $120[$121] = st[$121]; } ; } ; - $119.cells = insert2(v1.value0.value0)(v1.value0.value1)(st.cells); - $119.transitions = cons(function() { - var $116 = {}; - for (var $117 in v.value0.head) { - if ({}.hasOwnProperty.call(v.value0.head, $117)) { - $116[$117] = v["value0"]["head"][$117]; + $120.redraw = true; + $120.cells = insert2(v1.value0.value0)(v1.value0.value1)(st.cells); + $120.transitions = cons(function() { + var $117 = {}; + for (var $118 in v.value0.head) { + if ({}.hasOwnProperty.call(v.value0.head, $118)) { + $117[$118] = v["value0"]["head"][$118]; } ; } ; - $116.cells = $$delete2(v1.value0.value0)(v.value0.head.cells); - return $116; + $117.cells = $$delete2(v1.value0.value0)(v.value0.head.cells); + return $117; }())(v.value0.tail); - return $119; + return $120; }()); } ; - throw new Error("Failed pattern match at Main (line 249, column 49 - line 252, column 120): " + [v1.constructor.name]); + throw new Error("Failed pattern match at Main (line 271, column 49 - line 274, column 135): " + [v1.constructor.name]); }); } ; - throw new Error("Failed pattern match at Main (line 246, column 3 - line 252, column 120): " + [v.constructor.name]); + throw new Error("Failed pattern match at Main (line 268, column 3 - line 274, column 135): " + [v.constructor.name]); }); }; }; @@ -2167,30 +2102,61 @@ var newContext = function __do() { })(); var render = getContext2D(v.value0)(); setFont(render)("bold 0.8vw Iosevka Comfy")(); - var cellDim = toNumber(ceil2(width8 / 100)); + var cellWidth = toNumber(ceil2(width8 / 200)); + var cellHeight = cellWidth * 2; return new Just({ window: w, canvas: v.value0, render, width: width8, height: height8, - cellDim, - widthCells: ceil2(100), - heightCells: ceil2(height8 / cellDim), + cellHeight, + cellWidth, + widthCells: ceil2(200), + heightCells: ceil2(height8 / cellHeight), events: [new EventGfx(GfxWhiteout.value)] }); } ; - throw new Error("Failed pattern match at Main (line 77, column 45 - line 97, column 10): " + [v.constructor.name]); + throw new Error("Failed pattern match at Main (line 79, column 45 - line 101, column 10): " + [v.constructor.name]); }; var initialState = { tick: 0, cells: empty2, inverse: empty2, - transitions: [] + transitions: [], + redraw: true }; var gfxTransitions = function(ctx) { return function(v) { + var linkRight = function(y) { + return function(fgc) { + return function(bgc) { + return function(str) { + return function(url) { + return fromFoldable2(mapWithIndex2(function(i) { + return function(c) { + return new Tuple({ + x: ((ctx.widthCells - length2(str) | 0) - 8 | 0) + i | 0, + y + }, { + bg: bgc, + fg: fgc, + "char": singleton4(c), + click: function(st) { + return function __do3() { + $$void2(open(url)("_blank")("")(ctx.window))(); + return st; + }; + } + }); + }; + })(toCharArray(str))); + }; + }; + }; + }; + }; var link = function(y) { return function(fgc) { return function(bgc) { @@ -2219,7 +2185,7 @@ var gfxTransitions = function(ctx) { }; }; }; - var fg = unions2([link(0)("purple")("white")("twitch.tv/lcolonq")("https://twitch.tv/lcolonq"), link(1)("blue")("white")("twitter.com/lcolonq")("https://twitter.com/lcolonq")]); + var fg = unions2([link(0)("purple")("white")("twitch.tv/lcolonq")("https://twitch.tv/lcolonq"), link(1)("blue")("white")("twitter.com/lcolonq")("https://twitter.com/lcolonq"), link(ctx.heightCells - 1 | 0)("white")("black")("the previous one")("https://pub.colonq.computer/~llll/cgi-bin/ring?me=llll&offset=-1"), linkRight(ctx.heightCells - 1 | 0)("white")("black")("the next one")("https://pub.colonq.computer/~llll/cgi-bin/ring?me=llll&offset=1")]); var bg = fromFoldable2(concat(flip(map1)(range2(0)(ctx.widthCells))(function(x) { return flip(map1)(range2(0)(ctx.heightCells))(function(y) { return new Tuple({ @@ -2239,7 +2205,7 @@ var gfxTransitions = function(ctx) { return singleton4(v1.value0); } ; - throw new Error("Failed pattern match at Main (line 182, column 12 - line 184, column 39): " + [v1.constructor.name]); + throw new Error("Failed pattern match at Main (line 188, column 12 - line 190, column 39): " + [v1.constructor.name]); }() }); }); @@ -2275,7 +2241,8 @@ var pullEvents = function(dictMonadState) { if (v instanceof Just) { return discard3(liftEffect2(write({ canvas: ctx.canvas, - cellDim: ctx.cellDim, + cellHeight: ctx.cellHeight, + cellWidth: ctx.cellWidth, height: ctx.height, heightCells: ctx.heightCells, render: ctx.render, @@ -2288,6 +2255,7 @@ var pullEvents = function(dictMonadState) { return pure22({ cells: st.cells, inverse: st.inverse, + redraw: st.redraw, tick: st.tick, transitions: append3(st.transitions)(gfxTransitions(ctx)(v.value0.head.value0)) }); @@ -2306,7 +2274,7 @@ var pullEvents = function(dictMonadState) { return liftEffect2(v1.value0.click(st)); } ; - throw new Error("Failed pattern match at Main (line 234, column 11 - line 236, column 53): " + [v1.constructor.name]); + throw new Error("Failed pattern match at Main (line 256, column 11 - line 258, column 53): " + [v1.constructor.name]); } ; if (v.value0.head instanceof EventMouseMove) { @@ -2320,17 +2288,18 @@ var pullEvents = function(dictMonadState) { }))); return pure22({ cells: st.cells, + redraw: st.redraw, tick: st.tick, transitions: st.transitions, inverse: union2(inv)(st.inverse) }); } ; - throw new Error("Failed pattern match at Main (line 231, column 7 - line 240, column 53): " + [v.value0.head.constructor.name]); + throw new Error("Failed pattern match at Main (line 253, column 7 - line 262, column 53): " + [v.value0.head.constructor.name]); }); } ; - throw new Error("Failed pattern match at Main (line 227, column 10 - line 240, column 53): " + [v.constructor.name]); + throw new Error("Failed pattern match at Main (line 249, column 10 - line 262, column 53): " + [v.constructor.name]); }())(function(st$prime) { return put2(st$prime); }); @@ -2340,69 +2309,16 @@ var pullEvents = function(dictMonadState) { }; }; var pullEvents1 = /* @__PURE__ */ pullEvents(monadStateStateT2)(monadEffectState2); -var drawCell = function(ctx) { - return function(st) { - return function(v) { - return function(pos) { - var x = toNumber(pos.x) * ctx.cellDim; - var y = toNumber(pos.y) * ctx.cellDim; - var fg = function() { - var $150 = member2({ - x: pos.x, - y: pos.y - })(st.inverse); - if ($150) { - return v.bg; - } - ; - return v.fg; - }(); - var bg = function() { - var $151 = member2({ - x: pos.x, - y: pos.y - })(st.inverse); - if ($151) { - return v.fg; - } - ; - return v.bg; - }(); - return function __do3() { - setFillStyle(ctx.render)(bg)(); - fillRect(ctx.render)({ - x, - y, - width: ctx.cellDim, - height: ctx.cellDim - })(); - setFillStyle(ctx.render)(fg)(); - return fillText(ctx.render)(v["char"])(x + ctx.cellDim / 4)(y + ctx.cellDim / 1.4)(); - }; - }; - }; - }; -}; -var drawCells = function(ctx) { - return function(st) { - return function(cells2) { - return $$void2($$for2(toUnfoldable2(cells2))(function(v) { - return drawCell(ctx)(st)(v.value1)(v.value0); - })); - }; - }; -}; var loop2 = function(rc) { return function(st) { return function __do3() { var ctx = read(rc)(); - clearRect(ctx.render)({ + when2(st.redraw)(clearRect(ctx.render)({ x: 0, y: 0, width: ctx.width, height: ctx.height - })(); - drawCells(ctx)(st)(st.cells)(); + }))(); var v = flip(runStateT)(st)(discard22(tick1)(function() { return discard22(pullEvents1(rc))(function() { var v2 = head(st.transitions); @@ -2411,12 +2327,12 @@ var loop2 = function(rc) { } ; if (v2 instanceof Just) { - return when2(rem(st.tick)(v2.value0.cadence) === 0)(void1(for1(range2(0)(v2.value0.speed))(function(v1) { + return when1(rem(st.tick)(v2.value0.cadence) === 0)(void1($$for2(range2(0)(v2.value0.speed))(function(v1) { return pickCell1; }))); } ; - throw new Error("Failed pattern match at Main (line 264, column 5 - line 268, column 58): " + [v2.constructor.name]); + throw new Error("Failed pattern match at Main (line 287, column 5 - line 291, column 58): " + [v2.constructor.name]); }); }))(); return $$void2(requestAnimationFrame(loop2(rc)(v.value1))(ctx.window))(); @@ -2424,6 +2340,7 @@ var loop2 = function(rc) { }; }; var main = function __do2() { + log2(apiServer)(); var w = windowImpl(); (function __do3() { var v = newContext(); @@ -2444,7 +2361,7 @@ var main = function __do2() { return write(v1.value0)(rc)(); } ; - throw new Error("Failed pattern match at Main (line 56, column 24 - line 58, column 41): " + [v1.constructor.name]); + throw new Error("Failed pattern match at Main (line 58, column 24 - line 60, column 41): " + [v1.constructor.name]); }; })(); addEventListener("resize")(lresize)(false)(toEventTarget(w))(); @@ -2462,19 +2379,20 @@ var main = function __do2() { var ctx = read(rc)(); return write({ canvas: ctx.canvas, - cellDim: ctx.cellDim, + cellHeight: ctx.cellHeight, + cellWidth: ctx.cellWidth, height: ctx.height, heightCells: ctx.heightCells, render: ctx.render, width: ctx.width, widthCells: ctx.widthCells, window: ctx.window, - events: cons(h(floor2(px / ctx.cellDim))(floor2(py / ctx.cellDim)))(ctx.events) + events: cons(h(floor2(px / ctx.cellWidth))(floor2(py / ctx.cellHeight)))(ctx.events) })(rc)(); }; } ; - throw new Error("Failed pattern match at Main (line 60, column 42 - line 66, column 116): " + [v1.constructor.name]); + throw new Error("Failed pattern match at Main (line 62, column 42 - line 68, column 121): " + [v1.constructor.name]); }); }; var lmouseclick = lmouse(EventMouseClick.create)(); @@ -2484,7 +2402,7 @@ var main = function __do2() { return loop2(rc)(initialState)(); } ; - throw new Error("Failed pattern match at Main (line 51, column 18 - line 71, column 27): " + [v.constructor.name]); + throw new Error("Failed pattern match at Main (line 53, column 18 - line 73, column 27): " + [v.constructor.name]); })(); return unit; }; diff --git a/fig-frontend-client/src/Config.js b/fig-frontend-client/src/Config.js new file mode 100644 index 0000000..11a9792 --- /dev/null +++ b/fig-frontend-client/src/Config.js @@ -0,0 +1 @@ +export const apiServer = globalThis.apiServer; diff --git a/fig-frontend-client/src/Config.purs b/fig-frontend-client/src/Config.purs new file mode 100644 index 0000000..b464803 --- /dev/null +++ b/fig-frontend-client/src/Config.purs @@ -0,0 +1,3 @@ +module Config where + +foreign import apiServer :: String diff --git a/fig-frontend-client/src/Main.purs b/fig-frontend-client/src/Main.purs index 723acd8..871ef8a 100644 --- a/fig-frontend-client/src/Main.purs +++ b/fig-frontend-client/src/Main.purs @@ -2,13 +2,11 @@ module Main where import Prelude +import Config as Config import Control.Monad.State (class MonadState, get, modify_, put, runStateT) -import Data.Array (concat, cons, head, length, mapWithIndex, range, uncons, zip, (!!)) -import Data.Int (ceil, floor, rem, round, toNumber) -import Data.Map (Map, delete, empty, filter, fromFoldable, insert, isEmpty, lookup, member, toUnfoldable, union, unions) +import Data.Array (concat, cons, delete, filter, foldM, foldr, fromFoldable, head, length, mapWithIndex, null, range, uncons, updateAt, (!!)) +import Data.Int (ceil, floor, quot, rem, toNumber) import Data.Maybe (Maybe(..)) -import Data.Set (Set) -import Data.Set as Set import Data.String.CodeUnits (toCharArray) import Data.String.CodeUnits as String import Data.Traversable (for) @@ -16,6 +14,7 @@ import Data.Tuple (Tuple(..)) import Effect (Effect) import Effect.Class (class MonadEffect, liftEffect) import Effect.Console (log) +import Effect.Exception (throw) import Effect.Random (randomInt) import Effect.Ref (Ref, new, read, write) import Graphics.Canvas (CanvasElement, Context2D, clearRect, fillRect, fillText, getCanvasElementById, getContext2D, setCanvasDimensions, setFillStyle, setFont) @@ -32,12 +31,27 @@ type Context = , render :: Context2D , width :: Number , height :: Number - , cellDim :: Number + , cellWidth :: Number + , cellHeight :: Number , widthCells :: Int , heightCells :: Int , events :: Array Event } +lookupPos :: forall m a t. MonadEffect m => Context -> {x :: Int, y :: Int | t} -> Array a -> m a +lookupPos ctx pos a = do + let idx = pos.y * ctx.widthCells + pos.x + case a !! idx of + Nothing -> liftEffect $ throw "index out of bounds" + Just x -> pure x + +updatePos :: forall m a t. MonadEffect m => Context -> {x :: Int, y :: Int | t} -> a -> Array a -> m (Array a) +updatePos ctx pos v a = do + let idx = pos.y * ctx.widthCells + pos.x + case updateAt idx v a of + Nothing -> liftEffect $ throw "index out of bounds" + Just a' -> pure a' + main :: Effect Unit main = do -- d <- toDocument <$> document w @@ -47,6 +61,7 @@ main = do -- l <- eventListener \_e -> -- log "click" -- addEventListener click l false $ toEventTarget e + log $ Config.apiServer w <- window newContext >>= case _ of Nothing -> log "failed to find canvas" @@ -63,7 +78,7 @@ main = do let px = toNumber $ pageX me let py = toNumber $ pageY me ctx <- read rc - write (ctx { events = cons (h (floor $ px / ctx.cellDim) (floor $ py / ctx.cellDim)) ctx.events }) rc + write (ctx { events = cons (h (floor $ px / ctx.cellWidth) (floor $ py / ctx.cellHeight)) ctx.events }) rc lmouseclick <- lmouse EventMouseClick addEventListener (EventType "click") lmouseclick false $ Window.toEventTarget w lmousemove <- lmouse EventMouseMove @@ -82,17 +97,19 @@ newContext = do setCanvasDimensions canvas { width, height } render <- getContext2D canvas setFont render "bold 0.8vw Iosevka Comfy" - let widthCells = 100.0 - let cellDim = toNumber $ ceil $ width / widthCells + let widthCells = 200.0 + let cellWidth = toNumber $ ceil $ width / widthCells + let cellHeight = cellWidth * 2.0 pure $ Just { window: w , canvas , render , width , height - , cellDim + , cellHeight + , cellWidth , widthCells: ceil widthCells - , heightCells: ceil $ height / cellDim + , heightCells: ceil $ height / cellHeight , events: [EventGfx GfxWhiteout] } @@ -105,39 +122,43 @@ newtype Cell = Cell drawCell :: forall t. Context -> State -> Cell -> { x :: Int, y :: Int | t } -> Effect Unit drawCell ctx st (Cell c) pos = do - let x = toNumber pos.x * ctx.cellDim - let y = toNumber pos.y * ctx.cellDim - let fg = if member { x: pos.x, y: pos.y } st.inverse then c.bg else c.fg - let bg = if member { x: pos.x, y: pos.y } st.inverse then c.fg else c.bg + let x = toNumber pos.x * ctx.cellWidth + let y = toNumber pos.y * ctx.cellHeight + inv <- lookupPos ctx pos st.inverse + let fg = if inv /= 0 then c.bg else c.fg + let bg = if inv /= 0 then c.fg else c.bg setFillStyle ctx.render bg fillRect ctx.render { x , y - , width: ctx.cellDim - , height: ctx.cellDim + , width: ctx.cellWidth + , height: ctx.cellHeight } setFillStyle ctx.render fg - fillText ctx.render c.char (x + ctx.cellDim / 4.0) (y + ctx.cellDim / 1.4) + fillText ctx.render c.char (x + ctx.cellWidth / 4.0) (y + ctx.cellHeight / 1.4) -type Cells = Map { x :: Int, y :: Int } Cell - -drawCells :: Context -> State -> Cells -> Effect Unit +drawCells :: Context -> State -> Array Cell -> Effect Unit drawCells ctx st cells = do - void $ for (toUnfoldable cells :: Array _) \(Tuple pos c) -> do - drawCell ctx st c pos + void $ for (range 0 ctx.widthCells) \x -> do + for (range 0 ctx.heightCells) \y -> do + let pos = { x, y } + c <- lookupPos ctx pos cells + drawCell ctx st c pos -type Picker = Cells -> Effect (Maybe (Tuple { x :: Int, y :: Int} Cell)) +type Picker = Context -> Array Cell -> Effect (Maybe (Tuple { x :: Int, y :: Int} Cell)) pickRandom :: Picker -pickRandom cells = do - let arr = toUnfoldable cells - idx <- randomInt 0 $ length arr - 1 - case arr !! idx of +pickRandom ctx cells = do + idx <- randomInt 0 $ length cells - 1 + case cells !! idx of Nothing -> pure Nothing - Just c -> pure $ Just c + Just c -> + pure + $ Just + $ Tuple { x: rem idx ctx.widthCells, y: quot idx ctx.widthCells } c type Transition = - { cells :: Cells + { cells :: Array Cell , speed :: Int , cadence :: Int , picker :: Picker @@ -145,17 +166,19 @@ type Transition = type State = { tick :: Int - , cells :: Cells - , inverse :: Map { x :: Int, y :: Int } Int + , cells :: Array Cell + , inverse :: Array Int , transitions :: Array Transition + , redraw :: Boolean } initialState :: State initialState = { tick: 0 - , cells: empty - , inverse: empty + , cells: [] + , inverse: [] , transitions: [] + , redraw: true } data Event @@ -183,7 +206,7 @@ gfxTransitions ctx GfxWhiteout = Nothing -> "?" Just c -> String.singleton c } - link :: Int -> String -> String -> String -> String -> Cells + link :: Int -> String -> String -> String -> String -> Array Cell link y fgc bgc str url = fromFoldable $ mapWithIndex (\i c -> @@ -196,12 +219,28 @@ gfxTransitions ctx GfxWhiteout = } ) $ toCharArray str - fg = - unions - [ link 0 "purple" "white" "twitch.tv/lcolonq" "https://twitch.tv/lcolonq" - , link 1 "blue" "white" "twitter.com/lcolonq" "https://twitter.com/lcolonq" - ] - cells = union fg bg + linkRight :: Int -> String -> String -> String -> String -> Array Cell + linkRight y fgc bgc str url = + fromFoldable $ mapWithIndex + (\i c -> + Tuple { x: (ctx.widthCells - String.length str - 8) + i, y } + $ Cell + { bg: bgc, fg: fgc, char: String.singleton c + , click: \st -> do + void $ open url "_blank" "" ctx.window + pure st + } + ) + $ toCharArray str + -- fg = + -- unions + -- [ link 0 "purple" "white" "twitch.tv/lcolonq" "https://twitch.tv/lcolonq" + -- , link 1 "blue" "white" "twitter.com/lcolonq" "https://twitter.com/lcolonq" + -- , link (ctx.heightCells - 1) "white" "black" "the previous one" "https://pub.colonq.computer/~llll/cgi-bin/ring?me=llll&offset=-1" + -- , linkRight (ctx.heightCells - 1) "white" "black" "the next one" "https://pub.colonq.computer/~llll/cgi-bin/ring?me=llll&offset=1" + -- ] + -- cells = union fg bg + cells = bg in [ { cells , speed: 20 @@ -214,9 +253,10 @@ tick :: forall m. MonadState State m => m Unit tick = modify_ \st -> st { tick = st.tick + 1 + , redraw = false , transitions = case uncons st.transitions of Nothing -> st.transitions - Just { head, tail } -> if isEmpty head.cells then tail else st.transitions + Just { head, tail } -> if null head.cells then tail else st.transitions , inverse = filter (st.tick <= _) st.inverse } @@ -231,39 +271,41 @@ pullEvents rc = do case head of EventGfx gfx -> pure $ st { transitions = st.transitions <> gfxTransitions ctx gfx } EventMouseClick mx my -> - case lookup { x: mx, y: my } st.cells of + lookupPos ctx { x: mx, y: my } st.cells >>= case _ of Nothing -> pure st Just (Cell c) -> liftEffect $ c.click st EventMouseMove mx my -> do - let inv = fromFoldable $ concat $ flip map (range (mx - 1) (mx + 1)) \x -> - flip map (range (my - 1) (my + 1)) \y -> Tuple { x, y } (st.tick + 30) - pure st { inverse = union inv st.inverse } + inv <- foldM (\inv pos -> updatePos ctx pos (st.tick + 30) inv) st.inverse $ map (\x -> map (\y -> { x, y }) (range (my - 1) (my + 1))) (range (mx - 1) (mx + 1)) + pure st { inverse = inv } put st' -pickCell :: forall m. MonadState State m => MonadEffect m => m Unit -pickCell = do +pickCell :: forall m. MonadState State m => MonadEffect m => Context -> m Unit +pickCell ctx = do st <- get case uncons st.transitions of Nothing -> pure unit Just { head: trans, tail } -> liftEffect (trans.picker trans.cells) >>= case _ of Nothing -> pure unit - Just (Tuple pk pv) -> - put st { cells = insert pk pv st.cells, transitions = cons (trans { cells = (delete pk trans.cells) }) tail } + Just (Tuple pk pv) -> do + cells <- updatePos ctx pk pv st.cells + put st { redraw = true, cells = cells, transitions = cons (trans { cells = (delete pk trans.cells) }) tail } loop :: Ref Context -> State -> Effect Unit loop rc st = do ctx <- read rc -- render - clearRect ctx.render { x: 0.0, y: 0.0, width: ctx.width, height: ctx.height } - drawCells ctx st st.cells + when st.redraw do + clearRect ctx.render { x: 0.0, y: 0.0, width: ctx.width, height: ctx.height } + drawCells ctx st st.cells -- update - Tuple _ st' <- flip runStateT st do - tick - pullEvents rc - case head st.transitions of - Nothing -> pure unit - Just trans -> do - when (rem st.tick trans.cadence == 0) do - void $ for (range 0 trans.speed) \_ -> pickCell - void $ requestAnimationFrame (loop rc st') ctx.window + -- Tuple _ st' <- flip runStateT st do + -- tick + -- pullEvents rc + -- case head st.transitions of + -- Nothing -> pure unit + -- Just trans -> do + -- when (rem st.tick trans.cadence == 0) do + -- void $ for (range 0 trans.speed) \_ -> pickCell + -- void $ requestAnimationFrame (loop rc st') ctx.window + void $ requestAnimationFrame (loop rc st) ctx.window |
