diff --git a/changes/changelog.d/756.feature b/changes/changelog.d/756.feature
new file mode 100644
index 000000000..402b0e589
--- /dev/null
+++ b/changes/changelog.d/756.feature
@@ -0,0 +1 @@
+Dark theme (#756)
diff --git a/front/public/index.html b/front/public/index.html
index 7b09feaf0..142419ca6 100644
--- a/front/public/index.html
+++ b/front/public/index.html
@@ -9,7 +9,7 @@
Funkwhale
-
+
diff --git a/front/src/App.vue b/front/src/App.vue
index 5711466c5..e401d475e 100644
--- a/front/src/App.vue
+++ b/front/src/App.vue
@@ -251,6 +251,14 @@ export default {
this.$store.dispatch('instance/fetchSettings')
this.fetchNodeInfo()
},
+ '$store.state.ui.theme': {
+ immediate: true,
+ handler (newValue, oldValue) {
+ let oldTheme = oldValue || 'light'
+ document.body.classList.remove(`theme-${oldTheme}`)
+ document.body.classList.add(`theme-${newValue}`)
+ },
+ },
'$store.state.auth.authenticated' (newValue) {
if (!newValue) {
this.disconnect()
diff --git a/front/src/components/Footer.vue b/front/src/components/Footer.vue
index 2d06b50c7..d758b456b 100644
--- a/front/src/components/Footer.vue
+++ b/front/src/components/Footer.vue
@@ -33,6 +33,14 @@
Mobile and desktop apps
Keyboard shortcuts
+
@@ -76,15 +84,18 @@ export default {
parser.href = url
return parser.hostname
},
+ themes () {
+ return [
+ {
+ name: this.$pgettext('Footer/Settings/Dropdown.Label/Theme name', 'Light'),
+ key: 'light'
+ },
+ {
+ name: this.$pgettext('Footer/Settings/Dropdown.Label/Theme name', 'Dark'),
+ key: 'dark'
+ }
+ ]
+ }
}
}
-
diff --git a/front/src/components/notifications/NotificationRow.vue b/front/src/components/notifications/NotificationRow.vue
index b8e145647..95f8e3644 100644
--- a/front/src/components/notifications/NotificationRow.vue
+++ b/front/src/components/notifications/NotificationRow.vue
@@ -126,7 +126,4 @@ export default {
.read > span {
cursor: pointer;
}
-.disabled-row {
- color: rgba(40, 40, 40, 0.3);
-}
diff --git a/front/src/components/playlists/Card.vue b/front/src/components/playlists/Card.vue
index 60a322ab4..6efc2350e 100644
--- a/front/src/components/playlists/Card.vue
+++ b/front/src/components/playlists/Card.vue
@@ -59,12 +59,10 @@ export default {
diff --git a/front/src/store/index.js b/front/src/store/index.js
index 791dbb1e9..126368e23 100644
--- a/front/src/store/index.js
+++ b/front/src/store/index.js
@@ -40,7 +40,7 @@ export default new Vuex.Store({
}),
createPersistedState({
key: 'ui',
- paths: ['ui.currentLanguage', 'ui.momentLocale']
+ paths: ['ui.currentLanguage', 'ui.momentLocale', 'ui.theme']
}),
createPersistedState({
key: 'radios',
diff --git a/front/src/store/ui.js b/front/src/store/ui.js
index 8a8bc1da0..d13a92d88 100644
--- a/front/src/store/ui.js
+++ b/front/src/store/ui.js
@@ -10,6 +10,7 @@ export default {
maxMessages: 100,
messageDisplayDuration: 10000,
messages: [],
+ theme: 'light',
notifications: {
inbox: 0,
pendingReviewEdits: 0,
@@ -39,6 +40,9 @@ export default {
computeLastDate: (state) => {
state.lastDate = new Date()
},
+ theme: (state, value) => {
+ state.theme = value
+ },
addMessage (state, message) {
state.messages.push(message)
if (state.messages.length > state.maxMessages) {
diff --git a/front/src/style/_main.scss b/front/src/style/_main.scss
index 8f8ee8b16..bc527415c 100644
--- a/front/src/style/_main.scss
+++ b/front/src/style/_main.scss
@@ -10,6 +10,10 @@
Import this file into your LESS project to use Semantic UI without build tools
*/
+// Those semantic-ui-css/*.scss don't exist in the package, but we create them
+// via scripts/link-scss-files.sh on postinstall, so we can include theme
+// under a class namespace
+
/* Global */
@import "~semantic-ui-css/components/reset.css";
// we use our custom site css here to avoid loading google font
@@ -96,6 +100,7 @@ body {
#app > main, #app > .main {
flex: 1;
}
+
.instance-chooser {
margin-top: 2em;
}
@@ -126,14 +131,10 @@ body {
margin-left: 0;
margin-right: 0;
border: none;
- box-shadow: inset 0px -2px 0px 0px rgba(34, 36, 38, 0.15);
.ui.item {
border: none;
border-bottom-style: none;
margin-bottom: 0px;
- &.active {
- box-shadow: inset 0px -2px 0px 0px #000;
- }
}
@include media(">tablet") {
padding: 0 2.5rem;
@@ -148,7 +149,6 @@ body {
@include media(">widedesktop") {
left: $widedesktop-sidebar-width;
}
- background-color: white;
.item {
padding-top: 1.5em;
padding-bottom: 1.5em;
@@ -208,9 +208,6 @@ body {
}
}
-.discrete {
- color: rgba(0, 0, 0, 0.87);
-}
.link {
cursor: pointer;
}
@@ -355,3 +352,8 @@ input + .help {
.table td .ui.dropdown {
min-width: 150px;
}
+
+
+
+@import "./themes/_light.scss";
+@import "./themes/_dark.scss";
diff --git a/front/src/style/themes/_dark.scss b/front/src/style/themes/_dark.scss
new file mode 100644
index 000000000..f612914ca
--- /dev/null
+++ b/front/src/style/themes/_dark.scss
@@ -0,0 +1,222 @@
+$background-color: rgb(43, 58, 66);
+$button-hover-color: rgb(33, 48, 56);
+$light-background-color: rgb(51, 71, 82);
+$input-background-color: rgb(189, 211, 222);
+$loading-background-color: rgba(43, 58, 66, 0.9);
+$text-color: rgb(223, 235, 240);
+$discrete-text-color: rgba(223, 235, 240, 0.904);
+$border-color: rgb(63, 88, 102);
+$light-shadow-color: rgba(223, 235, 240, 0.15);
+$shadow-color: rgba(63, 102, 97, 0.95);
+$box-shadow: 0px 1px 3px 0px rgba(63, 88, 102, 0.95), 0px 0px 0px 1px rgba(63, 88, 102, 0.98);
+$link-color: rgb(255, 144, 0);
+
+.theme-dark {
+ background-color: $background-color;
+ .ui.labeled.input {
+ input, .label {
+ background-color: $input-background-color;
+ &::placeholder {
+ color: $light-background-color;
+
+ }
+
+ }
+ }
+ .ui.statistics .statistic {
+ > .label, > .value {
+ color: $text-color;
+ }
+ }
+ .ui.link.list.list .active.item, .ui.link.list.list .active.item a:not(.ui) {
+ color: inherit;
+ }
+ .ui.form textarea, .ui.form select, .ui.selection.dropdown, .ui.dropdown.selected, .ui.dropdown .menu .selected.item, .ui.form input:not([type]), .ui.form input[type="date"], .ui.form input[type="datetime-local"], .ui.form input[type="email"], .ui.form input[type="number"], .ui.form input[type="password"], .ui.form input[type="search"], .ui.form input[type="tel"], .ui.form input[type="time"], .ui.form input[type="text"], .ui.form input[type="file"], .ui.form input[type="url"] {
+ background-color: $input-background-color;
+ &::placeholder {
+ color: $light-background-color;
+
+ }
+ }
+ .ui.dropdown .menu .item:hover {
+ background-color: $light-background-color;
+ color: $text-color;
+
+ }
+ .main.pusher > .ui.secondary.menu {
+ background-color: $background-color;
+ box-shadow: inset 0px -2px 0px 0px $light-background-color;
+ .ui.item {
+ color: $text-color;
+ &.active {
+ box-shadow: inset 0px -2px 0px 0px $shadow-color;
+ }
+ }
+ }
+ .ui.modal {
+ > .header, > .content, > .actions {
+ background-color: $background-color;
+ }
+ > .header {
+ border-bottom: 1px solid $border-color;
+ }
+
+ > .actions {
+ border-top: 1px solid $border-color;
+ }
+ }
+ main, .main, footer, .modal {
+
+ .ui.menu {
+ background-color: $light-background-color;
+ .item {
+
+ color: $text-color;
+ }
+ }
+ .ui.secondary.menu .dropdown.item:hover, .ui.secondary.menu .link.item:hover, .ui.secondary.menu a.item:hover {
+ background: $background-color;
+ color: $text-color;
+ }
+ .header, .ui.form .field > label, .sub.header {
+ color: $text-color;
+ }
+ .ui.attached.header {
+ background-color: transparent;
+ }
+ .ui.toggle.checkbox input:checked ~ .box, .ui.toggle.checkbox input:checked ~ label {
+ color: $text-color !important;
+ }
+ .ui.toggle.checkbox .box::before, .ui.toggle.checkbox label::before {
+ background-color: $light-background-color;
+ }
+ a:not(.ui):not(.discrete) {
+ color: $link-color;
+ }
+ .ui.segment:not(.basic) {
+ background-color: $light-background-color;
+ }
+ .ui.list, .ui.dropdown {
+ .item, div.item, a.item, .button.item {
+ background-color: $background-color;
+ color: $discrete-text-color;
+ }
+ .selected.item:not(:hover) {
+ color: $background-color;
+ }
+ }
+ .ui.divided.items > .item:not(:first-child) {
+ border-top: 1px solid $border-color;
+ }
+ .ui.items {
+ .extra {
+ color: $discrete-text-color;
+ }
+ }
+ label, .toggle label {
+ color: $text-color !important;
+ }
+ &, .main.pusher, .ui.vertical.segment {
+ color: $text-color;
+ background-color: $background-color;
+ }
+
+ .discrete {
+ color: $discrete-text-color;
+ }
+
+ .ui.table thead th, .ui.table {
+ color: $text-color;
+ }
+ .ui.divider:not(.vertical):not(.horizontal) {
+ border-top: 1px solid $border-color;
+ border-bottom: 1px solid $border-color;
+ }
+ .ui.cards > .card, .ui.card {
+ color: $text-color;
+ background-color: $background-color;
+ box-shadow: $box-shadow;
+ .content, .header, .description {
+ color: $text-color;
+ }
+ .extra, .meta {
+ color: $discrete-text-color;
+ }
+ }
+ .playlist.card {
+ .attached.button {
+ background-color: $light-background-color;
+ }
+ }
+
+ // buttons
+ [class='ui button ui button'], [class='ui button'], [class='ui icon button'], [class='ui fluid button'], [class='ui cancel button'] {
+ background-color: $light-background-color;
+ color: $text-color;
+ &:hover {
+ background-color: $button-hover-color;
+
+ }
+ }
+ .ui.buttons > .ui.button:not(.basic):not(.inverted), .ui.buttons:not(.basic):not(.inverted) > .button {
+ box-shadow: 0px 0px 0px 1px $light-shadow-color inset;
+
+ }
+ .ui.basic.buttons:not(:hover):not(.green):not(.orange):not(.yellow):not(.red) .button, .ui.basic.button {
+ box-shadow: 0px 0px 0px 1px $text-color inset;
+ &:not(:hover):not(.green):not(.orange):not(.yellow):not(.red) {
+ color: $text-color !important;
+ }
+ }
+ .ui.basic.buttons .button, .ui.basic.button {
+ &:hover {
+ color: $text-color !important;
+ }
+
+ }
+ .ui.basic.buttons:not(.green):not(.orange):not(.yellow):not(.red) .button:hover, .ui.basic.button:not(.green):not(.orange):not(.yellow):not(.red):hover, .ui.basic.button:not(.green):not(.orange):not(.yellow):not(.red):active, .ui.basic.button:not(.green):not(.orange):not(.yellow):not(.red):focus {
+ color: $background-color !important;
+ }
+ // loading /dimmers
+ .ui.loading.form::before {
+ background-color: $loading-background-color;
+ }
+ .ui.inverted.dimmer {
+ background-color: $loading-background-color;
+ }
+ // table
+ .ui.basic.table tbody tr, .ui.table tr td {
+ border-bottom: 1px solid $border-color;
+ }
+ .ui.table thead th {
+ border-bottom: 1px solid $border-color;
+ }
+ .ui.table {
+ &:not(.basic) {
+ &, thead th {
+ background-color: $light-background-color;
+ }
+ }
+ }
+ }
+ .ui.link.list.list a.item:hover, .ui.link.list.list .item a:not(.ui):not(.button):hover {
+ color: $link-color;
+ }
+ [data-tooltip]::after {
+ background-color: $light-background-color;
+ color: $text-color;
+ }
+ .ui.progress > .label {
+ color: $text-color;
+
+ }
+ i.grey.icon {
+ color: $text-color !important;
+ }
+ input {
+ &::selection, &::-moz-selection {
+ background: $background-color;
+ color: $text-color;
+ }
+ }
+}
diff --git a/front/src/style/themes/_light.scss b/front/src/style/themes/_light.scss
new file mode 100644
index 000000000..a6e1a0cdf
--- /dev/null
+++ b/front/src/style/themes/_light.scss
@@ -0,0 +1,35 @@
+
+
+.theme-light {
+
+ .main.pusher > .ui.secondary.menu {
+ box-shadow: inset 0px -2px 0px 0px rgba(34, 36, 38, 0.15);
+ background-color: white;
+ .ui.item {
+ &.active {
+ box-shadow: inset 0px -2px 0px 0px #000;
+ }
+ }
+ }
+
+ .discrete {
+ color: rgba(0, 0, 0, 0.87);
+ }
+ .playlist.card {
+ .attached.button {
+ background-color: rgb(243, 244, 245);
+ }
+ }
+
+ .disabled-row {
+ color: rgba(40, 40, 40, 0.3);
+ }
+ footer p {
+ color: grey;
+ }
+
+ footer#footer div.item:hover {
+ color: rgba(0, 0, 0, 0.87);
+ }
+
+}