diff options
Diffstat (limited to 'apps/theming/src/AdminTheming.vue')
-rw-r--r-- | apps/theming/src/AdminTheming.vue | 202 |
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%; |