Skip to content

Commit

Permalink
Added timeline to profile. Formatting updates.
Browse files Browse the repository at this point in the history
  • Loading branch information
steve-bate committed Sep 3, 2024
1 parent 54a20ed commit befe367
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 31 deletions.
83 changes: 72 additions & 11 deletions firm_server/html/endpoint.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import logging
import mimetypes
import os
from dataclasses import dataclass
from typing import Any, Awaitable, Callable
from urllib.parse import urlparse

from firm.interfaces import HttpRequest, HttpResponse, get_url_prefix
from firm.interfaces import (
FIRM_NS,
HttpRequest,
HttpResponse,
ResourceStore,
get_url_prefix,
)
from firm.util import get_version
from starlette.requests import Request
from starlette.responses import FileResponse, JSONResponse, Response
Expand Down Expand Up @@ -42,10 +50,59 @@ async def _static_endpoint(request: Request):
return _static_endpoint


ACTOR_TEMPLATE = "actor.jinja2"
# TODO extend the document list
# TODO Move to core utils?
DOCUMENT_TYPES = ["Note", "Article", "Document"]


async def _create_timeline(uris: list[str], store: ResourceStore):
observed_resources = set()
timeline = []
for uri in uris:
activity = await store.get(uri)
obj = activity.get("object")
if isinstance(obj, str):
obj = await store.get(obj)
if (
obj
and obj["id"] not in observed_resources
and obj.get("type") in DOCUMENT_TYPES
):
if activity.get("type") in ["Create", "Update"]:
timeline.append(obj)
observed_resources.add(obj["id"])
if len(timeline) >= 10:
break
return timeline


async def _actor_context(uri: str, store: ResourceStore):
context = {}
credentials = await store.query_one(
{
"@prefix": "urn:",
"type": FIRM_NS.Credentials.value,
"attributedTo": uri,
}
)
context["roles"] = credentials.get(FIRM_NS.role.value) if credentials else []
actor = await store.get(uri)
outbox = await store.get(actor["outbox"])
timeline = await _create_timeline(outbox.get("orderedItems", []), store)
context["timeline"] = timeline
return context


@dataclass
class TemplateConfig:
template: str
context: Callable[[str, ResourceStore], Awaitable[dict[str, Any]]] | None = None


ACTOR_TEMPLATE = TemplateConfig("actor.jinja2", _actor_context)
DOCUMENT_TEMPLATE = "document.jinja2"

RESOURCE_TEMPLATES = {
RESOURCE_TEMPLATES: dict[str, TemplateConfig | str] = {
"Person": ACTOR_TEMPLATE,
"Organization": ACTOR_TEMPLATE,
"Group": ACTOR_TEMPLATE,
Expand Down Expand Up @@ -105,15 +162,19 @@ async def _endpoint(request: HttpRequest) -> HttpResponse:
if not resource:
return Response("Resource not found", status_code=404)
else:
if template := RESOURCE_TEMPLATES.get(resource.get("type")):
return templates.TemplateResponse(
template,
dict(
request=request,
get_version=get_version,
resource=resource,
),
if template_config := RESOURCE_TEMPLATES.get(resource.get("type")):
if isinstance(template_config, str):
template_config = TemplateConfig(template_config)
context = dict(
request=request,
get_version=get_version,
resource=resource,
)
if template_config.context:
context.update(
await template_config.context(str(request.url), store)
)
return templates.TemplateResponse(template_config.template, context)
return JSONResponse(resource)

return _endpoint
92 changes: 72 additions & 20 deletions firm_server/html/templates/actor.jinja2
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
{% extends "layout.jinja2" %}

{% set header_image = resource.image.url if resource.image else "https://placehold.co/1200x300" %}
{% set avatar_image = resource.icon.url if resource.icon else "https://placehold.co/80x80" %}

{% block style %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
<style>
header {
background-image: url('{{ resource.image.url }}');
background-image: url('{{ header_image }}');
background-size: cover;
background-position: center center;
height: 200px;
Expand All @@ -26,6 +28,11 @@
left: 20px;
}
.profile-info .address {
padding-left: 2rem;
font-size: 1.5rem;
}
.profile-info .address-type {
font-weight: bold;
}
Expand Down Expand Up @@ -103,6 +110,7 @@
display: flex;
justify-content: space-around;
margin-top: 1rem;
margin-bottom: 1rem;
border-bottom: 1px solid #444;
}
Expand All @@ -122,9 +130,10 @@
}
.post {
display: flex;
padding: 20px;
border-bottom: 1px solid #444;
/*display: flex;*/
/*padding: 20px;*/
/*border-bottom: 1px solid #444;*/
margin-bottom: 2rem;
}
.post img {
Expand All @@ -135,7 +144,8 @@
}
.post-content {
flex: 1;
margin-top: 1rem;
display: flex;
}
.post-content p {
Expand All @@ -158,7 +168,7 @@
.post-actions {
display: flex;
justify-content: space-around;
margin-top: 1.5rem;
margin-top: 2rem;
}
.post-actions i {
Expand All @@ -169,21 +179,40 @@
.profile-summary {
margin-top: 1rem;
}
.profile-properties {
margin-left: 4rem;
width: 80%;
}
@media (max-width: 600px) {
.profile-info .address {
padding-left: 0;
}
.profile-properties {
margin: 0;
width: 100%;
}
}
</style>
{% endblock %}

{% block header %}
<img src="{{ resource.icon.url }}" alt="Profile header" class="profile-avatar">
<img src="{{ avatar_image }}" alt="Profile header" class="profile-avatar">
<button class="edit-profile-btn">Edit profile</button>
{% endblock %}

{% block main %}
<section>
<div class="profile-info">
<h1>{{ resource.name }}</h1>
<p><span class="address-type">Mastodon:</span> @{{ resource.preferredUsername }}@{{ request.url.hostname }}</p>
<p><span class="address-type">ActivityPub:</span> {{ resource.id }}</p>
<p class="address"><span class="address-type">Mastodon:</span>
@{{ resource.preferredUsername }}@{{ request.url.hostname }}</p>
<p class="address"><span class="address-type">ActivityPub:</span> {{ resource.id }}</p>
{% if "server/admin" in roles or "tenant/admin" in roles %}
<div class="admin-badge"><i class="fas fa-user-shield"></i> Admin {{ request.url.hostname }}</div>
{% endif %}
<div class="profile-summary">{{ resource.summary | safe }}</div>
{% if resource.alsoKnownAs %}
<div class="actor-aka">
Expand All @@ -199,10 +228,24 @@
<li>{{ resource.alsoKnownAs }}</li>
</ul>
{% endif %}
<h2>
<h2>
</div>
{% endif %}
{% if resource.attachment %}
<h2>See Also:</h2>
<table class="profile-properties">
<tbody>
{% for p in resource.attachment %}
<tr>
<td>{{ p.name }}</td>
<td>{{ p.value | safe }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>

<!--
<div class="profile-details">
<p>JOINED</p>
Expand All @@ -214,19 +257,28 @@
<div>Posts and replies</div>
<div>Media</div>
</div>
{% for post in timeline %}
<div class="post">
<img src="{{ resource.icon.url }}" alt="Avatar">
<div class="post-content">
<p><span class="username">tester</span> <span class="handle">@tester</span></p>
<p>@steve What's up dude?</p>
<div class="post-actions">
<i class="far fa-comment"></i>
<i class="fas fa-retweet"></i>
<i class="far fa-heart"></i>
<i class="far fa-bookmark"></i>
<span class="time">5d</span>
<img src="{{ avatar_image }}" alt="Avatar">
<div>
<p>
<span class="username">{{ resource.name or resource.preferredUsername }}</span>
<span class="handle">@{{ resource.preferredUsername }}</span>
</p>
{# TODO sanitize content #}
<p>{{ post.content | safe }}</p>
</div>

</div>
<div class="post-actions">
<i class="far fa-comment"></i>
<i class="fas fa-retweet"></i>
<i class="far fa-heart"></i>
<i class="far fa-bookmark"></i>
<span class="time">5d</span>
</div>
</div>
{% endfor %}
</section>
{% endblock main %}

0 comments on commit befe367

Please sign in to comment.