Skip to content

Commit

Permalink
http + (buggy WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
lue-bird committed Nov 22, 2023
1 parent 96f0b2f commit 29abae4
Show file tree
Hide file tree
Showing 12 changed files with 556 additions and 94 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
build
elm-stuff
node_modules
zombie
zombie
.vscode
runner-compiled
44 changes: 40 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ type Event
\state ->
case state of
Ok iconAndContent ->
..your ui.. iconAndContent
..your ui using iconAndContent..

Err ... ->
..error ui..
Expand All @@ -144,22 +144,58 @@ type alias State =
\state ->
case ( state.icon, state.content ) of
( Ok icon, Ok content ) ->
..your ui.. { icon = icon, content = content }
..your ui using icon and content..

_ ->
[ state.icon
|> Result.withDefault
(Http.request { url = "...", decoder = Image.jsonDecoder }
|> Interface.on (\content -> { state | content = Ok content })
|> Interface.on (\result -> { state | icon = result })
)
, state.content
|> Result.withDefault
(Http.request { url = "...", decoder = Json.Decode.string }
|> Interface.on (\content -> { state | content = Ok content })
|> Interface.on (\result -> { state | content = result })
)
, ..error ui..
]
|> List.filterMap identity
}
```
which feels more explicit, declarative and less wiring-heavy at least.

## setup
```bash
npm install @lue-bird/elm-state-interface
```
in js
```js
import * as BrowserApp from "@lue-bird/elm-state-interface"

const elmApp = Elm.Main.init({});
BrowserApp.start(elmApp.ports, document.getElementById("app")) // with your main element
```
(if the state-interface is embedded in a larger elm app,
the given argument should be the inner app's element.)

in elm
```elm
port module Main exposing (main)

import BrowserApp
import Json.Encode

main : Program () (BrowserApp.State ..State..) (BrowserApp.Event ..State..)
main =
app |> BrowserApp.toProgram

app : BrowserApp.Config ..State..
app =
{ initialState = ...
, interface = ...
, ports = { fromJs = fromJs, toJs = toJs }
}

port toJs : Json.Encode.Value -> Cmd event_
port fromJs : (Json.Encode.Value -> event) -> Sub event
```
2 changes: 1 addition & 1 deletion example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
<body>
<main id="app">
</main>
<script type="module" src="./index.ts"></script>
<script src="./build/main.js"></script>
<script type="module" src="./index.js"></script>
</body>

</html>
4 changes: 4 additions & 0 deletions example/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import * as BrowserApp from "../runner-compiled/index.js"

const elmApp = window.Elm.Main.init({ node: document.getElementById("app") })
BrowserApp.start(elmApp.ports, document.getElementById("app"))
4 changes: 2 additions & 2 deletions example/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 7 additions & 5 deletions example/package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
{
"type": "module",
"name": "elm-jam-6-submission",
"name": "elm-state-interface",
"version": "1.0.0",
"description": "play against a custom chess computer",
"main": "public/main.js",
"description": "TEA but simpler, safer and more declarative",
"main": "index.js",
"keywords": [
"elm",
"chess"
"elm"
],
"scripts": {
"start": "elm-watch hot"
},
"dependencies": {
"elm-codegen": "^0.3.0",
"elm-optimize-level-2": "^0.3.4",
Expand Down
8 changes: 0 additions & 8 deletions example/src/index.ts

This file was deleted.

2 changes: 0 additions & 2 deletions runner/http/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// Http Task

export interface HttpRequest {
url: string;
method: string;
Expand Down
118 changes: 59 additions & 59 deletions runner/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { HttpRequest, HttpResponse } from "./http";
import * as fetchAdapter from "./http/fetch";
/*
import {
DomError,
DomElement,
Viewport,
SetViewportOptions,
SetViewportOfOptions,
} from "./browser";
import * as dom from "./browser/dom";
import * as dom from "./browser/dom"; */

export * from "./http";
export * from "./browser";
Expand All @@ -20,24 +20,6 @@ export interface ElmPorts {
}

export function start(ports: ElmPorts, appElement: HTMLElement) {

// copied and edited from https://github.com/elm/virtual-dom/blob/master/src/Elm/Kernel/VirtualDom.js
// XSS ATTACK VECTOR CHECKS
//
// For some reason, tabs can appear in href protocols and it still works.
// So '\tjava\tSCRIPT:alert("!!!")' and 'javascript:alert("!!!")' are the same
// in practice.
//
// Pulling the regular expressions out to the top level gives a slight speed
// boost in small benchmarks (4-10%) but hoisting values to reduce allocation
// can be unpredictable in large programs where JIT may have a harder time with
// functions are not fully self-contained. The benefit is more that the js and
// js_html ones are so weird that I prefer to see them near each other.
const RE_script = /^script$/i;
function noScript(tag: string) {
return RE_script.test(tag) ? 'p' : tag
}

const interfaceImplementations: { on: (event: any) => any, run: (config: any, sendToElm: (v: any) => void) => void }[] = [
{
on: event => event?.addRequestTimeNow,
Expand Down Expand Up @@ -75,11 +57,16 @@ export function start(ports: ElmPorts, appElement: HTMLElement) {
run: (_config, _sendToElm) => {
appElement.replaceChildren()
}
},
{
on: event => event?.http,
run: (config, sendToElm) => {
fetchAdapter.http(config).then(response => { sendToElm(response) })
}
}
]



ports.toJs.subscribe(function (fromElm) {
// console.log("elm → js: ", fromElm)
function sendToElm(eventData: void) {
Expand All @@ -96,18 +83,6 @@ export function start(ports: ElmPorts, appElement: HTMLElement) {
})
});

function getTimezoneName(): string | number {
try {
return Intl.DateTimeFormat().resolvedOptions().timeZone;
} catch (e) {
return new Date().getTimezoneOffset();
}
}

function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}

function renderDomNode(path: number[], node: any, sendToElm: (v: any) => void) {
const createdDomNode = createDomNode([], node, sendToElm)
if (path.length === 0) {
Expand All @@ -130,33 +105,58 @@ export function start(ports: ElmPorts, appElement: HTMLElement) {
}
}
}
}

function createDomNode(innerPath: number[], node: any, sendToElm: (v: any) => any): HTMLElement | Text {
if (node?.text) {
return document.createTextNode(node.text)
} else { // if (node?.element)
const createdDomElement: HTMLElement = document.createElement(noScript(node.element.tag))
for (let [attributeKey, attributeValue] of Object.entries(node.element.attributes)) {
createdDomElement.setAttribute(attributeKey, attributeValue as string)
}
for (let [styleKey, styleValue] of Object.entries(node.element.styles)) {
createdDomElement.style.setProperty(styleKey, styleValue as string)
}
node.element.eventListeners.forEach((eventListenerName: string) => {
createdDomElement.addEventListener(
eventListenerName,
(triggeredEvent) => {
sendToElm({ innerPath: innerPath, name: eventListenerName, event: triggeredEvent })
}
)
})
for (let subIndex = 0; subIndex <= node.element.subs.length - 1; subIndex++) {
const sub = node.element.subs[subIndex]
createdDomElement.appendChild(
createDomNode([subIndex].concat(innerPath), sub, sendToElm)
)
}
return createdDomElement
function getTimezoneName(): string | number {
try {
return Intl.DateTimeFormat().resolvedOptions().timeZone;
} catch (e) {
return new Date().getTimezoneOffset();
}
}

function createDomNode(innerPath: number[], node: any, sendToElm: (v: any) => any): HTMLElement | Text {
if (node?.text) {
return document.createTextNode(node.text)
} else { // if (node?.element)
const createdDomElement: HTMLElement = document.createElement(noScript(node.element.tag))
for (let [attributeKey, attributeValue] of Object.entries(node.element.attributes)) {
createdDomElement.setAttribute(attributeKey, attributeValue as string)
}
for (let [styleKey, styleValue] of Object.entries(node.element.styles)) {
createdDomElement.style.setProperty(styleKey, styleValue as string)
}
node.element.eventListeners.forEach((eventListenerName: string) => {
createdDomElement.addEventListener(
eventListenerName,
(triggeredEvent) => {
sendToElm({ innerPath: innerPath, name: eventListenerName, event: triggeredEvent })
}
)
})
for (let subIndex = 0; subIndex <= node.element.subs.length - 1; subIndex++) {
const sub = node.element.subs[subIndex]
createdDomElement.appendChild(
createDomNode([subIndex].concat(innerPath), sub, sendToElm)
)
}
return createdDomElement
}
}

// copied and edited from https://github.com/elm/virtual-dom/blob/master/src/Elm/Kernel/VirtualDom.js
// XSS ATTACK VECTOR CHECKS
//
// For some reason, tabs can appear in href protocols and it still works.
// So '\tjava\tSCRIPT:alert("!!!")' and 'javascript:alert("!!!")' are the same
// in practice.
//
// Pulling the regular expressions out to the top level gives a slight speed
// boost in small benchmarks (4-10%) but hoisting values to reduce allocation
// can be unpredictable in large programs where JIT may have a harder time with
// functions are not fully self-contained. The benefit is more that the js and
// js_html ones are so weird that I prefer to see them near each other.
const RE_script = /^script$/i;
function noScript(tag: string) {
return RE_script.test(tag) ? 'p' : tag
}
Loading

0 comments on commit 29abae4

Please sign in to comment.