From cd422832dd51b1dedbb55ec7bb60973f481cfbab Mon Sep 17 00:00:00 2001 From: Agate Date: Fri, 15 May 2020 14:12:36 +0200 Subject: [PATCH] New theming system --- .gitlab-ci.yml | 2 +- CONTRIBUTING.rst | 15 + api/funkwhale_api/static/css/project.css | 38 - api/funkwhale_api/static/fonts/.gitkeep | 0 api/funkwhale_api/static/images/favicon.ico | Bin 8348 -> 0 bytes api/funkwhale_api/static/js/project.js | 1 - api/funkwhale_api/static/sass/project.scss | 51 - front/package.json | 6 +- front/public/index.html | 2 +- front/scripts/fix-fomantic-css.py | 897 ++++++++++++++++++ front/scripts/fix-fomantic-css.sh | 8 + front/src/App.vue | 190 ---- front/src/components/About.vue | 49 +- front/src/components/Footer.vue | 34 +- front/src/components/Home.vue | 52 +- front/src/components/PageNotFound.vue | 4 - front/src/components/Pagination.vue | 9 +- front/src/components/Queue.vue | 213 +---- front/src/components/SetInstanceModal.vue | 3 - front/src/components/ShortcutsModal.vue | 4 - front/src/components/Sidebar.vue | 242 +---- front/src/components/admin/SettingsGroup.vue | 11 +- front/src/components/audio/AlbumEntries.vue | 2 +- front/src/components/audio/ChannelCard.vue | 11 +- .../src/components/audio/ChannelEntryCard.vue | 16 +- .../src/components/audio/ChannelSerieCard.vue | 19 +- front/src/components/audio/EmbedWizard.vue | 9 - .../components/audio/LibraryFollowButton.vue | 4 - front/src/components/audio/PlayButton.vue | 14 +- front/src/components/audio/Player.vue | 83 +- front/src/components/audio/Search.vue | 4 - front/src/components/audio/SearchBar.vue | 4 - front/src/components/audio/VolumeControl.vue | 36 +- front/src/components/audio/album/Card.vue | 26 +- front/src/components/audio/album/Widget.vue | 15 - front/src/components/audio/artist/Card.vue | 17 +- front/src/components/audio/artist/Widget.vue | 24 - front/src/components/audio/track/Row.vue | 22 +- front/src/components/audio/track/Table.vue | 15 +- front/src/components/audio/track/Widget.vue | 40 +- front/src/components/auth/ApplicationForm.vue | 14 +- front/src/components/auth/Authorize.vue | 20 +- front/src/components/auth/LoginForm.vue | 6 +- front/src/components/auth/Logout.vue | 4 - front/src/components/auth/Settings.vue | 16 +- front/src/components/auth/SignupForm.vue | 8 +- .../src/components/auth/SubsonicTokenForm.vue | 10 +- .../components/channels/SubscribeButton.vue | 4 - front/src/components/channels/UploadForm.vue | 8 +- .../src/components/common/ActionFeedback.vue | 2 +- front/src/components/common/ActionTable.vue | 12 +- front/src/components/common/ActorAvatar.vue | 7 - front/src/components/common/ContentForm.vue | 2 +- front/src/components/common/CopyInput.vue | 13 +- .../src/components/common/DangerousButton.vue | 2 +- front/src/components/common/EmptyState.vue | 12 +- front/src/components/common/UserLink.vue | 8 +- front/src/components/favorites/List.vue | 6 +- .../favorites/TrackFavoriteIcon.vue | 4 - .../src/components/federation/FetchButton.vue | 2 +- front/src/components/library/AlbumBase.vue | 4 +- front/src/components/library/AlbumDetail.vue | 4 - front/src/components/library/Albums.vue | 8 +- front/src/components/library/ArtistBase.vue | 2 +- front/src/components/library/ArtistDetail.vue | 4 - front/src/components/library/Artists.vue | 23 +- front/src/components/library/EditCard.vue | 14 +- front/src/components/library/EditForm.vue | 7 +- front/src/components/library/FileUpload.vue | 35 +- .../components/library/FileUploadWidget.vue | 4 - front/src/components/library/Home.vue | 4 - front/src/components/library/Library.vue | 34 +- front/src/components/library/Radios.vue | 8 +- front/src/components/library/TagDetail.vue | 10 +- front/src/components/library/TagsSelector.vue | 8 - front/src/components/library/TrackBase.vue | 2 +- front/src/components/library/TrackDetail.vue | 8 - .../src/components/library/radios/Builder.vue | 2 +- .../src/components/library/radios/Filter.vue | 6 +- front/src/components/manage/ChannelsTable.vue | 2 +- .../components/manage/library/AlbumsTable.vue | 2 +- .../manage/library/ArtistsTable.vue | 2 +- .../manage/library/LibrariesTable.vue | 2 +- .../components/manage/library/TagsTable.vue | 2 +- .../components/manage/library/TracksTable.vue | 2 +- .../manage/library/UploadsTable.vue | 2 +- .../manage/moderation/DomainsTable.vue | 2 +- .../manage/moderation/InstancePolicyCard.vue | 3 - .../manage/moderation/InstancePolicyForm.vue | 16 +- .../manage/moderation/NotesThread.vue | 2 +- .../manage/moderation/ReportCard.vue | 10 +- .../manage/moderation/UserRequestCard.vue | 10 +- .../manage/users/InvitationForm.vue | 3 - .../manage/users/InvitationsTable.vue | 4 +- .../components/manage/users/UsersTable.vue | 4 +- .../src/components/moderation/FilterModal.vue | 6 +- .../src/components/moderation/ReportModal.vue | 6 +- .../notifications/NotificationRow.vue | 13 +- front/src/components/playlists/Card.vue | 4 +- front/src/components/playlists/CardList.vue | 4 - front/src/components/playlists/Editor.vue | 22 +- front/src/components/playlists/Form.vue | 4 - .../components/playlists/PlaylistModal.vue | 13 +- .../playlists/TrackPlaylistIcon.vue | 4 - front/src/components/playlists/Widget.vue | 7 +- front/src/components/radios/Button.vue | 11 +- front/src/components/radios/Card.vue | 7 +- front/src/components/semantic/Modal.vue | 4 - front/src/components/tags/List.vue | 11 +- front/src/style/_css_vars.scss | 5 + front/src/style/_main.scss | 824 +--------------- front/src/style/_site.scss | 202 ---- front/src/style/_vars.scss | 115 +++ front/src/style/components/_action_table.scss | 8 + front/src/style/components/_album_card.scss | 7 + front/src/style/components/_avatar.scss | 12 + front/src/style/components/_button.scss | 83 ++ front/src/style/components/_card.scss | 125 +++ front/src/style/components/_content_form.scss | 17 + front/src/style/components/_copy_input.scss | 11 + front/src/style/components/_empty_state.scss | 6 + front/src/style/components/_file_upload.scss | 34 + front/src/style/components/_form.scss | 30 + front/src/style/components/_header.scss | 17 + front/src/style/components/_label.scss | 10 + front/src/style/components/_modal.scss | 4 + front/src/style/components/_pagination.scss | 12 + front/src/style/components/_placeholder.scss | 31 + front/src/style/components/_play_button.scss | 5 + front/src/style/components/_player.scss | 219 +++++ .../style/components/_playlist_editor.scss | 7 + front/src/style/components/_queue.scss | 233 +++++ .../src/style/components/_settings_group.scss | 5 + front/src/style/components/_sidebar.scss | 244 +++++ front/src/style/components/_table.scss | 38 + front/src/style/components/_tags_list.scss | 9 + front/src/style/components/_tooltip.scss | 14 + front/src/style/components/_track_table.scss | 17 + front/src/style/components/_track_widget.scss | 31 + front/src/style/components/_user_link.scss | 7 + .../src/style/components/_volume_control.scss | 31 + front/src/style/globals/_app.scss | 9 + front/src/style/globals/_channels.scss | 117 +++ front/src/style/globals/_fomantic.scss | 73 ++ front/src/style/globals/_layout.scss | 167 ++++ front/src/style/globals/_typography.scss | 18 + front/src/style/globals/_utils.scss | 106 +++ front/src/style/pages/_about.scss | 31 + .../style/pages/_admin_account_detail.scss | 9 + .../src/style/pages/_admin_domain_detail.scss | 5 + front/src/style/pages/_admin_library.scss | 6 + front/src/style/pages/_home.scss | 33 + front/src/style/pages/_library.scss | 27 + front/src/style/pages/_notifications.scss | 7 + front/src/style/pages/_profile.scss | 6 + front/src/style/themes/_dark.scss | 300 ------ front/src/style/themes/_light.scss | 52 - front/src/style/themes/dark/_main.scss | 7 + front/src/style/themes/dark/_vars.scss | 33 + front/src/style/themes/light/_main.scss | 9 + front/src/views/Notifications.vue | 11 +- front/src/views/admin/ChannelDetail.vue | 2 +- front/src/views/admin/library/AlbumDetail.vue | 2 +- .../src/views/admin/library/ArtistDetail.vue | 2 +- front/src/views/admin/library/Base.vue | 9 +- front/src/views/admin/library/EditsList.vue | 4 - .../src/views/admin/library/LibraryDetail.vue | 2 +- front/src/views/admin/library/TagDetail.vue | 2 +- front/src/views/admin/library/TrackDetail.vue | 2 +- .../src/views/admin/library/UploadDetail.vue | 2 +- .../views/admin/moderation/AccountsDetail.vue | 14 +- .../views/admin/moderation/AccountsList.vue | 4 - .../views/admin/moderation/DomainsDetail.vue | 11 +- .../views/admin/moderation/DomainsList.vue | 6 +- .../views/admin/moderation/ReportsList.vue | 4 - .../views/admin/moderation/RequestsList.vue | 4 - .../src/views/admin/users/InvitationsList.vue | 4 - front/src/views/admin/users/UsersList.vue | 4 - front/src/views/auth/EmailConfirm.vue | 6 +- front/src/views/auth/Login.vue | 4 - front/src/views/auth/PasswordReset.vue | 6 +- front/src/views/auth/PasswordResetConfirm.vue | 6 +- front/src/views/auth/ProfileBase.vue | 13 +- front/src/views/auth/Signup.vue | 4 - front/src/views/channels/DetailBase.vue | 2 +- front/src/views/content/libraries/Form.vue | 5 +- front/src/views/content/libraries/Home.vue | 4 - front/src/views/content/libraries/Quota.vue | 20 +- front/src/views/content/remote/Card.vue | 14 +- front/src/views/content/remote/Home.vue | 4 - front/src/views/library/Edit.vue | 10 +- front/src/views/playlists/Detail.vue | 8 +- front/src/views/playlists/List.vue | 8 +- front/src/views/radios/Detail.vue | 6 +- front/vue.config.js | 7 + front/yarn.lock | 470 ++------- 196 files changed, 3355 insertions(+), 3482 deletions(-) delete mode 100644 api/funkwhale_api/static/css/project.css delete mode 100644 api/funkwhale_api/static/fonts/.gitkeep delete mode 100644 api/funkwhale_api/static/images/favicon.ico delete mode 100644 api/funkwhale_api/static/js/project.js delete mode 100644 api/funkwhale_api/static/sass/project.scss create mode 100755 front/scripts/fix-fomantic-css.py create mode 100755 front/scripts/fix-fomantic-css.sh create mode 100644 front/src/style/_css_vars.scss delete mode 100644 front/src/style/_site.scss create mode 100644 front/src/style/_vars.scss create mode 100644 front/src/style/components/_action_table.scss create mode 100644 front/src/style/components/_album_card.scss create mode 100644 front/src/style/components/_avatar.scss create mode 100644 front/src/style/components/_button.scss create mode 100644 front/src/style/components/_card.scss create mode 100644 front/src/style/components/_content_form.scss create mode 100644 front/src/style/components/_copy_input.scss create mode 100644 front/src/style/components/_empty_state.scss create mode 100644 front/src/style/components/_file_upload.scss create mode 100644 front/src/style/components/_form.scss create mode 100644 front/src/style/components/_header.scss create mode 100644 front/src/style/components/_label.scss create mode 100644 front/src/style/components/_modal.scss create mode 100644 front/src/style/components/_pagination.scss create mode 100644 front/src/style/components/_placeholder.scss create mode 100644 front/src/style/components/_play_button.scss create mode 100644 front/src/style/components/_player.scss create mode 100644 front/src/style/components/_playlist_editor.scss create mode 100644 front/src/style/components/_queue.scss create mode 100644 front/src/style/components/_settings_group.scss create mode 100644 front/src/style/components/_sidebar.scss create mode 100644 front/src/style/components/_table.scss create mode 100644 front/src/style/components/_tags_list.scss create mode 100644 front/src/style/components/_tooltip.scss create mode 100644 front/src/style/components/_track_table.scss create mode 100644 front/src/style/components/_track_widget.scss create mode 100644 front/src/style/components/_user_link.scss create mode 100644 front/src/style/components/_volume_control.scss create mode 100644 front/src/style/globals/_app.scss create mode 100644 front/src/style/globals/_channels.scss create mode 100644 front/src/style/globals/_fomantic.scss create mode 100644 front/src/style/globals/_layout.scss create mode 100644 front/src/style/globals/_typography.scss create mode 100644 front/src/style/globals/_utils.scss create mode 100644 front/src/style/pages/_about.scss create mode 100644 front/src/style/pages/_admin_account_detail.scss create mode 100644 front/src/style/pages/_admin_domain_detail.scss create mode 100644 front/src/style/pages/_admin_library.scss create mode 100644 front/src/style/pages/_home.scss create mode 100644 front/src/style/pages/_library.scss create mode 100644 front/src/style/pages/_notifications.scss create mode 100644 front/src/style/pages/_profile.scss delete mode 100644 front/src/style/themes/_dark.scss delete mode 100644 front/src/style/themes/_light.scss create mode 100644 front/src/style/themes/dark/_main.scss create mode 100644 front/src/style/themes/dark/_vars.scss create mode 100644 front/src/style/themes/light/_main.scss diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 25d6ccb04..7a7e106e8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -108,7 +108,7 @@ flake8: variables: GIT_STRATEGY: fetch before_script: - - pip install flake8 + - pip install 'flake8<3.7' script: - flake8 -v api cache: diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index fe081b97c..d3cfb6828 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -704,6 +704,21 @@ Views: you can find some readable views tests in file: ``api/tests/users/test_vi Contributing to the front-end ----------------------------- +Styles and themes +^^^^^^^^^^^^^^^^^ + +Our UI framework is Fomantic UI (https://fomantic-ui.com/), and Funkwhale's custom styles are written in SCSS. All the styles are configured in ``front/src/styles/_main.scss``, +including imporing of Fomantic UI styles and components. + +We're applying several changes on top of the Fomantic CSS files, before they are imported: + +1. Many hardcoded color values are replaced by CSS vars: e.g ``color: orange`` is replaced by ``color: var(--vibrant-color)``. This makes theming way easier. +2. Unused components variations and icons are stripped from the source files, in order to reduce the final size of our CSS files + +This changes are applied automatically when running ``yarn install``, through a ``postinstall`` hook. Internally, ``front/scripts/fix-fomantic-css.py`` is called +and handle both kind of modifications. Please refer to this script if you need to use new icons to the project, or restore some components variations that +were stripped in order to use them. + Running tests ^^^^^^^^^^^^^ diff --git a/api/funkwhale_api/static/css/project.css b/api/funkwhale_api/static/css/project.css deleted file mode 100644 index 08505aeef..000000000 --- a/api/funkwhale_api/static/css/project.css +++ /dev/null @@ -1,38 +0,0 @@ -/* These styles are generated from project.scss. */ - -.alert-debug { - color: black; - background-color: white; - border-color: #d6e9c6; -} - -.alert-error { - color: #b94a48; - background-color: #f2dede; - border-color: #eed3d7; -} - -/* This is a fix for the bootstrap4 alpha release */ -@media (max-width: 47.9em) { - .navbar-nav .nav-item { - float: none; - width: 100%; - display: inline-block; - } - - .navbar-nav .nav-item + .nav-item { - margin-left: 0; - } - - .nav.navbar-nav.pull-right { - float: none !important; - } -} - -/* Display django-debug-toolbar. - See https://github.com/django-debug-toolbar/django-debug-toolbar/issues/742 - and https://github.com/pydanny/cookiecutter-django/issues/317 -*/ -[hidden][style="display: block;"] { - display: block !important; -} \ No newline at end of file diff --git a/api/funkwhale_api/static/fonts/.gitkeep b/api/funkwhale_api/static/fonts/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/api/funkwhale_api/static/images/favicon.ico b/api/funkwhale_api/static/images/favicon.ico deleted file mode 100644 index e1c1dd1a32a3a077c41a21e52bc7fb5ac90d3afb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8348 zcmeHLX-gGh6rQ3V&`92%;lcmGu`Jl^WK@OV`^XKh08PZoaH%l?rdiiWq>kJ2@6vM zhAH8L6=kTRD1!xR`-2o^hS&}loN!U1#gF+=YxEq~uqf5#iBw%p0;w;5ehm+6a!sSu zh;X+$+}oF$X1Q6DwS~>Y_Hpw^(&QvJO<5ZCPrn5laNp%s{B3x1hf%*?eW>oS{<{4s^u_yG zpDyG!_iV#~G=q<~slG@0Sx47XXQ%O;HY7ILVf}B-)R$6GV^A78)t0tN9`tu3r9Z+xM?NgUd8geu=c`0?r;-KR&IQjKs zSB#VCpg8CPW&M}$sth@H=VS%t;23!!j};F)bb;W3EkA!4QmCsY_p81^TK0{;$g>e1Hl998^0M+r0-7ZSN+l_cMSRuEALTE@|d6+3{GMP^;_|<yhubb{FF1IPgH|0>SH%pC!c)uFI)H z?jv4y0uO{P5WE>~I<$r!RFo0lN4r{xm;Jy4p$h~b3S*a#)$Z*nI}&Kc_C+*zZHz1v zIR8TBVH7Y?Mp~ zofpsr+SP@>EX4e*bQ{nAUYtKrQ&-5d4j;FF{^-^Dt2^5I`HSb!|22PN26n3>hKPOy zW2D*A*v+c{bFKCwozbB{dObp~e&1Nxrj<4Ih4~w)M`nl08o}Wq2 z#Jt(k+M>+}JY(=2gm(gdUq)^@e%tX(F)RBtotnB2^y>YKz-5eg_qO&n%lJ1nuQmTY zxmyE1NWjl1EGvD?Z|n;neT;sa?Q;Ef-#%$BYxXAhD4xF+@M>*qrR!x^sPN98|BX4; z!$NJcKF`^C7f(<_vlp%b>`pxL^8Y<&?KFx{n_!5C9VqLA*CP@zhXs2t#dmrAKu?d_ y^&_r5_e@uesG}CO*g!3o?-kB+I^cA`>44J#rvpw0oDMi0a5~_0!0Eu>4*Uj$LD0AW diff --git a/api/funkwhale_api/static/js/project.js b/api/funkwhale_api/static/js/project.js deleted file mode 100644 index d26d23b9b..000000000 --- a/api/funkwhale_api/static/js/project.js +++ /dev/null @@ -1 +0,0 @@ -/* Project specific Javascript goes here. */ diff --git a/api/funkwhale_api/static/sass/project.scss b/api/funkwhale_api/static/sass/project.scss deleted file mode 100644 index 37c69e454..000000000 --- a/api/funkwhale_api/static/sass/project.scss +++ /dev/null @@ -1,51 +0,0 @@ -// project specific CSS goes here - -// Alert colors - -$white: #fff; -$mint-green: #d6e9c6; -$black: #000; -$pink: #f2dede; -$dark-pink: #eed3d7; -$red: #b94a48; - -// bootstrap alert CSS, translated to the django-standard levels of -// debug, info, success, warning, error - -.alert-debug { - background-color: $white; - border-color: $mint-green; - color: $black; -} - -.alert-error { - background-color: $pink; - border-color: $dark-pink; - color: $red; -} - -// This is a fix for the bootstrap4 alpha release - -@media (max-width: 47.9em) { - .navbar-nav .nav-item { - display: inline-block; - float: none; - width: 100%; - } - - .navbar-nav .nav-item + .nav-item { - margin-left: 0; - } - - .nav.navbar-nav.pull-right { - float: none !important; - } -} - -// Display django-debug-toolbar. -// See https://github.com/django-debug-toolbar/django-debug-toolbar/issues/742 -// and https://github.com/pydanny/cookiecutter-django/issues/317 - -[hidden][style="display: block;"] { - display: block !important; -} diff --git a/front/package.json b/front/package.json index f64e619a9..004370f65 100644 --- a/front/package.json +++ b/front/package.json @@ -10,7 +10,9 @@ "test:unit": "vue-cli-service test:unit", "lint": "vue-cli-service lint", "i18n-compile": "scripts/i18n-compile.sh", - "i18n-extract": "scripts/i18n-extract.sh" + "i18n-extract": "scripts/i18n-extract.sh", + "fix-fomantic-css": "scripts/fix-fomantic-css.sh", + "postinstall": "yarn run fix-fomantic-css" }, "dependencies": { "axios": "^0.18.0", @@ -25,6 +27,7 @@ "qs": "^6.7.0", "register-service-worker": "^1.6.2", "sanitize-html": "^1.20.1", + "sass": "^1.26.5", "showdown": "^1.8.6", "text-clipper": "^1.3.0", "vue": "^2.6.10", @@ -54,7 +57,6 @@ "glob-all": "^3.1.0", "mocha": "^5.2.0", "moxios": "^0.4.0", - "node-sass": "^4.9.3", "preload-webpack-plugin": "^3.0.0-beta.4", "purgecss-webpack-plugin": "^1.6.0", "sass-loader": "^8.0.2", diff --git a/front/public/index.html b/front/public/index.html index 37fbacde4..9825df720 100644 --- a/front/public/index.html +++ b/front/public/index.html @@ -30,7 +30,7 @@ #orange-square { width: 56px; height: 56px; - background-color: #f2711c + background-color: #f2711c; } #fake-content { height: 100vh; diff --git a/front/scripts/fix-fomantic-css.py b/front/scripts/fix-fomantic-css.py new file mode 100755 index 000000000..0cdb4b5ff --- /dev/null +++ b/front/scripts/fix-fomantic-css.py @@ -0,0 +1,897 @@ +#!/usr/bin/env python3 +""" +This scripts handles all the heavy-lifting of parsing CSS files from ``fomantic-ui-css`` and: + +1. Replace hardcoded values by their CSS vars counterparts, for easier theming +2. Strip unused styles and icons to reduce the final size of CSS + +Updated files are not modified in place, but instead copied to another directory (``fomantic-ui-css/tweaked``), in order +to allow easy comparison detection of changes. + +If you change this file, you'll need to run ``yarn run fix-fomantic-css`` manually for the changes +to be picked up. If the ``NOSTRIP`` environment variable is set, the second step will be skipped. +""" +import argparse +import os + +STRIP_UNUSED = "NOSTRIP" not in os.environ + +# Perform a blind replacement of some strings in all fomantic CSS files +GLOBAL_REPLACES = [ + # some selectors are repeated in the stylesheet, for some reason + (".ui.ui.ui.ui", ".ui"), + (".ui.ui.ui", ".ui"), + (".ui.ui", ".ui"), + (".icon.icon.icon.icon", ".icon"), + (".icon.icon.icon", ".icon"), + (".icon.icon", ".icon"), + # actually useful stuff + ("'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif", "var(--font-family)"), + (".orange", ".vibrant"), + ("#F2711C", "var(--vibrant-color)"), + ("#FF851B", "var(--vibrant-color)"), + ("#f26202", "var(--vibrant-hover-color)"), + ("#e76b00", "var(--vibrant-hover-color)"), + ("#cf590c", "var(--vibrant-active-color)"), + ("#f56100", "var(--vibrant-active-color)"), + ("#e76b00", "var(--vibrant-active-color)"), + ("#e55b00", "var(--vibrant-focus-color)"), + ("#f17000", "var(--vibrant-focus-color)"), + (".green", ".success"), + ("#21BA45", "var(--success-color)"), + ("#2ECC40", "var(--success-color)"), + ("#16ab39", "var(--success-hover-color)"), + ("#1ea92e", "var(--success-hover-color)"), + ("#198f35", "var(--success-active-color)"), + ("#25a233", "var(--success-active-color)"), + ("#0ea432", "var(--success-focus-color)"), + ("#19b82b", "var(--success-focus-color)"), + (".blue", ".primary"), + ("#2185D0", "var(--primary-color)"), + ("#54C8FF", "var(--primary-color)"), + ("#54C8FF", "var(--primary-color)"), + ("#1678c2", "var(--primary-hover-color)"), + ("#21b8ff", "var(--primary-hover-color)"), + ("#1a69a4", "var(--primary-active-color)"), + ("#0d71bb", "var(--primary-focus-color)"), + ("#2bbbff", "var(--primary-focus-color)"), + (".yellow", ".warning"), + ("#FBBD08", "var(--warning-color)"), + ("#FFE21F", "var(--warning-color)"), + ("#eaae00", "var(--warning-hover-color)"), + ("#ebcd00", "var(--warning-hover-color)"), + ("#cd9903", "var(--warning-active-color)"), + ("#ebcd00", "var(--warning-active-color)"), + ("#daa300", "var(--warning-focus-color)"), + ("#f5d500", "var(--warning-focus-color)"), + (".red.", ".danger."), + ("#DB2828", "var(--danger-color)"), + ("#FF695E", "var(--danger-color)"), + ("#d01919", "var(--danger-hover-color)"), + ("#ff392b", "var(--danger-hover-color)"), + ("#b21e1e", "var(--danger-active-color)"), + ("#ca1010", "var(--danger-focus-color)"), + ("#ff4335", "var(--danger-focus-color)"), +] + +def discard_unused_icons(rule): + """ + Add an icon to this list if you want to use it in the app. + """ + used_icons = [ + ".angle", + ".arrow", + ".at", + ".ban", + ".bell", + ".book", + ".bookmark", + ".check", + ".clock", + ".close", + ".cloud", + ".code", + ".comment", + ".copy", + ".copyright", + ".danger", + ".database", + ".delete", + ".disc", + ".down angle", + ".download", + ".dropdown", + ".edit", + ".ellipsis", + ".eraser", + ".external", + ".eye", + ".feed", + ".file", + ".forward", + ".globe", + ".hashtag", + ".headphones", + ".heart", + ".home", + ".hourglass", + ".info", + ".layer", + ".lines", + ".link", + ".list", + ".loading", + ".lock", + ".minus", + ".mobile", + ".music", + ".paper", + ".pause", + ".pencil", + ".play", + ".plus", + ".podcast", + ".question", + ".question ", + ".random", + ".redo", + ".refresh", + ".repeat", + ".rss", + ".search", + ".server", + ".share", + ".shield", + ".sidebar", + ".sign", + ".spinner", + ".step", + ".stream", + ".track", + ".trash", + ".undo", + ".upload", + ".user", + ".users", + ".volume", + ".wikipedia", + ".wrench", + ".x", + ] + if ":before" not in rule["lines"][0]: + return False + + return not match(rule, used_icons) + + +""" +Below is the main configuration object that is used for fine-grained replacement of properties +in component files. It also handles removal of unused selectors. + +Example config for a component: + +REPLACEMENTS = { + # applies to fomantic-ui-css/components/component-name.css + "component-name": { + # Discard any CSS rule matching one of the selectors listed below + # matching is done using a simple string search, so ``.pink`` will remove + # rules applied to ``.pink``, ``.pink.button`` and `.pinkdark` + "skip": [ + ".unused.variation", + ".pink", + ], + # replace some CSS properties values in specific selectors + (".inverted", ".dark"): [ + ("background", "var(--inverted-background)"), + ("color", "var(--inverted-color)"), + ], + (".active"): [ + ("font-size", "var(--active-font-size)"), + ], + } +} + +Given the previous config, the following style sheet: + +.. code-block:: css + + .unsed.variation { + color: yellow; + } + + .primary { + color: white; + } + .primary.pink { + color: pink; + } + .inverted.primary { + background: black; + color: white; + border-top: 1px solid red; + } + .inverted.primary.active { + font-size: 12px; + } + +Would be converted to: + +.. code-block:: css + + .primary { + color: white; + } + .inverted.primary { + background: var(--inverted-background); + color: var(--inverted-color); + border-top: 1px solid red; + } + .inverted.primary.active { + font-size: var(--active-font-size); + } + +""" +REPLACEMENTS = { + "site": { + ("a",): [ + ("color", "var(--link-color)"), + ("text-decoration", "var(--link-text-decoration)"), + ], + ("a:hover",): [ + ("color", "var(--link-hover-color)"), + ("text-decoration", "var(--link-hover-text-decoration)"), + ], + ("body",): [ + ("background", "var(--site-background)"), + ("color", "var(--text-color)"), + ], + ("::-webkit-selection", "::-moz-selection", "::selection",): [ + ("color", "var(--text-selection-color)"), + ("background-color", "var(--text-selection-background)"), + ], + ( + "textarea::-webkit-selection", + "input::-webkit-selection", + "textarea::-moz-selection", + "input::-moz-selection", + "textarea::selection", + "input::selection", + ): [ + ("color", "var(--input-selection-color)"), + ("background-color", "var(--input-selection-background)"), + ], + }, + "button": { + "skip": [ + ".vertical", + ".animated", + ".active", + ".olive", + ".brown", + ".teal", + ".violet", + ".purple", + ".brown", + ".grey", + ".black", + ".positive", + ".negative", + ".secondary", + ".tertiary", + ".facebook", + ".twitter", + ".google.plus", + ".vk", + ".linkedin", + ".instagram", + ".youtube", + ".whatsapp", + ".telegram", + ], + (".ui.orange.button", ".ui.orange.button:hover"): [ + ("background-color", "var(--button-orange-background)") + ], + (".ui.basic.button",): [ + ("background", "var(--button-basic-background)"), + ("color", "var(--button-basic-color)"), + ("box-shadow", "var(--button-basic-box-shadow)"), + ], + (".ui.basic.button:hover",): [ + ("background", "var(--button-basic-hover-background)"), + ("color", "var(--button-basic-hover-color)"), + ("box-shadow", "var(--button-basic-hover-box-shadow)"), + ], + }, + "card": { + "skip": [ + ".inverted", + ".olive", + ".brown", + ".teal", + ".violet", + ".purple", + ".brown", + ".grey", + ".pink", + ".black", + ".vibrant", + ".success", + ".warning", + ".danger", + ".primary", + ".secondary", + ".horizontal", + ".raised", + ] + }, + "checkbox": { + ( + ".ui.toggle.checkbox label", + ".ui.toggle.checkbox input:checked ~ label", + '.ui.checkbox input[type="checkbox"]', + ".ui.checkbox input:focus ~ label", + ".ui.toggle.checkbox input:focus:checked ~ label", + ".ui.checkbox input:active ~ label", + ): [("color", "var(--form-label-color)"),], + (".ui.toggle.checkbox label:before",): [ + ("background", "var(--input-background)"), + ], + }, + "divider": { + (".ui.divider:not(.vertical):not(.horizontal)",): [ + ("border-top", "var(--divider)"), + ("border-bottom", "var(--divider)"), + ], + (".ui.divider",): [("color", "var(--text-color)"),], + }, + "dimmer": { + (".ui.inverted.dimmer",): [ + ("background-color", "var(--dimmer-background)"), + ("color", "var(--dropdown-color)"), + ], + }, + "dropdown": { + "skip": [".error", ".info", ".success", ".warning",], + ( + ".ui.selection.dropdown", + ".ui.selection.visible.dropdown > .text:not(.default)", + ".ui.dropdown .menu", + ): [ + ("background", "var(--dropdown-background)"), + ("color", "var(--dropdown-color)"), + ], + (".ui.dropdown .menu > .item",): [("color", "var(--dropdown-item-color)"),], + (".ui.dropdown .menu > .item:hover",): [ + ("color", "var(--dropdown-item-hover-color)"), + ("background", "var(--dropdown-item-hover-background)"), + ], + (".ui.dropdown .menu .selected.item",): [ + ("color", "var(--dropdown-item-selected-color)"), + ("background", "var(--dropdown-item-selected-background)"), + ], + (".ui.dropdown .menu > .header:not(.ui)",): [ + ("color", "var(--dropdown-header-color)"), + ], + (".ui.dropdown .menu > .divider",): [("border-top", "var(--divider)"),], + }, + "form": { + "skip": [".inverted", ".success", ".warning", ".error", ".info",], + ('.ui.form input[type="text"]', ".ui.form select", ".ui.input textarea"): [ + ("background", "var(--input-background)"), + ("color", "var(--input-color)"), + ], + ( + '.ui.form input[type="text"]:focus', + ".ui.form select:focus", + ".ui.form textarea:focus", + ): [ + ("background", "var(--input-focus-background)"), + ("color", "var(--input-focus-color)"), + ], + ( + ".ui.form ::-webkit-input-placeholder", + ".ui.form :-ms-input-placeholder", + ".ui.form ::-moz-placeholder", + ): [("color", "var(--input-placeholder-color)"),], + ( + ".ui.form :focus::-webkit-input-placeholder", + ".ui.form :focus:-ms-input-placeholder", + ".ui.form :focus::-moz-placeholder", + ): [("color", "var(--input-focus-placeholder-color)"),], + (".ui.form .field > label", ".ui.form .inline.fields .field > label",): [ + ("color", "var(--form-label-color)"), + ], + }, + "grid": { + "skip": [ + "wide tablet", + "screen", + "mobile only", + "tablet only", + "computer only", + "computer reversed", + "tablet reversed", + "wide computer", + "wide mobile", + "wide tablet", + "vertically", + ".celled", + ".doubling", + ".olive", + ".brown", + ".teal", + ".violet", + ".purple", + ".brown", + ".grey", + ".black", + ".positive", + ".negative", + ".secondary", + ".tertiary", + ".danger", + ".vibrant", + ".warning", + ".primary", + ".success", + ".justified", + ".centered", + ] + }, + "icon": {"skip": discard_unused_icons}, + "input": { + (".ui.input > input",): [ + ("background", "var(--input-background)"), + ("color", "var(--input-color)"), + ], + (".ui.input > input:focus",): [ + ("background", "var(--input-focus-background)"), + ("color", "var(--input-focus-color)"), + ], + ( + ".ui.input > input::-webkit-input-placeholder", + ".ui.input > input::-moz-placeholder", + ".ui.input > input:-ms-input-placeholder", + ): [("color", "var(--input-placeholder-color)"),], + ( + ".ui.input > input:focus::-webkit-input-placeholder", + ".ui.input > input:focus::-moz-placeholder", + ".ui.input > input:focus:-ms-input-placeholder", + ): [("color", "var(--input-focus-placeholder-color)"),], + }, + "item": { + (".ui.divided.items > .item",): [("border-top", "var(--divider)"),], + (".ui.items > .item > .content",): [("color", "var(--text-color)"),], + (".ui.items > .item .extra",): [ + ("color", "var(--really-discrete-text-color)"), + ], + }, + "header": { + "skip": [ + ".inverted", + ".block", + ".olive", + ".brown", + ".teal", + ".violet", + ".purple", + ".brown", + ".grey", + ".black", + ".pink", + ], + (".ui.header",): [("color", "var(--header-color)"),], + (".ui.header .sub.header",): [("color", "var(--header-color)"),], + }, + "label": { + "skip": [ + ".olive", + ".brown", + ".teal", + ".violet", + ".purple", + ".brown", + ".grey", + ".black", + ".positive", + ".negative", + ".secondary", + ".tertiary", + ".facebook", + ".twitter", + ".google.plus", + ".vk", + ".linkedin", + ".instagram", + ".youtube", + ".whatsapp", + ".telegram", + ".corner", + "ribbon", + "pointing", + "attached", + ], + }, + "list": { + "skip": [ + ".mini", + ".tiny", + ".small", + ".large", + ".big", + ".huge", + ".massive", + ".celled", + ".horizontal", + ".bulleted", + ".ordered", + ".suffixed", + ".inverted", + ".fitted", + "aligned", + ], + (".ui.list .list > .item a.header", ".ui.list .list > a.item"): [ + ("color", "var(--link-color)"), + ("text-decoration", "var(--link-text-decoration)"), + ], + ("a:hover", ".ui.list .list > a.item:hover"): [ + ("color", "var(--link-hover-color)"), + ("text-decoration", "var(--link-hover-text-decoration)"), + ], + }, + "loader": { + "skip": [ + ".olive", + ".brown", + ".teal", + ".violet", + ".purple", + ".brown", + ".grey", + ".black", + ".pink", + ".primary", + ".vibrant", + ".warning", + ".success", + ".danger", + ".elastic", + ], + (".ui.inverted.dimmer > .ui.loader",): [("color", "var(--dimmer-color)"),], + }, + "message": { + "skip": [ + ".olive", + ".brown", + ".teal", + ".violet", + ".purple", + ".brown", + ".grey", + ".black", + ".pink", + ".vibrant", + ".primary", + ".secondary", + ".floating", + ], + }, + "menu": { + "skip": [ + ".inverted.pointing", + ".olive", + ".brown", + ".teal", + ".violet", + ".purple", + ".brown", + ".grey", + ".black", + ".vertical.tabular", + ".primary.menu", + ".pink.menu", + ".vibrant.menu", + ".warning.menu", + ".success.menu", + ".danger.menu", + ".fitted", + "fixed", + ], + (".ui.menu .item",): [("color", "var(--menu-item-color)"),], + (".ui.vertical.inverted.menu .menu .item", ".ui.inverted.menu .item"): [ + ("color", "var(--inverted-menu-item-color)"), + ], + (".inverted-ui.menu .active.item",): [ + ("color", "var(--menu-inverted-active-item-color)"), + ], + (".ui.secondary.pointing.menu .active.item",): [ + ("color", "var(--secondary-menu-active-item-color)"), + ], + ( + ".ui.secondary.pointing.menu a.item:hover", + ".ui.secondary.pointing.menu .active.item:hover", + ): [("color", "var(--secondary-menu-hover-item-color)"),], + (".ui.menu .ui.dropdown .menu > .item",): [ + ("color", "var(--dropdown-item-color) !important"), + ], + (".ui.menu .ui.dropdown .menu > .item:hover",): [ + ("color", "var(--dropdown-item-hover-color) !important"), + ("background", "var(--dropdown-item-hover-background) !important"), + ], + (".ui.menu .dropdown.item .menu",): [ + ("color", "var(--dropdown--color)"), + ("background", "var(--dropdown-background)"), + ], + (".ui.menu .ui.dropdown .menu > .active.item",): [ + ("color", "var(--dropdown-item-selected-color)"), + ("background", "var(--dropdown-item-selected-background) !important"), + ], + }, + "modal": { + (".ui.modal", ".ui.modal > .actions", ".ui.modal > .content"): [ + ("background", "var(--modal-background)"), + ("border-bottom", "var(--divider)"), + ("border-top", "var(--divider)"), + ], + (".ui.modal > .close.inside",): [("color", "var(--text-color)"),], + (".ui.modal > .header",): [ + ("color", "var(--header-color)"), + ("background", "var(--modal-background)"), + ("border-bottom", "var(--divider)"), + ("border-top", "var(--divider)"), + ], + }, + "search": { + ( + ".ui.search > .results", + ".ui.search > .results .result", + ".ui.category.search > .results .category .results", + ".ui.category.search > .results .category", + ".ui.category.search > .results .category > .name", + ".ui.search > .results > .message .header", + ".ui.search > .results > .message .description", + ): [ + ("background", "var(--dropdown-background)"), + ("color", "var(--dropdown-item-color)"), + ], + ( + ".ui.search > .results .result .title", + ".ui.search > .results .result .description", + ): [("color", "var(--dropdown-item-color)"),], + (".ui.search > .results .result:hover",): [ + ("color", "var(--dropdown-item-hover-color)"), + ("background", "var(--dropdown-item-hover-background)"), + ], + }, + "segment": { + "skip": [ + ".stacked", + ".horizontal.segment", + ".inverted.segment", + ".circular", + ".piled", + ], + }, + "sidebar": { + (".ui.left.visible.sidebar",): [("box-shadow", "var(--sidebar-box-shadow)"),] + }, + "statistic": { + (".ui.statistic > .value", ".ui.statistic > .label"): [ + ("color", "var(--text-color)"), + ], + }, + "progress": { + (".ui.progress.success > .label",): [("color", "var(--text-color)"),], + }, + "table": { + "skip": [ + ".marked", + ".active", + ".olive", + ".brown", + ".teal", + ".violet", + ".purple", + ".brown", + ".grey", + ".black", + ".padded", + ".column.table", + ".inverted", + ".definition", + ".error", + ".negative", + ".structured", + "tablet stackable", + ], + (".ui.table", ".ui.table > thead > tr > th",): [ + ("color", "var(--text-color)"), + ("background", "var(--table-background)"), + ], + (".ui.table > tr > td", ".ui.table > tbody + tbody tr:first-child > td"): [ + ("border-top", "var(--table-border)"), + ], + }, +} + + +def match(rule, skip): + if hasattr(skip, "__call__"): + return skip(rule) + for s in skip: + for rs in rule["selectors"]: + if s in rs: + return True + + return False + + +def rules_from_media_query(rule): + internal = rule["lines"][1:-1] + return parse_rules("\n".join(internal)) + + +def wraps(rule, internal_rules): + return { + "lines": [rule["lines"][0]] + + [line for r in internal_rules for line in r["lines"]] + + ["}"] + } + + +def set_vars(component_name, rules): + """ + Given rules parsed via ``parse_rules``, replace properties values when needed + using ``REPLACEMENTS`` and ``GLOBAL_REPLACES``. + + Also remove unused styles if STRIP_UNUSED is set to True. + """ + final_rules = [] + try: + conf = REPLACEMENTS[component_name] + except KeyError: + return rules + selectors = list(conf.keys()) + list() + skip = None + if STRIP_UNUSED: + skip = conf.get("skip", []) + try: + skip = set(skip) + except TypeError: + pass + + for rule in rules: + if rule["lines"][0].startswith("@media"): + # manual handling of media queries, becaues our parser is really + # simplistic + internal_rules = rules_from_media_query(rule) + internal_rules = set_vars(component_name, internal_rules) + rule = wraps(rule, internal_rules) + if len(rule["lines"]) > 2: + final_rules.append(rule) + continue + + if skip and match(rule, skip): + # discard rule entirely + continue + + matching = [] + for s in selectors: + if set(s) & set(rule["selectors"]): + matching.append(s) + if not matching: + # no replacements to apply, keep rule as is + final_rules.append(rule) + continue + new_rule = {"lines": []} + + for m in matching: + # the block match one of our replacement rules, so we loop on each line + # and replace values if needed. + replacements = conf[m] + for line in rule["lines"]: + for property, new_value in replacements: + if line.strip().startswith("{}:".format(property)): + new_property = "{}: {};".format(property, new_value) + indentation = " " * (len(line) - len(line.lstrip(" "))) + line = indentation + new_property + break + new_rule["lines"].append(line) + final_rules.append(new_rule) + return final_rules + + +def parse_rules(text): + """ + Really basic CSS parsers that stores selectors and corresponding properties. Only works + because the source files have coma-separated selectors (one per line), and one + property/value per line. + + Returns a list of dictionaries, each dictionarry containing the selectors and + lines of of each block. + """ + rules = [] + current_rule = None + opened_brackets = 0 + current_selector = [] + for line in text.splitlines(): + if not current_rule and line.endswith(","): + current_selector.append(line.rstrip(",").strip()) + elif line.endswith(" {"): + # for media queries + opened_brackets += 1 + if not current_rule: + current_selector.append(line.rstrip("{").strip()) + current_rule = { + "lines": [",\n".join(current_selector) + " {"], + "selectors": current_selector, + } + else: + current_rule["lines"].append(line) + elif current_rule: + current_rule["lines"].append(line) + if line.strip() == "}": + opened_brackets -= 1 + if not opened_brackets: + # move on to next rule + rules.append(current_rule) + current_rule = None + current_selector = [] + + return rules + + +def serialize_rules(rules): + """ + Convert rules back to valid CSS. + """ + lines = [] + for rule in rules: + for line in rule["lines"]: + lines.append(line) + + return "\n".join(lines) + + +def iter_components(dir): + for dname, dirs, files in os.walk(dir): + for fname in files: + if fname.endswith(".min.css"): + continue + if fname.endswith(".js"): + continue + if "semantic" in fname: + continue + if fname.endswith(".css"): + yield os.path.join(dname, fname) + + +def replace_vars(source, dest): + components = list(sorted(iter_components(os.path.join(source, "components")))) + for c in components: + with open(c, "r") as f: + text = f.read() + + for s, r in GLOBAL_REPLACES: + text = text.replace(s, r) + text = text.replace(s.lower(), r) + text = text.replace(s.upper(), r) + rules = parse_rules(text) + name = c.split("/")[-1].split(".")[0] + updated_rules = set_vars(name, rules) + text = serialize_rules(updated_rules) + with open(os.path.join(dest, "{}.css".format(name)), "w") as f: + f.write(text) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Replace hardcoded values by CSS vars and strip unused rules") + parser.add_argument( + "source", help="Source path of the fomantic-ui-less distribution to fix" + ) + parser.add_argument( + "dest", help="Destination directory where fixed files should be written" + ) + args = parser.parse_args() + + replace_vars(source=args.source, dest=args.dest) diff --git a/front/scripts/fix-fomantic-css.sh b/front/scripts/fix-fomantic-css.sh new file mode 100755 index 000000000..0cb6268ef --- /dev/null +++ b/front/scripts/fix-fomantic-css.sh @@ -0,0 +1,8 @@ +#!/bin/bash -eux + +find node_modules/fomantic-ui-css/components -name "*.min.css" -delete +mkdir -p node_modules/fomantic-ui-css/tweaked +echo 'Removing google font…' +sed -i '/@import url(/d' node_modules/fomantic-ui-css/components/site.css +echo "Replacing hardcoded values by CSS vars…" +scripts/fix-fomantic-css.py node_modules/fomantic-ui-css node_modules/fomantic-ui-css/tweaked diff --git a/front/src/App.vue b/front/src/App.vue index 0dec8e783..de76d1c2a 100644 --- a/front/src/App.vue +++ b/front/src/App.vue @@ -444,194 +444,4 @@ export default { diff --git a/front/src/components/About.vue b/front/src/components/About.vue index ae8fd24dc..8dac106de 100644 --- a/front/src/components/About.vue +++ b/front/src/components/About.vue @@ -1,5 +1,5 @@ @@ -285,38 +285,3 @@ export default { } } - - - - - diff --git a/front/src/components/Footer.vue b/front/src/components/Footer.vue index 6e9f76167..7634debdb 100644 --- a/front/src/components/Footer.vue +++ b/front/src/components/Footer.vue @@ -9,16 +9,16 @@

About %{instanceUrl}

-