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>
pull/33733/head
greta 3 years ago committed by Christopher Ng
parent bd03c79785
commit 02cc42d40a
  1. 2
      apps/dashboard/appinfo/routes.php
  2. 72
      apps/dashboard/lib/Controller/DashboardController.php
  3. 76
      apps/dashboard/src/DashboardApp.vue
  4. 6
      apps/dashboard/src/helpers/getBackgroundUrl.js
  5. 2
      apps/dashboard/src/helpers/prefixWithBaseUrl.js
  6. 10
      apps/theming/appinfo/routes.php
  7. 0
      apps/theming/img/background/anatoly-mikhaltsov-butterfly-wing-scale.jpg
  8. 0
      apps/theming/img/background/bernard-spragg-new-zealand-fern.jpg
  9. 0
      apps/theming/img/background/bernie-cetonia-aurata-take-off-composition.jpg
  10. 0
      apps/theming/img/background/dejan-krsmanovic-ribbed-red-metal.jpg
  11. 0
      apps/theming/img/background/eduardo-neves-pedra-azul.jpg
  12. 0
      apps/theming/img/background/european-space-agency-barents-bloom.jpg
  13. 0
      apps/theming/img/background/hannes-fritz-flippity-floppity.jpg
  14. 0
      apps/theming/img/background/hannes-fritz-roulette.jpg
  15. 0
      apps/theming/img/background/hannes-fritz-sea-spray.jpg
  16. 0
      apps/theming/img/background/kamil-porembinski-clouds.jpg
  17. 0
      apps/theming/img/background/lali-masriera-yellow-bricks.jpg
  18. 0
      apps/theming/img/background/nasa-waxing-crescent-moon.jpg
  19. 0
      apps/theming/img/background/preview/anatoly-mikhaltsov-butterfly-wing-scale.jpg
  20. 0
      apps/theming/img/background/preview/bernard-spragg-new-zealand-fern.jpg
  21. 0
      apps/theming/img/background/preview/bernie-cetonia-aurata-take-off-composition.jpg
  22. 0
      apps/theming/img/background/preview/dejan-krsmanovic-ribbed-red-metal.jpg
  23. 0
      apps/theming/img/background/preview/eduardo-neves-pedra-azul.jpg
  24. 0
      apps/theming/img/background/preview/european-space-agency-barents-bloom.jpg
  25. 0
      apps/theming/img/background/preview/hannes-fritz-flippity-floppity.jpg
  26. 0
      apps/theming/img/background/preview/hannes-fritz-roulette.jpg
  27. 0
      apps/theming/img/background/preview/hannes-fritz-sea-spray.jpg
  28. 0
      apps/theming/img/background/preview/kamil-porembinski-clouds.jpg
  29. 0
      apps/theming/img/background/preview/lali-masriera-yellow-bricks.jpg
  30. 0
      apps/theming/img/background/preview/nasa-waxing-crescent-moon.jpg
  31. 0
      apps/theming/img/background/preview/rawpixel-pink-tapioca-bubbles.jpg
  32. 0
      apps/theming/img/background/preview/tommy-chau-already.jpg
  33. 0
      apps/theming/img/background/preview/tommy-chau-lion-rock-hill.jpg
  34. 0
      apps/theming/img/background/rawpixel-pink-tapioca-bubbles.jpg
  35. 0
      apps/theming/img/background/tommy-chau-already.jpg
  36. 0
      apps/theming/img/background/tommy-chau-lion-rock-hill.jpg
  37. 63
      apps/theming/lib/Controller/UserThemeController.php
  38. 62
      apps/theming/lib/Listener/BeforeTemplateRenderedListener.php
  39. 39
      apps/theming/lib/Service/BackgroundService.php
  40. 4
      apps/theming/lib/Settings/Personal.php
  41. 19
      apps/theming/lib/Themes/DefaultTheme.php
  42. 151
      apps/theming/src/UserThemes.vue
  43. 42
      apps/theming/src/components/BackgroundSettings.vue
  44. 51
      apps/theming/src/helpers/getBackgroundUrl.js
  45. 27
      apps/theming/src/helpers/prefixWithBaseUrl.js
  46. 9
      apps/theming/tests/Controller/UserThemeControllerTest.php
  47. 4
      apps/theming/tests/Settings/PersonalTest.php
  48. 4
      dist/dashboard-main.js
  49. 2
      dist/dashboard-main.js.map
  50. 4
      dist/theming-theming-settings.js
  51. 2
      dist/theming-theming-settings.js.map

@ -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'],

@ -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();
}
}

@ -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')

@ -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)

@ -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

@ -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' => [
[

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Before

Width:  |  Height:  |  Size: 186 KiB

After

Width:  |  Height:  |  Size: 186 KiB

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

@ -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)
]);
}
}

@ -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');
}

@ -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');

@ -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;
}

@ -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") . "')";
}
}

@ -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" />
<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>
<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 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>

@ -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)
}

@ -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)
}

@ -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

@ -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();

@ -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
Loading…
Cancel
Save