Browse Source
Migrating themes to Theming app
Migrating themes to Theming app
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>pull/31751/head
No known key found for this signature in database
GPG Key ID: 60C25B8C072916CF
24 changed files with 982 additions and 167 deletions
-
73apps/accessibility/css/style.scss
-
5apps/theming/appinfo/info.xml
-
114apps/theming/appinfo/routes.php
-
BINapps/theming/img/dark.jpg
-
BINapps/theming/img/default.jpg
-
BINapps/theming/img/highcontrast.jpg
-
BINapps/theming/img/opendyslexic.jpg
-
111apps/theming/lib/Controller/UserThemeController.php
-
34apps/theming/lib/ITheme.php
-
83apps/theming/lib/Service/ThemesService.php
-
26apps/theming/lib/Settings/Admin.php
-
20apps/theming/lib/Settings/AdminSection.php
-
93apps/theming/lib/Settings/Personal.php
-
100apps/theming/lib/Settings/PersonalSection.php
-
12apps/theming/lib/Themes/DarkHighContrastTheme.php
-
12apps/theming/lib/Themes/DarkTheme.php
-
22apps/theming/lib/Themes/DefaultTheme.php
-
75apps/theming/lib/Themes/DyslexiaFont.php
-
12apps/theming/lib/Themes/HighContrastTheme.php
-
175apps/theming/src/UserThemes.vue
-
121apps/theming/src/components/ItemPreview.vue
-
32apps/theming/src/settings.js
-
26apps/theming/templates/settings-personal.php
-
3webpack.modules.js
@ -1,73 +0,0 @@ |
|||
// Rules we could port to the rest of Nextcloud too |
|||
|
|||
// Proper highlight for links and focus feedback |
|||
#accessibility a { |
|||
font-weight: bold; |
|||
|
|||
&:hover, |
|||
&:focus { |
|||
text-decoration: underline; |
|||
} |
|||
} |
|||
|
|||
// Highlight checkbox label in bold for focus feedback |
|||
// Drawback: Text width increases a bit |
|||
#accessibility .checkbox:focus + label { |
|||
font-weight: bold; |
|||
} |
|||
|
|||
// Limit width of settings sections for readability |
|||
#accessibility.section p { |
|||
max-width: 800px; |
|||
} |
|||
|
|||
// End of rules we could port to rest of Nextcloud |
|||
|
|||
|
|||
|
|||
.preview-list { |
|||
display: flex; |
|||
flex-direction: column; |
|||
max-width: 800px; |
|||
} |
|||
|
|||
.preview { |
|||
display: flex; |
|||
justify-content: flex-start; |
|||
margin-top: 3em; |
|||
position: relative; |
|||
|
|||
&, |
|||
* { |
|||
user-select: none; |
|||
} |
|||
|
|||
.preview-image { |
|||
flex-basis: 200px; |
|||
flex-shrink: 0; |
|||
margin-right: 1em; |
|||
background-position: top left; |
|||
background-size: cover; |
|||
background-repeat: no-repeat; |
|||
border-radius: var(--border-radius); |
|||
} |
|||
|
|||
.preview-description { |
|||
display: flex; |
|||
flex-direction: column; |
|||
|
|||
label { |
|||
padding: 12px 0; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@media (max-width: ($breakpoint-mobile / 2)) { |
|||
.app-settings #accessibility .preview-list .preview { |
|||
display: unset; |
|||
|
|||
.preview-image { |
|||
height: 150px; |
|||
} |
|||
} |
|||
} |
|||
|
After Width: 2880 | Height: 1800 | Size: 369 KiB |
|
After Width: 2880 | Height: 1800 | Size: 382 KiB |
|
After Width: 1200 | Height: 600 | Size: 64 KiB |
|
After Width: 750 | Height: 450 | Size: 26 KiB |
@ -0,0 +1,111 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2018 John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> |
|||
* @copyright Copyright (c) 2019 Janis Köhr <janiskoehr@icloud.com> |
|||
* |
|||
* @author Christoph Wurst <christoph@winzerhof-wurst.at> |
|||
* @author Daniel Kesselberg <mail@danielkesselberg.de> |
|||
* @author Janis Köhr <janis.koehr@novatec-gmbh.de> |
|||
* @author John Molakvoæ <skjnldsv@protonmail.com> |
|||
* @author Roeland Jago Douma <roeland@famdouma.nl> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
* |
|||
*/ |
|||
namespace OCA\Theming\Controller; |
|||
|
|||
use OCA\Theming\Service\ThemesService; |
|||
use OCP\AppFramework\Http\DataResponse; |
|||
use OCP\AppFramework\OCS\OCSBadRequestException; |
|||
use OCP\AppFramework\OCSController; |
|||
use OCP\IConfig; |
|||
use OCP\IRequest; |
|||
use OCP\IUserSession; |
|||
use OCP\PreConditionNotMetException; |
|||
|
|||
class UserThemeController extends OCSController { |
|||
|
|||
protected string $userId; |
|||
private IConfig $config; |
|||
private IUserSession $userSession; |
|||
private ThemesService $themesService; |
|||
|
|||
/** |
|||
* Config constructor. |
|||
*/ |
|||
public function __construct(string $appName, |
|||
IRequest $request, |
|||
IConfig $config, |
|||
IUserSession $userSession, |
|||
ThemesService $themesService) { |
|||
parent::__construct($appName, $request); |
|||
$this->config = $config; |
|||
$this->userSession = $userSession; |
|||
$this->themesService = $themesService; |
|||
$this->userId = $userSession->getUser()->getUID(); |
|||
} |
|||
|
|||
/** |
|||
* @NoAdminRequired |
|||
* |
|||
* Enable theme |
|||
* |
|||
* @param string $themeId the theme ID |
|||
* @return DataResponse |
|||
* @throws OCSBadRequestException|PreConditionNotMetException |
|||
*/ |
|||
public function enableTheme(string $themeId): DataResponse { |
|||
if ($themeId === '' || !$themeId) { |
|||
throw new OCSBadRequestException('Invalid theme id: ' . $themeId); |
|||
} |
|||
|
|||
$themes = $this->themesService->getThemes(); |
|||
if (!isset($themes[$themeId])) { |
|||
throw new OCSBadRequestException('Invalid theme id: ' . $themeId); |
|||
} |
|||
|
|||
// Enable selected theme
|
|||
$this->themesService->enableTheme($themes[$themeId]); |
|||
return new DataResponse(); |
|||
} |
|||
|
|||
/** |
|||
* @NoAdminRequired |
|||
* |
|||
* Disable theme |
|||
* |
|||
* @param string $themeId the theme ID |
|||
* @return DataResponse |
|||
* @throws OCSBadRequestException|PreConditionNotMetException |
|||
*/ |
|||
public function disableTheme(string $themeId): DataResponse { |
|||
if ($themeId === '' || !$themeId) { |
|||
throw new OCSBadRequestException('Invalid theme id: ' . $themeId); |
|||
} |
|||
|
|||
$themes = $this->themesService->getThemes(); |
|||
if (!isset($themes[$themeId])) { |
|||
throw new OCSBadRequestException('Invalid theme id: ' . $themeId); |
|||
} |
|||
|
|||
// Enable selected theme
|
|||
$this->themesService->disableTheme($themes[$themeId]); |
|||
return new DataResponse(); |
|||
} |
|||
} |
|||
@ -0,0 +1,93 @@ |
|||
<?php |
|||
/** |
|||
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com> |
|||
* @copyright Copyright (c) 2019 Janis Köhr <janiskoehr@icloud.com> |
|||
* |
|||
* @author Christoph Wurst <christoph@winzerhof-wurst.at> |
|||
* @author John Molakvoæ <skjnldsv@protonmail.com> |
|||
* @author Roeland Jago Douma <roeland@famdouma.nl> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
* |
|||
*/ |
|||
namespace OCA\Theming\Settings; |
|||
|
|||
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; |
|||
|
|||
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; |
|||
} |
|||
|
|||
public function getForm(): TemplateResponse { |
|||
$themes = array_map(function($theme) { |
|||
return [ |
|||
'id' => $theme->getId(), |
|||
'type' => $theme->getType(), |
|||
'title' => $theme->getTitle(), |
|||
'enableLabel' => $theme->getEnableLabel(), |
|||
'description' => $theme->getDescription(), |
|||
'enabled' => $this->themesService->isEnabled($theme), |
|||
]; |
|||
}, $this->themesService->getThemes()); |
|||
|
|||
$this->initialStateService->provideInitialState('themes', array_values($themes)); |
|||
Util::addScript($this->appName, 'theming-settings'); |
|||
|
|||
return new TemplateResponse($this->appName, 'settings-personal'); |
|||
} |
|||
|
|||
/** |
|||
* @return string the section ID, e.g. 'sharing' |
|||
* @since 9.1 |
|||
*/ |
|||
public function getSection(): string { |
|||
return $this->appName; |
|||
} |
|||
|
|||
/** |
|||
* @return int whether the form should be rather on the top or bottom of |
|||
* the admin section. The forms are arranged in ascending order of the |
|||
* priority values. It is required to return a value between 0 and 100. |
|||
* |
|||
* E.g.: 70 |
|||
* @since 9.1 |
|||
*/ |
|||
public function getPriority(): int { |
|||
return 40; |
|||
} |
|||
} |
|||
@ -0,0 +1,100 @@ |
|||
<?php |
|||
/** |
|||
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com> |
|||
* |
|||
* @author Christoph Wurst <christoph@winzerhof-wurst.at> |
|||
* @author John Molakvoæ <skjnldsv@protonmail.com> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
* |
|||
*/ |
|||
namespace OCA\Theming\Settings; |
|||
|
|||
use OCP\IL10N; |
|||
use OCP\IURLGenerator; |
|||
use OCP\Settings\IIconSection; |
|||
|
|||
class PersonalSection implements IIconSection { |
|||
|
|||
/** @var string */ |
|||
protected $appName; |
|||
|
|||
/** @var IURLGenerator */ |
|||
private $urlGenerator; |
|||
|
|||
/** @var IL10N */ |
|||
private $l; |
|||
|
|||
/** |
|||
* Personal Section constructor. |
|||
* |
|||
* @param string $appName |
|||
* @param IURLGenerator $urlGenerator |
|||
* @param IL10N $l |
|||
*/ |
|||
public function __construct(string $appName, |
|||
IURLGenerator $urlGenerator, |
|||
IL10N $l) { |
|||
$this->appName = $appName; |
|||
$this->urlGenerator = $urlGenerator; |
|||
$this->l = $l; |
|||
} |
|||
|
|||
/** |
|||
* returns the relative path to an 16*16 icon describing the section. |
|||
* e.g. '/core/img/places/files.svg' |
|||
* |
|||
* @returns string |
|||
* @since 13.0.0 |
|||
*/ |
|||
public function getIcon() { |
|||
return $this->urlGenerator->imagePath($this->appName, 'app-dark.svg'); |
|||
} |
|||
|
|||
/** |
|||
* returns the ID of the section. It is supposed to be a lower case string, |
|||
* e.g. 'ldap' |
|||
* |
|||
* @returns string |
|||
* @since 9.1 |
|||
*/ |
|||
public function getID() { |
|||
return $this->appName; |
|||
} |
|||
|
|||
/** |
|||
* returns the translated name as it should be displayed, e.g. 'LDAP / AD |
|||
* integration'. Use the L10N service to translate it. |
|||
* |
|||
* @return string |
|||
* @since 9.1 |
|||
*/ |
|||
public function getName() { |
|||
return $this->l->t('Appearance and accessibility'); |
|||
} |
|||
|
|||
/** |
|||
* @return int whether the form should be rather on the top or bottom of |
|||
* the settings navigation. The sections are arranged in ascending order of |
|||
* the priority values. It is required to return a value between 0 and 99. |
|||
* |
|||
* E.g.: 70 |
|||
* @since 9.1 |
|||
*/ |
|||
public function getPriority() { |
|||
return 15; |
|||
} |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
<?php |
|||
declare(strict_types=1); |
|||
/** |
|||
* @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com> |
|||
* |
|||
* @author Joas Schilling <coding@schilljs.com> |
|||
* @author John Molakvoæ <skjnldsv@protonmail.com> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
* |
|||
*/ |
|||
namespace OCA\Theming\Themes; |
|||
|
|||
use OCA\Theming\ITheme; |
|||
|
|||
class DyslexiaFont extends DefaultTheme implements ITheme { |
|||
|
|||
public function getId(): string { |
|||
return 'opendyslexic'; |
|||
} |
|||
|
|||
public function getType(): int { |
|||
return ITheme::TYPE_FONT; |
|||
} |
|||
|
|||
public function getTitle(): string { |
|||
return $this->l->t('Dyslexia font'); |
|||
} |
|||
|
|||
public function getEnableLabel(): string { |
|||
return $this->l->t('Enable dyslexia font'); |
|||
} |
|||
|
|||
public function getDescription(): string { |
|||
return $this->l->t('OpenDyslexic is a free typeface/font designed to mitigate some of the common reading errors caused by dyslexia.'); |
|||
} |
|||
|
|||
public function getCSSVariables(): array { |
|||
$variables = parent::getCSSVariables(); |
|||
$originalFontFace = $variables['--font-face']; |
|||
|
|||
$variables = [ |
|||
'--font-face' => 'OpenDyslexic, ' . $originalFontFace |
|||
]; |
|||
|
|||
return $variables; |
|||
} |
|||
} |
|||
|
|||
// @font-face {
|
|||
// font-family: 'OpenDyslexic';
|
|||
// font-style: normal;
|
|||
// font-weight: 400;
|
|||
// src: url('../fonts/OpenDyslexic-Regular.woff') format('woff');
|
|||
// }
|
|||
|
|||
// @font-face {
|
|||
// font-family: 'OpenDyslexic';
|
|||
// font-style: normal;
|
|||
// font-weight: 700;
|
|||
// src: url('../fonts/OpenDyslexic-Bold.woff') format('woff');
|
|||
// }
|
|||
@ -0,0 +1,175 @@ |
|||
<template> |
|||
<SettingsSection class="theming" :title="t('themes', 'Appaerance and accessibility')"> |
|||
<p v-html="description" /> |
|||
<p v-html="descriptionDetail" /> |
|||
|
|||
<div class="theming__preview-list"> |
|||
<ItemPreview v-for="theme in themes" |
|||
:key="theme.id" |
|||
:theme="theme" |
|||
:selected="selectedTheme.id === theme.id" |
|||
:themes="themes" |
|||
type="theme" |
|||
@change="changeTheme" /> |
|||
<ItemPreview v-for="theme in fonts" |
|||
:key="theme.id" |
|||
:theme="theme" |
|||
:selected="theme.enabled" |
|||
:themes="fonts" |
|||
type="font" |
|||
@change="changeFont" /> |
|||
</div> |
|||
</SettingsSection> |
|||
</template> |
|||
|
|||
<script> |
|||
import { generateOcsUrl } from '@nextcloud/router' |
|||
import { loadState } from '@nextcloud/initial-state' |
|||
import axios from '@nextcloud/axios' |
|||
import SettingsSection from '@nextcloud/vue/dist/Components/SettingsSection' |
|||
|
|||
import ItemPreview from './components/ItemPreview' |
|||
|
|||
const availableThemes = loadState('theming', 'themes', []) |
|||
|
|||
console.debug('Available themes', availableThemes) |
|||
|
|||
export default { |
|||
name: 'UserThemes', |
|||
components: { |
|||
ItemPreview, |
|||
SettingsSection, |
|||
}, |
|||
|
|||
data() { |
|||
return { |
|||
availableThemes, |
|||
} |
|||
}, |
|||
|
|||
computed: { |
|||
themes() { |
|||
return this.availableThemes.filter(theme => theme.type === 1) |
|||
}, |
|||
fonts() { |
|||
return this.availableThemes.filter(theme => theme.type === 2) |
|||
}, |
|||
|
|||
// Selected theme, fallback on first (default) if none |
|||
selectedTheme() { |
|||
return this.themes.find(theme => theme.enabled === true) || this.themes[0] |
|||
}, |
|||
|
|||
description() { |
|||
// using the `t` replace method escape html, we have to do it manually :/ |
|||
return t( |
|||
'theming', |
|||
'Universal access is very important to us. We follow web standards and check to make everything usable also without mouse, and assistive software such as screenreaders. We aim to be compliant with the {guidelines}Web Content Accessibility Guidelines{linkend} 2.1 on AA level, with the high contrast theme even on AAA level.' |
|||
) |
|||
.replace('{guidelines}', this.guidelinesLink) |
|||
.replace('{linkend}', '</a>') |
|||
}, |
|||
guidelinesLink() { |
|||
return '<a target="_blank" href="https://www.w3.org/WAI/standards-guidelines/wcag/" rel="noreferrer nofollow">' |
|||
}, |
|||
descriptionDetail() { |
|||
return t( |
|||
'theming', |
|||
'If you find any issues, don’t hesitate to report them on {issuetracker}our issue tracker{linkend}. And if you want to get involved, come join {designteam}our design team{linkend}!' |
|||
) |
|||
.replace('{issuetracker}', this.issuetrackerLink) |
|||
.replace('{designteam}', this.designteamLink) |
|||
.replace(/\{linkend\}/g, '</a>') |
|||
}, |
|||
issuetrackerLink() { |
|||
return '<a target="_blank" href="https://github.com/nextcloud/server/issues/" rel="noreferrer nofollow">' |
|||
}, |
|||
designteamLink() { |
|||
return '<a target="_blank" href="https://nextcloud.com/design" rel="noreferrer nofollow">' |
|||
}, |
|||
}, |
|||
methods: { |
|||
changeTheme({ enabled, id }) { |
|||
// Reset selected and select new one |
|||
this.themes.forEach(theme => { |
|||
if (theme.id === id && enabled) { |
|||
theme.enabled = true |
|||
document.body.setAttribute(`data-theme-${theme.id}`, true) |
|||
return |
|||
} |
|||
theme.enabled = false |
|||
document.body.removeAttribute(`data-theme-${theme.id}`) |
|||
}) |
|||
|
|||
this.selectItem(enabled, id) |
|||
}, |
|||
changeFont({ enabled, id }) { |
|||
// Reset selected and select new one |
|||
this.fonts.forEach(font => { |
|||
if (font.id === id && enabled) { |
|||
font.enabled = true |
|||
document.body.setAttribute(`data-theme-${font.id}`, true) |
|||
return |
|||
} |
|||
font.enabled = false |
|||
document.body.removeAttribute(`data-theme-${font.id}`) |
|||
}) |
|||
|
|||
this.selectItem(enabled, id) |
|||
}, |
|||
|
|||
/** |
|||
* Commit a change and force reload css |
|||
* Fetching the file again will trigger the server update |
|||
* |
|||
* @param {boolean} enabled the theme state |
|||
* @param {string} themeId the theme ID to change |
|||
*/ |
|||
async selectItem(enabled, themeId) { |
|||
try { |
|||
if (enabled) { |
|||
await axios({ |
|||
url: generateOcsUrl('apps/theming/api/v1/theme/{themeId}/enable', { themeId }), |
|||
method: 'PUT', |
|||
}) |
|||
} else { |
|||
await axios({ |
|||
url: generateOcsUrl('apps/theming/api/v1/theme/{themeId}', { themeId }), |
|||
method: 'DELETE', |
|||
}) |
|||
} |
|||
|
|||
} catch (err) { |
|||
console.error(err, err.response) |
|||
OC.Notification.showTemporary(t('theming', err.response.data.ocs.meta.message + '. Unable to apply the setting.')) |
|||
} |
|||
}, |
|||
}, |
|||
} |
|||
</script> |
|||
<style lang="scss" scoped> |
|||
|
|||
.theming { |
|||
// Limit width of settings sections for readability |
|||
p { |
|||
max-width: 800px; |
|||
} |
|||
|
|||
// Proper highlight for links and focus feedback |
|||
&::v-deep a { |
|||
font-weight: bold; |
|||
|
|||
&:hover, |
|||
&:focus { |
|||
text-decoration: underline; |
|||
} |
|||
} |
|||
|
|||
&__preview-list { |
|||
display: flex; |
|||
flex-direction: column; |
|||
max-width: 800px; |
|||
} |
|||
} |
|||
|
|||
</style> |
|||
@ -0,0 +1,121 @@ |
|||
<template> |
|||
<div class="theming__preview"> |
|||
<div class="theming__preview-image" :style="{ backgroundImage: 'url(' + img + ')' }" /> |
|||
<div class="theming__preview-description"> |
|||
<h3>{{ theme.title }}</h3> |
|||
<p>{{ theme.description }}</p> |
|||
<CheckboxRadioSwitch class="theming__preview-toggle" |
|||
:checked.sync="checked" |
|||
:name="name" |
|||
:type="switchType"> |
|||
{{ theme.enableLabel }} |
|||
</CheckboxRadioSwitch> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { generateFilePath} from '@nextcloud/router' |
|||
import CheckboxRadioSwitch from '@nextcloud/vue/dist/Components/CheckboxRadioSwitch' |
|||
|
|||
export default { |
|||
name: 'ItemPreview', |
|||
components: { |
|||
CheckboxRadioSwitch, |
|||
}, |
|||
props: { |
|||
theme: { |
|||
type: Object, |
|||
required: true, |
|||
}, |
|||
selected: { |
|||
type: Boolean, |
|||
default: false, |
|||
}, |
|||
type: { |
|||
type: String, |
|||
default: '', |
|||
}, |
|||
themes: { |
|||
type: Array, |
|||
default: () => [], |
|||
}, |
|||
}, |
|||
computed: { |
|||
switchType() { |
|||
return this.themes.length === 1 ? 'switch' : 'radio' |
|||
}, |
|||
|
|||
name() { |
|||
return this.switchType === 'radio' ? this.type : null |
|||
}, |
|||
|
|||
img() { |
|||
return generateFilePath('theming', 'img', this.theme.id + '.jpg') |
|||
}, |
|||
|
|||
checked: { |
|||
get() { |
|||
return this.selected |
|||
}, |
|||
set(checked) { |
|||
console.debug('Selecting theme', this.theme, checked) |
|||
|
|||
// If this is a radio, we can only enable |
|||
if (this.switchType === 'radio') { |
|||
this.$emit('change', { enabled: true, id: this.theme.id }) |
|||
return |
|||
} |
|||
|
|||
// If this is a switch, we can disable the theme |
|||
this.$emit('change', { enabled: checked === true, id: this.theme.id }) |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
</script> |
|||
<style lang="scss" scoped> |
|||
|
|||
.theming__preview { |
|||
position: relative; |
|||
display: flex; |
|||
justify-content: flex-start; |
|||
height: 140px; |
|||
margin-top: 3em; |
|||
|
|||
&, |
|||
* { |
|||
user-select: none; |
|||
} |
|||
|
|||
&-image { |
|||
flex-basis: 200px; |
|||
flex-shrink: 0; |
|||
margin-right: 30px; |
|||
border-radius: var(--border-radius); |
|||
background-repeat: no-repeat; |
|||
background-position: top left; |
|||
background-size: cover; |
|||
} |
|||
|
|||
&-description { |
|||
display: flex; |
|||
flex-direction: column; |
|||
|
|||
label { |
|||
padding: 12px 0; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@media (max-width: (1024 / 2)) { |
|||
.theming__preview { |
|||
display: unset; |
|||
|
|||
&-image { |
|||
height: 150px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
</style> |
|||
@ -0,0 +1,32 @@ |
|||
/** |
|||
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com> |
|||
* |
|||
* @author John Molakvoæ <skjnldsv@protonmail.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/>.
|
|||
* |
|||
*/ |
|||
|
|||
import Vue from 'vue' |
|||
import App from './UserThemes.vue' |
|||
|
|||
// bind to window
|
|||
Vue.prototype.OC = OC |
|||
Vue.prototype.t = t |
|||
|
|||
const View = Vue.extend(App) |
|||
const accessibility = new View() |
|||
accessibility.$mount('#theming') |
|||
@ -0,0 +1,26 @@ |
|||
<?php |
|||
/** |
|||
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com> |
|||
* |
|||
* @author John Molakvoæ <skjnldsv@protonmail.com> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
* |
|||
*/ |
|||
|
|||
?>
|
|||
|
|||
<span id="theming"></span> |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue