Browse Source

Add a Theme Preview page in Settings

merge-requests/85/head
deing 5 years ago
committed by Deimos
parent
commit
810a54076e
  1. 16
      tildes/scss/modules/_settings.scss
  2. 3
      tildes/scss/themes/_atom_one_dark.scss
  3. 3
      tildes/scss/themes/_black.scss
  4. 3
      tildes/scss/themes/_default.scss
  5. 3
      tildes/scss/themes/_dracula.scss
  6. 6
      tildes/scss/themes/_gruvbox.scss
  7. 6
      tildes/scss/themes/_solarized.scss
  8. 10
      tildes/scss/themes/_theme_base.scss
  9. 3
      tildes/scss/themes/_zenburn.scss
  10. 4
      tildes/tildes/routes.py
  11. 2
      tildes/tildes/templates/settings.jinja2
  12. 77
      tildes/tildes/templates/settings_theme_previews.jinja2
  13. 137
      tildes/tildes/views/settings.py

16
tildes/scss/modules/_settings.scss

@ -28,3 +28,19 @@
border-color: inherit; border-color: inherit;
} }
} }
.theme-preview-blocks {
display: flex;
max-width: 40rem;
flex-wrap: wrap;
// The colors are assigned by a mixin from _theme_base.scss called in each theme file
span {
display: block;
flex-grow: 1;
min-width: 6rem;
text-align: center;
padding: 1rem;
font-weight: bold;
margin: .3rem;
}
}

3
tildes/scss/themes/_atom_one_dark.scss

@ -47,3 +47,6 @@ $theme-atom-one-dark: (
body.theme-atom-one-dark { body.theme-atom-one-dark {
@include use-theme($theme-atom-one-dark); @include use-theme($theme-atom-one-dark);
} }
body {
@include theme-preview-block($theme-atom-one-dark, "atom-one-dark");
}

3
tildes/scss/themes/_black.scss

@ -26,3 +26,6 @@ $theme-black: (
body.theme-black { body.theme-black {
@include use-theme($theme-black); @include use-theme($theme-black);
} }
body {
@include theme-preview-block($theme-black, "black");
}

3
tildes/scss/themes/_default.scss

@ -31,3 +31,6 @@ $default-theme: (
body { body {
@include use-theme($default-theme); @include use-theme($default-theme);
} }
body {
@include theme-preview-block($default-theme, "white");
}

3
tildes/scss/themes/_dracula.scss

@ -49,3 +49,6 @@ $theme-dracula: (
body.theme-dracula { body.theme-dracula {
@include use-theme($theme-dracula); @include use-theme($theme-dracula);
} }
body {
@include theme-preview-block($theme-dracula, "dracula");
}

6
tildes/scss/themes/_gruvbox.scss

@ -110,6 +110,9 @@ $gruvbox-dark: (
body.theme-gruvbox-dark { body.theme-gruvbox-dark {
@include use-theme(map-merge($gruvbox-base, $gruvbox-dark)); @include use-theme(map-merge($gruvbox-base, $gruvbox-dark));
} }
body {
@include theme-preview-block(map-merge($gruvbox-base, $gruvbox-dark), "gruvbox-dark");
}
// Light theme definition // Light theme definition
$gruvbox-light: ( $gruvbox-light: (
@ -130,3 +133,6 @@ $gruvbox-light: (
body.theme-gruvbox-light { body.theme-gruvbox-light {
@include use-theme(map-merge($gruvbox-base, $gruvbox-light)); @include use-theme(map-merge($gruvbox-base, $gruvbox-light));
} }
body {
@include theme-preview-block(map-merge($gruvbox-base, $gruvbox-light), "gruvbox-light");
}

6
tildes/scss/themes/_solarized.scss

@ -67,6 +67,9 @@ $solarized-dark: (
body.theme-solarized-dark { body.theme-solarized-dark {
@include use-theme(map-merge($solarized-base, $solarized-dark)); @include use-theme(map-merge($solarized-base, $solarized-dark));
} }
body {
@include theme-preview-block(map-merge($solarized-base, $solarized-dark), "solarized-dark");
}
// Light theme definition // Light theme definition
$solarized-light: ( $solarized-light: (
@ -84,3 +87,6 @@ $solarized-light: (
body.theme-solarized-light { body.theme-solarized-light {
@include use-theme(map-merge($solarized-base, $solarized-light)); @include use-theme(map-merge($solarized-base, $solarized-light));
} }
body {
@include theme-preview-block(map-merge($solarized-base, $solarized-light), "solarized-light");
}

10
tildes/scss/themes/_theme_base.scss

@ -917,3 +917,13 @@
"topic-tag-spoiler": $topic-tag-spoiler, "topic-tag-spoiler": $topic-tag-spoiler,
)); ));
} }
@mixin theme-preview-block($theme, $name) {
.theme-preview-blocks {
.theme-preview-block-#{$name} {
background-color: map-get($theme, "background-primary");
color: map-get($theme, "foreground-primary");
border: 1px solid map-get($theme, "background-secondary");
}
}
}

3
tildes/scss/themes/_zenburn.scss

@ -39,3 +39,6 @@ $theme-zenburn: (
body.theme-zenburn { body.theme-zenburn {
@include use-theme($theme-zenburn); @include use-theme($theme-zenburn);
} }
body {
@include theme-preview-block($theme-zenburn, "zenburn");
}

4
tildes/tildes/routes.py

@ -18,6 +18,7 @@ from tildes.resources.user import user_by_username
def includeme(config: Configurator) -> None: def includeme(config: Configurator) -> None:
"""Set up application routes.""" """Set up application routes."""
# pylint: disable=too-many-statements
config.add_route("home", "/") config.add_route("home", "/")
config.add_route("search", "/search") config.add_route("search", "/search")
@ -99,6 +100,9 @@ def includeme(config: Configurator) -> None:
config.add_route( config.add_route(
"settings_password_change", "/password_change", factory=LoggedInFactory "settings_password_change", "/password_change", factory=LoggedInFactory
) )
config.add_route(
"settings_theme_previews", "/theme_previews", factory=LoggedInFactory
)
config.add_route("bookmarks", "/bookmarks", factory=LoggedInFactory) config.add_route("bookmarks", "/bookmarks", factory=LoggedInFactory)

2
tildes/tildes/templates/settings.jinja2

@ -34,6 +34,8 @@
{% endfor %} {% endfor %}
</select> </select>
<a class="btn btn-primary" href="{{ request.route_url('settings_theme_previews') }}">Preview themes</a>
<button id="button-set-default-theme" class="btn btn-link d-none"> <button id="button-set-default-theme" class="btn btn-link d-none">
Set as account default Set as account default
</button> </button>

77
tildes/tildes/templates/settings_theme_previews.jinja2

@ -0,0 +1,77 @@
{# Copyright (c) 2018 Tildes contributors <code@tildes.net> #}
{# SPDX-License-Identifier: AGPL-3.0-or-later #}
{% from 'macros/comments.jinja2' import render_comment_tree, comment_label_options_template, comment_reply_template with context %}
{% from 'macros/topics.jinja2' import render_topic_for_listing with context %}
{% extends 'base_settings.jinja2' %}
{% block main_classes %}{% endblock %}
{% block title %}Theme previews{% endblock %}
{% block main_heading %}Theme previews{% endblock %}
{% block settings %}
<label for="theme">Choose a display theme:</label>
<form
class="form-inline"
name="account-default-theme"
data-ic-patch-to="{{ request.route_url(
'ic_user',
username=request.user.username
) }}"
>
<select class="form-select col-8 col-sm-12" name="theme" id="theme" data-js-theme-selector>
{% for theme, description in theme_options.items() %}
<option
value="{{ theme }}"
{{ 'selected' if theme == current_theme else '' }}
>
{{ description }}
</option>
{% endfor %}
</select>
<a class="btn btn-primary" href="{{ request.route_url('settings') }}">Return to Settings</a>
<button id="button-set-default-theme" class="btn btn-link d-none">
Set as account default
</button>
</form>
<section class="settings-section">
<h2>Quick overview</h2>
<div class="theme-preview-blocks">
{% for theme, description in theme_options.items() %}
{# The theme dict includes various (default) texts in the descriptions, cut them off #}
{# Also, replace all spaces with NBSPs so the theme names don't get linewrapped #}
<span class="theme-preview-block-{{ theme }}">{{ description.split(" (")[0]|replace(" ", "\u00a0") }}</span>
{% endfor %}
</div>
</section>
<section class="settings-section">
<h2>Topic listings</h2>
<ol class="topic-listing">
{% for fake_topic in fake_topics %}
<li>
{{ render_topic_for_listing(fake_topic, show_group=true) }}
</li>
{% endfor %}
</ol>
</section>
<section class="settings-section">
<h2>Comments</h2>
<ol class="comment-tree" id="comments">
{{ render_comment_tree(fake_comment_tree, mark_newer_than=last_visit, is_individual_comment=False) }}
</ol>
</section>
{% endblock %}
{% block templates %}
{% if request.user %}
{{ comment_reply_template() }}
{{ comment_label_options_template(comment_label_options) }}
{% endif %}
{% endblock %}

137
tildes/tildes/views/settings.py

@ -4,6 +4,7 @@
"""Views related to user settings.""" """Views related to user settings."""
from io import BytesIO from io import BytesIO
from typing import List, Optional
import pyotp import pyotp
import qrcode import qrcode
@ -13,7 +14,13 @@ from pyramid.response import Response
from pyramid.view import view_config from pyramid.view import view_config
from webargs.pyramidparser import use_kwargs from webargs.pyramidparser import use_kwargs
from tildes.enums import CommentLabelOption, CommentTreeSortOption
from tildes.lib.string import separate_string from tildes.lib.string import separate_string
from tildes.lib.datetime import utc_from_timestamp, utc_now
from tildes.models.comment import Comment, CommentLabel, CommentTree
from tildes.models.group import Group
from tildes.models.topic import Topic
from tildes.models.user import User
from tildes.schemas.user import ( from tildes.schemas.user import (
BIO_MAX_LENGTH, BIO_MAX_LENGTH,
EMAIL_ADDRESS_NOTE_MAX_LENGTH, EMAIL_ADDRESS_NOTE_MAX_LENGTH,
@ -27,6 +34,11 @@ PASSWORD_FIELD = UserSchema(only=("password",)).fields["password"]
@view_config(route_name="settings", renderer="settings.jinja2") @view_config(route_name="settings", renderer="settings.jinja2")
def get_settings(request: Request) -> dict: def get_settings(request: Request) -> dict:
"""Generate the user settings page.""" """Generate the user settings page."""
return generate_theme_chooser_dict(request)
def generate_theme_chooser_dict(request: Request) -> dict:
"""Generate the partial response dict necessary for the settings theme selector."""
site_default_theme = "white" site_default_theme = "white"
user_default_theme = request.user.theme_default or site_default_theme user_default_theme = request.user.theme_default or site_default_theme
@ -144,3 +156,128 @@ def post_settings_password_change(
request.user.change_password(old_password, new_password) request.user.change_password(old_password, new_password)
return Response("Your password has been updated") return Response("Your password has been updated")
@view_config(
route_name="settings_theme_previews", renderer="settings_theme_previews.jinja2"
)
def get_settings_theme_previews(request: Request) -> dict:
"""Generate the theme preview page.
On the site, the following data must not point to real data users could
inadvertently affect with the demo widgets:
- The user @Tildes
- The group ~groupname
- Topic ID 42_000_000_000
- Comment IDs 42_000_000_000 through 42_000_000_003
"""
fake_old_timestamp = utc_from_timestamp(int(utc_now().timestamp() - 60 * 60 * 24))
fake_last_visit_timestamp = utc_from_timestamp(
int(utc_now().timestamp() - 60 * 60 * 12)
)
fake_user = User("Tildes", "a_very_safe_password")
fake_user.user_id = 0
fake_user_not_op = User("Tildes", "another_very_safe_password")
fake_user_not_op.user_id = -1
fake_group = Group("groupname")
fake_group.is_user_treated_as_topic_source = False
fake_topics: List[Topic] = [
Topic.create_link_topic(
fake_group, fake_user, "Example Link Topic", "https://tildes.net/"
),
Topic.create_text_topic(
fake_group, fake_user, "Example Text Topic", "empty string"
),
]
for fake_topic in fake_topics:
fake_topic.topic_id = 42_000_000_000
fake_topic.tags = ["a tag", "another tag"]
fake_topic.created_time = utc_now()
fake_topic.group = fake_group
fake_topic.num_comments = 0
fake_topic.num_votes = 0
fake_topic.content_metadata = {
"excerpt": """Lorem ipsum dolor sit amet,
consectetur adipiscing elit. Nunc auctor purus at diam tempor,
id viverra nunc vulputate.""",
"word_count": 42,
}
def make_comment(
markdown: str, comment_id: int, parent_id: Optional[int], is_op: bool
) -> Comment:
"""Create a fake comment with enough data to make the template render fine."""
fake_comment = Comment(fake_topics[0], fake_user_not_op, markdown)
fake_comment.comment_id = comment_id
if parent_id:
fake_comment.parent_comment_id = parent_id
fake_comment.created_time = fake_old_timestamp
fake_comment.num_votes = 0
if is_op:
fake_comment.user = fake_user
return fake_comment
fake_comments: List[Comment] = []
fake_comments.append(
make_comment(
"""This is a regular comment, written by yourself. \
It has **formatting** and a [link](https://tildes.net).""",
42_000_000_000,
None,
False,
)
)
fake_comments[-1].user = request.user
fake_comments.append(
make_comment(
"""This is a reply written by the topic's OP. \
It's new and has the *Mark New Comments* stripe on its left, \
even if you didn't enable that feature.""",
42_000_000_001,
42_000_000_000,
True,
)
)
fake_comments[-1].created_time = utc_now()
fake_comments.append(
make_comment(
"""This reply is Exemplary. It also has a blockquote:\
\n> Hello World!""",
42_000_000_002,
42_000_000_000,
False,
)
)
fake_comments[-1].labels.append(
CommentLabel(fake_comments[-1], fake_user, CommentLabelOption.EXEMPLARY, 1.0)
)
fake_comments.append(
make_comment(
"""This is a regular reply with a code block in it:\
\n```js\
\nfunction foo() {\
\n ['1', '2', '3'].map(parseInt);\
\n}\
\n```""",
42_000_000_003,
42_000_000_000,
False,
)
)
fake_tree = CommentTree(
fake_comments, CommentTreeSortOption.RELEVANCE, request.user
)
return {
**generate_theme_chooser_dict(request),
"fake_topics": fake_topics,
"fake_comment_tree": fake_tree,
"comment_label_options": [label for label in CommentLabelOption],
"last_visit": fake_last_visit_timestamp,
}
Loading…
Cancel
Save