From c1dbec51abee659d0e75a82a9eafe379b3f375c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sch=C3=A4fer?= Date: Mon, 18 Jan 2021 17:25:38 +0100 Subject: [PATCH] Use icons instead of Unicode symbols for buttons Fixes #26. --- snikket_web/scss/app.scss | 46 ++++++---- snikket_web/static/img/icons.svg | 90 ++++++++++++++++++++ snikket_web/templates/admin_delete_user.html | 6 +- snikket_web/templates/admin_edit_invite.html | 11 ++- snikket_web/templates/admin_invites.html | 19 +++-- snikket_web/templates/admin_users.html | 3 +- snikket_web/templates/base.html | 2 +- snikket_web/templates/copy-snippet.html | 44 ++++++++++ snikket_web/templates/demo.html | 19 ++++- snikket_web/templates/library.j2 | 61 +++++++++++-- snikket_web/templates/login.html | 4 +- snikket_web/templates/user_logout.html | 5 +- snikket_web/templates/user_passwd.html | 7 +- snikket_web/templates/user_profile.html | 4 +- snikket_web/user.py | 4 + tools/icons.list | 17 ++++ tools/import-icons.sh | 40 +++++++++ 17 files changed, 333 insertions(+), 49 deletions(-) create mode 100644 snikket_web/static/img/icons.svg create mode 100644 tools/icons.list create mode 100644 tools/import-icons.sh diff --git a/snikket_web/scss/app.scss b/snikket_web/scss/app.scss index 681e1f3..caa2f43 100644 --- a/snikket_web/scss/app.scss +++ b/snikket_web/scss/app.scss @@ -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 { diff --git a/snikket_web/static/img/icons.svg b/snikket_web/static/img/icons.svg new file mode 100644 index 0000000..4419117 --- /dev/null +++ b/snikket_web/static/img/icons.svg @@ -0,0 +1,90 @@ + diff --git a/snikket_web/templates/admin_delete_user.html b/snikket_web/templates/admin_delete_user.html index 8bed98a..0173d26 100644 --- a/snikket_web/templates/admin_delete_user.html +++ b/snikket_web/templates/admin_delete_user.html @@ -1,5 +1,5 @@ {% extends "admin_app.html" %} -{% from "library.j2" import box %} +{% from "library.j2" import box, form_button, standard_button %} {% block content %}

{% trans user_name=target_user.localpart %}Delete user {{ user_name }}{% endtrans %}

@@ -20,8 +20,8 @@

The user and their data will be deleted irrevocably, permanently and immediately upon pushing thre below button. There is no way back!

{% endcall %}
- Back - {{ 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 -%}
{% endblock %} diff --git a/snikket_web/templates/admin_edit_invite.html b/snikket_web/templates/admin_edit_invite.html index 8c8e60e..b1ceaaf 100644 --- a/snikket_web/templates/admin_edit_invite.html +++ b/snikket_web/templates/admin_edit_invite.html @@ -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 @@
{% call showuri(invite.landing_page) %}{% endcall %}
- {#- -#} - {{ form.action_revoke(class="button secondary danger") }} - {#- -#} - {% trans %}Back{% endtrans %} - {#- -#} + {%- 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 %}
diff --git a/snikket_web/templates/admin_invites.html b/snikket_web/templates/admin_invites.html index 1c140c8..38007dc 100644 --- a/snikket_web/templates/admin_invites.html +++ b/snikket_web/templates/admin_invites.html @@ -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 @@

{% trans %}Create new invitation{% endtrans %}

{% trans %}Create a new invitation link to invite more users to your Snikket instance by clicking the button below.{% endtrans %}

- {{ form.action_create_invite(class="primary") }} + {%- call form_button("create_link", form.action_create_invite, class="primary") %}{% endcall -%}

{% trans %}Pending invitations{% endtrans %}

@@ -32,13 +33,15 @@ {{ invite.created_at | format_date }} {{ (invite.expires - now) | format_timedelta(add_direction=True) }} - {#- -#} - {% trans %}Show invite details{% endtrans %} - {#- -#} - 📋 - {#- -#} - - {#- -#} + {%- 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 -%} {% endfor %} diff --git a/snikket_web/templates/admin_users.html b/snikket_web/templates/admin_users.html index 36da7d4..dcc4d63 100644 --- a/snikket_web/templates/admin_users.html +++ b/snikket_web/templates/admin_users.html @@ -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 @@ {% call value_or_hint(user.email) %}{% endcall %} {% call value_or_hint(user.phone) %}{% endcall %} - {#- -#}{% trans user_name=user.localpart %}Delete user {{ user_name }}{% endtrans %} + {#- -#}{% call icon("remove") %}{% trans user_name=user.localpart %}Delete user {{ user_name }}{% endtrans %}{% endcall %} {#- -#} diff --git a/snikket_web/templates/base.html b/snikket_web/templates/base.html index 8510912..769ea82 100644 --- a/snikket_web/templates/base.html +++ b/snikket_web/templates/base.html @@ -1,5 +1,5 @@ - + {% block head_lead %}{% endblock %} diff --git a/snikket_web/templates/copy-snippet.html b/snikket_web/templates/copy-snippet.html index c2f9eda..08975da 100644 --- a/snikket_web/templates/copy-snippet.html +++ b/snikket_web/templates/copy-snippet.html @@ -73,6 +73,50 @@ var copy_to_clipboard = function(el) { }); }; +var get_current_icon = function(svg_el) { + var use_el = svg_el.firstChild; + // don’t 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; + // don’t 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"); }); diff --git a/snikket_web/templates/demo.html b/snikket_web/templates/demo.html index eda56b5..504a6c6 100644 --- a/snikket_web/templates/demo.html +++ b/snikket_web/templates/demo.html @@ -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 %} Theme Demo – Snikket Web Portal @@ -238,5 +238,22 @@ {% endfor %} #} +

Icons

+

Icons can be used in a variety of ways. For example in an enumeration:

+ +

Or, more importantly, on buttons:

+
+

Do the thing?

+ +
{% endblock %} diff --git a/snikket_web/templates/library.j2 b/snikket_web/templates/library.j2 index 643f487..77d0a9e 100644 --- a/snikket_web/templates/library.j2 +++ b/snikket_web/templates/library.j2 @@ -10,16 +10,65 @@ {%- endif -%} {%- endmacro %} -{% macro clipboard_button(label=_("Copy link"), caller=None) -%} -{%- set text = caller() -%} -{{ label }} -{%- endmacro %} - {% macro showuri(uri, caller=None) %} {%- if uri is none -%} {%- else -%}
-
{% call clipboard_button() %}{{ uri }}{% endcall %}
+
{% call clipboard_button(uri, show_label=True) %}{% trans %}Copy link{% endtrans %}{% endcall %}
{%- endif -%} {% endmacro %} + +{% macro icon(name, caller=None) -%} +{%- set alt = "" if caller is none else caller() -%} +{%- if alt %}{{ alt }}{% endif %} +{%- endmacro %} + +{% macro standard_button(icon_name, href, caller=None, class=None) -%} +{%- set label = caller() -%} +{% call icon(icon_name) %}{% endcall %}{{ label }} +{%- endmacro %} + +{% macro form_button(icon_name, button_obj, caller=None, class=None) -%} + +{%- endmacro %} + +{% macro custom_form_button(icon_name, name, value, caller=None, slim=False, class=None) -%} +{%- set text = caller() -%} + +{%- endmacro %} + +{% macro action_button(icon_name, href, caller=None, class=None) -%} +{%- set a11y = caller() -%} +{% call icon(icon_name) %}{% endcall %} +{%- endmacro %} + +{% macro clipboard_button(data, show_label=False, caller=None, class=None) -%} +{%- set label = caller() -%} + + {%- call icon("copy") %}{% endcall -%} +{%- if show_label %} + {{ label }} +{% endif -%} + +{%- endmacro %} diff --git a/snikket_web/templates/login.html b/snikket_web/templates/login.html index 80d31c0..e96ce08 100644 --- a/snikket_web/templates/login.html +++ b/snikket_web/templates/login.html @@ -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 %} {{ _("Snikket Login") }} @@ -28,7 +28,7 @@ {{ form.password(placeholder=form.password.label.text) }}
- +
diff --git a/snikket_web/templates/user_logout.html b/snikket_web/templates/user_logout.html index 94637ca..b33cb7e 100644 --- a/snikket_web/templates/user_logout.html +++ b/snikket_web/templates/user_logout.html @@ -1,4 +1,5 @@ {% extends "app.html" %} +{% from "library.j2" import icon %} {% block head_lead %} Snikket Web Portal {% endblock %} @@ -9,9 +10,9 @@ {{ form.csrf_token }}
{#- -#} - {% trans %}Back{% endtrans %} + {% call icon("back") %}{% endcall %}{% trans %}Back{% endtrans %} {#- -#} - + {#- -#}
diff --git a/snikket_web/templates/user_passwd.html b/snikket_web/templates/user_passwd.html index 5463bff..4f1b342 100644 --- a/snikket_web/templates/user_passwd.html +++ b/snikket_web/templates/user_passwd.html @@ -1,4 +1,5 @@ {% extends "app.html" %} +{% from "library.j2" import standard_button, custom_form_button %} {% block head_lead %} Snikket Web Portal {% endblock %} @@ -36,8 +37,10 @@

{% trans %}After changing your password, you will have to enter the new password on all of your devices.{% endtrans %}

- {% trans %}Back{% endtrans %} - + {%- 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 -%}
{% endblock %} diff --git a/snikket_web/templates/user_profile.html b/snikket_web/templates/user_profile.html index e3467f2..2baf111 100644 --- a/snikket_web/templates/user_profile.html +++ b/snikket_web/templates/user_profile.html @@ -1,4 +1,5 @@ {% extends "app.html" %} +{% from "library.j2" import standard_button, form_button %} {% block head_lead %} Snikket Web Portal {% endblock %} @@ -21,7 +22,8 @@ {{ form.profile_access_model }}
- {% trans %}Back{% endtrans %} + {%- call standard_button("back", url_for('.index'), class="secondary") %}{% trans %}Back{% endtrans %}{% endcall -%} + {%- call form_button("done", form.action_save, class="primary") %}{% endcall -%}
{% endblock %} diff --git a/snikket_web/user.py b/snikket_web/user.py index 0ea074a..20db824 100644 --- a/snikket_web/user.py +++ b/snikket_web/user.py @@ -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() diff --git a/tools/icons.list b/tools/icons.list new file mode 100644 index 0000000..644730a --- /dev/null +++ b/tools/icons.list @@ -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 diff --git a/tools/import-icons.sh b/tools/import-icons.sh new file mode 100644 index 0000000..92c869b --- /dev/null +++ b/tools/import-icons.sh @@ -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 '' + +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 '\n' "$src_svg" >> "$output_file" + printf '\n' "$name" >> "$output_file" + xpath -q -e '/svg/*' "$root/$src_svg" >> "$output_file" + printf '\n' >> "$output_file" + + printf '

\n' "$name" +done < "$iconlist_file" +printf '\n' >> "$output_file"