rest api basics working, still some old crap here
commit
f2e8e074b6
@ -0,0 +1,2 @@
|
|||||||
|
__pycache__
|
||||||
|
venv
|
@ -0,0 +1,2 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/workspace.xml
|
@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JavaScriptSettings">
|
||||||
|
<option name="languageLevel" value="ES6" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8 (family)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/totpal.iml" filepath="$PROJECT_DIR$/.idea/totpal.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="Flask">
|
||||||
|
<option name="enabled" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3.8 (family)" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="TemplatesService">
|
||||||
|
<option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
|
||||||
|
<option name="TEMPLATE_FOLDERS">
|
||||||
|
<list>
|
||||||
|
<option value="$MODULE_DIR$/templates" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</module>
|
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -0,0 +1,11 @@
|
|||||||
|
FROM python:3.8
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
COPY requirements.txt ./
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
CMD [ "python", "app.py" ]
|
@ -0,0 +1,26 @@
|
|||||||
|
from os import getenv
|
||||||
|
from flask import Flask
|
||||||
|
from flask_socketio import SocketIO
|
||||||
|
|
||||||
|
from server import setup
|
||||||
|
|
||||||
|
import eventlet
|
||||||
|
eventlet.monkey_patch()
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
app.secret_key = getenv('APP_SECRET')
|
||||||
|
|
||||||
|
if not app.secret_key:
|
||||||
|
print("Please set a secret key via environment variable APP_SECRET")
|
||||||
|
import sys
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
socketio = SocketIO(app, async_mode='eventlet')
|
||||||
|
|
||||||
|
setup(app, socketio)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("starting")
|
||||||
|
socketio.run(app, debug=(getenv('DEV', 'false') == 'true'), port=int(getenv('PORT', 5000)), host='0.0.0.0')
|
@ -0,0 +1,11 @@
|
|||||||
|
with import <nixpkgs> {};
|
||||||
|
|
||||||
|
stdenv.mkDerivation rec {
|
||||||
|
name = "totpal-run-env";
|
||||||
|
|
||||||
|
buildInputs = [
|
||||||
|
(python3.withPackages (ps: with ps; [
|
||||||
|
flask flask-socketio eventlet pylint
|
||||||
|
]))
|
||||||
|
];
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
neo4j:
|
||||||
|
image: neo4j:4.1
|
||||||
|
volumes:
|
||||||
|
- 'db-data:/data'
|
||||||
|
environment:
|
||||||
|
NEO4J_AUTH: neo4j/my_password
|
||||||
|
ports:
|
||||||
|
- '127.0.0.1:7474:7474'
|
||||||
|
- '127.0.0.1:7687:7687'
|
||||||
|
|
||||||
|
python:
|
||||||
|
build: .
|
||||||
|
environment:
|
||||||
|
PORT: 5000
|
||||||
|
NEO4J_HOST: 'neo4j'
|
||||||
|
NEO4J_USER: 'neo4j'
|
||||||
|
NEO4J_PW: 'my_password'
|
||||||
|
UPLOAD_DIR: '/run/uploads'
|
||||||
|
DEV: 'true'
|
||||||
|
APP_SECRET: 'dev'
|
||||||
|
volumes:
|
||||||
|
- 'uploads:/run/uploads'
|
||||||
|
- './:/usr/src/app'
|
||||||
|
ports:
|
||||||
|
- '127.0.0.1:5000:5000'
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db-data:
|
||||||
|
uploads:
|
@ -0,0 +1,4 @@
|
|||||||
|
flask
|
||||||
|
flask-socketio
|
||||||
|
eventlet
|
||||||
|
neomodel
|
@ -0,0 +1,15 @@
|
|||||||
|
from os import getenv
|
||||||
|
from neomodel import config
|
||||||
|
|
||||||
|
pw = getenv('NEO4J_PW')
|
||||||
|
user = getenv('NEO4J_USER')
|
||||||
|
host = getenv('NEO4J_HOST')
|
||||||
|
port = getenv('NEO4J_PORT', 7687)
|
||||||
|
|
||||||
|
config.DATABASE_URL = f'bolt://{user}:{pw}@{host}:{port}'
|
||||||
|
|
||||||
|
from .api import attach_to_flask
|
||||||
|
|
||||||
|
|
||||||
|
def setup(app, socketio):
|
||||||
|
attach_to_flask(app)
|
@ -0,0 +1,120 @@
|
|||||||
|
import sys
|
||||||
|
from .models import *
|
||||||
|
from flask import Flask, request, jsonify
|
||||||
|
from typing import Dict, Type, List, Final
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class ApiError(BaseException):
|
||||||
|
def __init__(self, msg, status=400):
|
||||||
|
super().__init__()
|
||||||
|
self.msg = msg
|
||||||
|
self.status = status
|
||||||
|
|
||||||
|
|
||||||
|
def connect_callback(attr, target):
|
||||||
|
"""returns a callback to connect attr with target"""
|
||||||
|
return lambda: attr.connect(target)
|
||||||
|
|
||||||
|
|
||||||
|
def construct_object_from_request(cls, uid=None):
|
||||||
|
if uid:
|
||||||
|
obj: Final = cls.nodes.get_or_none(uid=uid)
|
||||||
|
|
||||||
|
if not obj:
|
||||||
|
raise ApiError(f"No {cls.__name__} found with uid {request.form.get('uid')}!", status=404)
|
||||||
|
else:
|
||||||
|
obj: Final = cls()
|
||||||
|
|
||||||
|
relationship_attach_callbacks = list()
|
||||||
|
|
||||||
|
for name, value in cls.__dict__.items():
|
||||||
|
if name[0] == '_' or not name[0].islower():
|
||||||
|
continue
|
||||||
|
|
||||||
|
cls_name = value.__class__.__name__
|
||||||
|
convert_with = None
|
||||||
|
|
||||||
|
if cls_name == 'StringProperty':
|
||||||
|
convert_with = str
|
||||||
|
elif cls_name == 'DateProperty':
|
||||||
|
convert_with = datetime.fromisoformat
|
||||||
|
elif cls_name == 'IntegerProperty':
|
||||||
|
convert_with = int
|
||||||
|
elif cls_name == 'FloatProperty':
|
||||||
|
convert_with = float
|
||||||
|
elif cls_name == 'RelationshipDefinition':
|
||||||
|
if name not in request.form:
|
||||||
|
continue
|
||||||
|
|
||||||
|
target_cls = value._raw_class
|
||||||
|
# convert class string to class by loading it from module object
|
||||||
|
if isinstance(target_cls, str):
|
||||||
|
target_cls = getattr(cls.__module__, target_cls)
|
||||||
|
|
||||||
|
for val in request.form.getlist(name):
|
||||||
|
target = target_cls.nodes.first_or_none(uid=val)
|
||||||
|
if not target:
|
||||||
|
raise ApiError(f'Cannot find referenced object uid={val} of type {target_cls.__name__}!')
|
||||||
|
|
||||||
|
relationship_attach_callbacks.append(connect_callback(getattr(obj, name), target))
|
||||||
|
if not convert_with:
|
||||||
|
# skip this property
|
||||||
|
continue
|
||||||
|
|
||||||
|
if name in request.form:
|
||||||
|
setattr(obj, name, convert_with(request.form.get(name)))
|
||||||
|
|
||||||
|
return obj, lambda: [c() for c in relationship_attach_callbacks]
|
||||||
|
|
||||||
|
|
||||||
|
def func_api_obj(cls: Type):
|
||||||
|
"""basic method for handling /api/type calls"""
|
||||||
|
if request.method == 'GET':
|
||||||
|
return jsonify([p.json(include_relations=False) for p in cls.nodes.all()])
|
||||||
|
if request.method == 'POST':
|
||||||
|
p, attach_relationships = construct_object_from_request(cls)
|
||||||
|
p.save()
|
||||||
|
attach_relationships()
|
||||||
|
return jsonify(p.json())
|
||||||
|
|
||||||
|
|
||||||
|
def func_api_obj_id(cls: Type, uid: str):
|
||||||
|
if request.method == 'GET':
|
||||||
|
person = Person.nodes.get_or_none(uid=uid)
|
||||||
|
if not person:
|
||||||
|
return "Not found", 404
|
||||||
|
return jsonify(person.json())
|
||||||
|
if request.method == 'PUT':
|
||||||
|
p, attach_relationships = construct_object_from_request(cls, uid=uid)
|
||||||
|
p.save()
|
||||||
|
attach_relationships()
|
||||||
|
return jsonify(p.json())
|
||||||
|
|
||||||
|
|
||||||
|
def attach_to_flask(app: Flask):
|
||||||
|
@app.route('/api/person', methods=['POST', 'GET'])
|
||||||
|
def func_api_person():
|
||||||
|
return func_api_obj(Person)
|
||||||
|
|
||||||
|
@app.route('/api/person/<uid>', methods=['PUT', 'GET'])
|
||||||
|
def func_api_person_id(uid):
|
||||||
|
return func_api_obj_id(Person, uid)
|
||||||
|
|
||||||
|
@app.route('/api/event', methods=['POST', 'GET'])
|
||||||
|
def func_api_event():
|
||||||
|
return func_api_obj(Event)
|
||||||
|
|
||||||
|
@app.route('/api/event/<uid>', methods=['PUT', 'GET'])
|
||||||
|
def func_api_event_id(uid):
|
||||||
|
return func_api_obj_id(Event, uid)
|
||||||
|
|
||||||
|
@app.route('/api/place', methods=['POST', 'GET'])
|
||||||
|
def func_api_place():
|
||||||
|
return func_api_obj(Place)
|
||||||
|
|
||||||
|
@app.route('/api/place/<uid>', methods=['PUT', 'GET'])
|
||||||
|
def func_api_place_id(uid):
|
||||||
|
return func_api_obj_id(Place, uid)
|
||||||
|
|
||||||
|
|
@ -0,0 +1 @@
|
|||||||
|
from .models import Place, Person, Event, Image, VisitRel
|
@ -0,0 +1,71 @@
|
|||||||
|
from neomodel import StructuredNode, StringProperty, RelationshipTo, RelationshipFrom, \
|
||||||
|
UniqueIdProperty, DateProperty, StructuredRel, RelationshipManager
|
||||||
|
from neomodel.cardinality import ZeroOrOne, ZeroOrMore, One, OneOrMore
|
||||||
|
|
||||||
|
|
||||||
|
class Jsonifiable(object):
|
||||||
|
def json(self, include_relations=True):
|
||||||
|
json = dict()
|
||||||
|
for name, value in vars(self).items():
|
||||||
|
if name == 'id':
|
||||||
|
continue
|
||||||
|
print(name, value)
|
||||||
|
if isinstance(value, RelationshipManager):
|
||||||
|
if not include_relations:
|
||||||
|
continue
|
||||||
|
if isinstance(value, (One, ZeroOrOne)):
|
||||||
|
val = value.get_or_none()
|
||||||
|
print(val)
|
||||||
|
json[name] = val.json(include_relations=False) if val else None
|
||||||
|
elif isinstance(value, (OneOrMore, ZeroOrMore)):
|
||||||
|
json[name] = [val.json(include_relations=False) for val in value.all()]
|
||||||
|
else:
|
||||||
|
json[name] = value
|
||||||
|
return json
|
||||||
|
|
||||||
|
|
||||||
|
class VisitRel(StructuredRel, Jsonifiable):
|
||||||
|
title = StringProperty(required=True)
|
||||||
|
description = StringProperty()
|
||||||
|
date = DateProperty()
|
||||||
|
|
||||||
|
|
||||||
|
class Person(StructuredNode, Jsonifiable):
|
||||||
|
uid = UniqueIdProperty()
|
||||||
|
name = StringProperty(required=True)
|
||||||
|
father = RelationshipTo('Person', 'FATHER', cardinality=ZeroOrOne)
|
||||||
|
mother = RelationshipTo('Person', 'MOTHER', cardinality=ZeroOrOne)
|
||||||
|
birthdate = DateProperty()
|
||||||
|
deceased = DateProperty()
|
||||||
|
description = StringProperty()
|
||||||
|
picture = StringProperty()
|
||||||
|
places = RelationshipTo('Place', 'VISITOR', ZeroOrMore, model=VisitRel)
|
||||||
|
|
||||||
|
|
||||||
|
class Event(StructuredNode, Jsonifiable):
|
||||||
|
uid = UniqueIdProperty()
|
||||||
|
name = StringProperty(required=True)
|
||||||
|
participants = RelationshipTo(Person, 'PARTICIPANT', cardinality=ZeroOrMore)
|
||||||
|
places = RelationshipTo('Place', 'HAPPENED_AT', cardinality=ZeroOrMore)
|
||||||
|
date = DateProperty()
|
||||||
|
end_date = DateProperty()
|
||||||
|
related_events = RelationshipTo('Event', 'RELATED_EVENT')
|
||||||
|
|
||||||
|
|
||||||
|
class Place(StructuredNode, Jsonifiable):
|
||||||
|
uid = UniqueIdProperty()
|
||||||
|
name = StringProperty(required=True)
|
||||||
|
description = StringProperty()
|
||||||
|
visitors = RelationshipFrom(Person, 'VISITOR', ZeroOrMore, model=VisitRel)
|
||||||
|
|
||||||
|
|
||||||
|
class Image(StructuredNode, Jsonifiable):
|
||||||
|
uid = StringProperty(unique_index=True, required=True)
|
||||||
|
description = StringProperty()
|
||||||
|
pictured = RelationshipTo(Person, 'PICTURED')
|
||||||
|
place = RelationshipTo(Place, 'TAKEN_AT')
|
||||||
|
date = DateProperty()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
|||||||
|
with import <nixpkgs> {};
|
||||||
|
let
|
||||||
|
extensions = (with pkgs.vscode-extensions; [
|
||||||
|
ms-python.python
|
||||||
|
]);
|
||||||
|
vscode-with-extensions = pkgs.vscode-with-extensions.override {
|
||||||
|
vscodeExtensions = extensions;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
stdenv.mkDerivation rec {
|
||||||
|
name = "totpal-dev-env";
|
||||||
|
|
||||||
|
buildInputs = [
|
||||||
|
(python3.withPackages (ps: with ps; [
|
||||||
|
flask flask-socketio eventlet pylint
|
||||||
|
]))
|
||||||
|
git
|
||||||
|
openjdk11
|
||||||
|
jetbrains.pycharm-professional
|
||||||
|
];
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
onload(() => {
|
||||||
|
const socket = io.connect(document.location.host);
|
||||||
|
|
||||||
|
socket.on('flash_msg', flash_msg);
|
||||||
|
|
||||||
|
socket.on('player_state', (data) => {
|
||||||
|
if (!data.secret) {
|
||||||
|
// our secret seems to be to old
|
||||||
|
localStorage.removeItem('secret');
|
||||||
|
return register();
|
||||||
|
}
|
||||||
|
//flash_msg("Logged in!");
|
||||||
|
localStorage.setItem('secret', data.secret);
|
||||||
|
localStorage.setItem('name', data.username);
|
||||||
|
document.cookie = `secret=${data.secret}`;
|
||||||
|
window.username = data.username;
|
||||||
|
$('.username').innerText = 'playing as ' + window.username;
|
||||||
|
});
|
||||||
|
|
||||||
|
function register() {
|
||||||
|
const name = localStorage.getItem('name') || prompt("Whats your username?");
|
||||||
|
socket.emit('signup', name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!localStorage.getItem('secret')) {
|
||||||
|
register()
|
||||||
|
} else {
|
||||||
|
socket.emit('resume', localStorage.getItem('secret'))
|
||||||
|
}
|
||||||
|
|
||||||
|
window.socket = socket;
|
||||||
|
});
|
@ -0,0 +1,83 @@
|
|||||||
|
html, body {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
|
font-family: 'roboto', 'open-sans', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-grow {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 70%;
|
||||||
|
max-width: 1080px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* specific styles */
|
||||||
|
|
||||||
|
html {
|
||||||
|
background: rgb(35, 36, 37);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
background: rgb(22, 22, 22);
|
||||||
|
margin-bottom: 32px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav h1 {
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav h1 span {
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav .container div {
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav .container .username {
|
||||||
|
padding: 16px;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: rgb(85, 150, 255);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-msg {
|
||||||
|
border: 1px solid;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin: 16px;
|
||||||
|
margin-top: -16px;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
background: rgb(48, 50, 51)
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-msg .close-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<script>(() => {onload_list = []; window.onload = (f) => {onload_list.push(f)}; window.__is_loaded = () => {onload_list.forEach(x => x());window.__is_loaded = () => undefined} })()</script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.1/socket.io.js"></script>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>TOTPAL - {{ title }}</title>
|
||||||
|
<script src="{{ url_for('static', filename='site.js') }}"></script>
|
||||||
|
{% block head %}
|
||||||
|
{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav><div class="container flex">
|
||||||
|
{% block nav %}
|
||||||
|
<div class="">
|
||||||
|
<h1>TOTPAL <span>{{ title }}</span> </h1>
|
||||||
|
</div>
|
||||||
|
<div class="username flex-grow"></div>
|
||||||
|
<div>
|
||||||
|
{% block links %}
|
||||||
|
<a href="/">Home</a>
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
</div></nav>
|
||||||
|
<main class="flex-grow">
|
||||||
|
<div class="container">
|
||||||
|
{% for message in get_flashed_messages() %}
|
||||||
|
<div class="flash-msg flex"><span class="flex-grow">{{ message | safe }}</span> <span class="close-btn">×</span></div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
No content
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<div class="container">
|
||||||
|
{% block footer %}
|
||||||
|
© Copyright by AntonLydike - play at your own risk
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<script>
|
||||||
|
if (__is_loaded) {__is_loaded()}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,6 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Error: {{ name }}</h1>
|
||||||
|
<p>{{ message }}</p>
|
||||||
|
{% endblock %}
|
@ -0,0 +1,78 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<script src="{{ url_for('static', filename='socket.js') }}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Game phase: <span class="game-phase">None</span></h1>
|
||||||
|
<h2>Players:</h2>
|
||||||
|
<ul class="player-list"></ul>
|
||||||
|
<div class="state-show state-searching">
|
||||||
|
<button class="start-game">Start game</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="state-show state-selecting">
|
||||||
|
<p>The player is <span class="guesser-name"></span></p>
|
||||||
|
<form id="select-url">
|
||||||
|
<input type="text" name="url" placeholder="url"/>
|
||||||
|
<input type="text" name="title" placeholder="title"/>
|
||||||
|
<button type="submit">Select</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="state-show state-guessing state-resolution">
|
||||||
|
<p>The articles title is: <span class="article-title"></span>...</p>
|
||||||
|
<button class="show-resolution state-guessing state-show">We are done guessing</button>
|
||||||
|
</div>
|
||||||
|
<div class="state-show state-resolution">
|
||||||
|
<p>The article was submitted by <span class="selected-player"></span>!</p>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
onload(() => {
|
||||||
|
socket.on('room_state', update_state);
|
||||||
|
|
||||||
|
function update_state(state) {
|
||||||
|
console.log(state);
|
||||||
|
print_players(state);
|
||||||
|
$('.article-title').innerText = state.article;
|
||||||
|
$('.selected-player').innerText = state.selected_player ? state.selected_player.name : '';
|
||||||
|
$('.game-phase').innerText = state.state.toLowerCase();
|
||||||
|
$('.guesser-name').innerText = state.guesser ? state.guesser.name : '';
|
||||||
|
|
||||||
|
$$('.state-show').forEach(elm => {
|
||||||
|
elm.hidden = !elm.classList.contains('state-' + state.state.toLowerCase());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function print_players(state) {
|
||||||
|
const root = $('.player-list');
|
||||||
|
root.innerHTML = "";
|
||||||
|
|
||||||
|
state.players.forEach(player => {
|
||||||
|
const elm = create_elm(`<li>${(player.online ? '●' : '○')} ${player.name}</li>`)[0]
|
||||||
|
root.appendChild(elm)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
update_state({{ room | safe }});
|
||||||
|
|
||||||
|
$('form#select-url').addEventListener('submit', e => {
|
||||||
|
e.preventDefault();
|
||||||
|
let url = e.target.elements.url.value;
|
||||||
|
let title = e.target.elements.title.value;
|
||||||
|
|
||||||
|
socket.emit('add_article', title, url)
|
||||||
|
});
|
||||||
|
|
||||||
|
$('button.start-game').addEventListener('click', e => {
|
||||||
|
socket.emit('start_selecting')
|
||||||
|
});
|
||||||
|
|
||||||
|
$('button.show-resolution').addEventListener('click', e => {
|
||||||
|
socket.emit('start_resolving')
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
@ -0,0 +1,42 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<script src="{{ url_for('static', filename='socket.js') }}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Games waiting:</h1>
|
||||||
|
|
||||||
|
<form action="/room" method="POST" id="create-room">
|
||||||
|
<input type="text" placeholder="Name" name="name">
|
||||||
|
<button class="create-room" type="submit">Create game</button>
|
||||||
|
<input type="hidden" name="secret" class="insert-secret"/>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<ul class="active-lobbies"></ul>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
onload(() => {
|
||||||
|
socket.on('room_list', draw_rooms);
|
||||||
|
|
||||||
|
function draw_rooms(list) {
|
||||||
|
console.log(list);
|
||||||
|
const root = $('.active-lobbies');
|
||||||
|
root.innerHTML = "";
|
||||||
|
|
||||||
|
list.forEach(room => {
|
||||||
|
const elm = create_elm(`<li>${room.name} with ${room.players.map(x => (x.online ? '● ' : '○ ') + x.name).join(", ")} <a href="/room/${room.id}">JOIN</a></li>`)[0]
|
||||||
|
root.appendChild(elm)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$('form#create-room').addEventListener('submit', e => {
|
||||||
|
e.target.elements.secret.value = localStorage.getItem('secret')
|
||||||
|
});
|
||||||
|
|
||||||
|
draw_rooms({{ rooms | safe }})
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue