From 826639058e1de0f910d1a05d55eedbcbe1eb9a9c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin <4533074+alexAubin@users.noreply.github.com> Date: Tue, 30 Apr 2024 22:54:07 +0200 Subject: [PATCH] appgenerator: rework css using tailwind + misc cleanup, wording (#2259) * appgenerator: rework css using tailwind + misc cleanup, wording * appgenerator: tweak gettext + update translations (at least fr) * appgenerator: bump version idk --- tools/app_generator/app.py | 258 +++++---- tools/app_generator/requirements.txt | 1 - tools/app_generator/static/fetch_assets | 6 + tools/app_generator/static/stylesheet.css | 45 -- tools/app_generator/static/tailwind-local.css | 105 ++++ tools/app_generator/static/tailwind.config.js | 17 + .../app_generator/static/yunohost-package.png | Bin 0 -> 24647 bytes tools/app_generator/templates/base.html | 17 + tools/app_generator/templates/index.html | 140 ++--- tools/app_generator/templates/wtf.html | 213 ++++++++ .../translations/en/LC_MESSAGES/messages.po | 516 ++++++++++++------ .../translations/es/LC_MESSAGES/messages.po | 516 ++++++++++++------ .../translations/fr/LC_MESSAGES/messages.po | 430 ++++++++------- 13 files changed, 1432 insertions(+), 832 deletions(-) create mode 100644 tools/app_generator/static/fetch_assets delete mode 100644 tools/app_generator/static/stylesheet.css create mode 100644 tools/app_generator/static/tailwind-local.css create mode 100644 tools/app_generator/static/tailwind.config.js create mode 100644 tools/app_generator/static/yunohost-package.png create mode 100644 tools/app_generator/templates/base.html create mode 100644 tools/app_generator/templates/wtf.html diff --git a/tools/app_generator/app.py b/tools/app_generator/app.py index a4a204f9..9dec9ae2 100644 --- a/tools/app_generator/app.py +++ b/tools/app_generator/app.py @@ -16,7 +16,6 @@ from markupsafe import Markup # No longer imported from Flask # Form libraries from flask_wtf import FlaskForm -from flask_bootstrap import Bootstrap from wtforms import ( StringField, RadioField, @@ -37,7 +36,7 @@ from wtforms.validators import ( # Translations from flask_babel import Babel -from flask_babel import gettext, lazy_gettext +from flask_babel import lazy_gettext as _ from flask import redirect, request, make_response # Language swap by redirecting @@ -51,12 +50,11 @@ from urllib import parse from secrets import token_urlsafe #### GLOBAL VARIABLES -YOLOGEN_VERSION = "0.9.2.1" +YOLOGEN_VERSION = "0.10" GENERATOR_DICT = {"GENERATOR_VERSION": YOLOGEN_VERSION} #### Create FLASK and Jinja Environments app = Flask(__name__) -Bootstrap(app) app.config["SECRET_KEY"] = token_urlsafe(16) # Necessary for the form CORS cors = CORS(app) @@ -67,7 +65,7 @@ BABEL_TRANSLATION_DIRECTORIES = "translations" babel = Babel() -LANGUAGES = {"en": gettext("English"), "fr": gettext("French")} +LANGUAGES = {"en": _("English"), "fr": _("French")} @app.context_processor @@ -126,7 +124,7 @@ def markdown_file_to_html_string(file): # Use it in the HTML with {{ form_field(main_form.generator_language) }} class Translations(FlaskForm): generator_language = SelectField( - gettext("Select language"), + _("Select language"), choices=[("none", "")] + [language for language in LANGUAGES.items()], default=["en"], id="selectLanguage", @@ -136,8 +134,8 @@ class Translations(FlaskForm): class GeneralInfos(FlaskForm): app_id = StringField( - Markup(lazy_gettext("Application identifier (id)")), - description=lazy_gettext("Small caps and without spaces"), + Markup(_("Application identifier (id)")), + description=_("Small caps and without spaces"), validators=[DataRequired(), Regexp("[a-z_1-9]+.*(? \ -Do not give the software name at the beginning, as it will be integrated an 'Overview' subpart""" + _( + """doc/DESCRIPTION.md: A comprehensive presentation of the app, possibly listing the main features, possible warnings and specific details on its functioning in Yunohost (e.g. warning about integration issues).""" ) ), validators=[Optional()], render_kw={ - "class": "form-control", "spellcheck": "false", }, ) pre_install = TextAreaField( - lazy_gettext("Type the PRE_INSTALL.md file content"), + _("doc/PRE_INSTALL.md: important info to be shown to the admin before installing the app"), + description=_("Leave empty if not relevant"), validators=[Optional()], render_kw={ - "class": "form-control", "spellcheck": "false", }, ) post_install = TextAreaField( - lazy_gettext("Type the POST_INSTALL.md file content"), + _("doc/POST_INSTALL.md: important info to be shown to the admin after installing the app"), + description=_("Leave empty if not relevant"), validators=[Optional()], render_kw={ - "class": "form-control", "spellcheck": "false", }, ) pre_upgrade = TextAreaField( - lazy_gettext("Type the PRE_UPGRADE.md file content"), + _("doc/PRE_UPGRADE.md: important info to be shown to the admin before upgrading the app"), + description=_("Leave empty if not relevant"), validators=[Optional()], render_kw={ - "class": "form-control", "spellcheck": "false", }, ) post_upgrade = TextAreaField( - lazy_gettext("Type the POST_UPGRADE.md file content"), + _("doc/POST_UPGRADE.md: important info to be shown to the admin after upgrading the app"), + description=_("Leave empty if not relevant"), validators=[Optional()], render_kw={ - "class": "form-control", "spellcheck": "false", }, ) admin = TextAreaField( - lazy_gettext("Type the ADMIN.md file content"), + _("doc/ADMIN.md: general tips on how to administrate this app"), + description=_("Leave empty if not relevant"), validators=[Optional()], render_kw={ - "class": "form-control", "spellcheck": "false", }, ) @@ -584,20 +580,20 @@ Do not give the software name at the beginning, as it will be integrated an 'Ove class MoreAdvanced(FlaskForm): enable_change_url = BooleanField( - lazy_gettext("Handle app install URL change (change_url script)"), + _("Handle app install URL change (change_url script)"), default=True, render_kw={ - "title": lazy_gettext( + "title": _( "Should changing the app URL be allowed ? (change_url change)" ) }, ) use_logrotate = BooleanField( - lazy_gettext("Use logrotate for the app logs"), + _("Use logrotate for the app logs"), default=True, render_kw={ - "title": lazy_gettext( + "title": _( "If the app generates logs, this option permit to handle their archival. Recommended." ) }, @@ -605,23 +601,23 @@ class MoreAdvanced(FlaskForm): # TODO : specify custom log file # custom_log_file = "/var/log/$app/$app.log" "/var/log/nginx/${domain}-error.log" use_fail2ban = BooleanField( - lazy_gettext( + _( "Protect the application against brute force attacks (via fail2ban)" ), default=False, render_kw={ - "title": lazy_gettext( + "title": _( "If the app generates failed connexions logs, this option allows to automatically banish the related IP after a certain number of failed password tries. Recommended." ) }, ) use_cron = BooleanField( - lazy_gettext("Add a CRON task for this application"), - description=lazy_gettext("Corresponds to some app periodic operations"), + _("Add a CRON task for this application"), + description=_("Corresponds to some app periodic operations"), default=False, ) cron_config_file = TextAreaField( - lazy_gettext("Type the CRON file content"), + _("Type the CRON file content"), validators=[Optional()], render_kw={ "class": "form-control", @@ -630,13 +626,13 @@ class MoreAdvanced(FlaskForm): ) fail2ban_regex = StringField( - lazy_gettext("Regular expression for fail2ban"), + _("Regular expression for fail2ban"), # Regex to match into the log for a failed login validators=[Optional()], render_kw={ - "placeholder": lazy_gettext("A regular expression"), + "placeholder": _("A regular expression"), "class": "form-control", - "title": lazy_gettext( + "title": _( "Regular expression to check in the log file to activate failban (search for a line that indicates a credentials error)." ), }, @@ -660,25 +656,25 @@ class GeneratorForm( csrf = False generator_mode = SelectField( - lazy_gettext("Generator mode"), - description=lazy_gettext( + _("Generator mode"), + description=_( "In tutorial version, the generated app will contain additionnal comments to ease the understanding. In steamlined version, the generated app will only contain the necessary minimum." ), choices=[ - ("simple", lazy_gettext("Streamlined version")), - ("tutorial", lazy_gettext("Tutorial version")), + ("simple", _("Streamlined version")), + ("tutorial", _("Tutorial version")), ], default="true", validators=[DataRequired()], ) - submit_preview = SubmitField(lazy_gettext("Previsualise")) - submit_download = SubmitField(lazy_gettext("Download the .zip")) + submit_preview = SubmitField(_("Previsualise")) + submit_download = SubmitField(_("Download the .zip")) submit_demo = SubmitField( - lazy_gettext("Fill with demo values"), + _("Fill with demo values"), render_kw={ "onclick": "fillFormWithDefaultValues()", - "title": lazy_gettext( + "title": _( "Generate a complete and functionnal minimalistic app that you can iterate from" ), }, diff --git a/tools/app_generator/requirements.txt b/tools/app_generator/requirements.txt index 665d10bd..fa96e391 100644 --- a/tools/app_generator/requirements.txt +++ b/tools/app_generator/requirements.txt @@ -4,7 +4,6 @@ click==8.1.7 dominate==2.8.0 Flask==3.0.0 flask_babel~=4.0.0 -Flask-Bootstrap==3.3.7.1 Flask-Cors==4.0.0 Flask-Misaka==1.0.0 Flask-WTF==1.2.1 diff --git a/tools/app_generator/static/fetch_assets b/tools/app_generator/static/fetch_assets new file mode 100644 index 00000000..b1a2689b --- /dev/null +++ b/tools/app_generator/static/fetch_assets @@ -0,0 +1,6 @@ +# Download standalone tailwind to compile what we need +wget https://github.com/tailwindlabs/tailwindcss/releases/download/v3.3.3/tailwindcss-linux-x64 +chmod +x tailwindcss-linux-x64 +./tailwindcss-linux-x64 --input tailwind-local.css --output tailwind.css --minify + + diff --git a/tools/app_generator/static/stylesheet.css b/tools/app_generator/static/stylesheet.css deleted file mode 100644 index d21d0762..00000000 --- a/tools/app_generator/static/stylesheet.css +++ /dev/null @@ -1,45 +0,0 @@ -.form-horizontal .form-group { - margin-left: 0; - margin-right: 0; -} - -h2 { - font-weight: bold; - font-size: 1.3em !important; -} - -.checkbox { - margin-bottom: 15px !important; -} - -.checkbox label { - font-weight: bold; -} - - - -.active, .collapse-button:hover { - background-color: #318ddc; -} - -.collapse-title:after { - content: '\002B'; - color: white; - font-weight: bold; - float: right; - margin-left: 5px; -} - -.expanded .collapse-title::after { - content: "\2212"; -} - -.collapsed { - padding: 0px 15px 0px 15px; -} - -.collapsible { - max-height: 0px; - overflow: hidden; - transition: max-height 0.2s ease-out; -} \ No newline at end of file diff --git a/tools/app_generator/static/tailwind-local.css b/tools/app_generator/static/tailwind-local.css new file mode 100644 index 00000000..a81992f2 --- /dev/null +++ b/tools/app_generator/static/tailwind-local.css @@ -0,0 +1,105 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer utilities { + + body { + @apply text-gray-800; + } + + h1 { + @apply text-2xl font-bold; + } + + h2 { + @apply text-xl font-bold; + } + + h3 { + @apply text-lg font-bold; + } + + .hide { + display: none; + } + + .btn { + @apply text-sm font-medium rounded-md px-4 py-2 transition bg-gray-200 m-1; + } + .btn-sm { + @apply text-xs font-medium rounded-md px-2 py-2 transition; + } + .btn-success { + @apply text-white bg-green-500 hover:bg-green-700; + } + .btn-primary { + @apply text-white bg-blue-500 hover:bg-blue-700; + } + .btn-link { + @apply bg-gray-400 hover:bg-gray-200; + } + .panel { + @apply block rounded-lg border border-gray-400 mb-2; + } + .panel-heading { + @apply text-white bg-blue-500 hover:bg-blue-700 p-2 font-bold; + } + .panel-body { + @apply p-2; + } + + .alert-info { + @apply text-blue-900 border-blue-900 bg-blue-200 rounded-lg p-4; + } + + .active, .collapse-button:hover { + background-color: #318ddc; + } + + .collapse-title:after { + content: '\002B'; + color: white; + font-weight: bold; + float: right; + margin-left: 5px; + } + + .expanded .collapse-title::after { + content: "\2212"; + } + + .collapsed { + padding: 0px 15px 0px 15px; + } + + .collapsible { + max-height: 0px; + overflow: hidden; + transition: max-height 0.2s ease-out; + } + + label { + @apply font-bold; + } + + input { + @apply rounded-lg; + } + + .form-group, .checkbox { + @apply px-2 py-4; + } + + .form-control { + @apply block w-full rounded-md border-gray-300; + } + + .help-block { + @apply text-gray-500 text-sm; + } + + .tip { + @apply italic p-2; + } +} diff --git a/tools/app_generator/static/tailwind.config.js b/tools/app_generator/static/tailwind.config.js new file mode 100644 index 00000000..c5b0b2fc --- /dev/null +++ b/tools/app_generator/static/tailwind.config.js @@ -0,0 +1,17 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['../templates/*.html'], + theme: { + extend: {}, + }, + plugins: [ + require('@tailwindcss/forms'), + ], + safelist: [ + 'safelisted', + { + pattern: /^(text-[a-z]+-600|border-[a-z]+-400)$/, + }, + ] +} + diff --git a/tools/app_generator/static/yunohost-package.png b/tools/app_generator/static/yunohost-package.png new file mode 100644 index 0000000000000000000000000000000000000000..61434f40dc7127bc421768a1090cf6fcf9c077ca GIT binary patch literal 24647 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 + + + + {{ gettext("YunoHost app generator") }} + + + + + + +
+ {% block main -%} + {%- endblock main %} +
+ + diff --git a/tools/app_generator/templates/index.html b/tools/app_generator/templates/index.html index db8d8fe2..62e95eff 100644 --- a/tools/app_generator/templates/index.html +++ b/tools/app_generator/templates/index.html @@ -1,4 +1,4 @@ -{% import "bootstrap/wtf.html" as wtf %} +{% import "wtf.html" as wtf %} {% macro form_field(field, form_type="basic", @@ -19,64 +19,38 @@ {% endmacro %} -{% extends "bootstrap/base.html" %} +{% extends "base.html" %} -{% block title %} -YunoHost app generator -{% endblock %} - -{% block styles %} -{{super()}} - -{% endblock %} - - -{% block content %} -
-

{{ gettext("Yunohost application generation form") }}

-

Version: {{ generator_info['GENERATOR_VERSION'] }}

- - - - - - -
- {% for lang in AVAILABLE_LANGUAGES.items() %} - - {% endfor %} +{% block main %} +
+ YunoHost application logo +

+ {{ _("Yunohost application generation form") }} +

+

Version: {{ generator_info['GENERATOR_VERSION'] }}

+ +
+ {% for lang in AVAILABLE_LANGUAGES.items() %} + + {% endfor %} +
+
- {{ main_form.hidden_tag() }} -
+
{{ wtf.form_errors(main_form, hiddens="only") }}
{{ form_field(main_form.generator_mode) }} - -
+

{{ gettext("1/9 - General information") }}

@@ -91,12 +65,12 @@ function changeLanguage(lang) {
-
+
-

{{ gettext("2/9 - Informations about the application") }}

+

{{ gettext("2/9 - Upstream information") }}