Rework UI to have vertical menus

This commit is contained in:
Andrew Godwin 2022-11-17 17:43:00 -07:00
parent f5eafb0ca0
commit 2154e6f022
29 changed files with 344 additions and 246 deletions

View File

@ -40,7 +40,7 @@ class Home(FormView):
)
.select_related("subject_post", "subject_post__author")
.prefetch_related("subject_post__attachments")
.order_by("-created")[:100]
.order_by("-created")[:50]
)
context["interactions"] = PostInteraction.get_event_interactions(
context["events"], self.request.identity
@ -68,7 +68,7 @@ class Local(TemplateView):
Post.objects.filter(visibility=Post.Visibilities.public, author__local=True)
.select_related("author")
.prefetch_related("attachments")
.order_by("-created")[:100]
.order_by("-created")[:50]
)
context["current_page"] = "local"
return context
@ -85,7 +85,7 @@ class Federated(TemplateView):
Post.objects.filter(visibility=Post.Visibilities.public)
.select_related("author")
.prefetch_related("attachments")
.order_by("-created")[:100]
.order_by("-created")[:50]
)
context["current_page"] = "federated"
return context

View File

@ -100,6 +100,7 @@ class Config(models.Model):
site_name: str = "takahē"
highlight_color: str = "#449c8c"
post_length: int = 500
identity_max_age: int = 24 * 60 * 60
class UserOptions(pydantic.BaseModel):

View File

@ -16,6 +16,7 @@ figure,
blockquote,
dl,
dd,
fieldset,
menu {
margin: 0;
padding: 0;
@ -166,6 +167,9 @@ header menu a.identity {
border-right: 0;
text-align: right;
padding-right: 10px;
background: var(--color-bg-menu);
border-radius: 0 5px 0 0;
width: 250px;
}
header menu a i {
@ -188,26 +192,47 @@ header menu a small {
}
nav {
display: flex;
height: 40px;
background: var(--color-bg-menu);
padding: 10px 10px 20px 0;
}
nav h3 {
text-transform: uppercase;
font-weight: bold;
font-size: 90%;
padding: 15px 18px 7px 16px;
}
nav h3:first-child {
padding-top: 0;
}
nav a {
display: block;
color: var(--color-text-dull);
text-transform: uppercase;
font-weight: bold;
padding: 9px 18px 9px 18px;
padding: 7px 18px 7px 13px;
border-left: 3px solid transparent;
}
nav a.selected {
color: var(--color-text-main);
border-bottom: 3px solid var(--color-highlight);
background: var(--color-bg-main);
border-radius: 0 5px 5px 0;
}
nav a:hover {
color: var(--color-text-main);
border-left: 3px solid var(--color-highlight);
}
nav a.selected:hover {
border-left: 3px solid transparent;
}
nav a i {
width: 20px;
text-align: center;
margin-right: 4px;
display: inline-block;
}
/* Left-right columns */
@ -225,6 +250,7 @@ nav a:hover {
.right-column {
width: 250px;
background: var(--color-bg-menu);
border-radius: 0 0 5px 0;
}
.right-column h2 {
@ -237,22 +263,16 @@ nav a:hover {
/* Icon menus */
.icon-menu {
display: flex;
flex-wrap: wrap;
padding: 30px 0 0 30px;
}
.icon-menu {}
.icon-menu>a {
margin: 0px 40px 40px 0;
display: block;
margin: 0px 0 20px 0;
background: var(--color-bg-box);
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.1);
width: 370px;
height: 100px;
line-height: 100px;
color: inherit;
text-decoration: none;
padding: 0 20px;
padding: 10px 20px;
border: 2px solid rgba(255, 255, 255, 0);
border-radius: 3px;
}
@ -291,8 +311,21 @@ nav a:hover {
/* Forms */
form {
padding: 20px 40px 20px 30px;
fieldset {
border: 0;
background: var(--color-bg-box);
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.1);
margin: 25px 0 45px 0;
padding: 5px 15px;
}
fieldset legend {
position: relative;
top: -15px;
left: -15px;
font-weight: bold;
text-transform: uppercase;
color: var(--color-text-dull);
}
.right-column form,
@ -316,10 +349,16 @@ form p {
}
form .field {
margin: 25px 0 25px 0;
background: var(--color-bg-box);
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.1);
padding: 10px 10px;
margin: 0 0 25px 0;
display: flex;
}
form .field:last-of-type {
margin-bottom: 10px;
}
form .field .label-input {
flex-grow: 1;
}
.right-column form .field {
@ -334,6 +373,7 @@ form label {
text-transform: uppercase;
font-size: 100%;
font-weight: bold;
margin: 0 0 5px 0;
}
form label small {
@ -349,7 +389,7 @@ form label small {
form .help {
color: var(--color-text-dull);
font-size: 90%;
margin: 2px 0 6px 0;
margin: -5px 0 5px 0;
}
form .errorlist {
@ -422,9 +462,16 @@ form input[type=submit]:hover {
background: var(--color-button-main-hover);
}
form img.preview {
max-height: 100%;
max-width: 100px;
margin: 0 0 0 20px;
align-self: center;
}
form .buttons {
text-align: right;
margin: 25px 0 15px 0;
margin: -20px 0 15px 0;
}
.right-column form .buttons {
@ -459,6 +506,12 @@ form .button.toggle {
background: var(--color-bg-main);
}
form button.left,
form .button.left {
float: left;
margin: 0 5px 0 0;
}
form button.toggle.enabled,
form .button.toggle.enabled {
background: var(--color-highlight);
@ -661,3 +714,37 @@ h1.identity small {
font-size: 20px;
}
}
@media (max-width: 700px) {
header menu a.identity {
width: 50px;
padding: 10px 10px 0 0;
}
.right-column {
width: 50px;
}
.right-column nav {
padding-right: 0;
}
.right-column nav a {
font-size: 0;
padding: 10px 0 10px 10px;
}
.right-column nav a i {
font-size: 22px;
}
.right-column h3 {
visibility: hidden;
}
.right-column h2,
.right-column .compose {
display: none;
}
}

View File

@ -1,6 +1,27 @@
<nav>
<a href="/" {% if current_page == "home" %}class="selected"{% endif %}>Home</a>
<a href="/notifications/" {% if current_page == "notifications" %}class="selected"{% endif %}>Notifications</a>
<a href="/local/" {% if current_page == "local" %}class="selected"{% endif %}>Local</a>
<a href="/federated/" {% if current_page == "federated" %}class="selected"{% endif %}>Federated</a>
<a href="/" {% if current_page == "home" %}class="selected"{% endif %}>
<i class="fa-solid fa-home"></i> Home
</a>
<a href="/notifications/" {% if current_page == "notifications" %}class="selected"{% endif %}>
<i class="fa-solid fa-at"></i> Notifications
</a>
<a href="/local/" {% if current_page == "local" %}class="selected"{% endif %}>
<i class="fa-solid fa-city"></i> Local
</a>
<a href="/federated/" {% if current_page == "federated" %}class="selected"{% endif %}>
<i class="fa-solid fa-globe"></i> Federated
</a>
</nav>
{% if current_page == "home" %}
<h2>Compose</h2>
<form action="/compose/" method="POST" class="compose">
{% csrf_token %}
{{ form.text }}
{{ form.content_warning }}
<div class="buttons">
<span class="button toggle" _="on click toggle .enabled then toggle .hidden on #id_content_warning">CW</span>
<button>{% if config_identity.toot_mode %}Toot!{% else %}Post{% endif %}</button>
</div>
</form>
{% endif %}

View File

@ -3,15 +3,14 @@
{% block title %}Compose{% endblock %}
{% block content %}
<nav>
<a href="." class="selected">Compose</a>
</nav>
<form action="." method="POST">
{% csrf_token %}
{% for field in form %}
{% include "forms/_field.html" %}
{% endfor %}
<fieldset>
<legend>Content</legend>
{% include "forms/_field.html" with field=form.text %}
{% include "forms/_field.html" with field=form.content_warning %}
{% include "forms/_field.html" with field=form.visibility %}
</fieldset>
<div class="buttons">
<button>{% if config_identity.toot_mode %}Toot!{% else %}Post{% endif %}</button>
</div>

View File

@ -3,19 +3,9 @@
{% block title %}Federated Timeline{% endblock %}
{% block content %}
{% include "activities/_home_menu.html" %}
<section class="columns">
<div class="left-column">
{% for post in posts %}
{% include "activities/_post.html" %}
{% empty %}
No posts yet.
{% endfor %}
</div>
<div class="right-column">
<h2>?</h2>
</div>
</section>
{% endblock %}

View File

@ -3,11 +3,6 @@
{% block title %}Home{% endblock %}
{% block content %}
{% include "activities/_home_menu.html" %}
<section class="columns">
<div class="left-column">
{% for event in events %}
{% if event.type == "post" %}
{% include "activities/_post.html" with post=event.subject_post %}
@ -22,20 +17,4 @@
{% empty %}
Nothing to show yet.
{% endfor %}
</div>
<div class="right-column">
<h2>Compose</h2>
<form action="/compose/" method="POST" class="compose">
{% csrf_token %}
{{ form.text }}
{{ form.content_warning }}
<div class="buttons">
<span class="button toggle" _="on click toggle .enabled then toggle .hidden on #id_content_warning">CW</span>
<button>{% if config_identity.toot_mode %}Toot!{% else %}Post{% endif %}</button>
</div>
</form>
</div>
</section>
{% endblock %}

View File

@ -3,19 +3,9 @@
{% block title %}Local Timeline{% endblock %}
{% block content %}
{% include "activities/_home_menu.html" %}
<section class="columns">
<div class="left-column">
{% for post in posts %}
{% include "activities/_post.html" %}
{% empty %}
No posts yet.
{% endfor %}
</div>
<div class="right-column">
<h2>?</h2>
</div>
</section>
{% endblock %}

View File

@ -3,20 +3,9 @@
{% block title %}Notifications{% endblock %}
{% block content %}
{% include "activities/_home_menu.html" %}
<section class="columns">
<div class="left-column">
{% for event in events %}
{% include "activities/_event.html" %}
{% empty %}
No events yet.
{% endfor %}
</div>
<div class="right-column">
<h2>?</h2>
</div>
</section>
{% endblock %}

View File

@ -1,6 +0,0 @@
<nav>
<a href="{% url "admin_basic" %}" {% if section == "basic" %}class="selected"{% endif %}>Basic</a>
<a href="{% url "admin_domains" %}" {% if section == "domains" %}class="selected"{% endif %}>Domains</a>
<a href="{% url "admin_users" %}" {% if section == "users" %}class="selected"{% endif %}>Users</a>
<a href="{% url "admin_identities" %}" {% if section == "identities" %}class="selected"{% endif %}>Identities</a>
</nav>

View File

@ -1,11 +1,8 @@
{% extends "base.html" %}
{% extends "settings/base.html" %}
{% block title %}Add Domain - Admin{% endblock %}
{% block content %}
{% block menu %}
{% include "admin/_menu.html" %}
{% endblock %}
<form action="." method="POST">
<h1>Add A Domain</h1>
<p>
@ -28,12 +25,17 @@
service domain.
</p>
{% csrf_token %}
{% for field in form %}
{% include "forms/_field.html" %}
{% endfor %}
<fieldset>
<legend>Domain Details</legend>
{% include "forms/_field.html" with field=form.domain %}
{% include "forms/_field.html" with field=form.service_domain %}
</fieldset>
<fieldset>
<legend>Access Control</legend>
{% include "forms/_field.html" with field=form.public %}
</fieldset>
<div class="buttons">
<a href="{{ domain.urls.delete }}" class="button delete">Delete</a>
<button>Save</button>
<button>Create</button>
</div>
</form>
{% endblock %}

View File

@ -1,12 +1,8 @@
{% extends "base.html" %}
{% extends "settings/base.html" %}
{% block title %}Delete {{ domain.domain }} - Admin{% endblock %}
{% block content %}
{% block menu %}
{% include "admin/_menu.html" %}
{% endblock %}
<form action="." method="POST">
{% csrf_token %}
@ -28,6 +24,5 @@
<button class="delete">Confirm Deletion</button>
</div>
{% endif %}
</form>
{% endblock %}

View File

@ -1,16 +1,19 @@
{% extends "base.html" %}
{% extends "settings/base.html" %}
{% block title %}{{ domain.domain }} - Admin{% endblock %}
{% block subtitle %}{{ domain.domain }}{% endblock %}
{% block content %}
{% block menu %}
{% include "admin/_menu.html" %}
{% endblock %}
<form action="." method="POST">
{% csrf_token %}
{% for field in form %}
{% include "forms/_field.html" %}
{% endfor %}
<fieldset>
<legend>Domain Details</legend>
{% include "forms/_field.html" with field=form.domain %}
{% include "forms/_field.html" with field=form.service_domain %}
</fieldset>
<fieldset>
<legend>Access Control</legend>
{% include "forms/_field.html" with field=form.public %}
</fieldset>
<div class="buttons">
<a href="{{ domain.urls.delete }}" class="button delete">Delete</a>
<button>Save</button>

View File

@ -1,11 +1,8 @@
{% extends "base.html" %}
{% extends "settings/base.html" %}
{% block title %}{{ section.title }} - Admin{% endblock %}
{% block subtitle %}Domains{% endblock %}
{% block content %}
{% block menu %}
{% include "admin/_menu.html" %}
{% endblock %}
<section class="icon-menu">
{% for domain in domains %}
<a class="option" href="{{ domain.urls.edit }}">

View File

@ -1,14 +1,9 @@
{% extends "base.html" %}
{% extends "settings/base.html" %}
{% block title %}Identities - Admin{% endblock %}
{% block subtitle %}Identities{% endblock %}
{% block content %}
{% block menu %}
{% include "admin/_menu.html" %}
{% endblock %}
<form>
<p>
Please use the <a href="/djadmin/users/identity/">Django Admin</a> for now.
</p>
</form>
{% endblock %}

View File

@ -1,18 +0,0 @@
{% extends "base.html" %}
{% block title %}{{ section.title }} - Admin{% endblock %}
{% block content %}
{% block menu %}
{% include "admin/_menu.html" %}
{% endblock %}
<form action="." method="POST">
{% csrf_token %}
{% for field in form %}
{% include "forms/_field.html" %}
{% endfor %}
<div class="buttons">
<button>Save</button>
</div>
</form>
{% endblock %}

View File

@ -1,14 +1,9 @@
{% extends "base.html" %}
{% extends "settings/base.html" %}
{% block title %}Users - Admin{% endblock %}
{% block subtitle %}Users{% endblock %}
{% block content %}
{% block menu %}
{% include "admin/_menu.html" %}
{% endblock %}
<form>
<p>
Please use the <a href="/djadmin/users/user/">Django Admin</a> for now.
</p>
</form>
{% endblock %}

View File

@ -31,14 +31,12 @@
<a href="/compose/" title="Compose" {% if top_section == "compose" %}class="selected"{% endif %}>
<i class="fa-solid fa-feather"></i> Compose
</a>
<a href="#" title="Search" {% if top_section == "search" %}class="selected"{% endif %}>
<i class="fa-solid fa-search"></i> Search
</a>
<a href="{% url "settings" %}" title="Settings" {% if top_section == "settings" %}class="selected"{% endif %}>
<i class="fa-solid fa-gear"></i> Settings
</a>
{% if request.user.admin %}
<a href="{% url "admin" %}" title="Admin" {% if top_section == "admin" %}class="selected"{% endif %}>
<i class="fa-solid fa-toolbox"></i> Admin
</a>
{% endif %}
<div class="gap"></div>
<a href="/identity/select/" class="identity">
{% if not request.identity %}
@ -61,8 +59,19 @@
</menu>
</header>
{% block full_content %}
<div class="columns">
<div class="left-column">
{% block content %}
{% endblock %}
</div>
<div class="right-column">
{% block right_content %}
{% include "activities/_home_menu.html" %}
{% endblock %}
</div>
</div>
{% endblock %}
</main>
</body>

View File

@ -1,4 +1,5 @@
<div class="field">
<div class="label-input">
<label for="{{ field.id_for_label }}">
{{ field.label }}
{% if field.field.required %}<small>(Required)</small>{% endif %}
@ -10,4 +11,8 @@
{% endif %}
{{ field.errors }}
{{ field }}
</div>
{% if preview %}
<img class="preview" src="{{ preview }}">
{% endif %}
</div>

View File

@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block title %}{% block subtitle %}{% endblock %} - Settings{% endblock %}
{% block right_content %}
{% include "identity/_menu.html" %}
{% endblock %}

View File

@ -1,17 +1,19 @@
{% extends "base.html" %}
{% extends "identity/base.html" %}
{% block title %}Create Identity{% endblock %}
{% block content %}
{% include "identity/_identity_menu.html" %}
<form action="." method="POST">
<h1>Create New Identity</h1>
<p>You can have multiple identities - they are totally separate, and share
nothing apart from your login details. Use them for alternates, projects, and more.</p>
{% csrf_token %}
{% for field in form %}
{% include "forms/_field.html" %}
{% endfor %}
<fieldset>
<legend>Identity Details</legend>
{% include "forms/_field.html" with field=form.username %}
{% include "forms/_field.html" with field=form.domain %}
{% include "forms/_field.html" with field=form.name %}
</fieldset>
<div class="buttons">
<button>Create</button>
</div>

View File

@ -1,18 +1,12 @@
{% extends "base.html" %}
{% load static %}
{% extends "identity/base.html" %}
{% block title %}Select Identity{% endblock %}
{% block content %}
{% include "identity/_identity_menu.html" %}
<section class="icon-menu">
{% for identity in identities %}
<a class="option" href="{{ identity.urls.activate }}">
{% if identity.icon_uri %}
<img src="{{ identity.icon_uri }}">
{% else %}
<img src="{% static "img/unknown-icon-128.png" %}">
{% endif %}
<img src="{{ identity.local_icon_url }}">
<span class="handle">
{{ identity.name_or_handle }}
<small>@{{ identity.handle }}</small>

View File

@ -1,5 +1,28 @@
<nav>
<a href="{% url "settings_profile" %}" {% if section == "profile" %}class="selected"{% endif %}>Profile</a>
<a href="#" {% if section == "interface" %}class="selected"{% endif %}>Interface</a>
<a href="#" {% if section == "filtering" %}class="selected"{% endif %}>Filtering</a>
<h3>Identity</h3>
<a href="{% url "settings_profile" %}" {% if section == "profile" %}class="selected"{% endif %}>
<i class="fa-solid fa-user"></i> Profile
</a>
<a href="{% url "settings_interface" %}" {% if section == "interface" %}class="selected"{% endif %}>
<i class="fa-solid fa-display"></i> Interface
</a>
{% if request.user.admin %}
<h3>Account</h3>
<a href="#" {% if section == "login" %}class="selected"{% endif %}>
<i class="fa-solid fa-key"></i> Login &amp; Security
</a>
<h3>Administration</h3>
<a href="{% url "admin_basic" %}" {% if section == "basic" %}class="selected"{% endif %}>
<i class="fa-solid fa-book"></i> Basic
</a>
<a href="{% url "admin_domains" %}" {% if section == "domains" %}class="selected"{% endif %}>
<i class="fa-solid fa-globe"></i> Domains
</a>
<a href="{% url "admin_users" %}" {% if section == "users" %}class="selected"{% endif %}>
<i class="fa-solid fa-users"></i> Users
</a>
<a href="{% url "admin_identities" %}" {% if section == "identities" %}class="selected"{% endif %}>
<i class="fa-solid fa-id-card"></i> Identities
</a>
{% endif %}
</nav>

View File

@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block title %}{% block subtitle %}{% endblock %} - Settings{% endblock %}
{% block right_content %}
{% include "settings/_menu.html" %}
{% endblock %}

View File

@ -1,18 +1,22 @@
{% extends "base.html" %}
{% extends "settings/base.html" %}
{% block title %}Profile - Settings{% endblock %}
{% block subtitle %}Profile{% endblock %}
{% block content %}
{% block menu %}
{% include "settings/_menu.html" %}
{% endblock %}
<form action="." method="POST" enctype="multipart/form-data" >
{% csrf_token %}
{% for field in form %}
{% include "forms/_field.html" %}
{% endfor %}
<fieldset>
<legend>Details</legend>
{% include "forms/_field.html" with field=form.name %}
{% include "forms/_field.html" with field=form.summary %}
</fieldset>
<fieldset>
<legend>Images</legend>
{% include "forms/_field.html" with field=form.icon preview=request.identity.icon.url %}
{% include "forms/_field.html" with field=form.image preview=request.identity.image.url %}
</fieldset>
<div class="buttons">
<a href="{{ request.identity.urls.view }}" class="button secondary">View Profile</a>
<a href="{{ request.identity.urls.view }}" class="button secondary left">View Profile</a>
<button>Save</button>
</div>
</form>

View File

@ -1,16 +1,18 @@
{% extends "base.html" %}
{% extends "settings/base.html" %}
{% block title %}{{ section.title }} - Settings{% endblock %}
{% block subtitle %}{{ section.title }}{% endblock %}
{% block content %}
{% block menu %}
{% include "settings/_menu.html" %}
{% endblock %}
<form action="." method="POST">
{% csrf_token %}
{% for field in form %}
{% for title, fields in fieldsets.items %}
<fieldset>
<legend>{{ title }}</legend>
{% for field in fields %}
{% include "forms/_field.html" %}
{% endfor %}
</fieldset>
{% endfor %}
<div class="buttons">
<button>Save</button>
</div>

View File

@ -24,7 +24,6 @@ class AdminSettingsPage(SettingsPage):
at the bottom of the page. Don't add this to a URL directly - subclass!
"""
template_name = "admin/settings.html"
options_class = Config.SystemOptions
def load_config(self):
@ -47,6 +46,15 @@ class BasicPage(AdminSettingsPage):
"title": "Highlight Color",
"help_text": "Used for logo background and other highlights",
},
"post_length": {
"title": "Maximum Post Length",
"help_text": "The maximum number of characters allowed per post",
},
}
layout = {
"Branding": ["site_name", "highlight_color"],
"Posts": ["post_length"],
}

View File

@ -1,5 +1,5 @@
from functools import partial
from typing import ClassVar, Dict
from typing import ClassVar, Dict, List
from django import forms
from django.shortcuts import redirect
@ -27,6 +27,7 @@ class SettingsPage(FormView):
template_name = "settings/settings.html"
section: ClassVar[str]
options: Dict[str, Dict[str, str]]
layout: Dict[str, List[str]]
def get_form_class(self):
# Create the fields dict from the config object
@ -42,6 +43,8 @@ class SettingsPage(FormView):
)
elif config_field.type_ is str:
form_field = forms.CharField
elif config_field.type_ is int:
form_field = forms.IntegerField
else:
raise ValueError(f"Cannot render settings type {config_field.type_}")
fields[key] = form_field(
@ -68,6 +71,10 @@ class SettingsPage(FormView):
def get_context_data(self):
context = super().get_context_data()
context["section"] = self.section
# Gather fields into fieldsets
context["fieldsets"] = {}
for title, fields in self.layout.items():
context["fieldsets"][title] = [context["form"][field] for field in fields]
return context
def form_valid(self, form):
@ -87,10 +94,12 @@ class InterfacePage(SettingsPage):
options = {
"toot_mode": {
"title": "I Will Toot As I Please",
"help_text": "If enabled, changes all 'Post' buttons to 'Toot!'",
"help_text": "Changes all 'Post' buttons to 'Toot!'",
}
}
layout = {"Posting": ["toot_mode"]}
@method_decorator(identity_required, name="dispatch")
class ProfilePage(FormView):
@ -102,9 +111,18 @@ class ProfilePage(FormView):
class form_class(forms.Form):
name = forms.CharField(max_length=500)
summary = forms.CharField(widget=forms.Textarea, required=False)
icon = forms.ImageField(required=False)
image = forms.ImageField(required=False)
summary = forms.CharField(
widget=forms.Textarea,
required=False,
help_text="Describe you and your interests",
label="Bio",
)
icon = forms.ImageField(
required=False, help_text="Shown next to all your posts and activities"
)
image = forms.ImageField(
required=False, help_text="Shown at the top of your profile"
)
def get_initial(self):
return {