etiquette/frontends/etiquette_flask/templates/user.html
Ethan Dalool da5c1ee008 Let PhotoTags have timestamps; use more js cards.
Tags on photos can now have timestamps, so that if you are tagging
a video or audio you can reference a specific moment with your tag.
In the interface, this means the tag is clickable and seeks to that
point in the media.

For the user interface, I am finding I need to move away from jinja
for the object cards because it is too much hassle to keep the code
for jinja-based cards for static rendering and the js-based cards
for dynamic rendering in sync. Rather than write the same cards in
two languages I can dump the JSON into the script and render the cards
on load. Which makes the static HTML worse but that's what the JSON
API is for anyway.
2023-09-17 14:07:22 -07:00

187 lines
5.6 KiB
HTML

<!DOCTYPE html>
<html class="theme_{{theme}}">
<head>
{% import "header.html" as header %}
{% import "cards.html" as cards %}
<title class="dynamic_user_display_name">{{user.display_name}}</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="icon" href="/favicon.png" type="image/png"/>
<link rel="stylesheet" href="/static/css/common.css">
<link rel="stylesheet" href="/static/css/etiquette.css">
<link rel="stylesheet" href="/static/css/cards.css">
<script src="/static/js/common.js"></script>
<script src="/static/js/api.js"></script>
<script src="/static/js/cards.js"></script>
<script src="/static/js/editor.js"></script>
<script src="/static/js/http.js"></script>
<script src="/static/js/spinners.js"></script>
<style>
#content_body
{
grid-row-gap: 8px;
grid-auto-rows: max-content;
}
#hierarchy_photos:not(:has(.photo_card)),
#hierarchy_albums:not(:has(.album_card)),
#hierarchy_tags:not(:has(.tag_card)),
#hierarchy_bookmarks:not(:has(.bookmark_card))
{
display: none;
}
#tags_list .tag_card
{
margin: 4px;
}
</style>
</head>
<body>
{{header.make_header(session=request.session)}}
<div id="content_body">
<div id="hierarchy_self" class="panel">
<h1 id="display_name">{{user.display_name}}</h1>
{% if user.display_name != user.username %}
<p>Username: {{user.username}}</p>
{% endif %}
<p>ID: <a href="/userid/{{user.id}}"><code>{{user.id}}</code></a></p>
<p>User since <span title="{{user.created|timestamp_to_8601}}">{{user.created|timestamp_to_naturaldate}}.</span></p>
</div>
<div id="hierarchy_photos" class="panel">
<h2><a href="/search?author={{user.id}}">Photos by <span class="dynamic_user_display_name">{{user.display_name}}</span></a></h2>
<div id="photos_list">
</div>
</div>
<div id="hierarchy_tags" class="panel">
<h2>Tags by <span class="dynamic_user_display_name">{{user.display_name}}</span></h2>
<div id="tags_list">
</div>
</div>
<div id="hierarchy_albums" class="panel">
<h2>Albums by <span class="dynamic_user_display_name">{{user.display_name}}</span></h2>
<div id="albums_list">
</div>
</div>
<div id="hierarchy_bookmarks" class="panel">
<h2>Bookmarks by <span class="dynamic_user_display_name">{{user.display_name}}</span></h2>
<div id="bookmarks_list">
</div>
</div>
</div>
</body>
<script type="text/javascript">
const PHOTOS = [
{% for photo in user.get_photos(direction='desc')|islice(0, 15) %}
{{photo.jsonify(include_albums=False)|tojson|safe}},
{% endfor %}
];
const ALBUMS = [
{% for album in user.get_albums()|islice(0, 20) %}
{{album.jsonify(include_photos=False, include_children=False, include_parents=False, count_children=True, count_photos=True)|tojson|safe}},
{% endfor %}
];
const TAGS = [
{% for tag in user.get_tags(direction='desc')|islice(0, 100) %}
{{tag.jsonify()|tojson|safe}},
{% endfor %}
];
const BOOKMARKS = [
{% for bookmark in user.get_bookmarks()|islice(0, 50) %}
{{bookmark.jsonify()|tojson|safe}},
{% endfor %}
];
function on_pageload()
{
for (const photo of PHOTOS)
{
const photo_card = cards.photos.create({photo: photo});
document.getElementById("photos_list").append(photo_card);
}
for (const album of ALBUMS)
{
const album_card = cards.albums.create({album: album});
document.getElementById("albums_list").append(album_card);
}
for (const tag of TAGS)
{
const tag_card = cards.tags.create({tag: tag});
document.getElementById("tags_list").append(tag_card);
}
for (const bookmark of BOOKMARKS)
{
const bookmark_card = cards.bookmarks.create({
bookmark: bookmark,
add_author: false,
add_delete_button: false,
add_url_element: false,
});
document.getElementById("bookmarks_list").append(bookmark_card);
}
}
document.addEventListener("DOMContentLoaded", on_pageload);
{% if user.id == request.session.user.id %}
const USERNAME = "{{user.username}}";
profile_ed_on_open = undefined;
function profile_ed_on_save(ed)
{
function callback(response)
{
ed.hide_spinner();
if (! response.meta.json_ok)
{
alert(JSON.stringify(response));
return;
}
if ("error_type" in response.data)
{
ed.show_error(`${response.data.error_type} ${response.data.error_message}`);
return;
}
// The data that comes back from the server will have been normalized.
const new_display_name = response.data.display_name;
common.update_dynamic_elements("dynamic_user_display_name", new_display_name);
ed.elements["display_name"].edit.value = new_display_name;
ed.save();
}
ed.show_spinner();
api.users.edit(USERNAME, ed.elements["display_name"].edit.value, callback);
}
const profile_ed_on_cancel = undefined;
const profile_ed_elements = [
{
"id": "display_name",
"element": document.getElementById("display_name"),
"placeholder": "Display name",
"empty_text": USERNAME,
"autofocus": true,
},
];
const profile_ed = new editor.Editor(
profile_ed_elements,
profile_ed_on_open,
profile_ed_on_save,
profile_ed_on_cancel,
);
{% endif %}
</script>
</html>