Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: POC: Configure theme colors in backend #666

Closed
wants to merge 30 commits into from

Conversation

drikusroor
Copy link
Contributor

@drikusroor drikusroor commented Dec 17, 2023

This PR contains a POC (proof of concept) for we could approach the feature of a configurable theme in Django's backend. Basically it's a new model (ThemeConfig), with admin form pages (/admin/theme/themeconfigs), and an output page (/theme/theme.css).

The ThemeConfig model:

class ThemeConfig(models.Model):
    css_variables = models.TextField()  # Store all hard-coded variables as text
    additional_variables = models.TextField(blank=True)
    global_css = models.TextField(blank=True)
  • The css_variables column contains css variables (key + value, e.g. --teal: #1b949d;) stored as json.
  • I've added additional_variables so that people can easily add custom values if needed. Maybe we don't need this column.
  • With global_css, the user can override anything in the css of the frontend application by (if it wants to) just doing stuff like
body {
  background: red;
}

The form

The form allows you to edit the pre-defined css variables (taken from the original variables.scss from the frontend.

First, it allows you to set the colors of the "color" css variables (teal, green, etc.). Then, you can use those colors in the "design token" css variables (primary, secondary, etc.), or pick a custom color for a design token.

The additional_variables and global_css are just free form textarea.

There is some validation on the css_variables that checks whether there are valid hex colors and valid variable names. The free form textareas don't have strict validation.

afbeelding

The output

The output theme file, which you can load through /theme/theme.css, has the following structure:

/* Theme configuration */

:root {

  /* Color & Design Token Configuration */
  --teal: #1b949d;
  --yellow: #cdbf1f;
  --pink: #ff3eed;
  --red: #ff0009;
  --blue: #0007de;
  --green: #36f424;
  --indigo: #00fcff;
  --gray: #aaaaaa;
  --gray_900: #4b4b4b;
  --black: #000000;
  --primary: var(--teal);
  --success: var(--green);
  --positive: var(--green);
  --negative: var(--red);
  --secondary: var(--yellow);
  --info: var(--gray);

  /* Additional Variables */
  --aqua: #00ffff;
}

/* Global CSS */
{ background-color: var(--aqua); }

We can then import this stylesheet into the React application and use the css variables that are initialized and assigned. As I was working on this, I realized that the scss configuration that we currently have is not fully compatible with using css variables as we use some scss functions under the hood in build time (darken, lighten, making colors transparent), which we cannot do run-time. So we have to think about providing these colors to the frontend. There are several options:

  • Use a python library like colour to auto-generate darker, lighter and semi-transparent versions of all the colors in the theme file. This will then generate new colors under the hood for --teal-light or --negative-dark, etc. If we generate these during the submit of a theme instead of during the fetch of /theme/theme.css, we shouldn't run into performance issues. We can also think about caching this theme file in memory or something. It's not big and won't change often probably.
  • Use javascript functions at the initialization that generate the darker, lighter, transparenter versions that we need and inject them into the html. Basically the frontend version of the previous suggestion.
  • Use css filters and opacity to simulate the same effect of making colors darker and lighter and more transparent. This is pretty easy, but the result might not be the nicest.

@drikusroor drikusroor self-assigned this Dec 17, 2023
@BeritJanssen
Copy link
Collaborator

BeritJanssen commented Dec 19, 2023

Nice! I assume this is aimed towards #293 ? In my vision, we'd just have different stylesheets live next to each other in the frontend (and control usage through env variables), or ask other groups to fork and override. An advantage of that approach would be that the source of truth of the styling is always to be found in the repository.

Setting colours through the database would certainly be nice, but I wonder how much to "grow" this feature, and what its limits are. We also want other groups using the system to be able to choose different fonts, font sizes, images, favicons... Would it for instance be feasible that some user uploaded their own font and defined usage of it, and then collect that all into one custom css before frontend build? And can we cater for all potential elements of the current layout that future users may want to override? In my opinion, it would be "all or nothing" - if not every style element can be overridden in this way, it would probably be confusing to be able to do it with some.

backend/theme/static/theme_admin.js Fixed Show resolved Hide resolved
backend/theme/static/img_preview.js Fixed Show fixed Hide fixed
backend/theme/static/font_preview.js Fixed Show fixed Hide fixed
element.parentNode.insertBefore(preview, element.nextSibling);

const url = element.value;
preview.src = url;

Check warning

Code scanning / CodeQL

DOM text reinterpreted as HTML Medium

DOM text
is reinterpreted as HTML without escaping meta-characters.
if (fontName) {
// Load the font
const link = document.createElement('link');
link.href = fontUrl;

Check warning

Code scanning / CodeQL

DOM text reinterpreted as HTML Medium

DOM text
is reinterpreted as HTML without escaping meta-characters.
DOM text
is reinterpreted as HTML without escaping meta-characters.
// inject the cssVariables into the document
const cssVariablesString = cssVariables.join(' ');
const style = document.createElement('style');
style.innerHTML = `:root { ${cssVariablesString} }`;

Check warning

Code scanning / CodeQL

DOM text reinterpreted as HTML Medium

DOM text
is reinterpreted as HTML without escaping meta-characters.
@drikusroor drikusroor force-pushed the feat/configure-theme branch 4 times, most recently from 53d0610 to 6a1399e Compare February 22, 2024 10:07
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to redo the migrations if you pick this branch up again.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please give the migrations more descriptive names. Perhaps even go back to _initial (manage.py migrate theme 0001) and "rewrite" the migration history so there's less AlterField operations.

@drikusroor
Copy link
Contributor Author

Closing this POC PR as the issue is split in two issues now:

@drikusroor drikusroor closed this Feb 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants