Compare commits

..

44 Commits

Author SHA1 Message Date
Dominic Zimmer
b43439eab0 Add session view 2020-04-21 00:23:59 +02:00
Dominic Zimmer
f1430cd49b Add item moving to master view 2020-04-20 23:34:54 +02:00
Dominic Zimmer
96a1b49693 Update master view, Add sortable.js example 2020-04-20 11:07:15 +02:00
Dominic Zimmer
de35cfdc13 Add items to model, tehsession.json contains example 2020-04-20 00:33:23 +02:00
Dominic Zimmer
d386c771f0 Update foo 2020-04-19 21:20:12 +02:00
Dominic Zimmer
30515c4328 Add Modularity! 2020-04-19 20:03:59 +02:00
Dominic Zimmer
af2ef29585 Overhaul Model, Start Rendering of Lobby view 2020-04-19 16:01:00 +02:00
Dominic Zimmer
cc35dec294 Add stuff 2020-04-17 13:49:07 +02:00
Kai Vogelgesang
92065a7fa7 Add default value to login 2020-04-17 03:35:55 +02:00
Kai Vogelgesang
1d55095691 Make path relative 2020-04-17 03:13:14 +02:00
Kai Vogelgesang
f1d98376d4 Merge remote-tracking branch 'origin/master' 2020-04-17 03:10:56 +02:00
Kai Vogelgesang
4886f78357 Implement login 2020-04-17 03:10:52 +02:00
Dominic Zimmer
39301255a8 Now with variable usernames 2020-04-17 02:57:46 +02:00
Dominic Zimmer
f966ee4abc Merge branch 'master' of leafbla.de:dominic/webpnp 2020-04-17 02:48:43 +02:00
Dominic Zimmer
8fe6fa4884 USERNAMES 2020-04-17 02:48:40 +02:00
Kai Vogelgesang
4eb1009afe Make /static path relative 2020-04-17 02:44:53 +02:00
Kai Vogelgesang
364d155f9d Add boilerplate to index 2020-04-17 02:39:49 +02:00
Kai Vogelgesang
dd0490ee13 Change all_sessions to buttons 2020-04-17 02:33:24 +02:00
Kai Vogelgesang
40dd6ea865 Merge remote-tracking branch 'origin/master' 2020-04-17 02:25:16 +02:00
Kai Vogelgesang
785504ab6c Update session display 2020-04-17 02:25:12 +02:00
Dominic Zimmer
5f3cad8884 Update 2020-04-17 02:24:22 +02:00
Dominic Zimmer
940fed4bdf Suscription sends data 2020-04-17 02:21:07 +02:00
Dominic Zimmer
b90858b5cb Merge branch 'master' of leafbla.de:dominic/webpnp 2020-04-17 02:19:30 +02:00
Dominic Zimmer
f05503b871 Add allsessions 2020-04-17 02:19:28 +02:00
Kai Vogelgesang
ee708e9127 Implement session display 2020-04-17 02:18:24 +02:00
Dominic Zimmer
0f23259e04 Enable session switching 2020-04-17 02:17:02 +02:00
Dominic Zimmer
a4a776d2fb Send current session to player 2020-04-17 02:12:00 +02:00
Dominic Zimmer
ab931a5717 Fix an oops 2020-04-17 02:07:17 +02:00
Kai Vogelgesang
7824ca8c33 Implement API magic 2020-04-17 02:01:45 +02:00
Dominic Zimmer
125ecfd437 Fix typo 2020-04-17 02:00:17 +02:00
Dominic Zimmer
457d20bd44 Write model 2020-04-17 01:58:49 +02:00
Dominic Zimmer
43a2a40207 Add Createsession, joinsession to ui 2020-04-17 01:48:38 +02:00
Dominic Zimmer
db3d21a1fe Merge branch 'master' of leafbla.de:dominic/webpnp 2020-04-17 01:35:38 +02:00
Dominic Zimmer
21416bdc66 Add decor 2020-04-17 01:35:35 +02:00
Kai Vogelgesang
e1aa97aa71 Reformat 2020-04-17 01:20:53 +02:00
Kai Vogelgesang
3ae36aa759 ... 2020-04-17 01:00:33 +02:00
Kai Vogelgesang
3c852bcbd5 Merge remote-tracking branch 'origin/master' 2020-04-17 01:00:18 +02:00
Kai Vogelgesang
defb39b5cc Add backups and model file to gitignore 2020-04-17 01:00:15 +02:00
Dominic Zimmer
5a6d0cd052 Await it 2020-04-17 01:00:01 +02:00
Dominic Zimmer
866833df65 Syntax fix 2020-04-17 00:58:59 +02:00
Dominic Zimmer
05c472a3de More views 2020-04-17 00:58:32 +02:00
Dominic Zimmer
fc233deb7e Now with saved state 2020-04-17 00:46:30 +02:00
Dominic Zimmer
d94c1f4e3d Merge branch 'master' of leafbla.de:dominic/webpnp 2020-04-17 00:45:40 +02:00
Dominic Zimmer
cf0a2aa3b4 Return True 2020-04-17 00:45:38 +02:00
14 changed files with 830 additions and 113 deletions

3
.gitignore vendored
View File

@@ -121,3 +121,6 @@ venv.bak/
pip-selfcheck.json
# End of https://www.gitignore.io/api/python,virtualenv
tehmodel.json
backups

View File

@@ -1 +0,0 @@
{"bar": "foo", "foo": 3, "test": 4, "kso72uuzubg22om7": {}}

View File

@@ -4,38 +4,24 @@
<head>
<title>leafblade Minecraft Server</title>
<meta charset="UTF-8" />
<style type="text/css">
html,
body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
#map {
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 100%;
}
@media (max-width:768px) {
#overlay { width: 100% }
}
</style>
<script type="text/javascript">
</script>
<meta charset="UTF-8"/>
</head>
<body>
this is the base site
this is the base site
<a href="/newclient">Log in</a>
<br>
<input id="username" type="text" placeholder="Joe"/>
<button id="btn_register">idk</button>
<script>
document.getElementById("btn_register").onclick = function (e) {
let username = document.getElementById("username");
window.location.replace(`register/${encodeURIComponent(username.value || username.placeholder)}`)
};
</script>
</body>
</html>

41
main.py
View File

@@ -1,4 +1,8 @@
import aiohttp.web
import traceback
import urllib.parse
import os
import json
from model import Model
@@ -23,7 +27,6 @@ def get_client(request: aiohttp.web.Request):
print(f'[get_client] model does not know {client=}')
raise aiohttp.web.HTTPBadRequest()
return client
@@ -31,7 +34,7 @@ def get_client(request: aiohttp.web.Request):
async def handler(request: aiohttp.web.Request):
# this handler prevents people missing trailing slashes
client = get_client(request)
raise aiohttp.web.HTTPFound(f"/{client}/")
raise aiohttp.web.HTTPFound(f"{client}/")
@routes.get(CLIENT_REGEX + '/')
@@ -41,22 +44,26 @@ async def handler(request: aiohttp.web.Request):
return aiohttp.web.FileResponse('ui.html')
@routes.post(CLIENT_REGEX + '/api')
@routes.post(CLIENT_REGEX + '/api/{method}')
async def handler(request: aiohttp.web.Request):
client = get_client(request)
method = request.match_info.get('method', None)
model = request.app['model']
data = await request.json()
print(f'{client=} {data=}')
print(f'{method=} {client=} {data=}')
try:
assert await model.handle_post(client, data)
assert method in model.ApiMethod.dict
await model.ApiMethod.dict[method](model, client, **data)
return aiohttp.web.Response(status=200)
except Exception as e:
print(e)
del e # unused?
traceback.print_exc()
return aiohttp.web.Response(status=400)
finally:
await model.send_state(client)
@routes.get(CLIENT_REGEX + '/ws')
async def _(request: aiohttp.web.Request):
@@ -67,7 +74,7 @@ async def _(request: aiohttp.web.Request):
await ws.prepare(request)
print(f'[WS] client {client} connected, {ws=}')
model.subscribe(client, ws)
await model.subscribe(client, ws)
async for msg in ws:
print(f'[WS] incoming message from client {client}, {ws=}, {msg=}')
@@ -82,7 +89,7 @@ async def _(request: aiohttp.web.Request):
async def handler(request: aiohttp.web.Request):
model = request.app['model']
return aiohttp.web.json_response(model.sessions)
return aiohttp.web.json_response(model.model)
@routes.get('/')
@@ -92,11 +99,13 @@ async def handler(request):
return aiohttp.web.FileResponse('index.html')
@routes.get('/newclient')
@routes.get('/register/{username:.+}')
async def handler(request):
model = request.app['model']
username = request.match_info.get('username', 'Joe')
username = urllib.parse.unquote(username)
client = model.create_client()
client = model.create_client(username)
raise aiohttp.web.HTTPFound(f"/{client}/")
@@ -104,7 +113,13 @@ if __name__ == '__main__':
app = aiohttp.web.Application()
app.add_routes(routes)
app['model'] = Model()
data = {}
filename = "tehsession.json"
if os.path.isfile(filename):
with open(filename) as f:
data = json.load(f)
app['model'] = Model(model = data)
aiohttp.web.run_app(app, port=42042)
print("should save state")
app['model'].save()

381
model.py
View File

@@ -12,64 +12,343 @@ def generate_random_id(_s=set()):
return new_id
class Model:
def __init__(self, filename = "tehmodel.json"):
self.sockets = {} # mapping: client -> socket
self.filename = filename
self.model = None
if os.path.isfile(filename):
with open(filename) as f:
try:
self.model = json.load(f)
except:
self.model = {}
else:
self.model = {}
self.assert_model()
def assert_model(self):
if not "clients" in self.model:
self.model["clients"] = {}
if not "sessions" in self.model:
self.model["sessions"] = {}
class Model(object):
class ApiMethod:
dict = dict()
async def handle_post(self, clientid, data):
print("I have received P O S T data: " + str(data))
print("let me tell everyone")
for k, v in self.sockets.items():
await v.send_json({"message": "nudes"})
def __init__(self, fun):
self.dict[fun.__name__] = fun
def __contains__(self, item):
return item in self.dict
def __init__(self, model = {}):
if "sessions" not in model:
model["sessions"] = []
if "clients" not in model:
model["clients"] = []
if "items" not in model:
model["items"] = []
self.sockets = {} # mapping: clientid -> sockets
self.sessions = { session: Session(model = model["sessions"][session]) for session in model["sessions"] }
self.clients = { client: Client(model = model["clients"][client]) for client in model["clients"] }
self.items = { item: Item(model = model["items"][item]) for item in model["items"] }
self.filename = "tehsession.json"
def to_json(self):
model = {
"sessions": {session: self.sessions[session].to_json() for session in self.sessions },
"clients": {client: self.clients[client].to_json() for client in self.clients },
"items": {item: self.items[item].to_json() for item in self.items },
}
return model
@ApiMethod
async def test_api(self, clientid):
print(f'test_api {clientid=}')
@ApiMethod
async def test_yeet(self, clientid):
raise Exception('yeet')
@ApiMethod
async def change_username(self, clientid, username) -> str:
self.clients[clientid].name = username
@ApiMethod
async def create_session(self, clientid, sessionname) -> str:
if not sessionname:
raise Exception(f"Sessionname cant be empty!")
session = Session(name = sessionname, owner = clientid)
self.sessions[session.id] = session
print("create_session was called")
@ApiMethod
async def change_sessionname(self, clientid, sessionid, sessionname) -> str:
if not sessionname:
raise Exception("Cant be empty!")
session = self.sessions[sessionid]
if not (session.owner == clientid):
raise Exception("Ownly owner can change sessionname")
session.name = sessionname
@ApiMethod
async def create_item(self, clientid, name, description, image):
client = self.clients[clientid]
if not client.session:
raise Exception("create_item requires the client to be host. But client is in no session")
session = self.sessions[client.session]
if (session.owner != client.id):
raise Exception("create_item requires the client to be host. But client is not the owner")
if not name:
raise Exception("create_item requires an item name")
item = Item(name = name, description = description, image = image)
self.items[item.id] = item
@ApiMethod
async def leave_session(self, clientid):
client = self.clients[clientid]
sessionid = client.session
if sessionid:
session = self.sessions[sessionid]
session.clients.remove(clientid)
self.clients[clientid].session = ""
@ApiMethod
async def join_session(self, clientid, sessionid):
client = self.clients[clientid]
old_sessionid = client.session
# leave old session
if old_sessionid:
old_session = self.sessions[old_sessionid]
old_session.clients.remove(clientid)
session = self.sessions[sessionid]
client.session = sessionid
session.clients.append(client.id)
@ApiMethod
async def move_item(self, clientid, fromplayer, toplayer, itemid, toslot, fromslot):
client = self.clients[clientid]
fromclient = next((client for client in self.clients.values() if client.name == fromplayer), None)
toclient = next((client for client in self.clients.values() if client.name == toplayer), None)
if fromplayer == "master":
# create item
if not toclient:
raise Exception("to-client is illegal")
session = self.sessions[client.session]
if not session:
raise Exception("move item must be used in session")
if client.id != session.owner:
raise Exception("Only owner can move items")
toslot -= 1
if toslot > len(session.inventories[toclient.id]):
raise Exception("Index out of toplayers range")
session.inventories[toclient.id].insert(toslot, itemid)
elif toplayer == "master":
if not fromclient:
raise Exception("from-client is illegal")
session = self.sessions[client.session]
if not session:
raise Exception("move item must be used in session")
if client.id != session.owner:
raise Exception("Only owner can move items")
if itemid not in session.inventories[fromclient.id]:
raise Exception("he does not have that item")
fromslot -= 1
if fromslot > len(session.inventories[fromclient.id]):
raise Exception("Index out of fromplayer range")
session.inventories[fromclient.id].pop(fromslot)
else:
if not fromclient and not toclient:
raise Exception("from- or to-client are illegal")
session = self.sessions[client.session]
if not session:
raise Exception("move item must be used in session")
if client.id != session.owner:
raise Exception("Only owner can move items")
if itemid not in session.inventories[fromclient.id]:
raise Exception("he does not have that item")
toslot -= 1
if toslot > len(session.inventories[toclient.id]):
raise Exception("Index out of toplayers range")
fromslot -= 1
if fromslot > len(session.inventories[fromclient.id]):
raise Exception("Index out of fromplayer range")
session.inventories[fromclient.id].pop(fromslot)
session.inventories[toclient.id].insert(toslot, itemid)
@ApiMethod
async def draw(self, clientid):
await self.send_state(clientid)
async def send_lobby_view(self, clientid):
data = {}
client = self.clients[clientid]
data["view"] = "lobby"
data["username"] = client.name
data["sessions"] = {
session.id: {
"id": session.id,
"name": session.name,
"owned": session.owner == client.id,
} for session in self.sessions.values()
}
for socket in self.sockets[clientid]:
await socket.send_json(data)
async def send_master_view(self, clientid):
data = {}
client = self.clients[clientid]
session = self.sessions[client.session]
data["view"] = "master"
data["username"] = client.name
data["session"] = session.to_json()
data["items"] = self.to_json()["items"]
data["inventories"] = {}
for _client in session.inventories:
_client = self.clients[_client]
inventory = session.get_items(_client.id)
inventory = list(map(lambda itemid: self.to_json()["items"][itemid], inventory))
data["inventories"][_client.name] = inventory
for socket in self.sockets[clientid]:
await socket.send_json(data)
async def send_session_view(self, clientid):
data = {}
client = self.clients[clientid]
session = self.sessions[client.session]
data["view"] = "session"
data["username"] = client.name
data["session"] = session.name
data["inventories"] = {}
for _client in session.inventories:
_client = self.clients[_client]
inventory = session.get_items(_client.id)
inventory = list(map(lambda itemid: self.to_json()["items"][itemid], inventory))
data["inventories"][_client.name] = inventory
for socket in self.sockets[clientid]:
await socket.send_json(data)
async def send_state(self, clientid):
# TODO: compute state, send to client
data = {}
client = self.clients[clientid]
session = self.sessions[client.session] if client.session else None
if session:
if session.owner == client.id:
await self.send_master_view(clientid)
else:
await self.send_session_view(clientid)
else:
await self.send_lobby_view(clientid)
def save(self):
with open(self.filename, "w") as f:
json.dump(self.model, f)
if not os.path.isdir("backups"):
try:
os.mkdir("backups")
except FileExistsError:
print("backups is a file, no directory. Please delete yourself")
datestring = datetime.datetime.strftime(datetime.datetime.now(), "%Y-%m-%d-%H%M%S")
with open(f"backups/{datestring}_{self.filename}", "w") as f:
json.dump(self.model, f)
json.dump(self.to_json(), f)
def exists_client(self, clientid: str) -> bool:
return clientid in self.model["clients"]
return clientid in self.clients
def create_client(self) -> str:
clientname = generate_random_id()
newclient = {"id": clientname}
self.model["clients"][clientname] = newclient
return clientname
def create_client(self, name="Joe") -> str:
if not name:
raise Exception("Username cannot be empty!")
client = Client()
client.name = name
self.clients[client.id] = client
return client.id
def create_session(self) -> str:
sessionname = generate_random_id()
newsession = {"id": sessionname, "clients": []}
self.model["sessions"][sessionname] = newsession
return sessionname
async def subscribe(self, clientid, socket):
if not clientid in self.sockets:
self.sockets[clientid] = []
self.sockets[clientid].append(socket)
await self.send_state(clientid)
def subscribe(self, clientid, socket):
self.sockets[clientid] = socket
def unsubscribe(self, socket):
for k,v in self.sockets.items():
if v == socket:
self.sockets.pop(k)
for client in self.sockets:
if socket in self.sockets[client]:
self.sockets[client].remove(socket)
class Session:
def __init__(self, model = None, owner = None, name = None):
if model:
self.id = model["id"]
self.name = model["name"]
self.clients = model["clients"]
self.owner = model["owner"]
if "inventories" in model:
self.inventories = model["inventories"]
else:
self.inventories = {}
elif owner and name:
self.id = generate_random_id()
self.clients = []
self.owner = owner
self.name = name
self.inventories = {}
else:
raise Exception("Illegal session constructor")
def get_items(self, playerid):
if playerid in self.inventories:
return self.inventories[playerid]
else:
return []
def give_item(self, playerid, itemid):
if not playerid in self.inventories:
self.inventories[playerid] = []
self.inventories[playerid].append(itemid)
def to_json(self):
model = {
"id": self.id,
"name": self.name,
"clients": self.clients,
"owner": self.owner,
"inventories": self.inventories
}
return model
class Client:
def __init__(self, model = None):
if model:
self.id = model["id"]
self.name = model["name"]
self.session = model["session"]
else:
self.id = generate_random_id()
self.name = "Default Client Name"
self.session = ""
def to_json(self):
model = {
"id": self.id,
"name": self.name,
"session": self.session,
}
return model
class Item:
def __init__(self, model = None, name = None, description = "", image = "", tags = {}):
if model:
self.id = model["id"]
self.name = model["name"]
self.description = model["description"]
self.image = model["image"]
if "tags" in model:
self.tags = model["tags"]
else:
self.tags = {}
elif name:
self.id = generate_random_id()
self.name = name
self.description = description
self.image = image
self.tags = tags
else:
raise Exception("Illegal Item Constructor")
def to_json(self):
model = {
"id": self.id,
"name": self.name,
"description": self.description,
"image": self.image,
"tags": self.tags,
}
return model

View File

@@ -2,8 +2,33 @@ const ws_url = new URL('ws', window.location.href);
ws_url.protocol = ws_url.protocol.replace('http', 'ws');
const ws = new WebSocket(ws_url.href);
ws.onmessage = function(event) {
ws.onmessage = async function(event) {
const msg = JSON.parse(event.data);
console.log(msg);
};
if (msg.hasOwnProperty('view')) {
view = msg.view;
console.log(view)
contentdiv = document.getElementById("content")
response = await fetch('../static/views/' + view + '/template.html')
contentdiv.innerHTML = await response.text();
console.log('../static/views/' + view + '/template.html'+":")
console.log(contentdiv.innerHTML)
var tehscript = document.createElement('script');
tehscript.type = 'text/javascript';
tehscript.src = '../static/views/' + view + '/script.js';
var tehmsg = document.createElement('text');
tehmsg.style.display = "none"
tehmsg.id = "msg"
tehmsg.innerText = JSON.stringify(msg);
contentdiv.appendChild(tehmsg);
contentdiv.appendChild(tehscript);
}
};

View File

@@ -0,0 +1,143 @@
var text = document.getElementById("msg").innerText
msg = JSON.parse(text)
if (msg.hasOwnProperty('username')) {
document.getElementById('label-username').innerText = msg.username;
}
// username management
document.getElementById("btn-edit-username").onclick = async function (e) {
document.getElementById("btn-confirm-username").style.display = "inline-block";
document.getElementById("btn-discard-username").style.display = "inline-block";
document.getElementById("btn-edit-username").style.display = "none";
document.getElementById("label-username").style.display = "none";
document.getElementById("input-set-username").style.display = "inline-block";
document.getElementById("input-set-username").value = document.getElementById("label-username").innerText;
}
document.getElementById("btn-discard-username").onclick = async function (e) {
document.getElementById("btn-confirm-username").style.display = "none";
document.getElementById("btn-discard-username").style.display = "none";
document.getElementById("btn-edit-username").style.display = "inline-block";
document.getElementById("label-username").style.display = "inline-block";
document.getElementById("input-set-username").style.display = "none";
}
document.getElementById("btn-confirm-username").onclick = async function (e) {
var text = document.getElementById("input-set-username").value;
if (Boolean(text)) {
let data = {"username": text};
await fetch('api/change_username', {
method: 'POST',
body: JSON.stringify(data),
})
document.getElementById("btn-confirm-username").style.display = "none";
document.getElementById("btn-discard-username").style.display = "none";
document.getElementById("btn-edit-username").style.display = "inline-block";
document.getElementById("label-username").style.display = "inline-block";
document.getElementById("input-set-username").style.display = "none";
} else {
console.log("cant be empty");
document.getElementById("btn-confirm-username").style.display = "none";
document.getElementById("btn-discard-username").style.display = "none";
document.getElementById("btn-edit-username").style.display = "inline-block";
document.getElementById("label-username").style.display = "inline-block";
document.getElementById("input-set-username").style.display = "none";
}
}
document.getElementById("btn-create-session").onclick = async function (e) {
var text = document.getElementById("input-create-session").value;
if (Boolean(text)) {
let data = {"sessionname": text};
await fetch('api/create_session', {
method: 'POST',
body: JSON.stringify(data),
})
}
};
if (msg.hasOwnProperty('sessions')) {
const sessions = document.getElementById('sessions');
while (sessions.children.length) sessions.lastChild.remove();
Object.keys(msg.sessions).forEach( session => {
session = msg.sessions[session];
var sessionid = session["id"];
var sessionname = session["name"];
var owned = session["owned"];
const tehsession = document.createElement('div');
const labelname = document.createElement('span');
labelname.innerText = sessionname;
tehsession.appendChild(labelname);
if (owned) {
const inputname = document.createElement('input');
inputname.style.display = "none";
const btnedit = document.createElement('button');
btnedit.innerText = '🖉'
const btnconfirm = document.createElement('button');
btnconfirm.innerText = '✔'
btnconfirm.style.display = "none"
const btndiscard = document.createElement('button');
btndiscard.innerText = '✘'
btndiscard.style.display = "none"
btnedit.onclick = async function (e) {
inputname.style.display = "inline-block";
inputname.value = sessionname;
btnedit.style.display = "none";
btnconfirm.style.display = "inline-block";
btndiscard.style.display = "inline-block";
labelname.style.display = "none";
}
btndiscard.onclick = async function (e) {
inputname.style.display = "none";
btnedit.style.display = "inline-block";
btnconfirm.style.display = "none";
btndiscard.style.display = "none";
labelname.style.display = "inline-block";
}
btnconfirm.onclick = async function (e) {
text = inputname.value;
if (Boolean(text)) {
let data = {"sessionid": sessionid, "sessionname": text};
await fetch('api/change_sessionname', {
method: 'POST',
body: JSON.stringify(data),
});
inputname.style.display = "none";
btnedit.style.display = "inline-block";
btnconfirm.style.display = "none";
btndiscard.style.display = "none";
labelname.style.display = "inline-block";
} else {
console.log("cant be empty");
}
}
tehsession.appendChild(inputname);
tehsession.appendChild(btnedit);
tehsession.appendChild(btnconfirm);
tehsession.appendChild(btndiscard);
}
if ((! msg.hasOwnProperty('session')) || msg.session["id"] != sessionid) {
const btnjoin = document.createElement('button');
btnjoin.innerText = "Join";
btnjoin.onclick = async (e) => await fetch('api/join_session', {
method: 'POST', body: JSON.stringify({"sessionid": sessionid})
});
tehsession.appendChild(btnjoin);
}
sessions.appendChild(tehsession);
})
}

View File

@@ -0,0 +1,21 @@
<span id="greeting"></span><br>
<div id="username-area">
<span id="greeting">Hello, </span>
<span id="label-username">_</span>
<input id="input-set-username" style="display: none;">
<button id="btn-edit-username">🖉</button>
<button id="btn-discard-username" style="display: none;"></button>
<button id="btn-confirm-username" style="display: none;"></button>
</div>
<br>
<span>Available sessions</span><br>
<div id="sessions"></div><br>
<br>
<br>
<span>Create Session</span><br>
<input id="input-create-session">
<button id="btn-create-session">+</button>
<br>

View File

@@ -0,0 +1,135 @@
var text = document.getElementById("msg").innerText
msg = JSON.parse(text)
function onDrag (evt) {
var item = evt.item; // dragged HTMLElement
fromplayer = evt.from.getAttribute("inventory")
toplayer = evt.to.getAttribute("inventory")
itemid = item.getAttribute("itemid")
toslot = evt.newIndex
fromslot = evt.oldIndex
data = {
"fromplayer": fromplayer,
"toplayer": toplayer,
"itemid": itemid,
"fromslot": fromslot,
"toslot": toslot,
}
fetch('api/move_item', {
method: 'POST',
body: JSON.stringify(data),
});
}
function draw_item(item) {
const itemdiv = document.createElement('itemdiv');
var name = item["name"]
var description = item["description"]
var image = item["image"] || "../static/empty.jpg"
namespan = document.createElement('b');
namespan.innerText = name;
namespan.style.display = "block"
namespan.style.textAlign = "center"
descriptionspan = document.createElement('span');
descriptionspan.innerText = description;
descriptionspan.style.display = "block"
descriptionspan.style.width = "8rem";
imageimg = document.createElement('img');
imageimg.src = image;
imageimg.style.width = "8rem";
imageimg.style.display = "block"
itemdiv.appendChild(namespan);
itemdiv.appendChild(imageimg);
itemdiv.appendChild(descriptionspan);
itemdiv.style.display = "inline-block"
itemdiv.style.background = "lightcoral"
itemdiv.setAttribute("itemid", item["id"]);
return itemdiv
}
document.getElementById("btn-leave-session").onclick = async (e) => await fetch('api/leave_session', {
method: 'POST', body: JSON.stringify({})
});
var session = msg.session;
document.getElementById("session").innerText = session["name"];
document.getElementById('label-username').innerText = msg.username;
document.getElementById("btn-gen-item").onclick = async (e) => {
name = document.getElementById("input-gen-item-name").value;
description = document.getElementById("input-gen-item-description").value;
image = document.getElementById("input-gen-item-image").value;
if (Boolean(name)) {
await fetch('api/create_item', { method: 'POST', body: JSON.stringify(
{'name': name, 'description': description, 'image': image}
)});
}
};
var wrapper = document.getElementById("items");
var items = msg.items;
var itemsdiv = document.createElement('div');
var itemsheading = document.createElement('h3');
itemsheading.innerText = "All Items";
wrapper.style.background = "lightblue";
wrapper.style.display = "table";
while (wrapper.children.length) wrapper.lastChild.remove();
Object.keys(items).forEach( item => {
var item = items[item];
itemdiv = draw_item(item);
itemsdiv.appendChild(itemdiv);
});
Sortable.create(itemsdiv,
{
"group": "items",
"sort": "false",
"draggable": "itemdiv",
"onEnd": onDrag,
});
itemsdiv.setAttribute("inventory", "master");
wrapper.appendChild(itemsheading);
wrapper.appendChild(itemsdiv);
var inventories = document.getElementById("inventories");
while (inventories.children.length) inventories.lastChild.remove();
Object.keys(msg.inventories).forEach( name => {
inventory = msg.inventories[name];
inventorydiv = document.createElement('div');
inventorydiv.style.display = "block";
titlespan = document.createElement('h4');
titlespan.innerText = name;
titlespan.style.textAlign = "center";
inventorydiv.appendChild(titlespan);
Object.keys(inventory).forEach( item => {
item = inventory[item];
var thediv = draw_item(item);
//thediv.style.display = "block";
inventorydiv.appendChild(thediv);
inventorydiv.style.display = "inline-block";
});
inventorydiv.style.background = "lightgreen";
Sortable.create(inventorydiv,
{
"group": "items",
"sort": "false",
"draggable": "itemdiv",
"onEnd": onDrag,
});
inventorydiv.setAttribute("inventory", name);
inventories.appendChild(inventorydiv);
});
inventories.style.display = "inline-block";

View File

@@ -0,0 +1,14 @@
Welcome to <b id="session"></b>, lobbymaster <span id="label-username"></span>
<br><br>
<button id="btn-leave-session">Leave Session</button>
<div id="div-gen-item">
Name: <input id="input-gen-item-name" /> <br>
Description: <input id="input-gen-item-description" /> <br>
Image: <input id="input-gen-item-image" /> <br>
<button id="btn-gen-item">Create item</button>
</div>
<div id="items">
</div>
<div id="inventories">
</div>

View File

@@ -0,0 +1,80 @@
function draw_item(item) {
const itemdiv = document.createElement('itemdiv');
var name = item["name"]
var description = item["description"]
var image = item["image"] || "../static/empty.jpg"
namespan = document.createElement('b');
namespan.innerText = name;
namespan.style.display = "block"
namespan.style.textAlign = "center"
descriptionspan = document.createElement('span');
descriptionspan.innerText = description;
descriptionspan.style.display = "block"
descriptionspan.style.width = "8rem";
imageimg = document.createElement('img');
imageimg.src = image;
imageimg.style.width = "8rem";
imageimg.style.display = "block"
itemdiv.appendChild(namespan);
itemdiv.appendChild(imageimg);
itemdiv.appendChild(descriptionspan);
itemdiv.style.display = "inline-block"
itemdiv.style.background = "lightcoral"
itemdiv.setAttribute("itemid", item["id"]);
return itemdiv
}
var text = document.getElementById("msg").innerText
msg = JSON.parse(text)
document.getElementById("btn-leave-session").onclick = async (e) => await fetch('api/leave_session', {
method: 'POST', body: JSON.stringify({})
});
document.getElementById("session").innerText = msg.session;
document.getElementById('label-username').innerText = msg.username;
//fill inventory and inventories
inventorydiv = document.getElementById("inventory");
inventoriesdiv = document.getElementById("inventories");
while (inventorydiv.children.length) inventorydiv.lastChild.remove();
while (inventoriesdiv.children.length) inventoriesdiv.lastChild.remove();
username = msg.username;
inventories = msg.inventories;
if (username in inventories) {
inventory = inventories[username]
Object.keys(inventory).forEach( item => {
var item = inventory[item];
itemdiv = draw_item(item);
inventorydiv.appendChild(itemdiv);
});
}
Object.keys(inventories).forEach( inventory => {
if (!(inventory in inventories))
return;
if (inventory == username)
return;
var wrapper = document.createElement('div')
var usernamespan = document.createElement('span')
usernamespan.innerText = inventory;
var inventorydiv = document.createElement('div')
Object.keys(inventories[inventory]).forEach( item => {
var item = inventories[inventory][item];
itemdiv = draw_item(item);
inventorydiv.appendChild(itemdiv);
});
//inventorydiv.appendChild(itemdiv);
wrapper.appendChild(usernamespan)
wrapper.appendChild(inventorydiv)
inventoriesdiv.appendChild(wrapper);
});

View File

@@ -0,0 +1,13 @@
<div>
Hello <span id="label-username"></span>
<br><br>
Welkome to <b id="session"></b>! <br>
<button id="btn-leave-session">Leave Session</button>
<br><br>
</div>
<h3>Your items</h3>
<div id="inventory">
</div>
<h3>Other players' inventories</h3>
<div id="inventories">
</div>

1
tehsession.json Normal file
View File

@@ -0,0 +1 @@
{"sessions": {"2b7jrklcn6eulwbw": {"id": "2b7jrklcn6eulwbw", "name": "Die coole session von Player 1", "clients": ["hmrcjnvo2ngs7265", "bkrqopf5j6q3tpta", "cho2o2ntflm4mq3u"], "owner": "bkrqopf5j6q3tpta", "inventories": {"cho2o2ntflm4mq3u": ["hr3gqfed4zelzdcz"], "bkrqopf5j6q3tpta": ["tuqjjll2cltlw6i6"]}}}, "clients": {"bkrqopf5j6q3tpta": {"id": "bkrqopf5j6q3tpta", "name": "Player 1", "session": "2b7jrklcn6eulwbw"}, "cho2o2ntflm4mq3u": {"id": "cho2o2ntflm4mq3u", "name": "Player 2", "session": "2b7jrklcn6eulwbw"}, "hmrcjnvo2ngs7265": {"id": "hmrcjnvo2ngs7265", "name": "Joe", "session": "2b7jrklcn6eulwbw"}}, "items": {"hr3gqfed4zelzdcz": {"id": "hr3gqfed4zelzdcz", "name": "Revive", "description": "Revives a KO pokemon and brings it to 50% health", "image": "https://1.bp.blogspot.com/-Hz8sIf0g7cY/VsyVIWiLsnI/AAAAAAAAnII/DjW20xLr0R4/s1600/max_revive_by_peetzaahhh2010-d8oki1o.png", "tags": {}}, "7htnswtslkla6rqh": {"id": "7htnswtslkla6rqh", "name": "Potion", "description": "Heals your Pokey-Man by 20 hp", "image": "https://cdn.bulbagarden.net/upload/thumb/4/45/PotionBaseSet94.jpg/200px-PotionBaseSet94.jpg", "tags": {}}, "tuqjjll2cltlw6i6": {"id": "tuqjjll2cltlw6i6", "name": "Revive\u2122", "description": "When mom tells you that there is Max-Revive at home", "image": "https://www.pokewiki.de/images/5/58/Vitalkraut_Traumwelt.png", "tags": {}}}}

39
ui.html
View File

@@ -1,27 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<title>leafblade Minecraft Server</title>
<meta charset="UTF-8"/>
</head>
<head>
<title>leafblade Minecraft Server</title>
<script src="https://raw.githack.com/SortableJS/Sortable/master/Sortable.js"></script>
<meta charset="UTF-8"/>
<style>
div {
/* border: 1px solid; */
margin: 5px;
}
</style>
</head>
<body>
this is the UI
<body>
<div id="content">
</div>
</body>
<button id="test_api">Test API</button>
<script src="../static/renderer.js"></script>
<script src="/static/renderer.js"></script>
<script>
document.getElementById("test_api").onclick = async function (e) {
let data = {foo: 'bar'};
await fetch('api', {
<script>
fetch('api/draw', {
method: 'POST',
body: JSON.stringify(data),
})
};
</script>
</body>
body: JSON.stringify({}),
});
</script>
</html>