add: user status

This commit is contained in:
trisua 2025-05-03 11:29:31 -04:00
parent 1724f798ca
commit a009ef9e34
10 changed files with 259 additions and 138 deletions

View file

@ -107,7 +107,7 @@ article {
padding: 0; padding: 0;
} }
body .card:not(.card *):not(#stream *), body .card:not(.card *):not(#stream *):not(.user_plate),
body .pillmenu:not(.card *) > a, body .pillmenu:not(.card *) > a,
body .card-nest:not(.card *) > .card, body .card-nest:not(.card *) > .card,
body .banner { body .banner {
@ -340,6 +340,18 @@ img.contain {
max-height: 350px; max-height: 350px;
} }
.user_status span {
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.user_status .icon {
min-width: 1em;
min-height: 1em;
}
/* table */ /* table */
table { table {
border-collapse: collapse; border-collapse: collapse;
@ -416,6 +428,10 @@ table ol {
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
} }
.card.tiny {
padding: 0.5rem;
}
.card.secondary { .card.secondary {
background: var(--color-surface); background: var(--color-surface);
} }
@ -477,6 +493,12 @@ button.small,
font-size: 16px; font-size: 16px;
} }
button.small.square,
.button.small.square {
width: 32px;
height: 32px;
}
button.big_icon svg, button.big_icon svg,
.button.big_icon svg { .button.big_icon svg {
height: 16px; height: 16px;

View file

@ -1,6 +1,7 @@
{% extends "root.html" %} {% block head %} {% extends "root.html" %} {% block head %}
<title>Chats - {{ config.name }}</title> <title>Chats - {{ config.name }}</title>
{% endblock %} {% block body %} {{ macros::nav(selected="chats") }} {% endblock %} {% block body %} {{ macros::nav(selected="chats",
hide_user_menu=true) }}
<nav class="chats_nav"> <nav class="chats_nav">
<button <button
class="flex gap-2 items-center active" class="flex gap-2 items-center active"
@ -44,13 +45,14 @@
{% endif %} {% endfor %} {% endif %} {% endfor %}
</div> </div>
<div class="sidebar flex flex-col gap-2" id="channels_list"> <div class="sidebar flex flex-col gap-2 justify-between" id="channels_list">
<div class="flex flex-col gap-2 w-full">
<div class="title flex justify-between"> <div class="title flex justify-between">
{% if community %} {% if community %}
<b class="name shorter"> <b class="name shorter">
{% if community.context.display_name %} {{ {% if community.context.display_name %} {{
community.context.display_name }} {% else %} {{ community.title community.context.display_name }} {% else %} {{
}} {% endif %} community.title }} {% endif %}
</b> </b>
{% else %} {% else %}
<b>{{ text "chats:label.my_chats" }}</b> <b>{{ text "chats:label.my_chats" }}</b>
@ -69,7 +71,8 @@
<a href="/community/{{ selected_community }}"> <a href="/community/{{ selected_community }}">
{{ icon "book-heart" }} {{ icon "book-heart" }}
<span <span
>{{ text "communities:label.show_community" }}</span >{{ text "communities:label.show_community"
}}</span
> >
</a> </a>
{% endif %} {% if can_manage_channels %} {% endif %} {% if can_manage_channels %}
@ -99,6 +102,9 @@
></turbo-frame> ></turbo-frame>
</div> </div>
{{ components::user_plate(user=user, show_menu=true) }}
</div>
{% if channel %} {% if channel %}
<div class="w-full flex flex-col gap-2" id="stream" style="padding: 1rem"> <div class="w-full flex flex-col gap-2" id="stream" style="padding: 1rem">
<turbo-frame <turbo-frame
@ -253,7 +259,7 @@
z-index: 1; z-index: 1;
} }
.sidebar .title { .sidebar .title:not(.dropdown *) {
padding: 1rem; padding: 1rem;
border-bottom: solid 1px var(--color-super-lowered); border-bottom: solid 1px var(--color-super-lowered);
} }
@ -321,6 +327,10 @@
.chats_nav { .chats_nav {
display: flex; display: flex;
} }
.sidebar {
height: calc(100dvh - 42px * 2);
}
} }
</style> </style>

View file

@ -973,4 +973,128 @@ can_manage_message=false, grouped=false) -%}
</div> </div>
</div> </div>
</div> </div>
{%- endmacro %} {% macro user_menu() -%}
<div class="inner">
<b class="title">{{ user.username }}</b>
<a href="/@{{ user.username }}">
{{ icon "circle-user-round" }}
<span>{{ text "auth:link.my_profile" }}</span>
</a>
<a href="/settings">
{{ icon "settings" }}
<span>{{ text "auth:link.settings" }}</span>
</a>
{% if is_helper %}
<b class="title">{{ text "general:label.mod" }}</b>
<a href="/mod_panel/audit_log">
{{ icon "scroll-text" }}
<span>{{ text "general:link.audit_log" }}</span>
</a>
<a href="/mod_panel/reports">
{{ icon "flag" }}
<span>{{ text "general:link.reports" }}</span>
</a>
<a href="/mod_panel/ip_bans">
{{ icon "ban" }}
<span>{{ text "general:link.ip_bans" }}</span>
</a>
<a href="/mod_panel/stats">
{{ icon "chart-line" }}
<span>{{ text "general:link.stats" }}</span>
</a>
{% endif %}
<b class="title">{{ config.name }}</b>
<a href="https://github.com/trisuaso/tetratto">
{{ icon "code" }}
<span>{{ text "general:link.source_code" }}</span>
</a>
<a href="https://trisuaso.github.io/tetratto">
{{ icon "book" }}
<span>{{ text "general:link.reference" }}</span>
</a>
<div class="title"></div>
<button onclick="trigger('me::switch_account')">
{{ icon "ellipsis" }}
<span>{{ text "general:action.switch_account" }}</span>
</button>
<button class="red" onclick="trigger('me::logout')">
{{ icon "log-out" }}
<span>{{ text "auth:action.logout" }}</span>
</button>
</div>
{%- endmacro %} {% macro user_status(other_user) -%} {% if
other_user.settings.status %}
<div class="flex items-center gap-2">
<span>{{ other_user.settings.status }}</span>
<!-- connection icon -->
{% if (other_user.connections.LastFm[1].data and
other_user.connections.LastFm[1].data.track) or
(other_user.connections.Spotify[1].data and
other_user.connections.Spotify[1].data.track) %} {{ icon "music" }} {% endif
%}
</div>
{% elif other_user.connections.LastFm[0].data.name and
other_user.connections.LastFm[1].data and
other_user.connections.LastFm[1].data.track %}
<div class="flex items-center gap-2">
{{ icon "music" }}
<span
><b>Listening to</b> {{ other_user.connections.LastFm[1].data.artist
}}</span
>
</div>
{% elif other_user.connections.Spotify[0].data.name and
other_user.connections.Spotify[1].data and
other_user.connections.Spotify[1].data.track %}
<div class="flex items-center gap-2">
{{ icon "music" }}
<span
><b>Listening to</b> {{ other_user.connections.Spotify[1].data.artist
}}</span
>
</div>
{% endif %} {%- endmacro %} {% macro user_plate(user, show_menu=false,
secondary=false) -%}
<div
class="flex gap-2 items-center card tiny user_plate {% if secondary %}secondary{% endif %}"
>
<a href="/@{{ user.username }}">
{{ self::avatar(username=user.username, size="42px",
selector_type="username") }}
</a>
<div
class="flex justify-center flex-col"
style="{% if show_menu %}width: 60%{% endif %}"
>
{{ self::full_username(user=user) }}
<div class="user_status">{{ self::user_status(other_user=user) }}</div>
</div>
{% if show_menu %}
<div class="dropdown">
<button
class="camo small square"
onclick="trigger('atto::hooks::dropdown', [event])"
exclude="dropdown"
>
{{ icon "settings" c(dropdown-arrow) }}
</button>
{{ self::user_menu() }}
</div>
{% endif %}
</div>
{%- endmacro %} {%- endmacro %}

View file

@ -1,4 +1,4 @@
{% macro nav(selected="", show_lhs=true) -%} {% macro nav(selected="", show_lhs=true, hide_user_menu=false) -%}
<nav> <nav>
<div class="content_container"> <div class="content_container">
<div class="flex nav_side"> <div class="flex nav_side">
@ -72,79 +72,21 @@
> >
</a> </a>
{% if not hide_user_menu %}
<div class="dropdown"> <div class="dropdown">
<!-- prettier-ignore -->
<button <button
class="flex-row title" class="flex-row title"
onclick="trigger('atto::hooks::dropdown', [event])" onclick="trigger('atto::hooks::dropdown', [event])"
exclude="dropdown" exclude="dropdown"
style="gap: 0.25rem !important" style="gap: 0.25rem !important"
> >
{{ components::avatar(username=user.username, size="24px") }} {{ components::avatar(username=user.username, size="24px")
{{ icon "chevron-down" c(dropdown-arrow) }} }} {{ icon "chevron-down" c(dropdown-arrow) }}
</button> </button>
<div class="inner"> {{ components::user_menu() }}
<b class="title">{{ user.username }}</b>
<a href="/@{{ user.username }}">
{{ icon "circle-user-round" }}
<span>{{ text "auth:link.my_profile" }}</span>
</a>
<a href="/settings">
{{ icon "settings" }}
<span>{{ text "auth:link.settings" }}</span>
</a>
{% if is_helper %}
<b class="title">{{ text "general:label.mod" }}</b>
<a href="/mod_panel/audit_log">
{{ icon "scroll-text" }}
<span>{{ text "general:link.audit_log" }}</span>
</a>
<a href="/mod_panel/reports">
{{ icon "flag" }}
<span>{{ text "general:link.reports" }}</span>
</a>
<a href="/mod_panel/ip_bans">
{{ icon "ban" }}
<span>{{ text "general:link.ip_bans" }}</span>
</a>
<a href="/mod_panel/stats">
{{ icon "chart-line" }}
<span>{{ text "general:link.stats" }}</span>
</a>
{% endif %}
<b class="title">{{ config.name }}</b>
<a href="https://github.com/trisuaso/tetratto">
{{ icon "code" }}
<span>{{ text "general:link.source_code" }}</span>
</a>
<a href="https://trisuaso.github.io/tetratto">
{{ icon "book" }}
<span>{{ text "general:link.reference" }}</span>
</a>
<div class="title"></div>
<button onclick="trigger('me::switch_account')">
{{ icon "ellipsis" }}
<span>{{ text "general:action.switch_account" }}</span>
</button>
<button class="red" onclick="trigger('me::logout')">
{{ icon "log-out" }}
<span>{{ text "auth:action.logout" }}</span>
</button>
</div> </div>
</div> {% endif %} {% else %}
{% else %}
<div class="dropdown"> <div class="dropdown">
<button <button
class="title" class="title"

View file

@ -5,17 +5,23 @@
<span>{{ text "auth:label.followers" }}</span> <span>{{ text "auth:label.followers" }}</span>
</div> </div>
<div class="card flex flex-col gap-4"> <div class="card flex flex-wrap gap-4 flex-collapse">
<!-- prettier-ignore --> <!-- prettier-ignore -->
{% for item in list %} {% for item in list %}
<div class="card-nest"> {{ components::user_plate(user=item[1], secondary=true) }}
<div class="card small">
Since <span class="date">{{ item[0].created }}</span>
</div>
{{ components::user_card(user=item[1]) }}
</div>
{% endfor %} {{ components::pagination(page=page, items=list|length) }} {% endfor %} {{ components::pagination(page=page, items=list|length) }}
</div> </div>
</div> </div>
<style>
.user_plate {
width: calc(50% - 0.5rem);
}
@media screen and (max-width: 900px) {
.user_plate {
width: 100%;
}
}
</style>
{% endblock %} {% endblock %}

View file

@ -5,17 +5,23 @@
<span>{{ text "auth:label.following" }}</span> <span>{{ text "auth:label.following" }}</span>
</div> </div>
<div class="card flex flex-col gap-4"> <div class="card flex flex-wrap gap-4 flex-collapse">
<!-- prettier-ignore --> <!-- prettier-ignore -->
{% for item in list %} {% for item in list %}
<div class="card-nest"> {{ components::user_plate(user=item[1], secondary=true) }}
<div class="card small">
Since <span class="date">{{ item[0].created }}</span>
</div>
{{ components::user_card(user=item[1]) }}
</div>
{% endfor %} {{ components::pagination(page=page, items=list|length) }} {% endfor %} {{ components::pagination(page=page, items=list|length) }}
</div> </div>
</div> </div>
<style>
.user_plate {
width: calc(50% - 0.5rem);
}
@media screen and (max-width: 900px) {
.user_plate {
width: 100%;
}
}
</style>
{% endblock %} {% endblock %}

View file

@ -926,6 +926,11 @@
settings.biography, settings.biography,
"textarea", "textarea",
], ],
[
["status", "Status"],
settings.status,
"textarea",
],
[ [
["warning", "Profile warning"], ["warning", "Profile warning"],
settings.warning, settings.warning,

View file

@ -51,6 +51,8 @@ pub async fn handle_socket(socket: WebSocket, db: DataManager, community_id: Str
let mut user: Option<User> = None; let mut user: Option<User> = None;
let mut headers: Option<SocketHeaders> = None; let mut headers: Option<SocketHeaders> = None;
let channel_id = format!("chats/{community_id}");
// handle incoming messages on socket // handle incoming messages on socket
let dbc = db.clone(); let dbc = db.clone();
if let Some(Ok(WsMessage::Text(text))) = stream.next().await { if let Some(Ok(WsMessage::Text(text))) = stream.next().await {
@ -185,12 +187,13 @@ pub async fn handle_socket(socket: WebSocket, db: DataManager, community_id: Str
}); });
let dbc = db.clone(); let dbc = db.clone();
let channel_id_c = channel_id.clone();
let mut redis_task = tokio::spawn(async move { let mut redis_task = tokio::spawn(async move {
// forward messages from redis to the socket // forward messages from redis to the socket
let mut pubsub = dbc.2.client.get_async_pubsub().await.unwrap(); let mut pubsub = dbc.2.client.get_async_pubsub().await.unwrap();
pubsub.subscribe(user.id).await.unwrap(); pubsub.subscribe(user.id).await.unwrap();
pubsub.subscribe(community_id.clone()).await.unwrap(); pubsub.subscribe(channel_id_c).await.unwrap();
// listen for pubsub messages // listen for pubsub messages
let mut pubsub = pubsub.into_on_message(); let mut pubsub = pubsub.into_on_message();

View file

@ -165,13 +165,13 @@ impl DataManager {
// post event // post event
let mut con = self.2.get_con().await; let mut con = self.2.get_con().await;
if let Err(e) = con.publish::<usize, String, ()>( if let Err(e) = con.publish::<String, String, ()>(
if channel.community != 0 { if channel.community != 0 {
// broadcast to community ws // broadcast to community ws
channel.community format!("chats/{}", channel.community)
} else { } else {
// broadcast to channel ws // broadcast to channel ws
channel.id format!("chats/{}", channel.id)
}, },
serde_json::to_string(&SocketMessage { serde_json::to_string(&SocketMessage {
method: SocketMethod::Message, method: SocketMethod::Message,
@ -224,13 +224,13 @@ impl DataManager {
// post event // post event
let mut con = self.2.get_con().await; let mut con = self.2.get_con().await;
if let Err(e) = con.publish::<usize, String, ()>( if let Err(e) = con.publish::<String, String, ()>(
if channel.community != 0 { if channel.community != 0 {
// broadcast to community ws // broadcast to community ws
channel.community format!("chats/{}", channel.community)
} else { } else {
// broadcast to channel ws // broadcast to channel ws
channel.id format!("chats/{}", channel.id)
}, },
serde_json::to_string(&SocketMessage { serde_json::to_string(&SocketMessage {
method: SocketMethod::Delete, method: SocketMethod::Delete,

View file

@ -200,6 +200,9 @@ pub struct UserSettings {
/// If other users that you aren't following can add you to chats. /// If other users that you aren't following can add you to chats.
#[serde(default)] #[serde(default)]
pub private_chats: bool, pub private_chats: bool,
/// The user's status. Shows over connection info.
#[serde(default)]
pub status: String,
} }
impl Default for User { impl Default for User {