Sfoglia il codice sorgente

Add sample app and i/o

master
fenceFoil 4 anni fa
parent
commit
7432b6ad07
13 ha cambiato i file con 241 aggiunte e 114 eliminazioni
  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 Vedi File

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

+ 0
- 113
mockup/index.html Vedi 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 Vedi File

@@ -1 +0,0 @@
hrs

+ 2
- 0
src/blockingSampleApp.bat Vedi 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 Vedi 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 Vedi 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 Vedi File


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


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


+ 174
- 0
src/static/index.html Vedi 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 Vedi File


+ 1
- 0
src/static/reconnecting-websocket.js Vedi 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 Vedi File


Caricamento…
Annulla
Salva