Browse Source

Add sample app and i/o

master
fenceFoil 4 years ago
parent
commit
7432b6ad07
13 changed files with 241 additions and 114 deletions
  1. +2
    -0
      .gitignore
  2. +0
    -113
      mockup/index.html
  3. +0
    -1
      mockup/starthrs.bat
  4. +2
    -0
      src/blockingSampleApp.bat
  5. +53
    -0
      src/blockingSampleApp.py
  6. +9
    -0
      src/requirements.txt
  7. +0
    -0
      src/static/alpine.2.8.2.min.js
  8. +0
    -0
      src/static/custom.css
  9. +0
    -0
      src/static/htmx.min.js
  10. +174
    -0
      src/static/index.html
  11. +0
    -0
      src/static/normalize.css
  12. +1
    -0
      src/static/reconnecting-websocket.js
  13. +0
    -0
      src/templates/put_jinja2_templates_here.md

+ 2
- 0
.gitignore View File

@@ -0,0 +1,2 @@
__pycache__
venv

+ 0
- 113
mockup/index.html View File

@@ -1,113 +0,0 @@
<html>

<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="favicon.ico">
<link rel="stylesheet" href="/static/normalize.css">
<title>IF Game</title>
<!-- currently 1.1.0 -->
<script src="/static/htmx.min.js"></script>
<script scr="/static/alpine.2.8.2.min.js"></script>
<style>
#app {
background-color: gray;
height: 100vh;
display: flex;
flex-direction: column;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
#header {
background-color: black;
color: white;
position: sticky;

display: flex;
flex-direction: row;

padding: 0.7em;
}

#headerContent {
margin: auto auto;
}

#narrativeContainerWrapper {
overflow: auto;
display: flex;
flex-direction:column-reverse;

background-color: white;
width: 80ch;
max-width: 80ch;
height: calc(90% - 4em);
/*max-height: calc(90% - 2em);*/
margin: 0 auto;
margin-top: 1em;

scrollbar-width: none;
}

#narrativeContainerWrapper:hover {
scrollbar-width: unset;
}

.narrativeItem {
padding: 1em;
}

#playerEntry {
background-color: white;
width: 80ch;
margin: auto auto;

font-size: 1.2em;

display: flex;
align-items: center;
padding: 0 1em;
}

#playerEntry input {
border: 0;
outline: none;
width: 100%;
padding: 1rem;
font-weight: bold
}

</style>
<link rel="stylesheet" href="/static/custom.css">
</head>

<body>
<div id="app">
<div id="header">
<div id="headerContent">
I'm a header!
</div>
</div>
<div id="narrativeContainerWrapper">
<div id="narrativeContainer">
<script>
var x = 0;
setInterval(function () {
document.getElementById('lastNarrativeItem').insertAdjacentHTML('beforebegin', `<div style="height:100px" class="narrativeItem">Bogus content ${x}!</div>`)
x++;
}, 1000);
</script>
<div id="lastNarrativeItem">

</div>
</div>
</div>

<div id="playerEntry">
> <input type="text" spellcheck="false">
</div>
</div>
</body>

</html>

+ 0
- 1
mockup/starthrs.bat View File

@@ -1 +0,0 @@
hrs

+ 2
- 0
src/blockingSampleApp.bat View File

@@ -0,0 +1,2 @@
call ../venv/scripts/Activate.bat
uvicorn blockingSampleApp:app --reload --no-access-log --log-level warning --port 5777 --host 0.0.0.0

+ 53
- 0
src/blockingSampleApp.py View File

@@ -0,0 +1,53 @@
#standard
import asyncio
import html
import traceback
#custom
import dataset
from fastapi import FastAPI, WebSocket, Form, File, UploadFile, Response, Request, WebSocketDisconnect, websockets
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
import starlette.status as status
from starlette.responses import RedirectResponse

app = FastAPI()
app.mount('/static', StaticFiles(directory="./static"), name="static")
templates = Jinja2Templates(directory="templates")
@app.get('/', response_class=HTMLResponse)
async def getRoot(request: Request):
return RedirectResponse('/static/index.html', status_code=status.HTTP_302_FOUND)

db = dataset.connect('sqlite:///../state.db')

@app.websocket('/websocket')
async def runGameSession(websocket: WebSocket):
await websocket.accept()
try:
await websocket.send_json({
'type': 'header',
'rawHTML': 'Testing Header Text'
})

while True:
await websocket.send_json({
'type': 'narrative',
'rawHTML': 'Something <i>interesting</i> happened.'
})
while True:
try:
resp = await asyncio.wait_for(websocket.receive_json(), timeout=5)
await websocket.send_json({
'type': 'playerEcho',
'rawHTML': '> ' + html.escape(resp['playerInput'])
})
break
except asyncio.exceptions.TimeoutError:
# timeout
await websocket.send_json({
'type': 'narrativeUpdate',
'rawHTML': 'Something <i>EVEN MORE interesting</i> happened.'
})
except Exception as e:
traceback.print_exc()

+ 9
- 0
src/requirements.txt View File

@@ -0,0 +1,9 @@
dataset
click
tracery
stringcase
fastapi
aiofiles
python-multipart
uvicorn[standard]
jinja2

mockup/static/alpine.2.8.2.min.js → src/static/alpine.2.8.2.min.js View File


mockup/static/custom.css → src/static/custom.css View File


mockup/static/htmx.min.js → src/static/htmx.min.js View File


+ 174
- 0
src/static/index.html View File

@@ -0,0 +1,174 @@
<html>

<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="favicon.ico">
<link rel="stylesheet" href="/static/normalize.css">
<title>IF Game</title>
<!-- currently htmx 1.1.0 -->
<script src="/static/htmx.min.js"></script>
<script src="/static/alpine.2.8.2.min.js"></script>
<!-- https://github.com/joewalnes/reconnecting-websocket/blob/master/reconnecting-websocket.min.js -->
<script src="/static/reconnecting-websocket.js"></script>
<style>
#app {
background-color: gray;
height: 100vh;
display: flex;
flex-direction: column;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
#header {
background-color: black;
color: white;
position: sticky;

display: flex;
flex-direction: row;

padding: 0.7em;
}

#headerContent {
margin: auto auto;
}

#narrativeContainerWrapper {
overflow: auto;
display: flex;
flex-direction:column-reverse;

background-color: white;
width: 80ch;
max-width: 80ch;
height: calc(90% - 6em);
/*max-height: calc(90% - 2em);*/
margin: 0 auto;
margin-top: 1em;
margin-bottom: 1em;
padding-bottom: 1em;

scrollbar-width: none;
}

#narrativeContainerWrapper:hover {
scrollbar-width: unset;
}

.narrativeItem {
padding: 0.2em 1em;
}

.playerEchoItem {
padding: 0.2em 1em;
font-weight: bold;
}

#playerEntry {
background-color: white;
width: 80ch;
margin: auto auto;

font-size: 1.2em;

display: flex;
align-items: center;
padding: 0 1em;
}

#playerEntryInput {
border: 0;
outline: none;
width: 100%;
padding: 1rem;
font-weight: bold
}

</style>
<link rel="stylesheet" href="/static/custom.css">

<script>
var currNarrativeElement = 0;
function addNarrativeHTML(rawHTML) {
document.getElementById('lastNarrativeItem').insertAdjacentHTML('beforebegin', `<div class="narrativeItem" id="narrativeItem-${currNarrativeElement}">${rawHTML}</div>`)
currNarrativeElement++;
}

function replaceNarrativeHTML(rawHTML) {
document.getElementById(`narrativeItem-${currNarrativeElement-1}`).innerHTML = rawHTML;
}

var currPlayerEcho = 0;
function addPlayerEcho(rawHTML) {
document.getElementById('lastNarrativeItem').insertAdjacentHTML('beforebegin', `<div class="playerEchoItem" id="playerEchoItem-${currPlayerEcho}">${rawHTML}</div>`)
currPlayerEcho++;
}

function resetNarrative() {
// Clear narrative items and reset counter
currNarrativeElement = 0;
document.querySelectorAll('.narrativeItem').forEach(e => e.remove());
}

var ws = new ReconnectingWebSocket(`ws://${window.location.host}/websocket`);
ws.addEventListener('open', function(event) {
resetNarrative();
});
ws.addEventListener('message', function(event) {
console.log(event.data);
let message = JSON.parse(event.data);
if (message.type === 'narrative') {
addNarrativeHTML(message.rawHTML);
} else if (message.type === 'narrativeUpdate') {
replaceNarrativeHTML(message.rawHTML);
} else if (message.type === 'playerEcho') {
addPlayerEcho(message.rawHTML);
} else if (message.type === 'header') {
document.getElementById('headerContent').innerHTML = message.rawHTML;
}
});
</script>
</head>

<body>
<div id="app">
<div id="header">
<div id="headerContent">
I'm a header!
</div>
</div>
<div id="narrativeContainerWrapper">
<div id="narrativeContainer">
<div id="lastNarrativeItem"></div>
</div>
</div>

<div id="playerEntry">
> <input id="playerEntryInput" type="text" spellcheck="false">
<script>
document.getElementById('playerEntryInput').addEventListener('keyup', function(event){
if (event.key === 'Enter') {
let val = document.getElementById('playerEntryInput').value;
if (val === '') {
val = document.getElementById('playerEntryInput').placeholder;
}
ws.send(JSON.stringify({
'action': 'playerInputPrompt',
'playerInput': val
}));
document.getElementById('playerEntryInput').value = '';
document.getElementById('playerEntryInput').placeholder = val;
}
if (event.key === 'ArrowUp') {
document.getElementById('playerEntryInput').value = document.getElementById('playerEntryInput').placeholder;
}
});
</script>
</div>
</div>
</body>

</html>

mockup/static/normalize.css → src/static/normalize.css View File


+ 1
- 0
src/static/reconnecting-websocket.js View File

@@ -0,0 +1 @@
!function(a,b){"function"==typeof define&&define.amd?define([],b):"undefined"!=typeof module&&module.exports?module.exports=b():a.ReconnectingWebSocket=b()}(this,function(){function a(b,c,d){function l(a,b){var c=document.createEvent("CustomEvent");return c.initCustomEvent(a,!1,!1,b),c}var e={debug:!1,automaticOpen:!0,reconnectInterval:1e3,maxReconnectInterval:3e4,reconnectDecay:1.5,timeoutInterval:2e3};d||(d={});for(var f in e)this[f]="undefined"!=typeof d[f]?d[f]:e[f];this.url=b,this.reconnectAttempts=0,this.readyState=WebSocket.CONNECTING,this.protocol=null;var h,g=this,i=!1,j=!1,k=document.createElement("div");k.addEventListener("open",function(a){g.onopen(a)}),k.addEventListener("close",function(a){g.onclose(a)}),k.addEventListener("connecting",function(a){g.onconnecting(a)}),k.addEventListener("message",function(a){g.onmessage(a)}),k.addEventListener("error",function(a){g.onerror(a)}),this.addEventListener=k.addEventListener.bind(k),this.removeEventListener=k.removeEventListener.bind(k),this.dispatchEvent=k.dispatchEvent.bind(k),this.open=function(b){h=new WebSocket(g.url,c||[]),b||k.dispatchEvent(l("connecting")),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","attempt-connect",g.url);var d=h,e=setTimeout(function(){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","connection-timeout",g.url),j=!0,d.close(),j=!1},g.timeoutInterval);h.onopen=function(){clearTimeout(e),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onopen",g.url),g.protocol=h.protocol,g.readyState=WebSocket.OPEN,g.reconnectAttempts=0;var d=l("open");d.isReconnect=b,b=!1,k.dispatchEvent(d)},h.onclose=function(c){if(clearTimeout(e),h=null,i)g.readyState=WebSocket.CLOSED,k.dispatchEvent(l("close"));else{g.readyState=WebSocket.CONNECTING;var d=l("connecting");d.code=c.code,d.reason=c.reason,d.wasClean=c.wasClean,k.dispatchEvent(d),b||j||((g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onclose",g.url),k.dispatchEvent(l("close")));var e=g.reconnectInterval*Math.pow(g.reconnectDecay,g.reconnectAttempts);setTimeout(function(){g.reconnectAttempts++,g.open(!0)},e>g.maxReconnectInterval?g.maxReconnectInterval:e)}},h.onmessage=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onmessage",g.url,b.data);var c=l("message");c.data=b.data,k.dispatchEvent(c)},h.onerror=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onerror",g.url,b),k.dispatchEvent(l("error"))}},1==this.automaticOpen&&this.open(!1),this.send=function(b){if(h)return(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","send",g.url,b),h.send(b);throw"INVALID_STATE_ERR : Pausing to reconnect websocket"},this.close=function(a,b){"undefined"==typeof a&&(a=1e3),i=!0,h&&h.close(a,b)},this.refresh=function(){h&&h.close()}}return a.prototype.onopen=function(){},a.prototype.onclose=function(){},a.prototype.onconnecting=function(){},a.prototype.onmessage=function(){},a.prototype.onerror=function(){},a.debugAll=!1,a.CONNECTING=WebSocket.CONNECTING,a.OPEN=WebSocket.OPEN,a.CLOSING=WebSocket.CLOSING,a.CLOSED=WebSocket.CLOSED,a});

+ 0
- 0
src/templates/put_jinja2_templates_here.md View File


Loading…
Cancel
Save