Selaa lähdekoodia

Add sample app and i/o

master
fenceFoil 4 vuotta sitten
vanhempi
commit
7432b6ad07
13 muutettua tiedostoa jossa 241 lisäystä ja 114 poistoa
  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 Näytä tiedosto

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

+ 0
- 113
mockup/index.html Näytä tiedosto

@@ -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 Näytä tiedosto

@@ -1 +0,0 @@
hrs

+ 2
- 0
src/blockingSampleApp.bat Näytä tiedosto

@@ -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 Näytä tiedosto

@@ -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 Näytä tiedosto

@@ -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 Näytä tiedosto


mockup/static/custom.css → src/static/custom.css Näytä tiedosto


mockup/static/htmx.min.js → src/static/htmx.min.js Näytä tiedosto


+ 174
- 0
src/static/index.html Näytä tiedosto

@@ -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 Näytä tiedosto


+ 1
- 0
src/static/reconnecting-websocket.js Näytä tiedosto

@@ -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 Näytä tiedosto


Ladataan…
Peruuta
Tallenna