Use icons instead of Unicode symbols for buttons

Fixes #26.
This commit is contained in:
Jonas Schäfer
2021-01-18 17:25:38 +01:00
parent 16f990f475
commit c1dbec51ab
17 changed files with 333 additions and 49 deletions

View File

@@ -402,24 +402,21 @@ div.form.layout-expanded {
}
/* icon support */
svg.icon {
display: inline-block;
vertical-align: middle;
width: 1em;
height: 1em;
stroke-width: 0;
stroke: currentColor;
fill: currentColor;
}
/* form buttons */
.btn-edit:before {
content: '';
}
.btn-delete:before {
content: '🗑';
}
.btn-link:before {
content: '+';
}
.btn-more:before {
content: '';
}
input[type="submit"], button, .button {
margin: 0 $w-s2;
padding: $w-s3 $w-s1;
@@ -430,6 +427,23 @@ input[type="submit"], button, .button {
margin: 0 $w-s4;
padding: $w-s4 $w-s2;
}
& > svg.icon:first-child {
margin-right: $w-s2;
td & {
margin-right: $w-s3;
}
}
& > svg.icon:last-child {
margin-right: 0;
margin-top: -$w-s4;
td & {
margin-right: 0;
}
}
}
a.button {

View File

@@ -0,0 +1,90 @@
<svg aria-hidden="true" style="position: absolute; width: 0; height: 0; overflow: hidden;" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<!-- These icons are sourced from Googles Material Icons set,
licensed under the terms of the Apache 2.0 License -->
<!-- from: action/account_circle/materialiconsround/24px.svg -->
<symbol id="icon-profile" viewBox="0 0 24 24">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z" />
</symbol>
<!-- from: action/done/materialiconsround/24px.svg -->
<symbol id="icon-done" viewBox="0 0 24 24">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M9 16.2l-3.5-3.5c-.39-.39-1.01-.39-1.4 0-.39.39-.39 1.01 0 1.4l4.19 4.19c.39.39 1.02.39 1.41 0L20.3 7.7c.39-.39.39-1.01 0-1.4-.39-.39-1.01-.39-1.4 0L9 16.2z" />
</symbol>
<!-- from: action/logout/materialicons/24px.svg -->
<symbol id="icon-logout" viewBox="0 0 24 24">
<path d="M0 0h24v24H0z" fill="none" />
<path d="M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z" />
</symbol>
<!-- from: action/login/materialiconsround/24px.svg -->
<symbol id="icon-login" viewBox="0 0 24 24">
<g><rect fill="none" height="24" width="24" /><rect fill="none" height="24" width="24" /></g>
<g><path d="M10.3,7.7L10.3,7.7c-0.39,0.39-0.39,1.01,0,1.4l1.9,1.9H3c-0.55,0-1,0.45-1,1v0c0,0.55,0.45,1,1,1h9.2l-1.9,1.9 c-0.39,0.39-0.39,1.01,0,1.4l0,0c0.39,0.39,1.01,0.39,1.4,0l3.59-3.59c0.39-0.39,0.39-1.02,0-1.41L11.7,7.7 C11.31,7.31,10.69,7.31,10.3,7.7z M20,19h-7c-0.55,0-1,0.45-1,1v0c0,0.55,0.45,1,1,1h7c1.1,0,2-0.9,2-2V5c0-1.1-0.9-2-2-2h-7 c-0.55,0-1,0.45-1,1v0c0,0.55,0.45,1,1,1h7V19z" /></g>
</symbol>
<!-- from: communication/vpn_key/materialiconsround/24px.svg -->
<symbol id="icon-passwd" viewBox="0 0 24 24">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M12.65 10C11.7 7.31 8.9 5.5 5.77 6.12c-2.29.46-4.15 2.29-4.63 4.58C.32 14.57 3.26 18 7 18c2.61 0 4.83-1.67 5.65-4H17v2c0 1.1.9 2 2 2s2-.9 2-2v-2c1.1 0 2-.9 2-2s-.9-2-2-2h-8.35zM7 14c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z" />
</symbol>
<!-- from: content/add_circle_outline/materialiconsround/24px.svg -->
<symbol id="icon-add" viewBox="0 0 24 24">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M12 7c-.55 0-1 .45-1 1v3H8c-.55 0-1 .45-1 1s.45 1 1 1h3v3c0 .55.45 1 1 1s1-.45 1-1v-3h3c.55 0 1-.45 1-1s-.45-1-1-1h-3V8c0-.55-.45-1-1-1zm0-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z" />
</symbol>
<!-- from: content/add_link/materialicons/24px.svg -->
<symbol id="icon-create_link" viewBox="0 0 24 24">
<path d="M0 0h24v24H0" fill="none" />
<path d="M8 11h8v2H8zm12.1 1H22c0-2.76-2.24-5-5-5h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1zM3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM19 12h-2v3h-3v2h3v3h2v-3h3v-2h-3z" />
</symbol>
<!-- from: content/create/materialiconsround/24px.svg -->
<symbol id="icon-edit" viewBox="0 0 24 24">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M3 17.46v3.04c0 .28.22.5.5.5h3.04c.13 0 .26-.05.35-.15L17.81 9.94l-3.75-3.75L3.15 17.1c-.1.1-.15.22-.15.36zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" />
</symbol>
<!-- from: content/remove_circle_outline/materialiconsround/24px.svg -->
<symbol id="icon-remove" viewBox="0 0 24 24">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M7 12c0 .55.45 1 1 1h8c.55 0 1-.45 1-1s-.45-1-1-1H8c-.55 0-1 .45-1 1zm5-10C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z" />
</symbol>
<!-- from: content/content_copy/materialiconsround/24px.svg -->
<symbol id="icon-copy" viewBox="0 0 24 24">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M15 1H4c-1.1 0-2 .9-2 2v13c0 .55.45 1 1 1s1-.45 1-1V4c0-.55.45-1 1-1h10c.55 0 1-.45 1-1s-.45-1-1-1zm4 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm-1 16H9c-.55 0-1-.45-1-1V8c0-.55.45-1 1-1h9c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1z" />
</symbol>
<!-- from: content/link_off/materialiconsround/24px.svg -->
<symbol id="icon-remove_link" viewBox="0 0 24 24">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M21.94 11.23C21.57 8.76 19.32 7 16.82 7h-2.87c-.52 0-.95.43-.95.95s.43.95.95.95h2.9c1.6 0 3.04 1.14 3.22 2.73.17 1.43-.64 2.69-1.85 3.22l1.4 1.4c1.63-1.02 2.64-2.91 2.32-5.02zM4.12 3.56c-.39-.39-1.02-.39-1.41 0s-.39 1.02 0 1.41l2.4 2.4c-1.94.8-3.27 2.77-3.09 5.04C2.23 15.05 4.59 17 7.23 17h2.82c.52 0 .95-.43.95-.95s-.43-.95-.95-.95H7.16c-1.63 0-3.1-1.19-3.25-2.82-.15-1.72 1.11-3.17 2.75-3.35l2.1 2.1c-.43.09-.76.46-.76.92v.1c0 .52.43.95.95.95h1.78L13 15.27V17h1.73l3.3 3.3c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L4.12 3.56zM16 11.95c0-.52-.43-.95-.95-.95h-.66l1.49 1.49c.07-.13.12-.28.12-.44v-.1z" />
</symbol>
<!-- from: navigation/arrow_back/materialiconsround/24px.svg -->
<symbol id="icon-back" viewBox="0 0 24 24">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M19 11H7.83l4.88-4.88c.39-.39.39-1.03 0-1.42-.39-.39-1.02-.39-1.41 0l-6.59 6.59c-.39.39-.39 1.02 0 1.41l6.59 6.59c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L7.83 13H19c.55 0 1-.45 1-1s-.45-1-1-1z" />
</symbol>
<!-- from: navigation/arrow_forward/materialiconsround/24px.svg -->
<symbol id="icon-forward" viewBox="0 0 24 24">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M5 13h11.17l-4.88 4.88c-.39.39-.39 1.03 0 1.42.39.39 1.02.39 1.41 0l6.59-6.59c.39-.39.39-1.02 0-1.41l-6.58-6.6c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41L16.17 11H5c-.55 0-1 .45-1 1s.45 1 1 1z" />
</symbol>
<!-- from: navigation/cancel/materialiconsround/24px.svg -->
<symbol id="icon-cancel" viewBox="0 0 24 24">
<path d="M0 0h24v24H0V0z" fill="none" opacity=".87" />
<path d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm4.3 14.3c-.39.39-1.02.39-1.41 0L12 13.41 9.11 16.3c-.39.39-1.02.39-1.41 0-.39-.39-.39-1.02 0-1.41L10.59 12 7.7 9.11c-.39-.39-.39-1.02 0-1.41.39-.39 1.02-.39 1.41 0L12 10.59l2.89-2.89c.39-.39 1.02-.39 1.41 0 .39.39.39 1.02 0 1.41L13.41 12l2.89 2.89c.38.38.38 1.02 0 1.41z" />
</symbol>
<!-- from: navigation/more_vert/materialiconsround/24px.svg -->
<symbol id="icon-more" viewBox="0 0 24 24">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" />
</symbol>
<!-- from: social/groups/materialiconsround/24px.svg -->
<symbol id="icon-groups" viewBox="0 0 24 24">
<rect fill="none" height="24" width="24" />
<g><path d="M12,12.75c1.63,0,3.07,0.39,4.24,0.9c1.08,0.48,1.76,1.56,1.76,2.73L18,17c0,0.55-0.45,1-1,1H7c-0.55,0-1-0.45-1-1l0-0.61 c0-1.18,0.68-2.26,1.76-2.73C8.93,13.14,10.37,12.75,12,12.75z M4,13c1.1,0,2-0.9,2-2c0-1.1-0.9-2-2-2s-2,0.9-2,2 C2,12.1,2.9,13,4,13z M5.13,14.1C4.76,14.04,4.39,14,4,14c-0.99,0-1.93,0.21-2.78,0.58C0.48,14.9,0,15.62,0,16.43L0,17 c0,0.55,0.45,1,1,1l3.5,0v-1.61C4.5,15.56,4.73,14.78,5.13,14.1z M20,13c1.1,0,2-0.9,2-2c0-1.1-0.9-2-2-2s-2,0.9-2,2 C18,12.1,18.9,13,20,13z M24,16.43c0-0.81-0.48-1.53-1.22-1.85C21.93,14.21,20.99,14,20,14c-0.39,0-0.76,0.04-1.13,0.1 c0.4,0.68,0.63,1.46,0.63,2.29V18l3.5,0c0.55,0,1-0.45,1-1L24,16.43z M12,6c1.66,0,3,1.34,3,3c0,1.66-1.34,3-3,3s-3-1.34-3-3 C9,7.34,10.34,6,12,6z" /></g>
</symbol>
<!-- from: social/group_add/materialiconsround/24px.svg -->
<symbol id="icon-create_group" viewBox="0 0 24 24">
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M7 10H5V8c0-.55-.45-1-1-1s-1 .45-1 1v2H1c-.55 0-1 .45-1 1s.45 1 1 1h2v2c0 .55.45 1 1 1s1-.45 1-1v-2h2c.55 0 1-.45 1-1s-.45-1-1-1zm11 1c1.66 0 2.99-1.34 2.99-3S19.66 5 18 5c-.32 0-.63.05-.91.14.57.81.9 1.79.9 2.86s-.34 2.04-.9 2.86c.28.09.59.14.91.14zm-5 0c1.66 0 2.99-1.34 2.99-3S14.66 5 13 5s-3 1.34-3 3 1.34 3 3 3zm0 2c-2 0-6 1-6 3v1c0 .55.45 1 1 1h10c.55 0 1-.45 1-1v-1c0-2-4-3-6-3zm6.62.16c.83.73 1.38 1.66 1.38 2.84v1.5c0 .17-.02.34-.05.5h2.55c.28 0 .5-.22.5-.5V16c0-1.54-2.37-2.49-4.38-2.84z" />
</symbol>
</defs></svg>

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@@ -1,5 +1,5 @@
{% extends "admin_app.html" %}
{% from "library.j2" import box %}
{% from "library.j2" import box, form_button, standard_button %}
{% block content %}
<h1>{% trans user_name=target_user.localpart %}Delete user {{ user_name }}{% endtrans %}</h1>
<div class="form layout-expanded"><form method="POST">
@@ -20,8 +20,8 @@
<p>The user and their data will be deleted irrevocably, permanently and immediately upon pushing thre below button. <strong>There is no way back!</strong></p>
{% endcall %}
<div class="f-bbox">
<a class="button secondary" href="{{ url_for('.users') }}">Back</a>
{{ form.action_delete(class="primary danger") }}
{%- call standard_button("back", url_for(".index"), class="secondary") %}{% trans %}Back{% endtrans %}{% endcall -%}
{%- call form_button("remove", form.action_delete, class="primary danger") %}{% endcall -%}
</div>
</form></div>
{% endblock %}

View File

@@ -1,5 +1,5 @@
{% extends "admin_app.html" %}
{% from "library.j2" import showuri %}
{% from "library.j2" import showuri, form_button, standard_button %}
{% block head_lead %}
{{ super() }}
{% include "copy-snippet.html" %}
@@ -18,11 +18,10 @@
<dd>{% call showuri(invite.landing_page) %}{% endcall %}</dd>
</dl>
<div class="f-bbox">
{#- -#}
{{ form.action_revoke(class="button secondary danger") }}
{#- -#}
<a href="{{ url_for(".invitations") }}" class="button primary">{% trans %}Back{% endtrans %}</a>
{#- -#}
{%- call form_button("remove_link", form.action_revoke, class="secondary danger") %}{% endcall -%}
{%- call standard_button("back", url_for(".invitations"), class="primary") %}
{% trans %}Back{% endtrans %}
{%- endcall %}
</div>
</div>
</form>

View File

@@ -1,4 +1,5 @@
{% extends "admin_app.html" %}
{% from "library.j2" import action_button, icon, clipboard_button, form_button, custom_form_button %}
{% block head_lead %}
{{ super() }}
{% include "copy-snippet.html" %}
@@ -10,7 +11,7 @@
<h2 class="form-title">{% trans %}Create new invitation{% endtrans %}</h2>
<p class="form-descr weak">{% trans %}Create a new invitation link to invite more users to your Snikket instance by clicking the button below.{% endtrans %}</p>
<div class="f-bbox">
{{ form.action_create_invite(class="primary") }}
{%- call form_button("create_link", form.action_create_invite, class="primary") %}{% endcall -%}
</div>
</div>
<h2>{% trans %}Pending invitations{% endtrans %}</h2>
@@ -32,13 +33,15 @@
<td>{{ invite.created_at | format_date }}</td>
<td>{{ (invite.expires - now) | format_timedelta(add_direction=True) }}</td>
<td style="white-space: nowrap;">
{#- -#}
<a href="{{ url_for(".edit_invite", id_=invite.id_) }}" class="button secondary btn-more" title="{% trans %}Show invite details{% endtrans %}"><span class="a11y-only">{% trans %}Show invite details{% endtrans %}</span></a>
{#- -#}
<a class="button primary" title="{% trans %}Copy invite link to clipboard{% endtrans %}" aria-label="{% trans %}Copy invite link to clipboard{% endtrans %}" data-cliptext="{{ invite.landing_page }}" onclick="copy_to_clipboard(this); return false;"><span>📋</span></a>
{#- -#}
<button type="submit" class="secondary danger btn-delete" name="{{ form.action_revoke.name }}" value="{{ invite.id_ }}"><span class="a11y-only">{% trans %}Delete invitation{% endtrans %}</span></button>
{#- -#}
{%- call action_button("more", url_for(".edit_invite", id_=invite.id_), class="secondary") -%}
{% trans %}Show invite details{% endtrans %}
{%- endcall -%}
{%- call clipboard_button(invite.landing_page, class="primary") -%}
{% trans %}Copy invite link to clipboard{% endtrans %}
{%- endcall -%}
{%- call custom_form_button("remove_link", form.action_revoke.name, invite.id_, class="secondary danger", slim=True) -%}
{% trans %}Delete invitation{% endtrans %}
{%- endcall -%}
</td>
</tr>
{% endfor %}

View File

@@ -1,4 +1,5 @@
{% extends "admin_app.html" %}
{% from "library.j2" import icon %}
{% macro value_or_hint(v, caller=None) %}
{%- if v is not none -%}
{{- v -}}
@@ -26,7 +27,7 @@
<td class="collapsible">{% call value_or_hint(user.email) %}{% endcall %}</td>
<td class="collapsible">{% call value_or_hint(user.phone) %}{% endcall %}</td>
<td>
{#- -#}<a class="button secondary btn-delete" href="{{ url_for(".delete_user", localpart=user.localpart) }}" title="{% trans user_name=user.localpart %}Delete user {{ user_name }}{% endtrans %}"><span class="a11y-only">{% trans user_name=user.localpart %}Delete user {{ user_name }}{% endtrans %}</span></a>
{#- -#}<a class="button secondary btn-delete" href="{{ url_for(".delete_user", localpart=user.localpart) }}">{% call icon("remove") %}{% trans user_name=user.localpart %}Delete user {{ user_name }}{% endtrans %}{% endcall %}</a>
{#- -#}
</td>
</tr>

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="{{ lang }}">
<html lang="{{ lang }}" xmlns:xlink="http://www.w3.org/1999/xlink">
<head>
<meta charset="utf-8">
{% block head_lead %}{% endblock %}

View File

@@ -73,6 +73,50 @@ var copy_to_clipboard = function(el) {
});
};
var get_current_icon = function(svg_el) {
var use_el = svg_el.firstChild;
// dont ask. I hate js.
var tmp = "" + use_el.getAttribute("xlink:href");
return tmp.split("#")[1].split("-")[1];
}
var change_icon = function(svg_el, new_icon) {
var use_el = svg_el.firstChild;
// dont ask. I hate js.
var tmp = "" + use_el.getAttribute("xlink:href");
var base_url = tmp.split("#")[0];
use_el.setAttributeNS(
"http://www.w3.org/1999/xlink", "xlink:href",
base_url + "#icon-" + new_icon
);
}
var copy_to_clipboard_btn = function(el) {
var text = el.dataset.cliptext;
if (!text) {
console.error('copy_to_clipboard used on element without text to copy');
}
copyTextToClipboard(text, el, function(success) {
var existing_result_el = document.getElementById("clipboard-result");
if (existing_result_el !== null) {
existing_result_el.parentNode.removeChild(existing_result_el);
}
var icon = "done";
var label = "{% trans %}Copied to clipboard{% endtrans %}";
if (!success) {
icon = "cancel";
label = "{% trans %}Copy operation failed{% endtrans %}";
}
var icon_bak = get_current_icon(el.firstChild);
change_icon(el.firstChild, icon);
setTimeout(function() {
change_icon(el.firstChild, icon_bak);
el.blur();
}, 1500);
});
};
window.addEventListener('load', function() {
document.body.classList.remove("no-copy");
});

View File

@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% from "library.j2" import box %}
{% from "library.j2" import box, icon %}
{% set body_id = "no-lines" %}
{% block head_lead %}
<title>Theme Demo Snikket Web Portal</title>
@@ -238,5 +238,22 @@
{% endfor %}
#}
</form></div>
<h2>Icons</h2>
<p>Icons can be used in a variety of ways. For example in an enumeration:</p>
<ul>
<li>{% call icon("add") %}{% endcall %} Yaycon</li>
<li>{% call icon("remove") %}{% endcall %} Naycon</li>
</ul>
<p>Or, more importantly, on buttons:</p>
<div class="form layout-expanded">
<h3 class="form-title">Do the thing?</h3>
<div class="f-bbox">
{#- -#}
<a class="button secondary">{% call icon("cancel") %}{% endcall %}No</a>
{#- -#}
<a class="button primary">{% call icon("done") %}{% endcall %}Yes</a>
{#- -#}
</div>
</div>
{% endblock %}

View File

@@ -10,16 +10,65 @@
{%- endif -%}
{%- endmacro %}
{% macro clipboard_button(label=_("Copy link"), caller=None) -%}
{%- set text = caller() -%}
<a title="{% trans content=text %}Copy &quot;{{ content }}&quot; to clipboard{% endtrans %}" aria-label="{% trans content=text %}Copy &quot;{{ content }}&quot; to clipboard{% endtrans %}" class="copy-to-clipboard" onclick="copy_to_clipboard(this); return false;" data-cliptext="{{ text }}" href="#">{{ label }}</a>
{%- endmacro %}
{% macro showuri(uri, caller=None) %}
{%- if uri is none -%}
<em>—</em>
{%- else -%}
<div><input type="text" readonly="readonly" value="{{ uri }}"></div>
<div>{% call clipboard_button() %}{{ uri }}{% endcall %}</div>
<div>{% call clipboard_button(uri, show_label=True) %}{% trans %}Copy link{% endtrans %}{% endcall %}</div>
{%- endif -%}
{% endmacro %}
{% macro icon(name, caller=None) -%}
{%- set alt = "" if caller is none else caller() -%}
{%- if alt %}<span class="a11y-only">{{ alt }}</span>{% endif %}<svg class="icon icon-{{ name }}" aria-hidden="true"><use xlink:href="{{ url_for('static', filename='img/icons.svg' )}}#icon-{{ name }}"></use></svg>
{%- endmacro %}
{% macro standard_button(icon_name, href, caller=None, class=None) -%}
{%- set label = caller() -%}
<a href="{{ href }}" class="button {% if class %}{{ class }}{% endif %}" aria-label="{{ a11y }}" title="{{ a11y }}">{% call icon(icon_name) %}{% endcall %}<span>{{ label }}</span></a>
{%- endmacro %}
{% macro form_button(icon_name, button_obj, caller=None, class=None) -%}
<button {% if class %}class="{{ class }}"{% endif %} id="{{ button_obj.id }}" name="{{ button_obj.name }}" type="submit" value="{{ button_obj.data or 'true' }}">{% call icon(icon_name) %}{% endcall %}<span>{{ button_obj.label.text }}</span></button>
{%- endmacro %}
{% macro custom_form_button(icon_name, name, value, caller=None, slim=False, class=None) -%}
{%- set text = caller() -%}
<button
{% if class %}class="{{ class }}"{% endif %}
{% if name %}name="{{ name }}"{% endif %}
type="submit"
{% if value %} value="{{ value }}"{% endif %}
{% if slim %}
aria-label="{{ text }}"
title="{{ text }}"
{% endif %}
>
{%- call icon(icon_name) %}{% endcall -%}
{%- if not slim -%}
<span>{{ text }}</span>
{%- endif -%}
</button>
{%- endmacro %}
{% macro action_button(icon_name, href, caller=None, class=None) -%}
{%- set a11y = caller() -%}
<a href="{{ href }}" class="button {% if class %}{{ class }}{% endif %}" aria-label="{{ a11y }}" title="{{ a11y }}">{% call icon(icon_name) %}{% endcall %}</a>
{%- endmacro %}
{% macro clipboard_button(data, show_label=False, caller=None, class=None) -%}
{%- set label = caller() -%}
<a class="button{% if class %} {{ class }}{% endif %}"
{% if not show_label %}
aria-label="{{ label }}"
title="{{ label }}"
{% endif %}
data-cliptext="{{ data }}"
onclick="copy_to_clipboard_btn(this); return false;">
{%- call icon("copy") %}{% endcall -%}
{%- if show_label %}
<span>{{ label }}</span>
{% endif -%}
</a>
{%- endmacro %}

View File

@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% from "library.j2" import box %}
{% from "library.j2" import box, icon %}
{% set body_id = "login" %}
{% block head_lead %}
<title>{{ _("Snikket Login") }}</title>
@@ -28,7 +28,7 @@
{{ form.password(placeholder=form.password.label.text) }}
</div>
<div class="f-bbox">
<button type="submit" class="primary">{{ _("Log in") }}</button>
<button type="submit" class="primary">{% call icon("login") %}{% endcall %}{{ _("Log in") }}</button>
</div>
</from>
</div></main></div>

View File

@@ -1,4 +1,5 @@
{% extends "app.html" %}
{% from "library.j2" import icon %}
{% block head_lead %}
<title>Snikket Web Portal</title>
{% endblock %}
@@ -9,9 +10,9 @@
{{ form.csrf_token }}
<div class="f-bbox">
{#- -#}
<a href="{{ url_for('user.index') }}" class="button secondary">{% trans %}Back{% endtrans %}</a>
<a href="{{ url_for('user.index') }}" class="button secondary">{% call icon("back") %}{% endcall %}{% trans %}Back{% endtrans %}</a>
{#- -#}
<button type="submit" class="primary">{% trans %}Sign out{% endtrans %}</button>
<button type="submit" class="primary">{% call icon("logout") %}{% endcall %}{% trans %}Sign out{% endtrans %}</button>
{#- -#}
</div>
</form></div>

View File

@@ -1,4 +1,5 @@
{% extends "app.html" %}
{% from "library.j2" import standard_button, custom_form_button %}
{% block head_lead %}
<title>Snikket Web Portal</title>
{% endblock %}
@@ -36,8 +37,10 @@
<p>{% trans %}After changing your password, you will have to enter the new password on all of your devices.{% endtrans %}</p>
</div>
<div class="f-bbox">
<a href="{{ url_for('.index') }}" class="button secondary">{% trans %}Back{% endtrans %}</a>
<button type="submit" class="primary">{% trans %}Change password{% endtrans %}</button>
{%- call standard_button("back", url_for('.index'), class="secondary") %}{% trans %}Back{% endtrans %}{% endcall -%}
{%- call custom_form_button("done", "", "", class="primary") -%}
{% trans %}Change password{% endtrans %}
{%- endcall -%}
</div>
</form></div>
{% endblock %}

View File

@@ -1,4 +1,5 @@
{% extends "app.html" %}
{% from "library.j2" import standard_button, form_button %}
{% block head_lead %}
<title>Snikket Web Portal</title>
{% endblock %}
@@ -21,7 +22,8 @@
{{ form.profile_access_model }}
</div>
<div class="f-bbox">
<a href="{{ url_for('user.index') }}" class="button secondary">{% trans %}Back{% endtrans %}</a><button type="submit" class="primary">{% trans %}Apply{% endtrans %}</button>
{%- call standard_button("back", url_for('.index'), class="secondary") %}{% trans %}Back{% endtrans %}{% endcall -%}
{%- call form_button("done", form.action_save, class="primary") %}{% endcall -%}
</div>
</form></div>
{% endblock %}

View File

@@ -67,6 +67,10 @@ class ProfileForm(flask_wtf.FlaskForm): # type:ignore
choices=_ACCESS_MODEL_CHOICES,
)
action_save = wtforms.SubmitField(
_l("Apply"),
)
@bp.route("/")
@client.require_session()

17
tools/icons.list Normal file
View File

@@ -0,0 +1,17 @@
action/account_circle:profile
action/done:done
action/logout:logout
action/login:login
communication/vpn_key:passwd
content/add_circle_outline:add
content/add_link:create_link
content/create:edit
content/remove_circle_outline:remove
content/content_copy:copy
content/link_off:remove_link
navigation/arrow_back:back
navigation/arrow_forward:forward
navigation/cancel:cancel
navigation/more_vert:more
social/groups:groups
social/group_add:create_group

40
tools/import-icons.sh Normal file
View File

@@ -0,0 +1,40 @@
#!/bin/bash
set -euo pipefail
# usage: import-icons.sh ROOT ICONLIST FLAVOR SVGOUT
#
# positional arguments:
#
# ROOT path to the checkout of https://github.com/google/material-design-icons
# ICONLIST path to the icons.list file in the snikket-web-portal repository
# FLAVOR one of '', 'round', 'sharp', 'outlined', 'twoshade'
# SVGOUT path to the newly created SVG file
root="$1/src"
iconlist_file="$2"
flavor="$3"
output_file="$4"
printf '<svg aria-hidden="true" style="position: absolute; width: 0; height: 0; overflow: hidden;" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">\n<defs>\n' > "$output_file"
printf '<!-- These icons are sourced from Googles Material Icons set,\nlicensed under the terms of the Apache 2.0 License -->\n' >> "$output_file"
printf '<!DOCTYPE html>\n<html><body>'
IFS=$'\n'
while read -r icondef; do
path="$(cut -d':' -f1 <<<"$icondef")"
name="$(cut -d':' -f2 <<<"$icondef")"
src_path="$path/materialicons$flavor"
if [ ! -d "$root/$src_path" ]; then
printf 'warning: %q not found in flavor %q, falling back to default\n' "$path" "$flavor" >&2
src_path="$path/materialicons"
fi
src_svg="$src_path/24px.svg"
if [ ! -f "$root/$src_svg" ]; then
printf 'error: failed to find source file for %q: %s: does not exist\n' "$path" "$src_svg" >&2
fi
printf '<!-- from: %s -->\n' "$src_svg" >> "$output_file"
printf '<symbol id="icon-%s" viewBox="0 0 24 24">\n' "$name" >> "$output_file"
xpath -q -e '/svg/*' "$root/$src_svg" >> "$output_file"
printf '</symbol>\n' >> "$output_file"
printf '<p><svg><use xlink:href="#icon-%s"></use></svg></p>\n' "$name"
done < "$iconlist_file"
printf '</defs></svg>\n' >> "$output_file"