소스 검색

Add sample app and i/o

master
fenceFoil 4 년 전
부모
커밋
7432b6ad07
13개의 변경된 파일241개의 추가작업 그리고 114개의 파일을 삭제
  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 파일 보기

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

+ 0
- 113
mockup/index.html 파일 보기

@@ -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 파일 보기

@@ -1 +0,0 @@
hrs

+ 2
- 0
src/blockingSampleApp.bat 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기


mockup/static/custom.css → src/static/custom.css 파일 보기


mockup/static/htmx.min.js → src/static/htmx.min.js 파일 보기


+ 174
- 0
src/static/index.html 파일 보기

@@ -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 파일 보기


+ 1
- 0
src/static/reconnecting-websocket.js 파일 보기

@@ -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 파일 보기


불러오는 중...
취소
저장