aboutsummaryrefslogtreecommitdiffstats
path: root/apps/theming/src
diff options
context:
space:
mode:
authorJohn Molakvoæ <skjnldsv@protonmail.com>2022-10-20 13:18:06 +0200
committerJohn Molakvoæ <skjnldsv@protonmail.com>2022-11-29 11:22:13 +0100
commitcedae7c6d74e11c8aaa59b09a38db04dbebc818d (patch)
treeb95c77675542e0654084dd41f5d1f07a413b4db7 /apps/theming/src
parenta884f311b78341612adeb6d62f707dda1bae39e7 (diff)
downloadnextcloud-server-cedae7c6d74e11c8aaa59b09a38db04dbebc818d.tar.gz
nextcloud-server-cedae7c6d74e11c8aaa59b09a38db04dbebc818d.zip
Allow to remove the background and select a custom colour
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
Diffstat (limited to 'apps/theming/src')
-rw-r--r--apps/theming/src/UserThemes.vue25
-rw-r--r--apps/theming/src/components/BackgroundSettings.vue215
-rw-r--r--apps/theming/src/helpers/getBackgroundUrl.js49
-rw-r--r--apps/theming/src/helpers/prefixWithBaseUrl.js25
4 files changed, 143 insertions, 171 deletions
diff --git a/apps/theming/src/UserThemes.vue b/apps/theming/src/UserThemes.vue
index 5a41c019017..7220820a3c3 100644
--- a/apps/theming/src/UserThemes.vue
+++ b/apps/theming/src/UserThemes.vue
@@ -69,10 +69,7 @@
</template>
<template v-else>
<p>{{ t('theming', 'Set a custom background') }}</p>
- <BackgroundSettings class="background__grid"
- :background="background"
- :theming-default-background="themingDefaultBackground"
- @update:background="updateBackground" />
+ <BackgroundSettings class="background__grid" @update:background="refreshGlobalStyles" />
</template>
</NcSettingsSection>
</section>
@@ -92,8 +89,6 @@ const availableThemes = loadState('theming', 'themes', [])
const enforceTheme = loadState('theming', 'enforceTheme', '')
const shortcutsDisabled = loadState('theming', 'shortcutsDisabled', false)
-const background = loadState('theming', 'background')
-const themingDefaultBackground = loadState('theming', 'themingDefaultBackground')
const isUserThemingDisabled = loadState('theming', 'isUserThemingDisabled')
console.debug('Available themes', availableThemes)
@@ -111,10 +106,10 @@ export default {
data() {
return {
availableThemes,
+
+ // Admin defined configs
enforceTheme,
shortcutsDisabled,
- background,
- themingDefaultBackground,
isUserThemingDisabled,
}
},
@@ -173,9 +168,21 @@ export default {
},
methods: {
+ // Refresh server-side generated theming CSS
+ refreshGlobalStyles() {
+ [...document.head.querySelectorAll('link.theme')].forEach(theme => {
+ const url = new URL(theme.href)
+ url.searchParams.set('v', Date.now())
+ const newTheme = theme.cloneNode()
+ newTheme.href = url.toString()
+ newTheme.onload = () => theme.remove()
+ document.head.append(newTheme)
+ })
+ },
+
updateBackground(data) {
this.background = (data.type === 'custom' || data.type === 'default') ? data.type : data.value
- this.$emit('update:background')
+ this.refreshGlobalStyles()
},
changeTheme({ enabled, id }) {
diff --git a/apps/theming/src/components/BackgroundSettings.vue b/apps/theming/src/components/BackgroundSettings.vue
index 45e627fd378..9890f9ad3f0 100644
--- a/apps/theming/src/components/BackgroundSettings.vue
+++ b/apps/theming/src/components/BackgroundSettings.vue
@@ -26,68 +26,75 @@
<template>
<div class="background-selector">
<!-- Custom background -->
- <button class="background filepicker"
- :class="{ active: background === 'custom' }"
+ <button class="background background__filepicker"
+ :class="{ 'background--active': backgroundImage === 'custom' }"
tabindex="0"
@click="pickFile">
- {{ t('theming', 'Pick from Files') }}
+ {{ t('theming', 'Custom background') }}
</button>
<!-- Default background -->
- <button class="background default"
+ <button class="background background__default"
+ :class="{ 'icon-loading': loading === 'default', 'background--active': backgroundImage === 'default' }"
+ :data-color-bright="invertTextColor(Theming.defaultColor)"
+ :style="{ '--border-color': Theming.defaultColor }"
tabindex="0"
- :class="{ 'icon-loading': loading === 'default', active: background === 'default' }"
@click="setDefault">
- {{ t('theming', 'Default image') }}
+ {{ t('theming', 'Default background') }}
+ <Check :size="44" />
</button>
<!-- Custom color picker -->
<NcColorPicker v-model="Theming.color" @input="debouncePickColor">
- <button class="background color"
- :class="{ active: background === Theming.color}"
- tabindex="0"
+ <button class="background background__color"
:data-color="Theming.color"
:data-color-bright="invertTextColor(Theming.color)"
- :style="{ backgroundColor: Theming.color, color: invertTextColor(Theming.color) ? '#000000' : '#ffffff'}">
- {{ t('theming', 'Custom color') }}
+ :style="{ backgroundColor: Theming.color, '--border-color': Theming.color}"
+ tabindex="0">
+ {{ t('theming', 'Change color') }}
</button>
</NcColorPicker>
- <!-- Default admin primary color -->
- <button class="background color"
- :class="{ active: background === Theming.defaultColor }"
- tabindex="0"
- :data-color="Theming.defaultColor"
- :data-color-bright="invertTextColor(Theming.defaultColor)"
- :style="{ color: invertTextColor(Theming.defaultColor) ? '#000000' : '#ffffff'}"
- @click="debouncePickColor">
- {{ t('theming', 'Plain background') }}
- </button>
-
<!-- Background set selection -->
<button v-for="shippedBackground in shippedBackgrounds"
:key="shippedBackground.name"
v-tooltip="shippedBackground.details.attribution"
- :class="{ 'icon-loading': loading === shippedBackground.name, active: background === shippedBackground.name }"
- tabindex="0"
- class="background"
+ :class="{ 'icon-loading': loading === shippedBackground.name, 'background--active': backgroundImage === shippedBackground.name }"
:data-color-bright="shippedBackground.details.theming === 'dark'"
- :style="{ 'background-image': 'url(' + shippedBackground.preview + ')' }"
- @click="setShipped(shippedBackground.name)" />
+ :style="{ backgroundImage: 'url(' + shippedBackground.preview + ')', '--border-color': shippedBackground.details.primary_color }"
+ class="background background__shipped"
+ tabindex="0"
+ @click="setShipped(shippedBackground.name)">
+ <Check :size="44" />
+ </button>
+
+ <!-- Remove background -->
+ <button class="background background__delete"
+ tabindex="0"
+ @click="removeBackground">
+ {{ t('theming', 'Remove background') }}
+ <Close :size="24" />
+ </button>
</div>
</template>
<script>
-import { generateUrl } from '@nextcloud/router'
-import { getBackgroundUrl } from '../helpers/getBackgroundUrl.js'
+import { generateFilePath, generateUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
-import { prefixWithBaseUrl } from '../helpers/prefixWithBaseUrl.js'
import axios from '@nextcloud/axios'
+import Check from 'vue-material-design-icons/Check.vue'
+import Close from 'vue-material-design-icons/Close.vue'
import debounce from 'debounce'
import NcColorPicker from '@nextcloud/vue/dist/Components/NcColorPicker'
import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
+const backgroundColor = loadState('theming', 'backgroundColor')
+const backgroundImage = loadState('theming', 'backgroundImage')
const shippedBackgroundList = loadState('theming', 'shippedBackgrounds')
+const themingDefaultBackground = loadState('theming', 'themingDefaultBackground')
+const defaultShippedBackground = loadState('theming', 'defaultShippedBackground')
+
+const prefixWithBaseUrl = (url) => generateFilePath('theming', '', 'img/background/') + url
export default {
name: 'BackgroundSettings',
@@ -96,38 +103,49 @@ export default {
},
components: {
+ Check,
+ Close,
NcColorPicker,
},
- props: {
- background: {
- type: String,
- default: 'default',
- },
- themingDefaultBackground: {
- type: String,
- default: '',
- },
- },
-
data() {
return {
- backgroundImage: generateUrl('/apps/theming/background') + '?v=' + Date.now(),
loading: false,
Theming: loadState('theming', 'data', {}),
+
+ // User background image and color settings
+ backgroundImage,
+ backgroundColor,
}
},
computed: {
shippedBackgrounds() {
- return Object.keys(shippedBackgroundList).map(fileName => {
- return {
- name: fileName,
- url: prefixWithBaseUrl(fileName),
- preview: prefixWithBaseUrl('preview/' + fileName),
- details: shippedBackgroundList[fileName],
- }
- })
+ return Object.keys(shippedBackgroundList)
+ .map(fileName => {
+ return {
+ name: fileName,
+ url: prefixWithBaseUrl(fileName),
+ preview: prefixWithBaseUrl('preview/' + fileName),
+ details: shippedBackgroundList[fileName],
+ }
+ })
+ .filter(background => {
+ // If the admin did not changed the global background
+ // let's hide the default background to not show it twice
+ if (!this.isGlobalBackgroundDeleted && !this.isGlobalBackgroundDefault) {
+ return background.name !== defaultShippedBackground
+ }
+ return true
+ })
+ },
+
+ isGlobalBackgroundDefault() {
+ return !!themingDefaultBackground
+ },
+
+ isGlobalBackgroundDeleted() {
+ return themingDefaultBackground === 'backgroundColor'
},
},
@@ -163,20 +181,24 @@ export default {
: null
},
+ /**
+ * Update local state
+ *
+ * @param {object} data destructuring object
+ * @param {string} data.backgroundColor background color value
+ * @param {string} data.backgroundImage background image value
+ * @param {string} data.version cache buster number
+ * @see https://github.com/nextcloud/server/blob/c78bd45c64d9695724fc44fe8453a88824b85f2f/apps/theming/lib/Controller/UserThemeController.php#L187-L191
+ */
async update(data) {
- const background = data.type === 'custom' || data.type === 'default' ? data.type : data.value
- this.backgroundImage = getBackgroundUrl(background, data.version, this.themingDefaultBackground)
- if (data.type === 'color' || (data.type === 'default' && this.themingDefaultBackground === 'backgroundColor')) {
- this.$emit('update:background', data)
- this.loading = false
- return
- }
- const image = new Image()
- image.onload = () => {
- this.$emit('update:background', data)
- this.loading = false
- }
- image.src = this.backgroundImage
+ // Update state
+ this.backgroundImage = data.backgroundImage
+ this.backgroundColor = data.backgroundColor
+ this.Theming.color = data.backgroundColor
+
+ // Notify parent and reload style
+ this.$emit('update:background')
+ this.loading = false
},
async setDefault() {
@@ -197,15 +219,21 @@ export default {
this.update(result.data)
},
- debouncePickColor: debounce(function() {
- this.pickColor(...arguments)
- }, 200),
+ async removeBackground() {
+ this.loading = 'remove'
+ const result = await axios.delete(generateUrl('/apps/theming/background/custom'))
+ this.update(result.data)
+ },
+
async pickColor(event) {
this.loading = 'color'
const color = event?.target?.dataset?.color || this.Theming?.color || '#0082c9'
const result = await axios.post(generateUrl('/apps/theming/background/color'), { value: color })
this.update(result.data)
},
+ debouncePickColor: debounce(function() {
+ this.pickColor(...arguments)
+ }, 200),
pickFile() {
window.OC.dialogs.filepicker(t('theming', 'Select a background from your files'), (path, type) => {
@@ -225,50 +253,61 @@ export default {
justify-content: center;
.background {
+ overflow: hidden;
width: 176px;
height: 96px;
margin: 8px;
- background-size: cover;
- background-position: center center;
text-align: center;
- border-radius: var(--border-radius-large);
border: 2px solid var(--color-main-background);
- overflow: hidden;
+ border-radius: var(--border-radius-large);
+ background-position: center center;
+ background-size: cover;
- &.current {
- background-image: var(--color-background-dark);
+ &__default {
+ background-color: var(--color-primary-default);
+ background-image: var(--image-background-default);
}
- &.filepicker, &.default, &.color {
+ &__filepicker, &__default, &__color {
border-color: var(--color-border);
}
- &.color {
- background-color: var(--color-primary-default);
+ &__color {
color: var(--color-primary-text);
+ background-color: var(--color-primary-default);
+ }
+
+ // Text and svg icon dark on bright background
+ &[data-color-bright] {
+ color: black;
}
- &.active,
+ &--active,
&:hover,
&:focus {
- border: 2px solid var(--color-primary);
+ // Use theme color primary, see inline css variable in template
+ border: 2px solid var(--border-color, var(--color-primary)) !important;
}
- &.active:not(.icon-loading) {
- &:after {
- background-image: var(--original-icon-checkmark-white);
- background-repeat: no-repeat;
- background-position: center;
- background-size: 44px;
- content: '';
- display: block;
- height: 100%;
+ // Icon
+ span {
+ margin: 4px;
+ }
+
+ &__default,
+ &__shipped {
+ color: white;
+ span {
+ display: none;
}
+ }
- &[data-color-bright]:after {
- background-image: var(--original-icon-checkmark-dark);
+ &--active:not(.icon-loading) {
+ span {
+ display: block;
}
}
}
}
+
</style>
diff --git a/apps/theming/src/helpers/getBackgroundUrl.js b/apps/theming/src/helpers/getBackgroundUrl.js
deleted file mode 100644
index 88a3ab57291..00000000000
--- a/apps/theming/src/helpers/getBackgroundUrl.js
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- * @copyright Copyright (c) 2020 Julius Härtl <jus@bitgrid.net>
- *
- * @author Avior <florian.bouillon@delta-wings.net>
- * @author Julien Veyssier <eneiluj@posteo.net>
- * @author Julius Härtl <jus@bitgrid.net>
- *
- * @license AGPL-3.0-or-later
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-import { generateUrl } from '@nextcloud/router'
-import { prefixWithBaseUrl } from './prefixWithBaseUrl.js'
-
-export const getBackgroundUrl = (background, time = 0, themingDefaultBackground = '') => {
- const enabledThemes = window.OCA?.Theming?.enabledThemes || []
- const isDarkTheme = (enabledThemes.length === 0 || enabledThemes[0] === 'default')
- ? window.matchMedia('(prefers-color-scheme: dark)').matches
- : enabledThemes.join('').indexOf('dark') !== -1
-
- if (background === 'default') {
- if (themingDefaultBackground && themingDefaultBackground !== 'backgroundColor') {
- return generateUrl('/apps/theming/image/background') + '?v=' + window.OCA.Theming.cacheBuster
- }
-
- if (isDarkTheme) {
- return prefixWithBaseUrl('eduardo-neves-pedra-azul.jpg')
- }
-
- return prefixWithBaseUrl('kamil-porembinski-clouds.jpg')
- } else if (background === 'custom') {
- return generateUrl('/apps/theming/background') + '?v=' + time
- }
-
- return prefixWithBaseUrl(background)
-}
diff --git a/apps/theming/src/helpers/prefixWithBaseUrl.js b/apps/theming/src/helpers/prefixWithBaseUrl.js
deleted file mode 100644
index d2f42c93549..00000000000
--- a/apps/theming/src/helpers/prefixWithBaseUrl.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * @copyright Copyright (c) 2020 Julius Härtl <jus@bitgrid.net>
- *
- * @author Julius Härtl <jus@bitgrid.net>
- *
- * @license AGPL-3.0-or-later
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-import { generateFilePath } from '@nextcloud/router'
-
-export const prefixWithBaseUrl = (url) => generateFilePath('theming', '', 'img/background/') + url