Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix(settings): Make background selector be responsive to user changes
Signed-off-by: Ferdinand Thiessen <[email protected]>
  • Loading branch information
susnux committed May 21, 2024
commit 752e3b9000e826207f1361373c6964c708868930
38 changes: 0 additions & 38 deletions apps/theming/lib/Listener/BeforeTemplateRenderedListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
namespace OCA\Theming\Listener;

use OCA\Theming\AppInfo\Application;
use OCA\Theming\Service\BackgroundService;
use OCA\Theming\Service\JSDataService;
use OCA\Theming\Service\ThemeInjectionService;
use OCP\AppFramework\Http\Events\BeforeLoginTemplateRenderedEvent;
Expand Down Expand Up @@ -81,43 +80,6 @@ public function handle(Event $event): void {

$this->themeInjectionService->injectHeaders();

$user = $this->userSession->getUser();

if (!empty($user)) {
$userId = $user->getUID();

/** User background */
$this->initialState->provideInitialState(
'backgroundImage',
$this->config->getUserValue($userId, Application::APP_ID, 'background_image', BackgroundService::BACKGROUND_DEFAULT),
);

/** User color */
$this->initialState->provideInitialState(
'backgroundColor',
$this->config->getUserValue($userId, Application::APP_ID, 'background_color', BackgroundService::DEFAULT_COLOR),
);

/**
* Admin background. `backgroundColor` if disabled,
* mime type if defined and empty by default
*/
$this->initialState->provideInitialState(
'themingDefaultBackground',
$this->config->getAppValue('theming', 'backgroundMime', ''),
);
$this->initialState->provideInitialState(
'defaultShippedBackground',
BackgroundService::DEFAULT_BACKGROUND_IMAGE,
);

/** List of all shipped backgrounds */
$this->initialState->provideInitialState(
'shippedBackgrounds',
BackgroundService::SHIPPED_BACKGROUNDS,
);
}

// Making sure to inject just after core
\OCP\Util::addScript('theming', 'theming', 'core');
}
Expand Down
4 changes: 4 additions & 0 deletions apps/theming/lib/Settings/Admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
use OCA\Theming\AppInfo\Application;
use OCA\Theming\Controller\ThemingController;
use OCA\Theming\ImageManager;
use OCA\Theming\Service\BackgroundService;
use OCA\Theming\ThemingDefaults;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
Expand Down Expand Up @@ -79,6 +80,9 @@ public function getForm(): TemplateResponse {
'backgroundColor' => $this->themingDefaults->getDefaultColorBackground(),
'logoMime' => $this->config->getAppValue(Application::APP_ID, 'logoMime', ''),
'allowedMimeTypes' => $allowedMimeTypes,
'backgroundURL' => $this->imageManager->getImageUrl('background'),
'defaultBackgroundURL' => $this->urlGenerator->linkTo(Application::APP_ID, 'img/background/' . BackgroundService::DEFAULT_BACKGROUND_IMAGE),
'defaultBackgroundColor' => BackgroundService::DEFAULT_BACKGROUND_COLOR,
'backgroundMime' => $this->config->getAppValue(Application::APP_ID, 'backgroundMime', ''),
'logoheaderMime' => $this->config->getAppValue(Application::APP_ID, 'logoheaderMime', ''),
'faviconMime' => $this->config->getAppValue(Application::APP_ID, 'faviconMime', ''),
Expand Down
21 changes: 21 additions & 0 deletions apps/theming/lib/Settings/Personal.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
namespace OCA\Theming\Settings;

use OCA\Theming\ITheme;
use OCA\Theming\Service\BackgroundService;
use OCA\Theming\Service\ThemesService;
use OCA\Theming\ThemingDefaults;
use OCP\App\IAppManager;
Expand Down Expand Up @@ -71,6 +72,26 @@ public function getForm(): TemplateResponse {
// Get the default app enforced by admin
$forcedDefaultApp = $this->appManager->getDefaultAppForUser(null, false);

/** List of all shipped backgrounds */
$this->initialStateService->provideInitialState('shippedBackgrounds', BackgroundService::SHIPPED_BACKGROUNDS);

/**
* Admin theming
*/
$this->initialStateService->provideInitialState('themingDefaults', [
/** URL of admin configured background image */
'backgroundImage' => $this->themingDefaults->getBackground(),
/** `backgroundColor` if disabled, mime type if defined and empty by default */
'backgroundMime' => $this->config->getAppValue('theming', 'backgroundMime', ''),

Check notice

Code scanning / Psalm

DeprecatedMethod

The method OCP\IConfig::getAppValue has been marked as deprecated
/** Admin configured background color */
'backgroundColor' => $this->themingDefaults->getDefaultColorBackground(),
/** Admin configured primary color */
'primaryColor' => $this->themingDefaults->getDefaultColorPrimary(),
/** Nextcloud default background image */
'defaultShippedBackground' => BackgroundService::DEFAULT_BACKGROUND_IMAGE,
]);

$this->initialStateService->provideInitialState('userBackgroundImage', $this->config->getUserValue($this->userId, 'theming', 'background_image', BackgroundService::BACKGROUND_DEFAULT));
$this->initialStateService->provideInitialState('themes', array_values($themes));
$this->initialStateService->provideInitialState('enforceTheme', $enforcedTheme);
$this->initialStateService->provideInitialState('isUserThemingDisabled', $this->themingDefaults->isUserThemingDisabled());
Expand Down
174 changes: 91 additions & 83 deletions apps/theming/src/AdminTheming.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
:placeholder="field.placeholder"
:type="field.type"
:value.sync="field.value"
@update:theming="$emit('update:theming')" />
@update:theming="refreshStyles" />

<!-- Primary color picker -->
<ColorPickerField :name="primaryColorPickerField.name"
Expand All @@ -53,33 +53,41 @@
:display-name="primaryColorPickerField.displayName"
:value.sync="primaryColorPickerField.value"
data-admin-theming-setting-primary-color
@update:theming="$emit('update:theming')" />
@update:theming="refreshStyles" />

<!-- Background color picker -->
<ColorPickerField :name="backgroundColorPickerField.name"
:description="backgroundColorPickerField.description"
:default-value="defaultBackground"
:display-name="backgroundColorPickerField.displayName"
:value.sync="backgroundColorPickerField.value"
data-admin-theming-setting-primary-color
@update:theming="$emit('update:theming')" />
<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"
Expand All @@ -91,7 +99,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"
Expand All @@ -100,15 +108,15 @@
: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"
:display-name="userThemingField.displayName"
: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">
Expand All @@ -122,6 +130,7 @@

<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'
Expand All @@ -132,7 +141,10 @@ import TextField from './components/admin/TextField.vue'
import AppMenuSection from './components/admin/AppMenuSection.vue'

const {
defaultBackgroundURL,

backgroundMime,
backgroundURL,
backgroundColor,
canThemeIcons,
docUrl,
Expand Down Expand Up @@ -190,32 +202,6 @@ const primaryColorPickerField = {
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 backgroundColorPickerField = {
name: 'background_color',
value: backgroundColor,
displayName: t('theming', '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.'),
}

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',
Expand Down Expand Up @@ -278,18 +264,17 @@ export default {
TextField,
},

emits: [
'update:theming',
],

textFields,

data() {
return {
backgroundMime,
backgroundURL,
backgroundColor,
defaultBackgroundColor: '#0069c3',

logoMime,

textFields,
backgroundColorPickerField,
primaryColorPickerField,
fileInputFields,
advancedTextFields,
advancedFileInputFields,
userThemingField,
Expand All @@ -300,34 +285,64 @@ export default {
docUrlIcons,
isThemable,
notThemableErrorMessage,

defaultBackground: this.calculateDefaultBackground(),
}
},

computed: {
cssBackgroundImage() {
if (this.backgroundURL) {
return `url('${this.backgroundURL}')`
}
return 'unset'
},
},

watch: {
backgroundColorPickerField: {
deep: true,
handler() {
this.defaultBackground = this.calculateDefaultBackground()
},
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)
const style = window.getComputedStyle(document.body).backgroundImage
const match = style.match(/url\("(http.+)"\)/)
if (!match) {
return '#0082c9'
}
const context = document.createElement('canvas').getContext('2d')
const img = new Image()
img.src = match[1]
context.imageSmoothingEnabled = true
context.drawImage(img, 0, 0, 1, 1)
return '#' + [...context.getImageData(0, 0, 1, 1).data.slice(0, 3)].map(toHex).join('')

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
})
},
},
}
Expand All @@ -349,15 +364,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%;
Expand Down
Loading