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;
}
}
.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 {
@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 {
@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 {
@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 {
@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 {
@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
$gruvbox-light: (
@ -130,3 +133,6 @@ $gruvbox-light: (
body.theme-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 {
@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
$solarized-light: (
@ -84,3 +87,6 @@ $solarized-light: (
body.theme-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,
));
}
@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 {
@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:
"""Set up application routes."""
# pylint: disable=too-many-statements
config.add_route("home", "/")
config.add_route("search", "/search")
@ -99,6 +100,9 @@ def includeme(config: Configurator) -> None:
config.add_route(
"settings_password_change", "/password_change", factory=LoggedInFactory
)
config.add_route(
"settings_theme_previews", "/theme_previews", factory=LoggedInFactory
)
config.add_route("bookmarks", "/bookmarks", factory=LoggedInFactory)

2
tildes/tildes/templates/settings.jinja2

@ -34,6 +34,8 @@
{% endfor %}
</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">
Set as account default
</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."""
from io import BytesIO
from typing import List, Optional
import pyotp
import qrcode
@ -13,7 +14,13 @@ from pyramid.response import Response
from pyramid.view import view_config
from webargs.pyramidparser import use_kwargs
from tildes.enums import CommentLabelOption, CommentTreeSortOption
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 (
BIO_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")
def get_settings(request: Request) -> dict:
"""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"
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)
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