aboutsummaryrefslogtreecommitdiffstats
path: root/apps/theming/src/AdminTheming.vue
diff options
context:
space:
mode:
Diffstat (limited to 'apps/theming/src/AdminTheming.vue')
-rw-r--r--apps/theming/src/AdminTheming.vue202
1 files changed, 121 insertions, 81 deletions
diff --git a/apps/theming/src/AdminTheming.vue b/apps/theming/src/AdminTheming.vue
index 2face1629ef..e899024ca53 100644
--- a/apps/theming/src/AdminTheming.vue
+++ b/apps/theming/src/AdminTheming.vue
@@ -1,23 +1,6 @@
<!--
- - @copyright 2022 Christopher Ng <chrng8@gmail.com>
- -
- - @author Christopher Ng <chrng8@gmail.com>
- -
- - @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/>.
- -
+ - SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ - SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
@@ -44,31 +27,50 @@
:placeholder="field.placeholder"
:type="field.type"
:value.sync="field.value"
- @update:theming="$emit('update:theming')" />
+ @update:theming="refreshStyles" />
<!-- Primary color picker -->
- <ColorPickerField :name="colorPickerField.name"
- :default-value="colorPickerField.defaultValue"
- :display-name="colorPickerField.displayName"
- :value.sync="colorPickerField.value"
- @update:theming="$emit('update:theming')" />
+ <ColorPickerField :name="primaryColorPickerField.name"
+ :description="primaryColorPickerField.description"
+ :default-value="primaryColorPickerField.defaultValue"
+ :display-name="primaryColorPickerField.displayName"
+ :value.sync="primaryColorPickerField.value"
+ data-admin-theming-setting-primary-color
+ @update:theming="refreshStyles" />
+
+ <!-- Background color picker -->
+ <ColorPickerField name="background_color"
+ :description="t('theming', 'Instead of a background image you can also configure a plain background color. If you use a background image changing this color will influence the color of the app menu icons.')"
+ :default-value.sync="defaultBackgroundColor"
+ :display-name="t('theming', 'Background color')"
+ :value.sync="backgroundColor"
+ data-admin-theming-setting-background-color
+ @update:theming="refreshStyles" />
<!-- Default background picker -->
- <FileInputField v-for="field in fileInputFields"
- :key="field.name"
- :aria-label="field.ariaLabel"
- :data-admin-theming-setting-file="field.name"
- :default-mime-value="field.defaultMimeValue"
- :display-name="field.displayName"
- :mime-name="field.mimeName"
- :mime-value.sync="field.mimeValue"
- :name="field.name"
- @update:theming="$emit('update:theming')" />
+ <FileInputField :aria-label="t('theming', 'Upload new logo')"
+ data-admin-theming-setting-file="logo"
+ :display-name="t('theming', 'Logo')"
+ mime-name="logoMime"
+ :mime-value.sync="logoMime"
+ name="logo"
+ @update:theming="refreshStyles" />
+
+ <FileInputField :aria-label="t('theming', 'Upload new background and login image')"
+ data-admin-theming-setting-file="background"
+ :display-name="t('theming', 'Background and login image')"
+ mime-name="backgroundMime"
+ :mime-value.sync="backgroundMime"
+ name="background"
+ @uploaded="backgroundURL = $event"
+ @update:theming="refreshStyles" />
+
<div class="admin-theming__preview" data-admin-theming-preview>
<div class="admin-theming__preview-logo" data-admin-theming-preview-logo />
</div>
</div>
</NcSettingsSection>
+
<NcSettingsSection :name="t('theming', 'Advanced options')">
<div class="admin-theming-advanced">
<TextField v-for="field in advancedTextFields"
@@ -80,7 +82,7 @@
:display-name="field.displayName"
:placeholder="field.placeholder"
:maxlength="field.maxlength"
- @update:theming="$emit('update:theming')" />
+ @update:theming="refreshStyles" />
<FileInputField v-for="field in advancedFileInputFields"
:key="field.name"
:name="field.name"
@@ -89,7 +91,7 @@
:default-mime-value="field.defaultMimeValue"
:display-name="field.displayName"
:aria-label="field.ariaLabel"
- @update:theming="$emit('update:theming')" />
+ @update:theming="refreshStyles" />
<CheckboxField :name="userThemingField.name"
:value="userThemingField.value"
:default-value="userThemingField.defaultValue"
@@ -97,7 +99,7 @@
:label="userThemingField.label"
:description="userThemingField.description"
data-admin-theming-setting-disable-user-theming
- @update:theming="$emit('update:theming')" />
+ @update:theming="refreshStyles" />
<a v-if="!canThemeIcons"
:href="docUrlIcons"
rel="noreferrer noopener">
@@ -111,9 +113,10 @@
<script>
import { loadState } from '@nextcloud/initial-state'
+import { refreshStyles } from './helpers/refreshStyles.js'
-import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
-import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection.js'
+import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
+import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection'
import CheckboxField from './components/admin/CheckboxField.vue'
import ColorPickerField from './components/admin/ColorPickerField.vue'
import FileInputField from './components/admin/FileInputField.vue'
@@ -121,9 +124,12 @@ import TextField from './components/admin/TextField.vue'
import AppMenuSection from './components/admin/AppMenuSection.vue'
const {
+ defaultBackgroundURL,
+
backgroundMime,
+ backgroundURL,
+ backgroundColor,
canThemeIcons,
- color,
docUrl,
docUrlIcons,
faviconMime,
@@ -133,6 +139,7 @@ const {
logoMime,
name,
notThemableErrorMessage,
+ primaryColor,
privacyPolicyUrl,
slogan,
url,
@@ -170,32 +177,14 @@ const textFields = [
},
]
-const colorPickerField = {
- name: 'color',
- value: color,
+const primaryColorPickerField = {
+ name: 'primary_color',
+ value: primaryColor,
defaultValue: '#0082c9',
- displayName: t('theming', 'Color'),
+ displayName: t('theming', 'Primary color'),
+ description: t('theming', 'The primary color is used for highlighting elements like important buttons. It might get slightly adjusted depending on the current color schema.'),
}
-const fileInputFields = [
- {
- name: 'logo',
- mimeName: 'logoMime',
- mimeValue: logoMime,
- defaultMimeValue: '',
- displayName: t('theming', 'Logo'),
- ariaLabel: t('theming', 'Upload new logo'),
- },
- {
- name: 'background',
- mimeName: 'backgroundMime',
- mimeValue: backgroundMime,
- defaultMimeValue: '',
- displayName: t('theming', 'Background and login image'),
- ariaLabel: t('theming', 'Upload new background and login image'),
- },
-]
-
const advancedTextFields = [
{
name: 'imprintUrl',
@@ -258,17 +247,17 @@ export default {
TextField,
},
- emits: [
- 'update:theming',
- ],
-
- textFields,
-
data() {
return {
+ backgroundMime,
+ backgroundURL,
+ backgroundColor,
+ defaultBackgroundColor: '#0069c3',
+
+ logoMime,
+
textFields,
- colorPickerField,
- fileInputFields,
+ primaryColorPickerField,
advancedTextFields,
advancedFileInputFields,
userThemingField,
@@ -281,6 +270,64 @@ export default {
notThemableErrorMessage,
}
},
+
+ computed: {
+ cssBackgroundImage() {
+ if (this.backgroundURL) {
+ return `url('${this.backgroundURL}')`
+ }
+ return 'unset'
+ },
+ },
+
+ watch: {
+ backgroundMime() {
+ if (this.backgroundMime === '') {
+ // Reset URL to default value for preview
+ this.backgroundURL = defaultBackgroundURL
+ } else if (this.backgroundMime === 'backgroundColor') {
+ // Reset URL to empty image when only color is configured
+ this.backgroundURL = ''
+ }
+ },
+ async backgroundURL() {
+ // When the background is changed we need to emulate the background color change
+ if (this.backgroundURL !== '') {
+ const color = await this.calculateDefaultBackground()
+ this.defaultBackgroundColor = color
+ this.backgroundColor = color
+ }
+ },
+ },
+
+ async mounted() {
+ if (this.backgroundURL) {
+ this.defaultBackgroundColor = await this.calculateDefaultBackground()
+ }
+ },
+
+ methods: {
+ refreshStyles,
+
+ /**
+ * Same as on server - if a user uploads an image the mean color will be set as the background color
+ */
+ calculateDefaultBackground() {
+ const toHex = (num) => `00${num.toString(16)}`.slice(-2)
+
+ return new Promise((resolve, reject) => {
+ const img = new Image()
+ img.src = this.backgroundURL
+ img.onload = () => {
+ const context = document.createElement('canvas').getContext('2d')
+ context.imageSmoothingEnabled = true
+ context.drawImage(img, 0, 0, 1, 1)
+ resolve('#' + [...context.getImageData(0, 0, 1, 1).data.slice(0, 3)].map(toHex).join(''))
+ }
+ img.onerror = reject
+ })
+ },
+ },
}
</script>
@@ -300,15 +347,8 @@ export default {
background-position: center;
text-align: center;
margin-top: 10px;
- /* This is basically https://github.com/nextcloud/server/blob/master/core/css/guest.css
- But without the user variables. That way the admin can preview the render as guest*/
- /* As guest, there is no user color color-background-plain */
- background-color: var(--color-primary-element-default);
- /* As guest, there is no user background (--image-background)
- 1. Empty background if defined
- 2. Else default background
- 3. Finally default gradient (should not happened, the background is always defined anyway) */
- background-image: var(--image-background-plain, var(--image-background-default));
+ background-color: v-bind('backgroundColor');
+ background-image: v-bind('cssBackgroundImage');
&-logo {
width: 20%;