aboutsummaryrefslogtreecommitdiffstats
path: root/apps/theming/src/components/BackgroundSettings.vue
diff options
context:
space:
mode:
Diffstat (limited to 'apps/theming/src/components/BackgroundSettings.vue')
-rw-r--r--apps/theming/src/components/BackgroundSettings.vue114
1 files changed, 88 insertions, 26 deletions
diff --git a/apps/theming/src/components/BackgroundSettings.vue b/apps/theming/src/components/BackgroundSettings.vue
index 9890f9ad3f0..e1d8b731bc4 100644
--- a/apps/theming/src/components/BackgroundSettings.vue
+++ b/apps/theming/src/components/BackgroundSettings.vue
@@ -1,10 +1,10 @@
<!--
- @copyright Copyright (c) 2020 Julius Härtl <jus@bitgrid.net>
- - @copyright Copyright (c) 2022 Greta Doci <gretadoci@gmail.com>
-
- - @author Julius Härtl <jus@bitgrid.net>
- - @author Greta Doci <gretadoci@gmail.com>
- @author Christopher Ng <chrng8@gmail.com>
+ - @author Greta Doci <gretadoci@gmail.com>
+ - @author John Molakvoæ <skjnldsv@protonmail.com>
+ - @author Julius Härtl <jus@bitgrid.net>
-
- @license GNU AGPL version 3 or any later version
-
@@ -24,13 +24,16 @@
-->
<template>
- <div class="background-selector">
+ <div class="background-selector" data-user-theming-background-settings>
<!-- Custom background -->
<button class="background background__filepicker"
- :class="{ 'background--active': backgroundImage === 'custom' }"
+ :class="{ 'icon-loading': loading === 'custom', 'background--active': backgroundImage === 'custom' }"
+ :data-color-bright="invertTextColor(Theming.color)"
+ data-user-theming-background-custom
tabindex="0"
@click="pickFile">
{{ t('theming', 'Custom background') }}
+ <Check :size="44" />
</button>
<!-- Default background -->
@@ -38,6 +41,7 @@
:class="{ 'icon-loading': loading === 'default', 'background--active': backgroundImage === 'default' }"
:data-color-bright="invertTextColor(Theming.defaultColor)"
:style="{ '--border-color': Theming.defaultColor }"
+ data-user-theming-background-default
tabindex="0"
@click="setDefault">
{{ t('theming', 'Default background') }}
@@ -50,6 +54,7 @@
:data-color="Theming.color"
:data-color-bright="invertTextColor(Theming.color)"
:style="{ backgroundColor: Theming.color, '--border-color': Theming.color}"
+ data-user-theming-background-color
tabindex="0">
{{ t('theming', 'Change color') }}
</button>
@@ -61,6 +66,7 @@
v-tooltip="shippedBackground.details.attribution"
:class="{ 'icon-loading': loading === shippedBackground.name, 'background--active': backgroundImage === shippedBackground.name }"
:data-color-bright="shippedBackground.details.theming === 'dark'"
+ :data-user-theming-background-shipped="shippedBackground.name"
:style="{ backgroundImage: 'url(' + shippedBackground.preview + ')', '--border-color': shippedBackground.details.primary_color }"
class="background background__shipped"
tabindex="0"
@@ -70,16 +76,17 @@
<!-- Remove background -->
<button class="background background__delete"
+ data-user-theming-background-clear
tabindex="0"
@click="removeBackground">
{{ t('theming', 'Remove background') }}
- <Close :size="24" />
+ <Close :size="32" />
</button>
</div>
</template>
<script>
-import { generateFilePath, generateUrl } from '@nextcloud/router'
+import { generateFilePath, generateRemoteUrl, generateUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
import axios from '@nextcloud/axios'
import Check from 'vue-material-design-icons/Check.vue'
@@ -87,6 +94,10 @@ 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'
+import Vibrant from 'node-vibrant'
+import { Palette } from 'node-vibrant/lib/color'
+import { getFilePickerBuilder } from '@nextcloud/dialogs'
+import { getCurrentUser } from '@nextcloud/auth'
const backgroundColor = loadState('theming', 'backgroundColor')
const backgroundImage = loadState('theming', 'backgroundImage')
@@ -95,6 +106,12 @@ const themingDefaultBackground = loadState('theming', 'themingDefaultBackground'
const defaultShippedBackground = loadState('theming', 'defaultShippedBackground')
const prefixWithBaseUrl = (url) => generateFilePath('theming', '', 'img/background/') + url
+const picker = getFilePickerBuilder(t('theming', 'Select a background from your files'))
+ .setMultiSelect(false)
+ .setModal(true)
+ .setType(1)
+ .setMimeTypeFilter(['image/png', 'image/gif', 'image/jpeg', 'image/svg+xml', 'image/svg'])
+ .build()
export default {
name: 'BackgroundSettings',
@@ -213,9 +230,9 @@ export default {
this.update(result.data)
},
- async setFile(path) {
+ async setFile(path, color = null) {
this.loading = 'custom'
- const result = await axios.post(generateUrl('/apps/theming/background/custom'), { value: path })
+ const result = await axios.post(generateUrl('/apps/theming/background/custom'), { value: path, color })
this.update(result.data)
},
@@ -228,19 +245,55 @@ export default {
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 })
+ const result = await axios.post(generateUrl('/apps/theming/background/color'), { 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) => {
- if (type === OC.dialogs.FILEPICKER_TYPE_CHOOSE) {
- this.setFile(path)
- }
- }, false, ['image/png', 'image/gif', 'image/jpeg', 'image/svg'], true, OC.dialogs.FILEPICKER_TYPE_CHOOSE)
+ async pickFile() {
+ const path = await picker.pick()
+ this.loading = 'custom'
+
+ // Extract primary color from image
+ let response = null
+ let color = null
+ try {
+ const fileUrl = generateRemoteUrl('dav/files/' + getCurrentUser().uid + path)
+ response = await axios.get(fileUrl, { responseType: 'blob' })
+ const blobUrl = URL.createObjectURL(response.data)
+ const palette = await this.getColorPaletteFromBlob(blobUrl)
+
+ // DarkVibrant is accessible AND visually pleasing
+ // Vibrant is not accessible enough and others are boring
+ color = palette?.DarkVibrant?.hex
+ this.setFile(path, color)
+
+ // Log data
+ console.debug('Extracted colour', color, 'from custom image', path, palette)
+ } catch (error) {
+ this.setFile(path)
+ console.error('Unable to extract colour from custom image', { error, path, response, color })
+ }
+ },
+
+ /**
+ * Extract a Vibrant color palette from a blob URL
+ *
+ * @param {string} blobUrl the blob URL
+ * @return {Promise<Palette>}
+ */
+ getColorPaletteFromBlob(blobUrl) {
+ return new Promise((resolve, reject) => {
+ const vibrant = new Vibrant(blobUrl)
+ vibrant.getPalette((error, palette) => {
+ if (error) {
+ reject(error)
+ }
+ resolve(palette)
+ })
+ })
},
},
}
@@ -263,6 +316,13 @@ export default {
background-position: center center;
background-size: cover;
+ &__filepicker {
+ &.background--active {
+ color: white;
+ background-image: var(--image-background);
+ }
+ }
+
&__default {
background-color: var(--color-primary-default);
background-image: var(--image-background-default);
@@ -277,6 +337,12 @@ export default {
background-color: var(--color-primary-default);
}
+ // Over a background image
+ &__default,
+ &__shipped {
+ color: white;
+ }
+
// Text and svg icon dark on bright background
&[data-color-bright] {
color: black;
@@ -294,18 +360,14 @@ export default {
margin: 4px;
}
- &__default,
- &__shipped {
- color: white;
- span {
- display: none;
- }
+ &__filepicker span,
+ &__default span,
+ &__shipped span {
+ display: none;
}
- &--active:not(.icon-loading) {
- span {
- display: block;
- }
+ &--active:not(.icon-loading) span {
+ display: block !important;
}
}
}