Add the start of the negotiation page
This commit is contained in:
parent
5ada93a0a4
commit
87b160e950
|
@ -0,0 +1,6 @@
|
|||
[packages]
|
||||
flask="*"
|
||||
pony="*"
|
||||
flask-socketio="*"
|
||||
redis="*"
|
||||
eventlet="*"
|
|
@ -0,0 +1,186 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "707653c558287744f0facfa4f7b2ab0340dcccd0a0461c7a40399200d71e58db"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.python.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"click": {
|
||||
"hashes": [
|
||||
"sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc",
|
||||
"sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a"
|
||||
],
|
||||
"version": "==7.1.1"
|
||||
},
|
||||
"dnspython": {
|
||||
"hashes": [
|
||||
"sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01",
|
||||
"sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d"
|
||||
],
|
||||
"version": "==1.16.0"
|
||||
},
|
||||
"eventlet": {
|
||||
"hashes": [
|
||||
"sha256:4c8ab42c51bff55204fef43cff32616558bedbc7538d876bb6a96ce820c7f9ed",
|
||||
"sha256:955f2cf538829bfcb7b3aa885ace40e8ae5965dcd5b876c384d0c5869702db1d"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.25.2"
|
||||
},
|
||||
"flask": {
|
||||
"hashes": [
|
||||
"sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060",
|
||||
"sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.1.2"
|
||||
},
|
||||
"flask-socketio": {
|
||||
"hashes": [
|
||||
"sha256:2172dff1e42415ba480cee02c30c2fc833671ff326f1598ee3d69aa02cf768ec",
|
||||
"sha256:7ff5b2f5edde23e875a8b0abf868584e5706e11741557449bc5147df2cd78268"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.2.1"
|
||||
},
|
||||
"greenlet": {
|
||||
"hashes": [
|
||||
"sha256:000546ad01e6389e98626c1367be58efa613fa82a1be98b0c6fc24b563acc6d0",
|
||||
"sha256:0d48200bc50cbf498716712129eef819b1729339e34c3ae71656964dac907c28",
|
||||
"sha256:23d12eacffa9d0f290c0fe0c4e81ba6d5f3a5b7ac3c30a5eaf0126bf4deda5c8",
|
||||
"sha256:37c9ba82bd82eb6a23c2e5acc03055c0e45697253b2393c9a50cef76a3985304",
|
||||
"sha256:51155342eb4d6058a0ffcd98a798fe6ba21195517da97e15fca3db12ab201e6e",
|
||||
"sha256:51503524dd6f152ab4ad1fbd168fc6c30b5795e8c70be4410a64940b3abb55c0",
|
||||
"sha256:7457d685158522df483196b16ec648b28f8e847861adb01a55d41134e7734122",
|
||||
"sha256:8041e2de00e745c0e05a502d6e6db310db7faa7c979b3a5877123548a4c0b214",
|
||||
"sha256:81fcd96a275209ef117e9ec91f75c731fa18dcfd9ffaa1c0adbdaa3616a86043",
|
||||
"sha256:853da4f9563d982e4121fed8c92eea1a4594a2299037b3034c3c898cb8e933d6",
|
||||
"sha256:8b4572c334593d449113f9dc8d19b93b7b271bdbe90ba7509eb178923327b625",
|
||||
"sha256:9416443e219356e3c31f1f918a91badf2e37acf297e2fa13d24d1cc2380f8fbc",
|
||||
"sha256:9854f612e1b59ec66804931df5add3b2d5ef0067748ea29dc60f0efdcda9a638",
|
||||
"sha256:99a26afdb82ea83a265137a398f570402aa1f2b5dfb4ac3300c026931817b163",
|
||||
"sha256:a19bf883b3384957e4a4a13e6bd1ae3d85ae87f4beb5957e35b0be287f12f4e4",
|
||||
"sha256:a9f145660588187ff835c55a7d2ddf6abfc570c2651c276d3d4be8a2766db490",
|
||||
"sha256:ac57fcdcfb0b73bb3203b58a14501abb7e5ff9ea5e2edfa06bb03035f0cff248",
|
||||
"sha256:bcb530089ff24f6458a81ac3fa699e8c00194208a724b644ecc68422e1111939",
|
||||
"sha256:beeabe25c3b704f7d56b573f7d2ff88fc99f0138e43480cecdfcaa3b87fe4f87",
|
||||
"sha256:d634a7ea1fc3380ff96f9e44d8d22f38418c1c381d5fac680b272d7d90883720",
|
||||
"sha256:d97b0661e1aead761f0ded3b769044bb00ed5d33e1ec865e891a8b128bf7c656",
|
||||
"sha256:e538b8dae561080b542b0f5af64d47ef859f22517f7eca617bb314e0e03fd7ef"
|
||||
],
|
||||
"version": "==0.4.15"
|
||||
},
|
||||
"itsdangerous": {
|
||||
"hashes": [
|
||||
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
|
||||
"sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
|
||||
],
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0",
|
||||
"sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"
|
||||
],
|
||||
"version": "==2.11.2"
|
||||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
|
||||
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
|
||||
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
|
||||
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
|
||||
"sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
|
||||
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
|
||||
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
|
||||
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
|
||||
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
|
||||
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
|
||||
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
|
||||
"sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
|
||||
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
|
||||
"sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
|
||||
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
|
||||
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
|
||||
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
|
||||
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
|
||||
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
|
||||
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
|
||||
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
|
||||
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
|
||||
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
|
||||
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
|
||||
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
|
||||
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
|
||||
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
|
||||
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
|
||||
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
|
||||
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
|
||||
"sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
|
||||
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
|
||||
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
|
||||
],
|
||||
"version": "==1.1.1"
|
||||
},
|
||||
"monotonic": {
|
||||
"hashes": [
|
||||
"sha256:23953d55076df038541e648a53676fb24980f7a1be290cdda21300b3bc21dfb0",
|
||||
"sha256:552a91f381532e33cbd07c6a2655a21908088962bb8fa7239ecbcc6ad1140cc7"
|
||||
],
|
||||
"version": "==1.5"
|
||||
},
|
||||
"pony": {
|
||||
"hashes": [
|
||||
"sha256:8a9e7339fe7a5182566c83047dbae8053aa1cf458bff0f21f1ae1b106a210cbb"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.7.13"
|
||||
},
|
||||
"python-engineio": {
|
||||
"hashes": [
|
||||
"sha256:222926adb4bc6e03a8fc8e0ef2a3309f030c1c3f8e0fcc94c9ba214574565f02",
|
||||
"sha256:2481732d93646998f7372ef0ecf003af7817b82720b881db173c3d50b4887916"
|
||||
],
|
||||
"version": "==3.12.1"
|
||||
},
|
||||
"python-socketio": {
|
||||
"hashes": [
|
||||
"sha256:149b98c33f8c3d09273fb4ebeb83781e4dc9411b56b27d9f058bec1bd1ed74b7",
|
||||
"sha256:81280cbbb7018d8ecdd006bf6025979733d347c0f2612282c1e21f6ed7d3b55b"
|
||||
],
|
||||
"version": "==4.5.1"
|
||||
},
|
||||
"redis": {
|
||||
"hashes": [
|
||||
"sha256:0dcfb335921b88a850d461dc255ff4708294943322bd55de6cfd68972490ca1f",
|
||||
"sha256:b205cffd05ebfd0a468db74f0eedbff8df1a7bfc47521516ade4692991bb0833"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.4.1"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
|
||||
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
|
||||
],
|
||||
"version": "==1.14.0"
|
||||
},
|
||||
"werkzeug": {
|
||||
"hashes": [
|
||||
"sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43",
|
||||
"sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"
|
||||
],
|
||||
"version": "==1.0.1"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
}
|
|
@ -2,11 +2,15 @@
|
|||
|
||||
## Prerequisites
|
||||
|
||||
apt install python3-flask python3-pony
|
||||
apt install pipenv
|
||||
|
||||
## Dependency installation
|
||||
|
||||
pipenv sync
|
||||
|
||||
## Running
|
||||
|
||||
FLASK_DEBUG=1 FLASK_APP=negotiation.py ./negotiation.py
|
||||
FLASK_ENV=development FLASK_APP=negotiation.py pipenv run ./negotiation.py
|
||||
|
||||
# License
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
version: '3'
|
||||
services:
|
||||
messagequeue:
|
||||
image: "redis:buster"
|
||||
ports:
|
||||
- "6379:6379"
|
|
@ -6,6 +6,7 @@ import urllib
|
|||
import uuid
|
||||
|
||||
import flask
|
||||
import flask_socketio
|
||||
import pony
|
||||
from pony.flask import Pony
|
||||
|
||||
|
@ -13,7 +14,12 @@ word_file = "/usr/share/dict/words"
|
|||
WORDS = open(word_file).read().splitlines()
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
socketio = flask_socketio.SocketIO(app)
|
||||
|
||||
db = None
|
||||
# @TODO Change this into using redis or something else for storage across
|
||||
# threads etc.
|
||||
session_map = {}
|
||||
|
||||
@app.route('/')
|
||||
@pony.orm.db_session
|
||||
|
@ -58,7 +64,8 @@ def negotiation_create():
|
|||
return flask.redirect('/') # @TODO Signal error to user. They don't have a uid cookie
|
||||
if len(n) > 128:
|
||||
return flask.redirect('/') # @TODO Signal error to user. Request too Large
|
||||
if pony.orm.exists(nego for nego in Negotiation if nego.name == n):
|
||||
nego = get_negotiation(n)
|
||||
if nego:
|
||||
# @TODO Signal to the user that the negotiation already exists and they
|
||||
# are being sent to it instead.
|
||||
return flask.redirect('/negotiations/{}'.format(n))
|
||||
|
@ -73,8 +80,57 @@ def negotiation_redir():
|
|||
@app.route('/negotiations/<negotiation>')
|
||||
def negotiation(negotiation):
|
||||
r = flask.Response()
|
||||
uid = get_uid(r)
|
||||
return "{}".format(negotiation)
|
||||
uid, user = get_uid(r)
|
||||
nego = get_negotiation(negotiation)
|
||||
if not nego:
|
||||
return "Error!"
|
||||
c = {
|
||||
'negotiation': nego,
|
||||
'user': uid,
|
||||
'room_owner': nego.owner,
|
||||
'room_owner_display_name': nego.owner.display_name if nego.owner.display_name else nego.owner.uid,
|
||||
'participant_count': len(get_room_participants(negotiation)),
|
||||
}
|
||||
r.data = render_page('negotiation.html', c)
|
||||
return r
|
||||
|
||||
@socketio.on('join negotiation')
|
||||
def handle_join_negotiation(json):
|
||||
r = flask.Response()
|
||||
app.logger.info('Received join request: {}'.format(json))
|
||||
uid, user = get_uid(r)
|
||||
flask_socketio.join_room(json['room'])
|
||||
session_map[flask.request.sid] = uid;
|
||||
app.logger.info('{} has joined room "{}" (sid: {})'.format(uid, json['room'],
|
||||
flask.request.sid))
|
||||
participants = get_room_participants(json['room'])
|
||||
app.logger.info('{} has {} connections'.format(
|
||||
json['room'], len(participants)))
|
||||
flask_socketio.emit('participants changed', {'participants': participants},
|
||||
room = json['room'])
|
||||
return True
|
||||
|
||||
def get_room_participants(room):
|
||||
sessions = [s for s in socketio.server.manager.get_participants('/', room)]
|
||||
participants = [p for s,p in session_map.items() if s in sessions]
|
||||
app.logger.debug(participants)
|
||||
return participants
|
||||
|
||||
@socketio.on('leave negotiation')
|
||||
def handle_leave_disconnect(json):
|
||||
r = flask.Response()
|
||||
app.logger.info('Received join request: {}'.format(json))
|
||||
uid, user = get_uid(r)
|
||||
del session_map[flask.request.sid]
|
||||
app.logger.info('{} has disconnected from {} (sid: {})'.format(uid, json['room'],flask.request.sid))
|
||||
participants = get_room_participants(json['room'])
|
||||
flask_socketio.emit('participants changed', {'participants': participants},
|
||||
room = json['room'])
|
||||
return True
|
||||
|
||||
@pony.orm.db_session
|
||||
def get_negotiation(negotiation):
|
||||
return pony.orm.select(nego for nego in Negotiation if nego.name == negotiation).first()
|
||||
|
||||
@pony.orm.db_session
|
||||
def generate_uid():
|
||||
|
@ -136,4 +192,4 @@ if __name__ == '__main__':
|
|||
db.bind(**app.config['PONY'])
|
||||
db.generate_mapping(create_tables=True)
|
||||
Pony(app)
|
||||
app.run(host = bindaddr, port = port)
|
||||
socketio.run(app, host = bindaddr, port = port)
|
||||
|
|
|
@ -285,6 +285,10 @@ th {
|
|||
content: '\e80a'
|
||||
}
|
||||
|
||||
.ic-settings:after {
|
||||
content: '\2699'
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Cardo';
|
||||
font-style: normal;
|
||||
|
@ -1410,11 +1414,13 @@ img {
|
|||
position: relative;
|
||||
z-index: 10;
|
||||
margin: 0 0 0.5em;
|
||||
font-family: 'NordSudA';
|
||||
font-size: 2em;
|
||||
line-height: 1em;
|
||||
font-weight: 700;
|
||||
text-indent: -1px;
|
||||
color: #000
|
||||
color: #600000;
|
||||
text-shadow: 3px 5px #1d0202;
|
||||
}
|
||||
|
||||
.has-cover .post-title {
|
||||
|
@ -1427,6 +1433,10 @@ img {
|
|||
font-weight: inherit
|
||||
}
|
||||
|
||||
#negotiation-header .post-title {
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
.home-surtitle {
|
||||
font-family: 'NordSudA';
|
||||
font-size: 3em;
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
jquery-3.5.0.min.js
|
|
@ -0,0 +1,19 @@
|
|||
class Negotiation {
|
||||
|
||||
constructor(room) {
|
||||
this.socket = io();
|
||||
this.socket.on('connect', function() {
|
||||
console.log("Trying to join room " + room);
|
||||
this.socket.emit('join negotiation', {"room": room});
|
||||
});
|
||||
window.addEventListener('beforeunload', function(e) {
|
||||
console.log("Trying to leave room " + room);
|
||||
negotiation.socket.emit('leave negotiation', {"room": room});
|
||||
negotiation.socket.disconnect();
|
||||
return true;
|
||||
});
|
||||
this.socket.on('participants changed', function(data) {
|
||||
console.log(data);
|
||||
});
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,64 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block header %}
|
||||
<header id="negotiation-header">
|
||||
<div class="inner">
|
||||
<nav id="navigation">
|
||||
<span id="home-button" class="nav-button">
|
||||
<a class="home-button"><i class="ic ic-settings"></i>Settings</a>
|
||||
</span>
|
||||
<h1 class="post-title">Red Markets Negotiations: {{ negotiation.name }}</h1>
|
||||
<span id="menu-button" class="nav-button">
|
||||
<a class="menu-button"><i class="ic ic-menu"></i> Menu</a>
|
||||
</span>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
{% endblock header %}
|
||||
|
||||
{% block content %}
|
||||
<div id="negotiation" class="negotiation-wrapper">
|
||||
<div class="negotiation-sidebar">{% include 'partials/negotiation-sidebar.html' %}</div>
|
||||
<div class="negotiation-panel">{% include 'partials/negotiation-panel.html' %}</div>
|
||||
</div>
|
||||
<script src="/static/js/jquery.min.js"></script>
|
||||
<script src="/static/js/socket.io.js"></script>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
// const negotiation = new Negotiation("{{negotiation.name}}")
|
||||
var socket = io();
|
||||
var room = "{{negotiation.name}}";
|
||||
socket.on('connect', function() {
|
||||
console.log("Trying to join room " + room);
|
||||
socket.emit('join negotiation', {"room": room});
|
||||
});
|
||||
window.addEventListener('beforeunload', function(e) {
|
||||
console.log("Trying to leave room " + room);
|
||||
socket.emit('leave negotiation', {"room": room});
|
||||
socket.disconnect();
|
||||
return true;
|
||||
});
|
||||
socket.on('participants changed', (data) => {
|
||||
console.log(data);
|
||||
$("#negotiation .participants .count").html(data['participants'].length);
|
||||
$("#negotiation .participant-list li").each(function() {
|
||||
if (!data['participants'].includes($(this).attr('participant-id'))) {
|
||||
console.log("Removing element for participant id " + $(this).attr('participant-id'));
|
||||
console.log($(this));
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
var num_p;
|
||||
var listed = $('#negotiation .participant-list li');
|
||||
for (num_p = 0; num_p < data['participants'].length ; num_p++) {
|
||||
var p = data['participants'][num_p];
|
||||
console.log('Checking to see if ' + p + ' is in the participant list');
|
||||
console.log($('#negotiation .participant-list li[participant-id="' + p + '"]'));
|
||||
if ($('#negotiation .participant-list li[participant-id="' + p + '"]').length < 1) {
|
||||
$("#negotiation .participant-list").append(
|
||||
'<li participant-id="' + p + '">' + p + '</li>'
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock content %}
|
|
@ -0,0 +1 @@
|
|||
@TODO Panel
|
|
@ -0,0 +1,15 @@
|
|||
<div class="negotiation-owner">
|
||||
<p>Negotiation {{negotiation.name}} is being run by {{room_owner_display_name}}</p>
|
||||
{% if room_owner_display_name == uid %}
|
||||
<p>Change your display name</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="participants">
|
||||
<h4>Participants</h4>
|
||||
<p><span class="count">{{ participant_count }}</span> <span> takers in this room</span></p>
|
||||
<ul class="participant-list">
|
||||
<li participant-id="{{user}}">{{user}}</li>
|
||||
</ul>
|
||||
</div>
|
Loading…
Reference in New Issue