Ethan Dalool
da5c1ee008
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.
187 lines
5.6 KiB
HTML
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>
|