Przeglądaj źródła

Add sample app and i/o

master
fenceFoil 4 lat temu
rodzic
commit
7432b6ad07
13 zmienionych plików z 241 dodań i 114 usunięć
  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 Wyświetl plik

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

+ 0
- 113
mockup/index.html Wyświetl plik

@@ -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 Wyświetl plik

@@ -1 +0,0 @@
hrs

+ 2
- 0
src/blockingSampleApp.bat Wyświetl plik

@@ -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 Wyświetl plik

@@ -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 Wyświetl plik

@@ -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 Wyświetl plik


mockup/static/custom.css → src/static/custom.css Wyświetl plik


mockup/static/htmx.min.js → src/static/htmx.min.js Wyświetl plik


+ 174
- 0
src/static/index.html Wyświetl plik

@@ -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 Wyświetl plik


+ 1
- 0
src/static/reconnecting-websocket.js Wyświetl plik

@@ -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 Wyświetl plik


Ładowanie…
Anuluj
Zapisz