diff options
Diffstat (limited to 'apps/theming/src/components')
-rw-r--r-- | apps/theming/src/components/AppOrderSelector.vue | 4 | ||||
-rw-r--r-- | apps/theming/src/components/AppOrderSelectorElement.vue | 6 | ||||
-rw-r--r-- | apps/theming/src/components/BackgroundSettings.vue | 193 | ||||
-rw-r--r-- | apps/theming/src/components/ItemPreview.vue | 31 | ||||
-rw-r--r-- | apps/theming/src/components/UserAppMenuSection.vue | 35 | ||||
-rw-r--r-- | apps/theming/src/components/UserPrimaryColor.vue | 160 | ||||
-rw-r--r-- | apps/theming/src/components/admin/AppMenuSection.vue | 18 | ||||
-rw-r--r-- | apps/theming/src/components/admin/CheckboxField.vue | 35 | ||||
-rw-r--r-- | apps/theming/src/components/admin/ColorPickerField.vue | 100 | ||||
-rw-r--r-- | apps/theming/src/components/admin/FileInputField.vue | 40 | ||||
-rw-r--r-- | apps/theming/src/components/admin/TextField.vue | 23 | ||||
-rw-r--r-- | apps/theming/src/components/admin/shared/field.scss | 23 |
12 files changed, 372 insertions, 296 deletions
diff --git a/apps/theming/src/components/AppOrderSelector.vue b/apps/theming/src/components/AppOrderSelector.vue index db1a0427cdb..9c065211bc1 100644 --- a/apps/theming/src/components/AppOrderSelector.vue +++ b/apps/theming/src/components/AppOrderSelector.vue @@ -1,3 +1,7 @@ +<!-- + - SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> <template> <Fragment> <div :id="statusInfoId" diff --git a/apps/theming/src/components/AppOrderSelectorElement.vue b/apps/theming/src/components/AppOrderSelectorElement.vue index 23e7b0b07c9..fc41e8e6165 100644 --- a/apps/theming/src/components/AppOrderSelectorElement.vue +++ b/apps/theming/src/components/AppOrderSelectorElement.vue @@ -1,3 +1,7 @@ +<!-- + - SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> <template> <li :data-cy-app-order-element="app.id" :class="{ @@ -61,7 +65,7 @@ import { defineComponent, nextTick, ref } from 'vue' import IconArrowDown from 'vue-material-design-icons/ArrowDown.vue' import IconArrowUp from 'vue-material-design-icons/ArrowUp.vue' -import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' +import NcButton from '@nextcloud/vue/components/NcButton' interface IApp { id: string // app id diff --git a/apps/theming/src/components/BackgroundSettings.vue b/apps/theming/src/components/BackgroundSettings.vue index deb0a93a51a..58b76dd9602 100644 --- a/apps/theming/src/components/BackgroundSettings.vue +++ b/apps/theming/src/components/BackgroundSettings.vue @@ -1,27 +1,7 @@ <!-- - - @copyright Copyright (c) 2020 Julius Härtl <jus@bitgrid.net> - - - - @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 - - - - 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: 2020 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> <template> <div class="background-selector" data-user-theming-background-settings> @@ -32,15 +12,34 @@ 'background background__filepicker': true, 'background--active': backgroundImage === 'custom' }" - :data-color-bright="invertTextColor(Theming.color)" data-user-theming-background-custom tabindex="0" @click="pickFile"> {{ t('theming', 'Custom background') }} - <ImageEdit v-if="backgroundImage !== 'custom'" :size="26" /> + <ImageEdit v-if="backgroundImage !== 'custom'" :size="20" /> <Check :size="44" /> </button> + <!-- Custom color picker --> + <NcColorPicker v-model="Theming.backgroundColor" @update:value="debouncePickColor"> + <button :class="{ + 'icon-loading': loading === 'color', + 'background background__color': true, + 'background--active': backgroundImage === 'color' + }" + :aria-pressed="backgroundImage === 'color'" + :data-color="Theming.backgroundColor" + :data-color-bright="invertTextColor(Theming.backgroundColor)" + :style="{ backgroundColor: Theming.backgroundColor, '--border-color': Theming.backgroundColor}" + data-user-theming-background-color + tabindex="0" + @click="backgroundImage !== 'color' && debouncePickColor(Theming.backgroundColor)"> + {{ t('theming', 'Plain background') /* TRANSLATORS: Background using a single color */ }} + <ColorPalette v-if="backgroundImage !== 'color'" :size="20" /> + <Check :size="44" /> + </button> + </NcColorPicker> + <!-- Default background --> <button :aria-pressed="backgroundImage === 'default'" :class="{ @@ -48,8 +47,8 @@ 'background background__default': true, 'background--active': backgroundImage === 'default' }" - :data-color-bright="invertTextColor(Theming.defaultColor)" - :style="{ '--border-color': Theming.defaultColor }" + :data-color-bright="invertTextColor(Theming.defaultBackgroundColor)" + :style="{ '--border-color': Theming.defaultBackgroundColor }" data-user-theming-background-default tabindex="0" @click="setDefault"> @@ -57,31 +56,6 @@ <Check :size="44" /> </button> - <!-- Custom color picker --> - <div class="background-color" - data-user-theming-background-color> - <NcColorPicker v-model="Theming.color" - @input="debouncePickColor"> - <NcButton type="ternary"> - {{ t('theming', 'Change color') }} - </NcButton> - </NcColorPicker> - </div> - - <!-- Remove background --> - <button :aria-pressed="isBackgroundDisabled" - :class="{ - 'background background__delete': true, - 'background--active': isBackgroundDisabled - }" - data-user-theming-background-clear - tabindex="0" - @click="removeBackground"> - {{ t('theming', 'No background') }} - <Close v-if="!isBackgroundDisabled" :size="32" /> - <Check :size="44" /> - </button> - <!-- Background set selection --> <button v-for="shippedBackground in shippedBackgrounds" :key="shippedBackground.name" @@ -93,7 +67,7 @@ 'icon-loading': loading === shippedBackground.name, 'background--active': backgroundImage === shippedBackground.name }" - :data-color-bright="shippedBackground.details.theming === 'dark'" + :data-color-bright="invertTextColor(shippedBackground.details.background_color)" :data-user-theming-background-shipped="shippedBackground.name" :style="{ backgroundImage: 'url(' + shippedBackground.preview + ')', '--border-color': shippedBackground.details.primary_color }" tabindex="0" @@ -104,25 +78,25 @@ </template> <script> -import { generateFilePath, generateRemoteUrl, generateUrl } from '@nextcloud/router' -import { getCurrentUser } from '@nextcloud/auth' +import { generateFilePath, generateUrl } from '@nextcloud/router' import { getFilePickerBuilder, showError } from '@nextcloud/dialogs' import { loadState } from '@nextcloud/initial-state' -import { Palette } from 'node-vibrant/lib/color.js' import axios from '@nextcloud/axios' import debounce from 'debounce' -import NcColorPicker from '@nextcloud/vue/dist/Components/NcColorPicker.js' -import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' -import Vibrant from 'node-vibrant' +import NcColorPicker from '@nextcloud/vue/components/NcColorPicker' import Check from 'vue-material-design-icons/Check.vue' -import Close from 'vue-material-design-icons/Close.vue' import ImageEdit from 'vue-material-design-icons/ImageEdit.vue' +import ColorPalette from 'vue-material-design-icons/PaletteOutline.vue' -const backgroundImage = loadState('theming', 'backgroundImage') const shippedBackgroundList = loadState('theming', 'shippedBackgrounds') -const themingDefaultBackground = loadState('theming', 'themingDefaultBackground') -const defaultShippedBackground = loadState('theming', 'defaultShippedBackground') +const backgroundImage = loadState('theming', 'userBackgroundImage') +const { + backgroundImage: defaultBackgroundImage, + // backgroundColor: defaultBackgroundColor, + backgroundMime: defaultBackgroundMime, + defaultShippedBackground, +} = loadState('theming', 'themingDefaults') const prefixWithBaseUrl = (url) => generateFilePath('theming', '', 'img/background/') + url @@ -131,9 +105,8 @@ export default { components: { Check, - Close, + ColorPalette, ImageEdit, - NcButton, NcColorPicker, }, @@ -150,7 +123,12 @@ export default { computed: { shippedBackgrounds() { return Object.keys(shippedBackgroundList) - .map(fileName => { + .filter((background) => { + // If the admin did not changed the global background + // let's hide the default background to not show it twice + return background !== defaultShippedBackground || !this.isGlobalBackgroundDefault + }) + .map((fileName) => { return { name: fileName, url: prefixWithBaseUrl(fileName), @@ -158,27 +136,18 @@ export default { 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 + return defaultBackgroundMime === '' }, isGlobalBackgroundDeleted() { - return themingDefaultBackground === 'backgroundColor' + return defaultBackgroundMime === 'backgroundColor' }, - isBackgroundDisabled() { - return this.backgroundImage === 'disabled' - || !this.backgroundImage + cssDefaultBackgroundImage() { + return `url('${defaultBackgroundImage}')` }, }, @@ -226,7 +195,7 @@ export default { async update(data) { // Update state this.backgroundImage = data.backgroundImage - this.Theming.color = data.backgroundColor + this.Theming.backgroundColor = data.backgroundColor // Notify parent and reload style this.$emit('update:background') @@ -245,9 +214,9 @@ export default { this.update(result.data) }, - async setFile(path, color = null) { + async setFile(path) { this.loading = 'custom' - const result = await axios.post(generateUrl('/apps/theming/background/custom'), { value: path, color }) + const result = await axios.post(generateUrl('/apps/theming/background/custom'), { value: path }) this.update(result.data) }, @@ -257,15 +226,15 @@ export default { this.update(result.data) }, - async pickColor(event) { + async pickColor(color) { this.loading = 'color' - const color = event?.target?.dataset?.color || this.Theming?.color || '#0082c9' - const result = await axios.post(generateUrl('/apps/theming/background/color'), { color }) - this.update(result.data) + const { data } = await axios.post(generateUrl('/apps/theming/background/color'), { color: color || '#0082c9' }) + this.update(data) }, + debouncePickColor: debounce(function(...args) { this.pickColor(...args) - }, 200), + }, 1000), pickFile() { const picker = getFilePickerBuilder(t('theming', 'Select a background from your files')) @@ -292,45 +261,7 @@ export default { } 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) - }) - }) + this.setFile(path) }, }, } @@ -359,21 +290,25 @@ export default { height: 96px; margin: 8px; text-align: center; + word-wrap: break-word; + hyphens: auto; border: 2px solid var(--color-main-background); border-radius: var(--border-radius-large); background-position: center center; background-size: cover; &__filepicker { + background-color: var(--color-background-dark); + &.background--active { - color: white; + color: var(--color-background-plain-text); background-image: var(--image-background); } } &__default { - background-color: var(--color-primary-default); - background-image: linear-gradient(to bottom, rgba(23, 23, 23, 0.5), rgba(23, 23, 23, 0.5)), var(--image-background-plain, var(--image-background-default)); + background-color: var(--color-background-plain); + background-image: linear-gradient(to bottom, rgba(23, 23, 23, 0.5), rgba(23, 23, 23, 0.5)), v-bind(cssDefaultBackgroundImage); } &__filepicker, &__default, &__color { diff --git a/apps/theming/src/components/ItemPreview.vue b/apps/theming/src/components/ItemPreview.vue index 5f817d04f49..e4a1acd3e2a 100644 --- a/apps/theming/src/components/ItemPreview.vue +++ b/apps/theming/src/components/ItemPreview.vue @@ -1,13 +1,22 @@ +<!-- + - SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> <template> <div :class="'theming__preview--' + theme.id" class="theming__preview"> <div class="theming__preview-image" :style="{ backgroundImage: 'url(' + img + ')' }" @click="onToggle" /> <div class="theming__preview-description"> <h3>{{ theme.title }}</h3> - <p class="theming__preview-explanation">{{ theme.description }}</p> + <p class="theming__preview-explanation"> + {{ theme.description }} + </p> <span v-if="enforced" class="theming__preview-warning" role="note"> {{ t('theming', 'Theme selection is enforced') }} </span> - <NcCheckboxRadioSwitch class="theming__preview-toggle" + + <!-- Only show checkbox if we can change themes --> + <NcCheckboxRadioSwitch v-show="!enforced" + class="theming__preview-toggle" :checked.sync="checked" :disabled="enforced" :name="name" @@ -20,7 +29,7 @@ <script> import { generateFilePath } from '@nextcloud/router' -import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' +import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch' export default { name: 'ItemPreview', @@ -67,6 +76,10 @@ export default { return this.selected }, set(checked) { + if (this.enforced) { + return + } + console.debug('Changed theme', this.theme.id, checked) // If this is a radio, we can only enable @@ -83,6 +96,10 @@ export default { methods: { onToggle() { + if (this.enforced) { + return + } + if (this.switchType === 'radio') { this.checked = true return @@ -100,11 +117,9 @@ export default { .theming__preview { // We make previews on 16/10 screens --ratio: 16; - position: relative; display: flex; justify-content: flex-start; - max-width: 800px; &, * { @@ -115,7 +130,7 @@ export default { flex-basis: calc(16px * var(--ratio)); flex-shrink: 0; height: calc(10px * var(--ratio)); - margin-right: var(--gap); + margin-inline-end: var(--gap); cursor: pointer; border-radius: var(--border-radius); background-repeat: no-repeat; @@ -141,10 +156,6 @@ export default { } } - &--default { - grid-column: span 2; - } - &-warning { color: var(--color-warning); } diff --git a/apps/theming/src/components/UserAppMenuSection.vue b/apps/theming/src/components/UserAppMenuSection.vue index 00cfa01525a..d4221190f6b 100644 --- a/apps/theming/src/components/UserAppMenuSection.vue +++ b/apps/theming/src/components/UserAppMenuSection.vue @@ -1,3 +1,7 @@ +<!-- + - SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> <template> <NcSettingsSection :name="t('theming', 'Navigation bar settings')"> <p> @@ -29,6 +33,7 @@ <script lang="ts"> import type { IApp } from './AppOrderSelector.vue' +import type { INavigationEntry } from '../../../../core/src/types/navigation.d.ts' import { showError } from '@nextcloud/dialogs' import { loadState } from '@nextcloud/initial-state' @@ -39,29 +44,9 @@ import { computed, defineComponent, ref } from 'vue' import axios from '@nextcloud/axios' import AppOrderSelector from './AppOrderSelector.vue' import IconUndo from 'vue-material-design-icons/Undo.vue' -import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' -import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js' -import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection.js' - -/** See NavigationManager */ -interface INavigationEntry { - /** Navigation id */ - id: string - /** Order where this entry should be shown */ - order: number - /** Target of the navigation entry */ - href: string - /** The icon used for the naviation entry */ - icon: string - /** Type of the navigation entry ('link' vs 'settings') */ - type: 'link' | 'settings' - /** Localized name of the navigation entry */ - name: string - /** Whether this is the default app */ - default?: boolean - /** App that registered this navigation entry (not necessarly the same as the id) */ - app?: string -} +import NcButton from '@nextcloud/vue/components/NcButton' +import NcNoteCard from '@nextcloud/vue/components/NcNoteCard' +import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection' /** The app order user setting */ type IAppOrder = Record<string, { order: number, app?: string }> @@ -94,9 +79,9 @@ export default defineComponent({ /** * Array of all available apps, it is set by a core controller for the app menu, so it is always available */ - const initialAppOrder = Object.values(loadState<Record<string, INavigationEntry>>('core', 'apps')) + const initialAppOrder = loadState<INavigationEntry[]>('core', 'apps') .filter(({ type }) => type === 'link') - .map((app) => ({ ...app, label: app.name, default: app.default && app.app === enforcedDefaultApp })) + .map((app) => ({ ...app, label: app.name, default: app.default && app.id === enforcedDefaultApp })) /** * Check if a custom app order is used or the default is shown diff --git a/apps/theming/src/components/UserPrimaryColor.vue b/apps/theming/src/components/UserPrimaryColor.vue new file mode 100644 index 00000000000..f10b8a01825 --- /dev/null +++ b/apps/theming/src/components/UserPrimaryColor.vue @@ -0,0 +1,160 @@ +<!-- + - SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> +<template> + <div class="primary-color__wrapper"> + <NcColorPicker v-model="primaryColor" + data-user-theming-primary-color + @update:value="debouncedOnUpdate"> + <button ref="trigger" + class="color-container primary-color__trigger" + :style="{ 'background-color': primaryColor }" + data-user-theming-primary-color-trigger> + {{ t('theming', 'Primary color') }} + <NcLoadingIcon v-if="loading" /> + <IconColorPalette v-else :size="20" /> + </button> + </NcColorPicker> + <NcButton type="tertiary" :disabled="isdefaultPrimaryColor" @click="onReset"> + <template #icon> + <IconUndo :size="20" /> + </template> + {{ t('theming', 'Reset primary color') }} + </NcButton> + </div> +</template> + +<script lang="ts"> +import { showError } from '@nextcloud/dialogs' +import { loadState } from '@nextcloud/initial-state' +import { translate as t } from '@nextcloud/l10n' +import { generateOcsUrl } from '@nextcloud/router' +import { colord } from 'colord' +import { defineComponent } from 'vue' +import axios from '@nextcloud/axios' +import debounce from 'debounce' + +import NcButton from '@nextcloud/vue/components/NcButton' +import NcColorPicker from '@nextcloud/vue/components/NcColorPicker' +import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon' +import IconColorPalette from 'vue-material-design-icons/PaletteOutline.vue' +import IconUndo from 'vue-material-design-icons/UndoVariant.vue' + +const { primaryColor, defaultPrimaryColor } = loadState('theming', 'data', { primaryColor: '#0082c9', defaultPrimaryColor: '#0082c9' }) + +export default defineComponent({ + name: 'UserPrimaryColor', + + components: { + IconColorPalette, + IconUndo, + NcButton, + NcColorPicker, + NcLoadingIcon, + }, + + emits: ['refresh-styles'], + + data() { + return { + primaryColor, + loading: false, + } + }, + + computed: { + isdefaultPrimaryColor() { + return colord(this.primaryColor).isEqual(colord(defaultPrimaryColor)) + }, + + debouncedOnUpdate() { + return debounce(this.onUpdate, 1000) + }, + }, + + methods: { + t, + + /** + * Global styles are reloaded so we might need to update the current value + */ + reload() { + const trigger = this.$refs.trigger as HTMLButtonElement + const newColor = window.getComputedStyle(trigger).backgroundColor + if (newColor.toLowerCase() !== this.primaryColor) { + this.primaryColor = newColor + } + }, + + onReset() { + this.primaryColor = defaultPrimaryColor + this.onUpdate(null) + }, + + async onUpdate(value: string | null) { + this.loading = true + const url = generateOcsUrl('apps/provisioning_api/api/v1/config/users/{appId}/{configKey}', { + appId: 'theming', + configKey: 'primary_color', + }) + try { + if (value) { + await axios.post(url, { + configValue: value, + }) + } else { + await axios.delete(url) + } + this.$emit('refresh-styles') + } catch (e) { + console.error('Could not update primary color', e) + showError(t('theming', 'Could not set primary color')) + } + this.loading = false + }, + }, +}) +</script> + +<style scoped lang="scss"> +.primary-color { + &__wrapper { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 12px; + } + + &__trigger { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + + background-color: var(--color-primary); + color: var(--color-primary-text); + width: 350px; + max-width: 100vw; + height: 96px; + + word-wrap: break-word; + hyphens: auto; + + border: 2px solid var(--color-main-background); + border-radius: var(--border-radius-large); + + &:active { + background-color: var(--color-primary-hover) !important; + } + + &:hover, + &:focus, + &:focus-visible { + border-color: var(--color-main-background) !important; + outline: 2px solid var(--color-main-text) !important; + } + } +} +</style> diff --git a/apps/theming/src/components/admin/AppMenuSection.vue b/apps/theming/src/components/admin/AppMenuSection.vue index bed170504c9..bf229f15df4 100644 --- a/apps/theming/src/components/admin/AppMenuSection.vue +++ b/apps/theming/src/components/admin/AppMenuSection.vue @@ -1,3 +1,7 @@ +<!-- + - SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> <template> <NcSettingsSection :name="t('theming', 'Navigation bar settings')"> <h3>{{ t('theming', 'Default app') }}</h3> @@ -26,6 +30,8 @@ </template> <script lang="ts"> +import type { INavigationEntry } from '../../../../../core/src/types/navigation' + import { showError } from '@nextcloud/dialogs' import { loadState } from '@nextcloud/initial-state' import { translate as t } from '@nextcloud/l10n' @@ -34,9 +40,9 @@ import { computed, defineComponent } from 'vue' import axios from '@nextcloud/axios' -import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' -import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js' -import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection.js' +import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch' +import NcSelect from '@nextcloud/vue/components/NcSelect' +import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection' import AppOrderSelector from '../AppOrderSelector.vue' export default defineComponent({ @@ -71,9 +77,8 @@ export default defineComponent({ /** * All enabled apps which can be navigated */ - const allApps = Object.values( - loadState<Record<string, { id: string, name?: string, icon: string }>>('core', 'apps'), - ).map(({ id, name, icon }) => ({ label: name, id, icon })) + const allApps = loadState<INavigationEntry[]>('core', 'apps') + .map(({ id, name, icon }) => ({ label: name, id, icon })) /** * Currently selected app, wrapps the setter @@ -110,6 +115,7 @@ export default defineComponent({ h3, h4 { font-weight: bold; } + h4, h5 { margin-block-start: 12px; } diff --git a/apps/theming/src/components/admin/CheckboxField.vue b/apps/theming/src/components/admin/CheckboxField.vue index fa8477a7283..42d86ded4e7 100644 --- a/apps/theming/src/components/admin/CheckboxField.vue +++ b/apps/theming/src/components/admin/CheckboxField.vue @@ -1,38 +1,23 @@ <!-- - - @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> <div class="field"> <label :for="id">{{ displayName }}</label> <div class="field__row"> - <NcCheckboxRadioSwitch type="switch" - :id="id" + <NcCheckboxRadioSwitch :id="id" + type="switch" :checked.sync="localValue" @update:checked="save"> {{ label }} </NcCheckboxRadioSwitch> </div> - <p class="field__description">{{ description }}</p> + <p class="field__description"> + {{ description }} + </p> <NcNoteCard v-if="errorMessage" type="error" @@ -43,8 +28,8 @@ </template> <script> -import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' -import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js' +import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch' +import NcNoteCard from '@nextcloud/vue/components/NcNoteCard' import TextValueMixin from '../../mixins/admin/TextValueMixin.js' @@ -90,7 +75,7 @@ export default { </script> <style lang="scss" scoped> -@import './shared/field.scss'; +@use './shared/field' as *; .field { &__description { diff --git a/apps/theming/src/components/admin/ColorPickerField.vue b/apps/theming/src/components/admin/ColorPickerField.vue index fad40408b37..4ec6d47fef6 100644 --- a/apps/theming/src/components/admin/ColorPickerField.vue +++ b/apps/theming/src/components/admin/ColorPickerField.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> @@ -26,27 +9,35 @@ <div class="field__row"> <NcColorPicker :value.sync="localValue" :advanced-fields="true" - data-admin-theming-setting-primary-color-picker @update:value="debounceSave"> - <NcButton type="secondary" - :id="id"> + <NcButton :id="id" + class="field__button" + type="primary" + :aria-label="t('theming', 'Select a custom color')" + data-admin-theming-setting-color-picker> <template #icon> - <Palette :size="20" /> + <NcLoadingIcon v-if="loading" + :appearance="calculatedTextColor === '#ffffff' ? 'light' : 'dark'" + :size="20" /> + <Palette v-else :size="20" /> </template> - {{ t('theming', 'Change color') }} + {{ value }} </NcButton> </NcColorPicker> - <div class="field__color-preview" data-admin-theming-setting-primary-color /> + <div class="field__color-preview" data-admin-theming-setting-color /> <NcButton v-if="value !== defaultValue" type="tertiary" :aria-label="t('theming', 'Reset to default')" - data-admin-theming-setting-primary-color-reset + data-admin-theming-setting-color-reset @click="undo"> <template #icon> <Undo :size="20" /> </template> </NcButton> </div> + <div v-if="description" class="description"> + {{ description }} + </div> <NcNoteCard v-if="errorMessage" type="error" @@ -57,10 +48,13 @@ </template> <script> -import { debounce } from 'debounce' -import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' -import NcColorPicker from '@nextcloud/vue/dist/Components/NcColorPicker.js' -import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js' +import { colord } from 'colord' +import debounce from 'debounce' + +import NcButton from '@nextcloud/vue/components/NcButton' +import NcColorPicker from '@nextcloud/vue/components/NcColorPicker' +import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon' +import NcNoteCard from '@nextcloud/vue/components/NcNoteCard' import Undo from 'vue-material-design-icons/UndoVariant.vue' import Palette from 'vue-material-design-icons/Palette.vue' @@ -72,6 +66,7 @@ export default { components: { NcButton, NcColorPicker, + NcLoadingIcon, NcNoteCard, Undo, Palette, @@ -86,10 +81,18 @@ export default { type: String, required: true, }, + description: { + type: String, + default: '', + }, value: { type: String, required: true, }, + textColor: { + type: String, + default: null, + }, defaultValue: { type: String, required: true, @@ -100,22 +103,55 @@ export default { }, }, + emits: ['update:theming'], + + data() { + return { + loading: false, + } + }, + + computed: { + calculatedTextColor() { + const color = colord(this.value) + return color.isLight() ? '#000000' : '#ffffff' + }, + usedTextColor() { + if (this.textColor) { + return this.textColor + } + return this.calculatedTextColor + }, + }, + methods: { debounceSave: debounce(async function() { + this.loading = true await this.save() + this.$emit('update:theming') + this.loading = false }, 200), }, } </script> <style lang="scss" scoped> -@import './shared/field.scss'; +@use './shared/field' as *; + +.description { + color: var(--color-text-maxcontrast); +} .field { + &__button { + background-color: v-bind('value') !important; + color: v-bind('usedTextColor') !important; + } + &__color-preview { width: var(--default-clickable-area); border-radius: var(--border-radius-large); - background-color: var(--color-primary-default); + background-color: v-bind('value'); } } </style> diff --git a/apps/theming/src/components/admin/FileInputField.vue b/apps/theming/src/components/admin/FileInputField.vue index 3d6fda9ec70..d5e0052f5bd 100644 --- a/apps/theming/src/components/admin/FileInputField.vue +++ b/apps/theming/src/components/admin/FileInputField.vue @@ -1,31 +1,14 @@ <!-- - - @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> <div class="field"> <label :for="id">{{ displayName }}</label> <div class="field__row"> - <NcButton type="secondary" - :id="id" + <NcButton :id="id" + type="secondary" :aria-label="ariaLabel" data-admin-theming-setting-file-picker @click="activateLocalFilePicker"> @@ -82,10 +65,10 @@ import axios from '@nextcloud/axios' import { generateUrl } from '@nextcloud/router' import { loadState } from '@nextcloud/initial-state' -import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' -import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js' -import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js' -import Delete from 'vue-material-design-icons/Delete.vue' +import NcButton from '@nextcloud/vue/components/NcButton' +import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon' +import NcNoteCard from '@nextcloud/vue/components/NcNoteCard' +import Delete from 'vue-material-design-icons/DeleteOutline.vue' import Undo from 'vue-material-design-icons/UndoVariant.vue' import Upload from 'vue-material-design-icons/Upload.vue' @@ -126,7 +109,7 @@ export default { }, defaultMimeValue: { type: String, - required: true, + default: '', }, displayName: { type: String, @@ -182,9 +165,10 @@ export default { const url = generateUrl('/apps/theming/ajax/uploadImage') try { this.showLoading = true - await axios.post(url, formData) + const { data } = await axios.post(url, formData) this.showLoading = false this.$emit('update:mime-value', file.type) + this.$emit('uploaded', data.data.url) this.handleSuccess() } catch (e) { this.showLoading = false @@ -225,7 +209,7 @@ export default { </script> <style lang="scss" scoped> -@import './shared/field.scss'; +@use './shared/field' as *; .field { &__loading-icon { diff --git a/apps/theming/src/components/admin/TextField.vue b/apps/theming/src/components/admin/TextField.vue index 5e96f45a95b..6ec52733aed 100644 --- a/apps/theming/src/components/admin/TextField.vue +++ b/apps/theming/src/components/admin/TextField.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> @@ -40,7 +23,7 @@ </template> <script> -import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' +import NcTextField from '@nextcloud/vue/components/NcTextField' import TextValueMixin from '../../mixins/admin/TextValueMixin.js' diff --git a/apps/theming/src/components/admin/shared/field.scss b/apps/theming/src/components/admin/shared/field.scss index 54fc57b3ee5..2347f31f7c5 100644 --- a/apps/theming/src/components/admin/shared/field.scss +++ b/apps/theming/src/components/admin/shared/field.scss @@ -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 */ .field { |