Move background settings from dashboard app to Appearance and accessibility settings

Signed-off-by: greta <gretadoci@gmail.com>
Signed-off-by: Christopher Ng <chrng8@gmail.com>
This commit is contained in:
greta 2022-08-29 15:11:41 +02:00 committed by Christopher Ng
parent bd03c79785
commit 02cc42d40a
51 changed files with 424 additions and 223 deletions

View file

@ -29,8 +29,6 @@ return [
['name' => 'dashboard#index', 'url' => '/', 'verb' => 'GET'],
['name' => 'dashboard#updateLayout', 'url' => '/layout', 'verb' => 'POST'],
['name' => 'dashboard#updateStatuses', 'url' => '/statuses', 'verb' => 'POST'],
['name' => 'dashboard#getBackground', 'url' => '/background', 'verb' => 'GET'],
['name' => 'dashboard#setBackground', 'url' => '/background/{type}', 'verb' => 'POST'],
],
'ocs' => [
['name' => 'dashboardApi#getWidgetItems', 'url' => '/api/v1/widget-items', 'verb' => 'GET'],

View file

@ -28,16 +28,12 @@ declare(strict_types=1);
*/
namespace OCA\Dashboard\Controller;
use OCA\Dashboard\Service\BackgroundService;
use OCA\Files\Event\LoadSidebar;
use OCA\Viewer\Event\LoadViewer;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\FileDisplayResponse;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\App\IAppManager;
use OCP\AppFramework\Services\IInitialState;
use OCP\Dashboard\IManager;
use OCP\Dashboard\IWidget;
@ -52,38 +48,28 @@ class DashboardController extends Controller {
private $inititalState;
/** @var IEventDispatcher */
private $eventDispatcher;
/** @var IAppManager */
private $appManager;
/** @var IManager */
private $dashboardManager;
/** @var IConfig */
private $config;
/** @var string */
private $userId;
/**
* @var BackgroundService
*/
private $backgroundService;
public function __construct(
string $appName,
IRequest $request,
IInitialState $initialState,
IEventDispatcher $eventDispatcher,
IAppManager $appManager,
IManager $dashboardManager,
IConfig $config,
BackgroundService $backgroundService,
$userId
) {
parent::__construct($appName, $request);
$this->inititalState = $initialState;
$this->eventDispatcher = $eventDispatcher;
$this->appManager = $appManager;
$this->dashboardManager = $dashboardManager;
$this->config = $config;
$this->backgroundService = $backgroundService;
$this->userId = $userId;
}
@ -119,18 +105,10 @@ class DashboardController extends Controller {
// It does not matter if some statuses are missing from the array, missing ones are considered enabled
$statuses = ($statuses && count($statuses) > 0) ? $statuses : ['weather' => true];
// if theming app is enabled and wants to override default, we pass it
$themingDefaultBackground = $this->appManager->isEnabledForUser('theming')
? $this->config->getAppValue('theming', 'backgroundMime', '')
: '';
$this->inititalState->provideInitialState('themingDefaultBackground', $themingDefaultBackground);
$this->inititalState->provideInitialState('panels', $widgets);
$this->inititalState->provideInitialState('statuses', $statuses);
$this->inititalState->provideInitialState('layout', $userLayout);
$this->inititalState->provideInitialState('firstRun', $this->config->getUserValue($this->userId, 'dashboard', 'firstRun', '1') === '1');
$this->inititalState->provideInitialState('shippedBackgrounds', BackgroundService::SHIPPED_BACKGROUNDS);
$this->inititalState->provideInitialState('background', $this->config->getUserValue($this->userId, 'dashboard', 'background', 'default'));
$this->inititalState->provideInitialState('version', $this->config->getUserValue($this->userId, 'dashboard', 'backgroundVersion', 0));
$this->config->setUserValue($this->userId, 'dashboard', 'firstRun', '0');
$response = new TemplateResponse('dashboard', 'index', [
@ -165,54 +143,4 @@ class DashboardController extends Controller {
$this->config->setUserValue($this->userId, 'dashboard', 'statuses', $statuses);
return new JSONResponse(['statuses' => $statuses]);
}
/**
* @NoAdminRequired
*/
public function setBackground(string $type = 'default', string $value = ''): JSONResponse {
$currentVersion = (int)$this->config->getUserValue($this->userId, 'dashboard', 'backgroundVersion', '0');
try {
switch ($type) {
case 'shipped':
$this->backgroundService->setShippedBackground($value);
break;
case 'custom':
$this->backgroundService->setFileBackground($value);
break;
case 'color':
$this->backgroundService->setColorBackground($value);
break;
case 'default':
$this->backgroundService->setDefaultBackground();
break;
default:
return new JSONResponse(['error' => 'Invalid type provided'], Http::STATUS_BAD_REQUEST);
}
} catch (\InvalidArgumentException $e) {
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
} catch (\Throwable $e) {
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
}
$currentVersion++;
$this->config->setUserValue($this->userId, 'dashboard', 'backgroundVersion', (string)$currentVersion);
return new JSONResponse([
'type' => $type,
'value' => $value,
'version' => $this->config->getUserValue($this->userId, 'dashboard', 'backgroundVersion', $currentVersion)
]);
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function getBackground(): Http\Response {
$file = $this->backgroundService->getBackground();
if ($file !== null) {
$response = new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => $file->getMimeType()]);
$response->cacheFor(24 * 60 * 60);
return $response;
}
return new NotFoundResponse();
}
}

View file

@ -73,11 +73,6 @@
<a v-if="isAdmin" :href="appStoreUrl" class="button">{{ t('dashboard', 'Get more widgets from the App Store') }}</a>
<h3>{{ t('dashboard', 'Change background image') }}</h3>
<BackgroundSettings :background="background"
:theming-default-background="themingDefaultBackground"
@update:background="updateBackground" />
<h3>{{ t('dashboard', 'Weather service') }}</h3>
<p>
{{ t('dashboard', 'For your privacy, the weather data is requested by your Nextcloud server on your behalf so the weather service receives no personal information.') }}
@ -93,7 +88,7 @@
</template>
<script>
import { generateUrl } from '@nextcloud/router'
import { generateUrl, imagePath } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
import { loadState } from '@nextcloud/initial-state'
import axios from '@nextcloud/axios'
@ -103,16 +98,16 @@ import NcModal from '@nextcloud/vue/dist/Components/NcModal'
import Pencil from 'vue-material-design-icons/Pencil.vue'
import Vue from 'vue'
import isMobile from './mixins/isMobile'
import BackgroundSettings from './components/BackgroundSettings'
import getBackgroundUrl from './helpers/getBackgroundUrl'
import isMobile from './mixins/isMobile.js'
import { getBackgroundUrl } from './helpers/getBackgroundUrl.js'
const panels = loadState('dashboard', 'panels')
const firstRun = loadState('dashboard', 'firstRun')
const background = loadState('dashboard', 'background')
const themingDefaultBackground = loadState('dashboard', 'themingDefaultBackground')
const version = loadState('dashboard', 'version')
const shippedBackgroundList = loadState('dashboard', 'shippedBackgrounds')
const background = loadState('theming', 'background')
const backgroundVersion = loadState('theming', 'backgroundVersion')
const themingDefaultBackground = loadState('theming', 'themingDefaultBackground')
const shippedBackgroundList = loadState('theming', 'shippedBackgrounds')
const statusInfo = {
weather: {
@ -128,7 +123,6 @@ const statusInfo = {
export default {
name: 'DashboardApp',
components: {
BackgroundSettings,
NcButton,
Draggable,
NcModal,
@ -158,12 +152,11 @@ export default {
statuses: {},
background,
themingDefaultBackground,
version,
}
},
computed: {
backgroundImage() {
return getBackgroundUrl(this.background, this.version, this.themingDefaultBackground)
return getBackgroundUrl(this.background, backgroundVersion, this.themingDefaultBackground)
},
backgroundStyle() {
if ((this.background === 'default' && this.themingDefaultBackground === 'backgroundColor')
@ -175,7 +168,6 @@ export default {
backgroundImage: this.background === 'default' ? 'var(--image-main-background)' : `url('${this.backgroundImage}')`,
}
},
greeting() {
const time = this.timer.getHours()
@ -280,6 +272,32 @@ export default {
},
methods: {
updateGlobalStyles() {
// Override primary-invert-if-bright and color-primary-text if background is set
const isBackgroundBright = shippedBackgroundList[this.background]?.theming === 'dark'
if (isBackgroundBright) {
document.querySelector('#header').style.setProperty('--primary-invert-if-bright', 'invert(100%)')
document.querySelector('#header').style.setProperty('--color-primary-text', '#000000')
// document.body.removeAttribute('data-theme-dark')
// document.body.setAttribute('data-theme-light', 'true')
} else {
document.querySelector('#header').style.setProperty('--primary-invert-if-bright', 'no')
document.querySelector('#header').style.setProperty('--color-primary-text', '#ffffff')
// document.body.removeAttribute('data-theme-light')
// document.body.setAttribute('data-theme-dark', 'true')
}
const themeElements = [document.documentElement, document.querySelector('#header'), document.querySelector('body')]
for (const element of themeElements) {
if (this.background === 'default') {
element.style.setProperty('--image-main-background', `url('${imagePath('core', 'app-background.jpg')}')`)
} else if (this.background.match(/#[0-9A-Fa-f]{6}/g)) {
element.style.setProperty('--image-main-background', undefined)
} else {
element.style.setProperty('--image-main-background', this.backgroundStyle.backgroundImage)
}
}
},
/**
* Method to register panels that will be called by the integrating apps
*
@ -354,30 +372,6 @@ export default {
this.firstRun = false
}, 1000)
},
updateBackground(data) {
this.background = data.type === 'custom' || data.type === 'default' ? data.type : data.value
this.version = data.version
this.updateGlobalStyles()
},
updateGlobalStyles() {
// Override primary-invert-if-bright and color-primary-text if background is set
const isBackgroundBright = shippedBackgroundList[this.background]?.theming === 'dark'
if (isBackgroundBright) {
document.querySelector('#header').style.setProperty('--primary-invert-if-bright', 'invert(100%)')
document.querySelector('#header').style.setProperty('--color-primary-text', '#000000')
// document.body.removeAttribute('data-theme-dark')
// document.body.setAttribute('data-theme-light', 'true')
} else {
document.querySelector('#header').style.setProperty('--primary-invert-if-bright', 'no')
document.querySelector('#header').style.setProperty('--color-primary-text', '#ffffff')
// document.body.removeAttribute('data-theme-light')
// document.body.setAttribute('data-theme-dark', 'true')
}
document.documentElement.style.setProperty('--image-main-background', this.backgroundStyle.backgroundImage)
document.querySelector('#header').style.setProperty('--image-main-background', this.backgroundStyle.backgroundImage)
document.querySelector('body').style.setProperty('--image-main-background', this.backgroundStyle.backgroundImage)
},
updateSkipLink() {
// Make sure "Skip to main content" link points to the app content
document.getElementsByClassName('skip-navigation')[0].setAttribute('href', '#app-dashboard')

View file

@ -23,9 +23,9 @@
*/
import { generateUrl } from '@nextcloud/router'
import prefixWithBaseUrl from './prefixWithBaseUrl'
import { prefixWithBaseUrl } from './prefixWithBaseUrl.js'
export default (background, time = 0, themingDefaultBackground = '') => {
export const getBackgroundUrl = (background, time = 0, themingDefaultBackground = '') => {
const enabledThemes = window.OCA?.Theming?.enabledThemes || []
const isDarkTheme = (enabledThemes.length === 0 || enabledThemes[0] === 'default')
? window.matchMedia('(prefers-color-scheme: dark)').matches
@ -42,7 +42,7 @@ export default (background, time = 0, themingDefaultBackground = '') => {
return prefixWithBaseUrl('kamil-porembinski-clouds.jpg')
} else if (background === 'custom') {
return generateUrl('/apps/dashboard/background') + '?v=' + time
return generateUrl('/apps/theming/background') + '?v=' + time
}
return prefixWithBaseUrl(background)

View file

@ -22,4 +22,4 @@
import { generateFilePath } from '@nextcloud/router'
export default (url) => generateFilePath('dashboard', '', 'img/') + url
export const prefixWithBaseUrl = (url) => generateFilePath('theming', '', 'img/background/') + url

View file

@ -78,6 +78,16 @@ return [
'verb' => 'GET',
'requirements' => ['image' => '.+']
],
[
'name' => 'userTheme#getBackground',
'url' => '/background',
'verb' => 'GET',
],
[
'name' => 'userTheme#setBackground',
'url' => '/background/{type}',
'verb' => 'POST',
],
],
'ocs' => [
[

View file

Before

Width:  |  Height:  |  Size: 1 MiB

After

Width:  |  Height:  |  Size: 1 MiB

View file

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View file

Before

Width:  |  Height:  |  Size: 249 KiB

After

Width:  |  Height:  |  Size: 249 KiB

View file

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

View file

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

View file

Before

Width:  |  Height:  |  Size: 523 KiB

After

Width:  |  Height:  |  Size: 523 KiB

View file

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View file

Before

Width:  |  Height:  |  Size: 1 MiB

After

Width:  |  Height:  |  Size: 1 MiB

View file

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

View file

Before

Width:  |  Height:  |  Size: 186 KiB

After

Width:  |  Height:  |  Size: 186 KiB

View file

Before

Width:  |  Height:  |  Size: 934 KiB

After

Width:  |  Height:  |  Size: 934 KiB

View file

Before

Width:  |  Height:  |  Size: 279 KiB

After

Width:  |  Height:  |  Size: 279 KiB

View file

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View file

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View file

Before

Width:  |  Height:  |  Size: 454 KiB

After

Width:  |  Height:  |  Size: 454 KiB

View file

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

View file

Before

Width:  |  Height:  |  Size: 239 KiB

After

Width:  |  Height:  |  Size: 239 KiB

View file

@ -30,9 +30,15 @@ declare(strict_types=1);
*/
namespace OCA\Theming\Controller;
use OCA\Theming\AppInfo\Application;
use OCA\Theming\ITheme;
use OCA\Theming\Service\BackgroundService;
use OCA\Theming\Service\ThemesService;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\FileDisplayResponse;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCS\OCSForbiddenException;
use OCP\AppFramework\OCSController;
@ -47,6 +53,7 @@ class UserThemeController extends OCSController {
private IConfig $config;
private IUserSession $userSession;
private ThemesService $themesService;
private BackgroundService $backgroundService;
/**
* Config constructor.
@ -55,11 +62,13 @@ class UserThemeController extends OCSController {
IRequest $request,
IConfig $config,
IUserSession $userSession,
ThemesService $themesService) {
ThemesService $themesService,
BackgroundService $backgroundService) {
parent::__construct($appName, $request);
$this->config = $config;
$this->userSession = $userSession;
$this->themesService = $themesService;
$this->backgroundService = $backgroundService;
$this->userId = $userSession->getUser()->getUID();
}
@ -91,7 +100,7 @@ class UserThemeController extends OCSController {
*/
public function disableTheme(string $themeId): DataResponse {
$theme = $this->validateTheme($themeId);
// Enable selected theme
$this->themesService->disableTheme($theme);
return new DataResponse();
@ -124,4 +133,54 @@ class UserThemeController extends OCSController {
return $themes[$themeId];
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function getBackground(): Http\Response {
$file = $this->backgroundService->getBackground();
if ($file !== null) {
$response = new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => $file->getMimeType()]);
$response->cacheFor(24 * 60 * 60, false, true);
return $response;
}
return new NotFoundResponse();
}
/**
* @NoAdminRequired
*/
public function setBackground(string $type = 'default', string $value = ''): JSONResponse {
$currentVersion = (int)$this->config->getUserValue($this->userId, Application::APP_ID, 'backgroundVersion', '0');
try {
switch ($type) {
case 'shipped':
$this->backgroundService->setShippedBackground($value);
break;
case 'custom':
$this->backgroundService->setFileBackground($value);
break;
case 'color':
$this->backgroundService->setColorBackground($value);
break;
case 'default':
$this->backgroundService->setDefaultBackground();
break;
default:
return new JSONResponse(['error' => 'Invalid type provided'], Http::STATUS_BAD_REQUEST);
}
} catch (\InvalidArgumentException $e) {
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
} catch (\Throwable $e) {
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
}
$currentVersion++;
$this->config->setUserValue($this->userId, Application::APP_ID, 'backgroundVersion', (string)$currentVersion);
return new JSONResponse([
'type' => $type,
'value' => $value,
'version' => $this->config->getUserValue($this->userId, Application::APP_ID, 'backgroundVersion', $currentVersion)
]);
}
}

View file

@ -26,40 +26,72 @@ declare(strict_types=1);
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 OCA\Theming\Service\ThemesService;
use OCP\AppFramework\Services\IInitialState;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\IConfig;
use OCP\IInitialStateService;
use OCP\IServerContainer;
use OCP\IURLGenerator;
use OCP\IUserSession;
use Psr\Container\ContainerInterface;
class BeforeTemplateRenderedListener implements IEventListener {
private IInitialStateService $initialStateService;
private IServerContainer $serverContainer;
private IInitialState $initialState;
private ContainerInterface $container;
private ThemeInjectionService $themeInjectionService;
private IUserSession $userSession;
private IConfig $config;
public function __construct(
IInitialStateService $initialStateService,
IServerContainer $serverContainer,
ThemeInjectionService $themeInjectionService
IInitialState $initialState,
ContainerInterface $container,
ThemeInjectionService $themeInjectionService,
IUserSession $userSession,
IConfig $config
) {
$this->initialStateService = $initialStateService;
$this->serverContainer = $serverContainer;
$this->initialState = $initialState;
$this->container = $container;
$this->themeInjectionService = $themeInjectionService;
$this->userSession = $userSession;
$this->config = $config;
}
public function handle(Event $event): void {
$serverContainer = $this->serverContainer;
$this->initialStateService->provideLazyInitialState(Application::APP_ID, 'data', function () use ($serverContainer) {
return $serverContainer->query(JSDataService::class);
});
$this->initialState->provideLazyInitialState(
'data',
fn () => $this->container->get(JSDataService::class),
);
$this->themeInjectionService->injectHeaders();
$user = $this->userSession->getUser();
if (!empty($user)) {
$userId = $user->getUID();
$this->initialState->provideInitialState(
'background',
$this->config->getUserValue($userId, Application::APP_ID, 'background', 'default'),
);
$this->initialState->provideInitialState(
'backgroundVersion',
$this->config->getUserValue($userId, Application::APP_ID, 'backgroundVersion', 0),
);
$this->initialState->provideInitialState(
'themingDefaultBackground',
$this->config->getAppValue('theming', 'backgroundMime', ''),
);
$this->initialState->provideInitialState(
'shippedBackgrounds',
BackgroundService::SHIPPED_BACKGROUNDS,
);
}
// Making sure to inject just after core
\OCP\Util::addScript('theming', 'theming', 'core');
}

View file

@ -7,6 +7,7 @@ declare(strict_types=1);
*
* @author Jan C. Borchardt <hey@jancborchardt.net>
* @author Julius Härtl <jus@bitgrid.net>
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
@ -24,10 +25,11 @@ declare(strict_types=1);
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Dashboard\Service;
namespace OCA\Theming\Service;
use InvalidArgumentException;
use OC\User\NoUserException;
use OCA\Theming\AppInfo\Application;
use OCP\Files\File;
use OCP\Files\IAppData;
use OCP\Files\IRootFolder;
@ -109,21 +111,18 @@ class BackgroundService {
'theming' => self::THEMING_MODE_DARK,
]
];
/**
* @var IRootFolder
*/
private $rootFolder;
/**
* @var IAppData
*/
private $appData;
/**
* @var IConfig
*/
private $config;
private $userId;
public function __construct(IRootFolder $rootFolder, IAppData $appData, IConfig $config, $userId) {
private IRootFolder $rootFolder;
private IAppData $appData;
private IConfig $config;
private string $userId;
public function __construct(
IRootFolder $rootFolder,
IAppData $appData,
IConfig $config,
?string $userId
) {
if ($userId === null) {
return;
}
@ -134,7 +133,7 @@ class BackgroundService {
}
public function setDefaultBackground(): void {
$this->config->deleteUserValue($this->userId, 'dashboard', 'background');
$this->config->deleteUserValue($this->userId, Application::APP_ID, 'background');
}
/**
@ -146,7 +145,7 @@ class BackgroundService {
* @throws NoUserException
*/
public function setFileBackground($path): void {
$this->config->setUserValue($this->userId, 'dashboard', 'background', 'custom');
$this->config->setUserValue($this->userId, Application::APP_ID, 'background', 'custom');
$userFolder = $this->rootFolder->getUserFolder($this->userId);
/** @var File $file */
$file = $userFolder->get($path);
@ -161,18 +160,18 @@ class BackgroundService {
if (!array_key_exists($fileName, self::SHIPPED_BACKGROUNDS)) {
throw new InvalidArgumentException('The given file name is invalid');
}
$this->config->setUserValue($this->userId, 'dashboard', 'background', $fileName);
$this->config->setUserValue($this->userId, Application::APP_ID, 'background', $fileName);
}
public function setColorBackground(string $color): void {
if (!preg_match('/^#([0-9a-f]{3}|[0-9a-f]{6})$/i', $color)) {
throw new InvalidArgumentException('The given color is invalid');
}
$this->config->setUserValue($this->userId, 'dashboard', 'background', $color);
$this->config->setUserValue($this->userId, Application::APP_ID, 'background', $color);
}
public function getBackground(): ?ISimpleFile {
$background = $this->config->getUserValue($this->userId, 'dashboard', 'background', 'default');
$background = $this->config->getUserValue($this->userId, Application::APP_ID, 'background', 'default');
if ($background === 'custom') {
try {
return $this->getAppDataFolder()->getFile('background.jpg');

View file

@ -30,7 +30,6 @@ use OCA\Theming\Service\ThemesService;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\IConfig;
use OCP\IUserSession;
use OCP\Settings\ISettings;
use OCP\Util;
@ -38,18 +37,15 @@ class Personal implements ISettings {
protected string $appName;
private IConfig $config;
private IUserSession $userSession;
private ThemesService $themesService;
private IInitialState $initialStateService;
public function __construct(string $appName,
IConfig $config,
IUserSession $userSession,
ThemesService $themesService,
IInitialState $initialStateService) {
$this->appName = $appName;
$this->config = $config;
$this->userSession = $userSession;
$this->themesService = $themesService;
$this->initialStateService = $initialStateService;
}

View file

@ -24,10 +24,11 @@ declare(strict_types=1);
*/
namespace OCA\Theming\Themes;
use OCA\Theming\AppInfo\Application;
use OCA\Theming\ImageManager;
use OCA\Theming\ITheme;
use OCA\Theming\ThemingDefaults;
use OCA\Theming\Util;
use OCA\Theming\ITheme;
use OCP\App\IAppManager;
use OCP\IConfig;
use OCP\IL10N;
@ -98,7 +99,7 @@ class DefaultTheme implements ITheme {
$colorPrimaryElementLight = $this->util->mix($colorPrimaryElement, $colorMainBackground, -80);
$hasCustomLogoHeader = $this->imageManager->hasImage('logo') || $this->imageManager->hasImage('logoheader');
$hasCustomPrimaryColour = !empty($this->config->getAppValue('theming', 'color'));
$hasCustomPrimaryColour = !empty($this->config->getAppValue(Application::APP_ID, 'color'));
$variables = [
'--color-main-background' => $colorMainBackground,
@ -210,7 +211,7 @@ class DefaultTheme implements ITheme {
'--image-main-background' => "url('" . $this->urlGenerator->imagePath('core', 'app-background.jpg') . "')",
];
$backgroundDeleted = $this->config->getAppValue('theming', 'backgroundMime', '') === 'backgroundColor';
$backgroundDeleted = $this->config->getAppValue(Application::APP_ID, 'backgroundMime', '') === 'backgroundColor';
// If primary as background has been request or if we have a custom primary colour
// let's not define the background image
if ($backgroundDeleted || $hasCustomPrimaryColour) {
@ -240,13 +241,13 @@ class DefaultTheme implements ITheme {
$appManager = Server::get(IAppManager::class);
$userSession = Server::get(IUserSession::class);
$user = $userSession->getUser();
if ($appManager->isEnabledForUser('dashboard') && $user !== null) {
$dashboardBackground = $this->config->getUserValue($user->getUID(), 'dashboard', 'background', 'default');
if ($appManager->isEnabledForUser(Application::APP_ID) && $user !== null) {
$themingBackground = $this->config->getUserValue($user->getUID(), Application::APP_ID, 'background', 'default');
if ($dashboardBackground === 'custom') {
$variables['--image-main-background'] = "url('" . $this->urlGenerator->linkToRouteAbsolute('dashboard.dashboard.getBackground') . "')";
} elseif ($dashboardBackground !== 'default' && substr($dashboardBackground, 0, 1) !== '#') {
$variables['--image-main-background'] = "url('/apps/dashboard/img/" . $dashboardBackground . "')";
if ($themingBackground === 'custom') {
$variables['--image-main-background'] = "url('" . $this->urlGenerator->linkToRouteAbsolute('theming.theming.getBackground') . "')";
} elseif ($themingBackground !== 'default' && substr($themingBackground, 0, 1) !== '#') {
$variables['--image-main-background'] = "url('" . $this->urlGenerator->linkTo(Application::APP_ID, "/img/background/$themingBackground") . "')";
}
}

View file

@ -1,42 +1,83 @@
<!--
- @copyright Copyright (c) 2020 Julius Härtl <jus@bitgrid.net>
- @copyright Copyright (c) 2022 Greta Doci <gretadoci@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/>.
-
-->
<template>
<NcSettingsSection class="theming" :title="t('themes', 'Appearance and accessibility')">
<p v-html="description" />
<p v-html="descriptionDetail" />
<section>
<NcSettingsSection class="theming" :title="t('theming', 'Appearance and accessibility')">
<p v-html="description" />
<p v-html="descriptionDetail" />
<div class="theming__preview-list">
<ItemPreview v-for="theme in themes"
:key="theme.id"
:enforced="theme.id === enforceTheme"
:selected="selectedTheme.id === theme.id"
:theme="theme"
:unique="themes.length === 1"
type="theme"
@change="changeTheme" />
</div>
<div class="theming__preview-list">
<ItemPreview v-for="theme in themes"
:key="theme.id"
:enforced="theme.id === enforceTheme"
:selected="selectedTheme.id === theme.id"
:theme="theme"
:unique="themes.length === 1"
type="theme"
@change="changeTheme" />
</div>
<div class="theming__preview-list">
<ItemPreview v-for="theme in fonts"
:key="theme.id"
:selected="theme.enabled"
:theme="theme"
:unique="fonts.length === 1"
type="font"
@change="changeFont" />
</div>
</NcSettingsSection>
<div class="theming__preview-list">
<ItemPreview v-for="theme in fonts"
:key="theme.id"
:selected="theme.enabled"
:theme="theme"
:unique="fonts.length === 1"
type="font"
@change="changeFont" />
</div>
</NcSettingsSection>
<NcSettingsSection :title="t('theming', 'Background')"
class="background">
<p>{{ t('theming', 'Set a custom background') }}</p>
<BackgroundSettings class="background__grid"
:background="background"
:theming-default-background="themingDefaultBackground"
@update:background="updateBackground" />
</NcSettingsSection>
</section>
</template>
<script>
import { generateOcsUrl } from '@nextcloud/router'
import { generateOcsUrl, imagePath } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
import axios from '@nextcloud/axios'
import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection'
import ItemPreview from './components/ItemPreview'
import BackgroundSettings from './components/BackgroundSettings.vue'
import ItemPreview from './components/ItemPreview.vue'
import { getBackgroundUrl } from '../src/helpers/getBackgroundUrl.js'
const availableThemes = loadState('theming', 'themes', [])
const enforceTheme = loadState('theming', 'enforceTheme', '')
const background = loadState('theming', 'background')
const backgroundVersion = loadState('theming', 'backgroundVersion')
const themingDefaultBackground = loadState('theming', 'themingDefaultBackground')
const shippedBackgroundList = loadState('theming', 'shippedBackgrounds')
console.debug('Available themes', availableThemes)
export default {
@ -44,16 +85,32 @@ export default {
components: {
ItemPreview,
NcSettingsSection,
BackgroundSettings,
},
data() {
return {
availableThemes,
enforceTheme,
background,
themingDefaultBackground,
}
},
computed: {
backgroundImage() {
return getBackgroundUrl(this.background, backgroundVersion, this.themingDefaultBackground)
},
backgroundStyle() {
if ((this.background === 'default' && this.themingDefaultBackground === 'backgroundColor')
|| this.background.match(/#[0-9A-Fa-f]{6}/g)) {
return null
}
return {
backgroundImage: this.background === 'default' ? 'var(--image-main-background)' : `url('${this.backgroundImage}')`,
}
},
themes() {
return this.availableThemes.filter(theme => theme.type === 1)
},
@ -94,7 +151,40 @@ export default {
return '<a target="_blank" href="https://nextcloud.com/design" rel="noreferrer nofollow">'
},
},
mounted() {
this.updateGlobalStyles()
},
methods: {
updateBackground(data) {
this.background = (data.type === 'custom' || data.type === 'default') ? data.type : data.value
this.updateGlobalStyles()
},
updateGlobalStyles() {
// Override primary-invert-if-bright and color-primary-text if background is set
const isBackgroundBright = shippedBackgroundList[this.background]?.theming === 'dark'
if (isBackgroundBright) {
document.querySelector('#header').style.setProperty('--primary-invert-if-bright', 'invert(100%)')
document.querySelector('#header').style.setProperty('--color-primary-text', '#000000')
// document.body.removeAttribute('data-theme-dark')
// document.body.setAttribute('data-theme-light', 'true')
} else {
document.querySelector('#header').style.setProperty('--primary-invert-if-bright', 'no')
document.querySelector('#header').style.setProperty('--color-primary-text', '#ffffff')
// document.body.removeAttribute('data-theme-light')
// document.body.setAttribute('data-theme-dark', 'true')
}
const themeElements = [document.documentElement, document.querySelector('#header'), document.querySelector('body')]
for (const element of themeElements) {
if (this.background === 'default') {
element.style.setProperty('--image-main-background', `url('${imagePath('core', 'app-background.jpg')}')`)
} else if (this.background.match(/#[0-9A-Fa-f]{6}/g)) {
element.style.setProperty('--image-main-background', undefined)
} else {
element.style.setProperty('--image-main-background', this.backgroundStyle.backgroundImage)
}
}
},
changeTheme({ enabled, id }) {
// Reset selected and select new one
this.themes.forEach(theme => {
@ -194,11 +284,16 @@ export default {
}
}
.background {
&__grid {
margin-top: 30px;
}
}
@media (max-width: 1440px) {
.theming__preview-list {
display: flex;
flex-direction: column;
}
}
</style>

View file

@ -1,7 +1,10 @@
<!--
- @copyright Copyright (c) 2020 Julius Härtl <jus@bitgrid.net>
- @copyright Copyright (c) 2022 Greta Doci <gretadoci@gmail.com>
-
- @author Julius Härtl <jus@bitgrid.net>
- @author Greta Doci <gretadoci@gmail.com>
- @author Christopher Ng <chrng8@gmail.com>
-
- @license GNU AGPL version 3 or any later version
-
@ -26,19 +29,19 @@
:class="{ active: background === 'custom' }"
tabindex="0"
@click="pickFile">
{{ t('dashboard', 'Pick from Files') }}
{{ t('theming', 'Pick from Files') }}
</button>
<button class="background default"
tabindex="0"
:class="{ 'icon-loading': loading === 'default', active: background === 'default' }"
@click="setDefault">
{{ t('dashboard', 'Default image') }}
{{ t('theming', 'Default image') }}
</button>
<button class="background color"
:class="{ active: background === 'custom' }"
tabindex="0"
@click="pickColor">
{{ t('dashboard', 'Plain background') }}
{{ t('theming', 'Plain background') }}
</button>
<button v-for="shippedBackground in shippedBackgrounds"
:key="shippedBackground.name"
@ -53,14 +56,19 @@
<script>
import axios from '@nextcloud/axios'
import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
import { generateUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
import getBackgroundUrl from './../helpers/getBackgroundUrl'
import prefixWithBaseUrl from './../helpers/prefixWithBaseUrl'
const shippedBackgroundList = loadState('dashboard', 'shippedBackgrounds')
import { getBackgroundUrl } from '../helpers/getBackgroundUrl.js'
import { prefixWithBaseUrl } from '../helpers/prefixWithBaseUrl.js'
const shippedBackgroundList = loadState('theming', 'shippedBackgrounds')
export default {
name: 'BackgroundSettings',
directives: {
Tooltip,
},
props: {
background: {
type: String,
@ -73,18 +81,18 @@ export default {
},
data() {
return {
backgroundImage: generateUrl('/apps/dashboard/background') + '?v=' + Date.now(),
backgroundImage: generateUrl('/apps/theming/background') + '?v=' + Date.now(),
loading: false,
}
},
computed: {
shippedBackgrounds() {
return Object.keys(shippedBackgroundList).map((item) => {
return Object.keys(shippedBackgroundList).map(fileName => {
return {
name: item,
url: prefixWithBaseUrl(item),
preview: prefixWithBaseUrl('previews/' + item),
details: shippedBackgroundList[item],
name: fileName,
url: prefixWithBaseUrl(fileName),
preview: prefixWithBaseUrl('preview/' + fileName),
details: shippedBackgroundList[fileName],
}
})
},
@ -107,27 +115,27 @@ export default {
},
async setDefault() {
this.loading = 'default'
const result = await axios.post(generateUrl('/apps/dashboard/background/default'))
const result = await axios.post(generateUrl('/apps/theming/background/default'))
this.update(result.data)
},
async setShipped(shipped) {
this.loading = shipped
const result = await axios.post(generateUrl('/apps/dashboard/background/shipped'), { value: shipped })
const result = await axios.post(generateUrl('/apps/theming/background/shipped'), { value: shipped })
this.update(result.data)
},
async setFile(path) {
this.loading = 'custom'
const result = await axios.post(generateUrl('/apps/dashboard/background/custom'), { value: path })
const result = await axios.post(generateUrl('/apps/theming/background/custom'), { value: path })
this.update(result.data)
},
async pickColor() {
this.loading = 'color'
const color = OCA && OCA.Theming ? OCA.Theming.color : '#0082c9'
const result = await axios.post(generateUrl('/apps/dashboard/background/color'), { value: color })
const result = await axios.post(generateUrl('/apps/theming/background/color'), { value: color })
this.update(result.data)
},
pickFile() {
window.OC.dialogs.filepicker(t('dashboard', 'Insert from {productName}', { productName: OC.theme.name }), (path, type) => {
window.OC.dialogs.filepicker(t('theming', 'Insert from {productName}', { productName: OC.theme.name }), (path, type) => {
if (type === OC.dialogs.FILEPICKER_TYPE_CHOOSE) {
this.setFile(path)
}

View file

@ -0,0 +1,51 @@
/**
* @copyright Copyright (c) 2020 Julius Härtl <jus@bitgrid.net>
*
* @author Avior <florian.bouillon@delta-wings.net>
* @author Julien Veyssier <eneiluj@posteo.net>
* @author Julius Härtl <jus@bitgrid.net>
*
* @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/>.
*
*/
// FIXME hoist this into a package? The same logic is used in `apps/dashboard/src/helpers/getBackgroundUrl.js`
import { generateUrl } from '@nextcloud/router'
import { prefixWithBaseUrl } from './prefixWithBaseUrl.js'
export const getBackgroundUrl = (background, time = 0, themingDefaultBackground = '') => {
const enabledThemes = window.OCA?.Theming?.enabledThemes || []
const isDarkTheme = (enabledThemes.length === 0 || enabledThemes[0] === 'default')
? window.matchMedia('(prefers-color-scheme: dark)').matches
: enabledThemes.join('').indexOf('dark') !== -1
if (background === 'default') {
if (themingDefaultBackground && themingDefaultBackground !== 'backgroundColor') {
return generateUrl('/apps/theming/image/background') + '?v=' + window.OCA.Theming.cacheBuster
}
if (isDarkTheme) {
return prefixWithBaseUrl('eduardo-neves-pedra-azul.jpg')
}
return prefixWithBaseUrl('kamil-porembinski-clouds.jpg')
} else if (background === 'custom') {
return generateUrl('/apps/theming/background') + '?v=' + time
}
return prefixWithBaseUrl(background)
}

View file

@ -0,0 +1,27 @@
/**
* @copyright Copyright (c) 2020 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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/>.
*
*/
// FIXME hoist this into a package? The same logic is used in `apps/dashboard/src/helpers/prefixWithBaseUrl.js`
import { generateFilePath } from '@nextcloud/router'
export const prefixWithBaseUrl = (url) => generateFilePath('theming', '', 'img/background/') + url

View file

@ -22,8 +22,10 @@
*/
namespace OCA\Theming\Tests\Controller;
use OCA\Theming\AppInfo\Application;
use OCA\Theming\Controller\UserThemeController;
use OCA\Theming\ITheme;
use OCA\Theming\Service\BackgroundService;
use OCA\Theming\Themes\DarkHighContrastTheme;
use OCA\Theming\Themes\DarkTheme;
use OCA\Theming\Themes\DefaultTheme;
@ -52,6 +54,9 @@ class UserThemeControllerTest extends TestCase {
private $userSession;
/** @var ThemeService|MockObject */
private $themesService;
/** @var BackgroundService|MockObject */
private $backgroundService;
/** @var ITheme[] */
private $themes;
@ -61,6 +66,7 @@ class UserThemeControllerTest extends TestCase {
$this->config = $this->createMock(IConfig::class);
$this->userSession = $this->createMock(IUserSession::class);
$this->themesService = $this->createMock(ThemesService::class);
$this->backgroundService = $this->createMock(BackgroundService::class);
$this->themes = [
'default' => $this->createMock(DefaultTheme::class),
@ -80,11 +86,12 @@ class UserThemeControllerTest extends TestCase {
->willReturn('user');
$this->userThemeController = new UserThemeController(
'theming',
Application::APP_ID,
$this->request,
$this->config,
$this->userSession,
$this->themesService,
$this->backgroundService,
);
parent::setUp();

View file

@ -45,12 +45,10 @@ use OCP\AppFramework\Services\IInitialState;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUserSession;
use Test\TestCase;
class PersonalTest extends TestCase {
private IConfig $config;
private IUserSession $userSession;
private ThemesService $themesService;
private IInitialState $initialStateService;
@ -60,7 +58,6 @@ class PersonalTest extends TestCase {
protected function setUp(): void {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->userSession = $this->createMock(IUserSession::class);
$this->themesService = $this->createMock(ThemesService::class);
$this->initialStateService = $this->createMock(IInitialState::class);
@ -74,7 +71,6 @@ class PersonalTest extends TestCase {
$this->admin = new Personal(
Application::APP_ID,
$this->config,
$this->userSession,
$this->themesService,
$this->initialStateService
);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long