aboutsummaryrefslogtreecommitdiffstats
path: root/apps/theming/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'apps/theming/src/components')
-rw-r--r--apps/theming/src/components/AppOrderSelector.vue4
-rw-r--r--apps/theming/src/components/AppOrderSelectorElement.vue6
-rw-r--r--apps/theming/src/components/BackgroundSettings.vue193
-rw-r--r--apps/theming/src/components/ItemPreview.vue31
-rw-r--r--apps/theming/src/components/UserAppMenuSection.vue35
-rw-r--r--apps/theming/src/components/UserPrimaryColor.vue160
-rw-r--r--apps/theming/src/components/admin/AppMenuSection.vue18
-rw-r--r--apps/theming/src/components/admin/CheckboxField.vue35
-rw-r--r--apps/theming/src/components/admin/ColorPickerField.vue100
-rw-r--r--apps/theming/src/components/admin/FileInputField.vue40
-rw-r--r--apps/theming/src/components/admin/TextField.vue23
-rw-r--r--apps/theming/src/components/admin/shared/field.scss23
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 {