testing the new room discovery changes. you can neow discover public rooms ( ones with no password )
This commit is contained in:
284
src/app.py
284
src/app.py
@@ -41,6 +41,7 @@ room_keys = {}
|
|||||||
user_sessions = {}
|
user_sessions = {}
|
||||||
room_passwords = {} # Separate password storage with hashing
|
room_passwords = {} # Separate password storage with hashing
|
||||||
room_creation_times = {}
|
room_creation_times = {}
|
||||||
|
room_metadata = {} # New: Store room titles and descriptions
|
||||||
ip_room_count = {} # Track rooms created per IP
|
ip_room_count = {} # Track rooms created per IP
|
||||||
failed_password_attempts = {} # Track failed password attempts
|
failed_password_attempts = {} # Track failed password attempts
|
||||||
message_hashes = {} # Store message hashes for duplicate detection
|
message_hashes = {} # Store message hashes for duplicate detection
|
||||||
@@ -151,6 +152,7 @@ def cleanup_room(room_id):
|
|||||||
room_keys.pop(room_id, None)
|
room_keys.pop(room_id, None)
|
||||||
room_passwords.pop(room_id, None)
|
room_passwords.pop(room_id, None)
|
||||||
room_creation_times.pop(room_id, None)
|
room_creation_times.pop(room_id, None)
|
||||||
|
room_metadata.pop(room_id, None) # Clean up metadata
|
||||||
message_hashes.pop(room_id, None)
|
message_hashes.pop(room_id, None)
|
||||||
|
|
||||||
def get_client_ip():
|
def get_client_ip():
|
||||||
@@ -201,15 +203,58 @@ def get_room_info(room_id):
|
|||||||
return jsonify({'error': 'Invalid room ID'}), 400
|
return jsonify({'error': 'Invalid room ID'}), 400
|
||||||
|
|
||||||
if room_id in chat_rooms:
|
if room_id in chat_rooms:
|
||||||
return jsonify({
|
room_info = {
|
||||||
'exists': True,
|
'exists': True,
|
||||||
'message_count': chat_rooms[room_id].get_message_count(),
|
'message_count': chat_rooms[room_id].get_message_count(),
|
||||||
'max_messages': MAX_MESSAGES,
|
'max_messages': MAX_MESSAGES,
|
||||||
'user_count': len(room_keys.get(room_id, {})),
|
'user_count': len(room_keys.get(room_id, {})),
|
||||||
'max_users': MAX_USERS_PER_ROOM
|
'max_users': MAX_USERS_PER_ROOM,
|
||||||
})
|
'is_password_protected': room_id in room_passwords,
|
||||||
|
'created_at': room_creation_times.get(room_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add metadata if available
|
||||||
|
if room_id in room_metadata:
|
||||||
|
room_info.update(room_metadata[room_id])
|
||||||
|
|
||||||
|
return jsonify(room_info)
|
||||||
return jsonify({'exists': False})
|
return jsonify({'exists': False})
|
||||||
|
|
||||||
|
@app.route('/api/rooms/public')
|
||||||
|
def get_public_rooms():
|
||||||
|
"""Get list of public (non-password-protected) rooms"""
|
||||||
|
try:
|
||||||
|
# Optional query parameters for filtering/sorting
|
||||||
|
sort_by = request.args.get('sort', 'activity') # activity, created, users, messages
|
||||||
|
limit = min(int(request.args.get('limit', 50)), 100) # Max 100 rooms
|
||||||
|
min_users = int(request.args.get('min_users', 0))
|
||||||
|
|
||||||
|
rooms_data = get_public_rooms_data(sort_by, min_users, limit)
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'rooms': rooms_data,
|
||||||
|
'total_count': len(rooms_data),
|
||||||
|
'sort_by': sort_by,
|
||||||
|
'timestamp': time.time()
|
||||||
|
})
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
return jsonify({'error': 'Invalid parameter values'}), 400
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in get_public_rooms: {str(e)}")
|
||||||
|
return jsonify({'error': 'Internal server error'}), 500
|
||||||
|
|
||||||
|
@app.route('/api/rooms/stats')
|
||||||
|
def get_room_stats():
|
||||||
|
"""Get general statistics about rooms"""
|
||||||
|
try:
|
||||||
|
stats_data = get_room_stats_data()
|
||||||
|
return jsonify(stats_data)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in get_room_stats: {str(e)}")
|
||||||
|
return jsonify({'error': 'Internal server error'}), 500
|
||||||
|
|
||||||
# Socket event handlers
|
# Socket event handlers
|
||||||
@socketio.on('connect')
|
@socketio.on('connect')
|
||||||
def handle_connect():
|
def handle_connect():
|
||||||
@@ -255,6 +300,10 @@ def handle_disconnect():
|
|||||||
# Clean up empty rooms
|
# Clean up empty rooms
|
||||||
if room_id in room_keys and len(room_keys[room_id]) == 0:
|
if room_id in room_keys and len(room_keys[room_id]) == 0:
|
||||||
cleanup_room(room_id)
|
cleanup_room(room_id)
|
||||||
|
else:
|
||||||
|
# Broadcast public rooms update if this was a public room with remaining users
|
||||||
|
if room_id not in room_passwords:
|
||||||
|
broadcast_public_rooms_update()
|
||||||
|
|
||||||
del user_sessions[request.sid]
|
del user_sessions[request.sid]
|
||||||
logger.info(f"User {user_id} disconnected")
|
logger.info(f"User {user_id} disconnected")
|
||||||
@@ -266,6 +315,8 @@ def handle_join_room(data):
|
|||||||
room_id = data.get('room_id', '').strip()
|
room_id = data.get('room_id', '').strip()
|
||||||
public_key = data.get('public_key', '')
|
public_key = data.get('public_key', '')
|
||||||
password = data.get('password', '')
|
password = data.get('password', '')
|
||||||
|
room_title = data.get('room_title', '').strip()
|
||||||
|
room_description = data.get('room_description', '').strip()
|
||||||
|
|
||||||
# Validation
|
# Validation
|
||||||
if not is_valid_room_id(room_id):
|
if not is_valid_room_id(room_id):
|
||||||
@@ -276,6 +327,15 @@ def handle_join_room(data):
|
|||||||
emit('room_joined', {'error': 'Invalid public key format'})
|
emit('room_joined', {'error': 'Invalid public key format'})
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Validate metadata length
|
||||||
|
if room_title and len(room_title) > 100:
|
||||||
|
emit('room_joined', {'error': 'Room title too long (max 100 characters)'})
|
||||||
|
return
|
||||||
|
|
||||||
|
if room_description and len(room_description) > 500:
|
||||||
|
emit('room_joined', {'error': 'Room description too long (max 500 characters)'})
|
||||||
|
return
|
||||||
|
|
||||||
client_ip = get_client_ip()
|
client_ip = get_client_ip()
|
||||||
user_id = user_sessions[request.sid]['user_id']
|
user_id = user_sessions[request.sid]['user_id']
|
||||||
|
|
||||||
@@ -295,6 +355,13 @@ def handle_join_room(data):
|
|||||||
room_creation_times[room_id] = time.time()
|
room_creation_times[room_id] = time.time()
|
||||||
message_hashes[room_id] = set()
|
message_hashes[room_id] = set()
|
||||||
|
|
||||||
|
# Store room metadata
|
||||||
|
room_metadata[room_id] = {
|
||||||
|
'title': room_title or f'Room {room_id}',
|
||||||
|
'description': room_description or 'A chat room',
|
||||||
|
'creator_ip': client_ip # For potential moderation
|
||||||
|
}
|
||||||
|
|
||||||
# Store hashed password if provided
|
# Store hashed password if provided
|
||||||
if password:
|
if password:
|
||||||
room_passwords[room_id] = hash_password(password)
|
room_passwords[room_id] = hash_password(password)
|
||||||
@@ -303,7 +370,7 @@ def handle_join_room(data):
|
|||||||
ip_room_count[client_ip] = rooms_by_ip + 1
|
ip_room_count[client_ip] = rooms_by_ip + 1
|
||||||
is_first_user = True
|
is_first_user = True
|
||||||
|
|
||||||
logger.info(f"Room {room_id} created by {user_id}")
|
logger.info(f"Room {room_id} created by {user_id} with title: {room_title}")
|
||||||
else:
|
else:
|
||||||
# Check room capacity
|
# Check room capacity
|
||||||
if len(room_keys[room_id]) >= MAX_USERS_PER_ROOM:
|
if len(room_keys[room_id]) >= MAX_USERS_PER_ROOM:
|
||||||
@@ -358,7 +425,8 @@ def handle_join_room(data):
|
|||||||
'users': list(user_keys.keys()),
|
'users': list(user_keys.keys()),
|
||||||
'user_keys': user_keys,
|
'user_keys': user_keys,
|
||||||
'message_count': chat_rooms[room_id].get_message_count(),
|
'message_count': chat_rooms[room_id].get_message_count(),
|
||||||
'is_first_user': is_first_user
|
'is_first_user': is_first_user,
|
||||||
|
'room_metadata': room_metadata.get(room_id, {})
|
||||||
})
|
})
|
||||||
|
|
||||||
# Notify others about new user
|
# Notify others about new user
|
||||||
@@ -382,6 +450,10 @@ def handle_join_room(data):
|
|||||||
|
|
||||||
logger.info(f"User {new_user_id} joined room {room_id}")
|
logger.info(f"User {new_user_id} joined room {room_id}")
|
||||||
|
|
||||||
|
# Broadcast public rooms update if this was a public room
|
||||||
|
if room_id not in room_passwords:
|
||||||
|
broadcast_public_rooms_update()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in join_room: {str(e)}")
|
logger.error(f"Error in join_room: {str(e)}")
|
||||||
emit('room_joined', {'error': 'Internal server error'})
|
emit('room_joined', {'error': 'Internal server error'})
|
||||||
@@ -446,6 +518,10 @@ def handle_send_message(data):
|
|||||||
'message_count': chat_rooms[room_id].get_message_count()
|
'message_count': chat_rooms[room_id].get_message_count()
|
||||||
}, room=room_id)
|
}, room=room_id)
|
||||||
|
|
||||||
|
# Broadcast public rooms update if this was a public room
|
||||||
|
if room_id not in room_passwords:
|
||||||
|
broadcast_public_rooms_update()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in send_message: {str(e)}")
|
logger.error(f"Error in send_message: {str(e)}")
|
||||||
|
|
||||||
@@ -495,12 +571,104 @@ def handle_share_session_key(data):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in share_session_key: {str(e)}")
|
logger.error(f"Error in share_session_key: {str(e)}")
|
||||||
|
|
||||||
|
@socketio.on('request_public_rooms')
|
||||||
|
def handle_request_public_rooms(data):
|
||||||
|
"""Handle request for public rooms data via WebSocket"""
|
||||||
|
try:
|
||||||
|
sort_by = data.get('sort', 'activity')
|
||||||
|
min_users = data.get('min_users', 0)
|
||||||
|
limit = data.get('limit', 50)
|
||||||
|
|
||||||
|
# Validate parameters
|
||||||
|
if sort_by not in ['activity', 'created', 'users', 'messages']:
|
||||||
|
sort_by = 'activity'
|
||||||
|
|
||||||
|
min_users = max(0, min(int(min_users), MAX_USERS_PER_ROOM))
|
||||||
|
limit = max(1, min(int(limit), 100))
|
||||||
|
|
||||||
|
rooms_data = get_public_rooms_data(sort_by, min_users, limit)
|
||||||
|
stats_data = get_room_stats_data()
|
||||||
|
|
||||||
|
emit('public_rooms_data', {
|
||||||
|
'rooms': rooms_data,
|
||||||
|
'stats': stats_data,
|
||||||
|
'sort_by': sort_by,
|
||||||
|
'timestamp': time.time()
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in request_public_rooms: {str(e)}")
|
||||||
|
emit('public_rooms_error', {'error': 'Failed to load rooms'})
|
||||||
|
|
||||||
|
@socketio.on('subscribe_public_rooms')
|
||||||
|
def handle_subscribe_public_rooms():
|
||||||
|
"""Subscribe client to public rooms updates"""
|
||||||
|
if request.sid not in user_sessions:
|
||||||
|
return
|
||||||
|
|
||||||
|
user_sessions[request.sid]['subscribed_to_public_rooms'] = True
|
||||||
|
logger.info(f"User {user_sessions[request.sid]['user_id']} subscribed to public rooms updates")
|
||||||
|
|
||||||
|
@socketio.on('unsubscribe_public_rooms')
|
||||||
|
def handle_unsubscribe_public_rooms():
|
||||||
|
"""Unsubscribe client from public rooms updates"""
|
||||||
|
if request.sid in user_sessions:
|
||||||
|
user_sessions[request.sid]['subscribed_to_public_rooms'] = False
|
||||||
|
logger.info(f"User {user_sessions[request.sid]['user_id']} unsubscribed from public rooms updates")
|
||||||
|
|
||||||
|
@socketio.on('request_public_rooms')
|
||||||
|
def handle_request_public_rooms(data):
|
||||||
|
"""Handle request for public rooms data via WebSocket"""
|
||||||
|
try:
|
||||||
|
sort_by = data.get('sort', 'activity')
|
||||||
|
min_users = data.get('min_users', 0)
|
||||||
|
limit = data.get('limit', 50)
|
||||||
|
|
||||||
|
# Validate parameters
|
||||||
|
if sort_by not in ['activity', 'created', 'users', 'messages']:
|
||||||
|
sort_by = 'activity'
|
||||||
|
|
||||||
|
min_users = max(0, min(int(min_users), MAX_USERS_PER_ROOM))
|
||||||
|
limit = max(1, min(int(limit), 100))
|
||||||
|
|
||||||
|
rooms_data = get_public_rooms_data(sort_by, min_users, limit)
|
||||||
|
stats_data = get_room_stats_data()
|
||||||
|
|
||||||
|
emit('public_rooms_data', {
|
||||||
|
'rooms': rooms_data,
|
||||||
|
'stats': stats_data,
|
||||||
|
'sort_by': sort_by,
|
||||||
|
'timestamp': time.time()
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in request_public_rooms: {str(e)}")
|
||||||
|
emit('public_rooms_error', {'error': 'Failed to load rooms'})
|
||||||
|
|
||||||
|
@socketio.on('subscribe_public_rooms')
|
||||||
|
def handle_subscribe_public_rooms():
|
||||||
|
"""Subscribe client to public rooms updates"""
|
||||||
|
if request.sid not in user_sessions:
|
||||||
|
return
|
||||||
|
|
||||||
|
user_sessions[request.sid]['subscribed_to_public_rooms'] = True
|
||||||
|
logger.info(f"User {user_sessions[request.sid]['user_id']} subscribed to public rooms updates")
|
||||||
|
|
||||||
|
@socketio.on('unsubscribe_public_rooms')
|
||||||
|
def handle_unsubscribe_public_rooms():
|
||||||
|
"""Unsubscribe client from public rooms updates"""
|
||||||
|
if request.sid in user_sessions:
|
||||||
|
user_sessions[request.sid]['subscribed_to_public_rooms'] = False
|
||||||
|
logger.info(f"User {user_sessions[request.sid]['user_id']} unsubscribed from public rooms updates")
|
||||||
|
|
||||||
# Background cleanup task
|
# Background cleanup task
|
||||||
def start_cleanup_task():
|
def start_cleanup_task():
|
||||||
def cleanup_worker():
|
def cleanup_worker():
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
cleanup_expired_rooms()
|
cleanup_expired_rooms()
|
||||||
|
# Broadcast updated room stats after cleanup
|
||||||
|
broadcast_public_rooms_update()
|
||||||
time.sleep(ROOM_CLEANUP_INTERVAL)
|
time.sleep(ROOM_CLEANUP_INTERVAL)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in cleanup task: {str(e)}")
|
logger.error(f"Error in cleanup task: {str(e)}")
|
||||||
@@ -508,6 +676,112 @@ def start_cleanup_task():
|
|||||||
cleanup_thread = threading.Thread(target=cleanup_worker, daemon=True)
|
cleanup_thread = threading.Thread(target=cleanup_worker, daemon=True)
|
||||||
cleanup_thread.start()
|
cleanup_thread.start()
|
||||||
|
|
||||||
|
def get_public_rooms_data(sort_by='activity', min_users=0, limit=50):
|
||||||
|
"""Get public rooms data - shared function for API and WebSocket"""
|
||||||
|
try:
|
||||||
|
public_rooms = []
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
for room_id, room_buffer in chat_rooms.items():
|
||||||
|
# Skip password-protected rooms
|
||||||
|
if room_id in room_passwords:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Skip expired rooms
|
||||||
|
if room_buffer.is_expired():
|
||||||
|
continue
|
||||||
|
|
||||||
|
user_count = len(room_keys.get(room_id, {}))
|
||||||
|
|
||||||
|
# Apply user count filters
|
||||||
|
if user_count < min_users:
|
||||||
|
continue
|
||||||
|
|
||||||
|
room_data = {
|
||||||
|
'room_id': room_id,
|
||||||
|
'user_count': user_count,
|
||||||
|
'message_count': room_buffer.get_message_count(),
|
||||||
|
'created_at': room_creation_times.get(room_id, current_time),
|
||||||
|
'last_activity': room_buffer.last_activity,
|
||||||
|
'is_password_protected': False
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add metadata if available
|
||||||
|
if room_id in room_metadata:
|
||||||
|
room_data.update(room_metadata[room_id])
|
||||||
|
else:
|
||||||
|
room_data.update({
|
||||||
|
'title': f'Room {room_id}',
|
||||||
|
'description': 'A public chat room'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Calculate activity metrics
|
||||||
|
room_data['minutes_since_activity'] = int((current_time - room_buffer.last_activity) / 60)
|
||||||
|
room_data['hours_since_created'] = int((current_time - room_data['created_at']) / 3600)
|
||||||
|
|
||||||
|
public_rooms.append(room_data)
|
||||||
|
|
||||||
|
# Sort rooms
|
||||||
|
if sort_by == 'activity':
|
||||||
|
public_rooms.sort(key=lambda x: x['last_activity'], reverse=True)
|
||||||
|
elif sort_by == 'created':
|
||||||
|
public_rooms.sort(key=lambda x: x['created_at'], reverse=True)
|
||||||
|
elif sort_by == 'users':
|
||||||
|
public_rooms.sort(key=lambda x: x['user_count'], reverse=True)
|
||||||
|
elif sort_by == 'messages':
|
||||||
|
public_rooms.sort(key=lambda x: x['message_count'], reverse=True)
|
||||||
|
|
||||||
|
return public_rooms[:limit]
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting public rooms data: {str(e)}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_room_stats_data():
|
||||||
|
"""Get room statistics - shared function for API and WebSocket"""
|
||||||
|
try:
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
total_rooms = len(chat_rooms)
|
||||||
|
public_rooms = sum(1 for room_id in chat_rooms.keys() if room_id not in room_passwords)
|
||||||
|
private_rooms = total_rooms - public_rooms
|
||||||
|
|
||||||
|
total_users = sum(len(users) for users in room_keys.values())
|
||||||
|
total_messages = sum(room.get_message_count() for room in chat_rooms.values())
|
||||||
|
|
||||||
|
# Active rooms (activity in last hour)
|
||||||
|
active_rooms = sum(1 for room in chat_rooms.values()
|
||||||
|
if current_time - room.last_activity < 3600)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'total_rooms': total_rooms,
|
||||||
|
'public_rooms': public_rooms,
|
||||||
|
'private_rooms': private_rooms,
|
||||||
|
'active_rooms': active_rooms,
|
||||||
|
'total_users': total_users,
|
||||||
|
'total_messages': total_messages,
|
||||||
|
'timestamp': current_time
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting room stats: {str(e)}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def broadcast_public_rooms_update():
|
||||||
|
"""Broadcast updated public rooms data to all connected clients"""
|
||||||
|
try:
|
||||||
|
rooms_data = get_public_rooms_data()
|
||||||
|
stats_data = get_room_stats_data()
|
||||||
|
|
||||||
|
socketio.emit('public_rooms_updated', {
|
||||||
|
'rooms': rooms_data,
|
||||||
|
'stats': stats_data,
|
||||||
|
'timestamp': time.time()
|
||||||
|
}, broadcast=True)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error broadcasting public rooms update: {str(e)}")
|
||||||
|
|
||||||
# Error handlers
|
# Error handlers
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
def not_found(e):
|
def not_found(e):
|
||||||
|
|||||||
319
src/static/rooms.js
Normal file
319
src/static/rooms.js
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
let publicRoomsData = [];
|
||||||
|
let roomsRefreshInterval = null;
|
||||||
|
let currentSortBy = 'activity';
|
||||||
|
let currentMinUsers = 0;
|
||||||
|
let isSubscribedToRooms = false;
|
||||||
|
|
||||||
|
// Show the public rooms browser
|
||||||
|
function showPublicRoomsBrowser() {
|
||||||
|
document.getElementById('publicRoomsBrowser').style.display = 'block';
|
||||||
|
loadPublicRoomsWebSocket();
|
||||||
|
subscribeToPublicRooms();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the public rooms browser
|
||||||
|
function closePublicRoomsBrowser() {
|
||||||
|
document.getElementById('publicRoomsBrowser').style.display = 'none';
|
||||||
|
unsubscribeFromPublicRooms();
|
||||||
|
|
||||||
|
// Clear auto-refresh
|
||||||
|
if (roomsRefreshInterval) {
|
||||||
|
clearInterval(roomsRefreshInterval);
|
||||||
|
roomsRefreshInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe to live public rooms updates
|
||||||
|
function subscribeToPublicRooms() {
|
||||||
|
if (typeof socket !== 'undefined' && socket.connected && !isSubscribedToRooms) {
|
||||||
|
socket.emit('subscribe_public_rooms');
|
||||||
|
isSubscribedToRooms = true;
|
||||||
|
console.log('Subscribed to public rooms updates');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsubscribe from public rooms updates
|
||||||
|
function unsubscribeFromPublicRooms() {
|
||||||
|
if (typeof socket !== 'undefined' && socket.connected && isSubscribedToRooms) {
|
||||||
|
socket.emit('unsubscribe_public_rooms');
|
||||||
|
isSubscribedToRooms = false;
|
||||||
|
console.log('Unsubscribed from public rooms updates');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load public rooms via WebSocket
|
||||||
|
function loadPublicRoomsWebSocket() {
|
||||||
|
try {
|
||||||
|
showLoadingState();
|
||||||
|
|
||||||
|
currentSortBy = document.getElementById('roomsSortSelect').value;
|
||||||
|
currentMinUsers = document.getElementById('minUsersFilter').value || 0;
|
||||||
|
|
||||||
|
if (typeof socket !== 'undefined' && socket.connected) {
|
||||||
|
socket.emit('request_public_rooms', {
|
||||||
|
sort: currentSortBy,
|
||||||
|
min_users: currentMinUsers,
|
||||||
|
limit: 50
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Fallback to HTTP API if WebSocket not available
|
||||||
|
loadPublicRoomsHTTP();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error requesting public rooms via WebSocket:', error);
|
||||||
|
loadPublicRoomsHTTP(); // Fallback to HTTP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback HTTP method
|
||||||
|
async function loadPublicRoomsHTTP() {
|
||||||
|
try {
|
||||||
|
showLoadingState();
|
||||||
|
|
||||||
|
const sortBy = document.getElementById('roomsSortSelect').value;
|
||||||
|
const minUsers = document.getElementById('minUsersFilter').value || 0;
|
||||||
|
|
||||||
|
const response = await fetch(`/api/rooms/public?sort=${sortBy}&min_users=${minUsers}&limit=50`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
publicRoomsData = data.rooms || [];
|
||||||
|
|
||||||
|
// Also load stats
|
||||||
|
const statsResponse = await fetch('/api/rooms/stats');
|
||||||
|
if (statsResponse.ok) {
|
||||||
|
const stats = await statsResponse.json();
|
||||||
|
updateStatsDisplay(stats);
|
||||||
|
}
|
||||||
|
|
||||||
|
displayRooms();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading public rooms via HTTP:', error);
|
||||||
|
showErrorState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh public rooms
|
||||||
|
function refreshPublicRooms() {
|
||||||
|
loadPublicRoomsWebSocket();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update stats display
|
||||||
|
function updateStatsDisplay(stats) {
|
||||||
|
const statsElement = document.getElementById('roomsStats');
|
||||||
|
statsElement.textContent = `${stats.public_rooms} public rooms • ${stats.total_users} users online`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show loading state
|
||||||
|
function showLoadingState() {
|
||||||
|
document.getElementById('roomsLoading').style.display = 'flex';
|
||||||
|
document.getElementById('roomsList').style.display = 'none';
|
||||||
|
document.getElementById('roomsEmpty').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show error state
|
||||||
|
function showErrorState() {
|
||||||
|
document.getElementById('roomsLoading').style.display = 'none';
|
||||||
|
document.getElementById('roomsList').style.display = 'none';
|
||||||
|
document.getElementById('roomsEmpty').style.display = 'flex';
|
||||||
|
|
||||||
|
const emptyElement = document.getElementById('roomsEmpty');
|
||||||
|
emptyElement.innerHTML = `
|
||||||
|
<div style="font-size: 3rem; margin-bottom: 1rem;">⚠️</div>
|
||||||
|
<h3 style="margin: 0 0 0.5rem 0; color: #ffffff;">Failed to Load Rooms</h3>
|
||||||
|
<p style="margin: 0 0 1rem 0;">Unable to connect to the server. Please try again.</p>
|
||||||
|
<button class="btn" onclick="refreshPublicRooms()">Retry</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display rooms
|
||||||
|
function displayRooms() {
|
||||||
|
const roomsList = document.getElementById('roomsList');
|
||||||
|
const roomsLoading = document.getElementById('roomsLoading');
|
||||||
|
const roomsEmpty = document.getElementById('roomsEmpty');
|
||||||
|
|
||||||
|
roomsLoading.style.display = 'none';
|
||||||
|
|
||||||
|
if (publicRoomsData.length === 0) {
|
||||||
|
roomsEmpty.style.display = 'flex';
|
||||||
|
roomsList.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
roomsEmpty.style.display = 'none';
|
||||||
|
roomsList.style.display = 'block';
|
||||||
|
|
||||||
|
roomsList.innerHTML = publicRoomsData.map(room => createRoomCard(room)).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create room card HTML
|
||||||
|
function createRoomCard(room) {
|
||||||
|
const activityClass = getActivityClass(room.minutes_since_activity);
|
||||||
|
const timeAgo = formatTimeAgo(room.minutes_since_activity);
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="room-card" onclick="joinPublicRoom('${room.room_id}')">
|
||||||
|
<div class="room-title">
|
||||||
|
${escapeHtml(room.title || room.room_id)}
|
||||||
|
<span class="activity-indicator ${activityClass}"></span>
|
||||||
|
</div>
|
||||||
|
<div class="room-description">
|
||||||
|
${escapeHtml(room.description || 'No description')}
|
||||||
|
</div>
|
||||||
|
<div class="room-stats">
|
||||||
|
<div class="room-stat">
|
||||||
|
<span>👥</span>
|
||||||
|
<span>${room.user_count} user${room.user_count !== 1 ? 's' : ''}</span>
|
||||||
|
</div>
|
||||||
|
<div class="room-stat">
|
||||||
|
<span>💬</span>
|
||||||
|
<span>${room.message_count} message${room.message_count !== 1 ? 's' : ''}</span>
|
||||||
|
</div>
|
||||||
|
<div class="room-stat">
|
||||||
|
<span>🕐</span>
|
||||||
|
<span>Active ${timeAgo}</span>
|
||||||
|
</div>
|
||||||
|
<div class="room-stat">
|
||||||
|
<span>🆔</span>
|
||||||
|
<span>${room.room_id}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get activity indicator class
|
||||||
|
function getActivityClass(minutesAgo) {
|
||||||
|
if (minutesAgo <= 5) return 'activity-active';
|
||||||
|
if (minutesAgo <= 30) return 'activity-recent';
|
||||||
|
return 'activity-old';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format time ago
|
||||||
|
function formatTimeAgo(minutes) {
|
||||||
|
if (minutes < 1) return 'just now';
|
||||||
|
if (minutes < 60) return `${minutes}m ago`;
|
||||||
|
|
||||||
|
const hours = Math.floor(minutes / 60);
|
||||||
|
if (hours < 24) return `${hours}h ago`;
|
||||||
|
|
||||||
|
const days = Math.floor(hours / 24);
|
||||||
|
return `${days}d ago`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape HTML to prevent XSS
|
||||||
|
function escapeHtml(text) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join a public room - integrated with your existing system
|
||||||
|
function joinPublicRoom(roomId) {
|
||||||
|
console.log(`Joining public room: ${roomId}`);
|
||||||
|
|
||||||
|
// Close the browser
|
||||||
|
closePublicRoomsBrowser();
|
||||||
|
|
||||||
|
// Fill in the room input with the selected room
|
||||||
|
document.getElementById('roomInput').value = roomId;
|
||||||
|
|
||||||
|
// Clear password field since these are public rooms
|
||||||
|
document.getElementById('roomPasswordInput').value = '';
|
||||||
|
|
||||||
|
// Trigger your existing join room functionality
|
||||||
|
const joinButton = document.getElementById('joinRoomBtn');
|
||||||
|
if (joinButton) {
|
||||||
|
joinButton.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event listeners for controls
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
document.getElementById('roomsSortSelect').addEventListener('change', refreshPublicRooms);
|
||||||
|
document.getElementById('minUsersFilter').addEventListener('change', refreshPublicRooms);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close on escape key
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'Escape' && document.getElementById('publicRoomsBrowser').style.display === 'block') {
|
||||||
|
closePublicRoomsBrowser();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close on backdrop click
|
||||||
|
document.getElementById('publicRoomsBrowser').addEventListener('click', function(e) {
|
||||||
|
if (e.target === this) {
|
||||||
|
closePublicRoomsBrowser();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// WebSocket event handlers for real-time updates
|
||||||
|
function setupPublicRoomsWebSocketHandlers() {
|
||||||
|
if (typeof socket === 'undefined') {
|
||||||
|
console.log('Socket not available, using HTTP fallback');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle public rooms data response
|
||||||
|
socket.on('public_rooms_data', function(data) {
|
||||||
|
console.log('Received public rooms data via WebSocket:', data);
|
||||||
|
publicRoomsData = data.rooms || [];
|
||||||
|
|
||||||
|
if (data.stats) {
|
||||||
|
updateStatsDisplay(data.stats);
|
||||||
|
}
|
||||||
|
|
||||||
|
displayRooms();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle live updates to public rooms
|
||||||
|
socket.on('public_rooms_updated', function(data) {
|
||||||
|
console.log('Received live public rooms update:', data);
|
||||||
|
|
||||||
|
// Only update if the browser is currently open and we're subscribed
|
||||||
|
if (document.getElementById('publicRoomsBrowser').style.display === 'block' && isSubscribedToRooms) {
|
||||||
|
publicRoomsData = data.rooms || [];
|
||||||
|
|
||||||
|
if (data.stats) {
|
||||||
|
updateStatsDisplay(data.stats);
|
||||||
|
}
|
||||||
|
|
||||||
|
displayRooms();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle WebSocket errors for public rooms
|
||||||
|
socket.on('public_rooms_error', function(data) {
|
||||||
|
console.error('Public rooms WebSocket error:', data);
|
||||||
|
showErrorState();
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Public rooms WebSocket handlers attached');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-setup WebSocket handlers when page loads
|
||||||
|
function waitForSocketAndSetupHandlers() {
|
||||||
|
if (typeof socket !== 'undefined' && socket.connected) {
|
||||||
|
setupPublicRoomsWebSocketHandlers();
|
||||||
|
} else {
|
||||||
|
setTimeout(waitForSocketAndSetupHandlers, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the setup process
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
waitForSocketAndSetupHandlers();
|
||||||
|
|
||||||
|
// Auto-refresh every 30 seconds when browser is open
|
||||||
|
setInterval(function() {
|
||||||
|
if (document.getElementById('publicRoomsBrowser').style.display === 'block') {
|
||||||
|
refreshPublicRooms();
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
|
});
|
||||||
@@ -617,3 +617,77 @@ ul.release-list a:hover {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* discovery */
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-card {
|
||||||
|
background: #252525;
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-card:hover {
|
||||||
|
background: #2a2a2a;
|
||||||
|
border-color: #00ff88;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-title {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
color: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-description {
|
||||||
|
color: #b0b0b0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin: 0 0 0.75rem 0;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-stats {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-stat {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-indicator {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-active { background: #00ff88; }
|
||||||
|
.activity-recent { background: #ffa500; }
|
||||||
|
.activity-old { background: #666; }
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.room-stats {
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-card {
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,13 +7,12 @@
|
|||||||
<meta http-equiv="X-Content-Type-Options" content="nosniff">
|
<meta http-equiv="X-Content-Type-Options" content="nosniff">
|
||||||
<meta http-equiv="X-Frame-Options" content="DENY">
|
<meta http-equiv="X-Frame-Options" content="DENY">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='stylesheet.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='stylesheet.css') }}">
|
||||||
<link rel="stylesheet" href="../static/stylesheet.css">
|
|
||||||
<link rel="icon" type="image/png" href="{{ url_for('static', filename='favicon.png') }}">
|
<link rel="icon" type="image/png" href="{{ url_for('static', filename='favicon.png') }}">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/socket.io-client@4.7.2/dist/socket.io.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/socket.io-client@4.7.2/dist/socket.io.min.js"></script>
|
||||||
<!-- Everything Locally Hosted ( javascript files for frontend / client encryption )-->
|
<!-- Everything Locally Hosted ( javascript files for frontend / client encryption )-->
|
||||||
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='frontend.js') }}"></script>
|
<script src="{{ url_for('static', filename='frontend.js') }}"></script>
|
||||||
<script src="{{ url_for("static", filename="rss.js") }}" async></script>
|
<script src="{{ url_for("static", filename="rss.js")}}" async></script>
|
||||||
</head>
|
</head>
|
||||||
<body style="overflow-y: auto;">
|
<body style="overflow-y: auto;">
|
||||||
<div class="chat-container">
|
<div class="chat-container">
|
||||||
@@ -61,6 +60,12 @@
|
|||||||
<button class="btn" id="joinRoomBtn" aria-label="Join existing room" style="flex: 1; min-width: 120px; font-size: clamp(0.85rem, 3.5vw, 1rem); padding: 0.6rem 1rem;">Join Room</button>
|
<button class="btn" id="joinRoomBtn" aria-label="Join existing room" style="flex: 1; min-width: 120px; font-size: clamp(0.85rem, 3.5vw, 1rem); padding: 0.6rem 1rem;">Join Room</button>
|
||||||
<button class="btn btn-secondary" id="createRoomBtn" aria-label="Create new room" style="flex: 1; min-width: 120px; font-size: clamp(0.85rem, 3.5vw, 1rem); padding: 0.6rem 1rem;">Create New Room</button>
|
<button class="btn btn-secondary" id="createRoomBtn" aria-label="Create new room" style="flex: 1; min-width: 120px; font-size: clamp(0.85rem, 3.5vw, 1rem); padding: 0.6rem 1rem;">Create New Room</button>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Browse Public Rooms Button -->
|
||||||
|
<div style="display: flex; justify-content: center; width: 100%;">
|
||||||
|
<button class="btn btn-secondary" onclick="showPublicRoomsBrowser()" style="font-size: clamp(0.85rem, 3.5vw, 1rem); padding: 0.6rem 1.5rem;">
|
||||||
|
Browse Public Rooms
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-text" id="statusText" style="font-size: clamp(0.8rem, 3vw, 0.9rem);">
|
<div class="status-text" id="statusText" style="font-size: clamp(0.8rem, 3vw, 0.9rem);">
|
||||||
<div class="loading-message">
|
<div class="loading-message">
|
||||||
@@ -77,13 +82,6 @@
|
|||||||
alt="ByteChat Desktop Application"
|
alt="ByteChat Desktop Application"
|
||||||
style="width: clamp(200px, 50vw, 400px); height: auto; border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); transition: transform 0.3s ease, box-shadow 0.3s ease; margin-bottom: 1.5rem; display: block;">
|
style="width: clamp(200px, 50vw, 400px); height: auto; border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); transition: transform 0.3s ease, box-shadow 0.3s ease; margin-bottom: 1.5rem; display: block;">
|
||||||
|
|
||||||
<!-- local testing image. uncomment if developing locally
|
|
||||||
<img class="laptopimg"
|
|
||||||
src="../static/laptop.png"
|
|
||||||
alt="ByteChat Desktop Application"
|
|
||||||
style="width: clamp(200px, 50vw, 400px); height: auto; border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); transition: transform 0.3s ease, box-shadow 0.3s ease; margin-bottom: 1.5rem; display: block;">
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- Desktop App Section -->
|
<!-- Desktop App Section -->
|
||||||
<div style="text-align: center; max-width: 600px; padding: 0 1rem;">
|
<div style="text-align: center; max-width: 600px; padding: 0 1rem;">
|
||||||
<h2 style="font-size: clamp(1.25rem, 5vw, 1.75rem); margin-bottom: 1rem; color: #00ff88;">Desktop Application</h2>
|
<h2 style="font-size: clamp(1.25rem, 5vw, 1.75rem); margin-bottom: 1rem; color: #00ff88;">Desktop Application</h2>
|
||||||
@@ -120,6 +118,180 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="publicRoomsBrowser" style="
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.9);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
z-index: 2000;
|
||||||
|
padding: 1rem;
|
||||||
|
|
||||||
|
/* Center content */
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
/* Optional: allow content to scroll if it overflows on small screens */
|
||||||
|
overflow: auto;
|
||||||
|
">
|
||||||
|
<div id="browserContent" style="
|
||||||
|
max-width: 95%;
|
||||||
|
max-height: 95%;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: #fff; /* or whatever your modal background is */
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: #1a1a1a;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #333;
|
||||||
|
min-height: 80vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
">
|
||||||
|
<!-- Header -->
|
||||||
|
<div style="
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
">
|
||||||
|
<div>
|
||||||
|
<h2 style="
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
font-size: clamp(1.25rem, 5vw, 1.75rem);
|
||||||
|
color: #00ff88;
|
||||||
|
">Public Rooms</h2>
|
||||||
|
<p style="
|
||||||
|
margin: 0;
|
||||||
|
color: #888;
|
||||||
|
font-size: clamp(0.85rem, 3.5vw, 1rem);
|
||||||
|
" id="roomsStats">Browse and join active public rooms</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="btn btn-secondary"
|
||||||
|
onclick="closePublicRoomsBrowser()"
|
||||||
|
style="
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
"
|
||||||
|
>Close</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Controls -->
|
||||||
|
<div style="
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
">
|
||||||
|
<select id="roomsSortSelect" style="
|
||||||
|
background: #333;
|
||||||
|
color: #ffffff;
|
||||||
|
border: 1px solid #555;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.5rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
min-width: 120px;
|
||||||
|
">
|
||||||
|
<option value="activity">Most Active</option>
|
||||||
|
<option value="created">Newest</option>
|
||||||
|
<option value="users">Most Users</option>
|
||||||
|
<option value="messages">Most Messages</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="minUsersFilter"
|
||||||
|
placeholder="Min users"
|
||||||
|
min="0"
|
||||||
|
max="1000"
|
||||||
|
style="
|
||||||
|
background: #333;
|
||||||
|
color: #ffffff;
|
||||||
|
border: 1px solid #555;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.5rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
width: 150px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
onclick="refreshPublicRooms()"
|
||||||
|
style="
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
"
|
||||||
|
>Refresh</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Loading State -->
|
||||||
|
<div id="roomsLoading" style="
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 3rem;
|
||||||
|
flex: 1;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
color: #888;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: 2px solid #333;
|
||||||
|
border-top: 2px solid #00ff88;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
"></div>
|
||||||
|
<span>Loading public rooms...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Rooms List -->
|
||||||
|
<div id="roomsList" style="
|
||||||
|
flex: 1;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
display: none;
|
||||||
|
">
|
||||||
|
<!-- Rooms will be populated here -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Empty State -->
|
||||||
|
<div id="roomsEmpty" style="
|
||||||
|
display: none; /* This should be none initially, not flex ; fuck CSS*/
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 3rem;
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
">
|
||||||
|
<div>
|
||||||
|
<h3 style="margin: 0 0 0.5rem 0; color: #ffffff;">… No Public Rooms Found</h3>
|
||||||
|
<p style="margin: 0; color: #888;">Check back later or create your own room</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Chat Screen -->
|
<!-- Chat Screen -->
|
||||||
<div id="chatScreen" style="display: none;" class="chat-screen">
|
<div id="chatScreen" style="display: none;" class="chat-screen">
|
||||||
<div class="room-info" id="roomInfo" style="display: none; padding: 0.6rem 0.75rem; flex-wrap: wrap; gap: 0.5rem;">
|
<div class="room-info" id="roomInfo" style="display: none; padding: 0.6rem 0.75rem; flex-wrap: wrap; gap: 0.5rem;">
|
||||||
@@ -165,10 +337,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script src="{{ url_for('static', filename="rooms.js") }}"></script>
|
||||||
<!-- <div class="security-indicator" id="securityIndicator" style="display: none; bottom: calc(0.75rem + 80px); right: 15px; padding: 0.4rem 0.8rem; font-size: clamp(0.7rem, 3vw, 0.8rem); z-index: 999;">
|
|
||||||
🔒 End-to-End Encrypted
|
|
||||||
</div>
|
|
||||||
-->
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user