From 373e8a21567a0d8c3c9ffceac011c296819523eb Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Thu, 14 Nov 2024 18:19:18 +0100 Subject: [PATCH] feat(systemtags): add color support Signed-off-by: skjnldsv --- apps/dav/lib/SystemTag/SystemTagPlugin.php | 5 + .../SystemTag/SystemTagsInUseCollection.php | 2 +- .../composer/composer/autoload_classmap.php | 2 + .../composer/composer/autoload_static.php | 2 + .../Version31000Date20241018063111.php | 8 +- .../Version31000Date20241114171300.php | 43 +++++++ .../src/components/SystemTagPicker.vue | 105 +++++++++++++----- apps/systemtags/src/services/api.ts | 3 +- apps/systemtags/src/types.ts | 2 + lib/private/SystemTag/SystemTag.php | 23 +--- lib/private/SystemTag/SystemTagManager.php | 2 +- lib/public/SystemTag/ISystemTag.php | 7 ++ 12 files changed, 151 insertions(+), 53 deletions(-) rename {core/Migrations => apps/systemtags/lib/Migration}/Version31000Date20241018063111.php (89%) create mode 100644 apps/systemtags/lib/Migration/Version31000Date20241114171300.php diff --git a/apps/dav/lib/SystemTag/SystemTagPlugin.php b/apps/dav/lib/SystemTag/SystemTagPlugin.php index 00585953b2917..ae04efe6356cb 100644 --- a/apps/dav/lib/SystemTag/SystemTagPlugin.php +++ b/apps/dav/lib/SystemTag/SystemTagPlugin.php @@ -49,6 +49,7 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin { public const NUM_FILES_PROPERTYNAME = '{http://nextcloud.org/ns}files-assigned'; public const REFERENCE_FILEID_PROPERTYNAME = '{http://nextcloud.org/ns}reference-fileid'; public const OBJECTIDS_PROPERTYNAME = '{http://nextcloud.org/ns}object-ids'; + public const COLOR_PROPERTYNAME = '{http://nextcloud.org/ns}color'; /** * @var \Sabre\DAV\Server $server @@ -243,6 +244,10 @@ public function handleGetProperties( return $this->tagManager->canUserAssignTag($node->getSystemTag(), $this->userSession->getUser()) ? 'true' : 'false'; }); + $propFind->handle(self::COLOR_PROPERTYNAME, function () use ($node) { + return $node->getSystemTag()->getColor() ?? ''; + }); + $propFind->handle(self::GROUPS_PROPERTYNAME, function () use ($node) { if (!$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) { // property only available for admins diff --git a/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php b/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php index 9b02a5be42c5e..f11482b04ee1e 100644 --- a/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php +++ b/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php @@ -73,7 +73,7 @@ public function getChildren(): array { $result = $this->systemTagsInFilesDetector->detectAssignedSystemTagsIn($userFolder, $this->mediaType); $children = []; foreach ($result as $tagData) { - $tag = new SystemTag((string)$tagData['id'], $tagData['name'], (bool)$tagData['visibility'], (bool)$tagData['editable'], $tagData['etag']); + $tag = new SystemTag((string)$tagData['id'], $tagData['name'], (bool)$tagData['visibility'], (bool)$tagData['editable'], $tagData['etag'], $tagData['color']); // read only, so we can submit the isAdmin parameter as false generally $node = new SystemTagNode($tag, $user, false, $this->systemTagManager, $this->tagMapper); $node->setNumberOfFiles((int)$tagData['number_files']); diff --git a/apps/systemtags/composer/composer/autoload_classmap.php b/apps/systemtags/composer/composer/autoload_classmap.php index 20174ee466797..e6c60728ff6ea 100644 --- a/apps/systemtags/composer/composer/autoload_classmap.php +++ b/apps/systemtags/composer/composer/autoload_classmap.php @@ -16,6 +16,8 @@ 'OCA\\SystemTags\\Listeners\\BeforeSabrePubliclyLoadedListener' => $baseDir . '/../lib/Listeners/BeforeSabrePubliclyLoadedListener.php', 'OCA\\SystemTags\\Listeners\\BeforeTemplateRenderedListener' => $baseDir . '/../lib/Listeners/BeforeTemplateRenderedListener.php', 'OCA\\SystemTags\\Listeners\\LoadAdditionalScriptsListener' => $baseDir . '/../lib/Listeners/LoadAdditionalScriptsListener.php', + 'OCA\\SystemTags\\Migration\\Version31000Date20241018063111' => $baseDir . '/../lib/Migration/Version31000Date20241018063111.php', + 'OCA\\SystemTags\\Migration\\Version31000Date20241114171300' => $baseDir . '/../lib/Migration/Version31000Date20241114171300.php', 'OCA\\SystemTags\\Search\\TagSearchProvider' => $baseDir . '/../lib/Search/TagSearchProvider.php', 'OCA\\SystemTags\\Settings\\Admin' => $baseDir . '/../lib/Settings/Admin.php', ); diff --git a/apps/systemtags/composer/composer/autoload_static.php b/apps/systemtags/composer/composer/autoload_static.php index 04dae4aa52f2a..3d61ea1a1c17d 100644 --- a/apps/systemtags/composer/composer/autoload_static.php +++ b/apps/systemtags/composer/composer/autoload_static.php @@ -31,6 +31,8 @@ class ComposerStaticInitSystemTags 'OCA\\SystemTags\\Listeners\\BeforeSabrePubliclyLoadedListener' => __DIR__ . '/..' . '/../lib/Listeners/BeforeSabrePubliclyLoadedListener.php', 'OCA\\SystemTags\\Listeners\\BeforeTemplateRenderedListener' => __DIR__ . '/..' . '/../lib/Listeners/BeforeTemplateRenderedListener.php', 'OCA\\SystemTags\\Listeners\\LoadAdditionalScriptsListener' => __DIR__ . '/..' . '/../lib/Listeners/LoadAdditionalScriptsListener.php', + 'OCA\\SystemTags\\Migration\\Version31000Date20241018063111' => __DIR__ . '/..' . '/../lib/Migration/Version31000Date20241018063111.php', + 'OCA\\SystemTags\\Migration\\Version31000Date20241114171300' => __DIR__ . '/..' . '/../lib/Migration/Version31000Date20241114171300.php', 'OCA\\SystemTags\\Search\\TagSearchProvider' => __DIR__ . '/..' . '/../lib/Search/TagSearchProvider.php', 'OCA\\SystemTags\\Settings\\Admin' => __DIR__ . '/..' . '/../lib/Settings/Admin.php', ); diff --git a/core/Migrations/Version31000Date20241018063111.php b/apps/systemtags/lib/Migration/Version31000Date20241018063111.php similarity index 89% rename from core/Migrations/Version31000Date20241018063111.php rename to apps/systemtags/lib/Migration/Version31000Date20241018063111.php index ce4c42df15918..f813c4e0dee5d 100644 --- a/core/Migrations/Version31000Date20241018063111.php +++ b/apps/systemtags/lib/Migration/Version31000Date20241018063111.php @@ -7,7 +7,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace OC\Core\Migrations; +namespace OCA\SystemTags\Migration; use Closure; use Doctrine\DBAL\Types\Types; @@ -26,12 +26,6 @@ #[AddIndex(table: 'systemtag_object_mapping', type: IndexType::INDEX, description: 'Adding objecttype index to systemtag_object_mapping')] class Version31000Date20241018063111 extends SimpleMigrationStep { - /** - * @param IOutput $output - * @param Closure(): ISchemaWrapper $schemaClosure - * @param array $options - * @return null|ISchemaWrapper - */ public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper { /** @var ISchemaWrapper $schema */ $schema = $schemaClosure(); diff --git a/apps/systemtags/lib/Migration/Version31000Date20241114171300.php b/apps/systemtags/lib/Migration/Version31000Date20241114171300.php new file mode 100644 index 0000000000000..5830d3aefae63 --- /dev/null +++ b/apps/systemtags/lib/Migration/Version31000Date20241114171300.php @@ -0,0 +1,43 @@ +hasTable('systemtag')) { + $table = $schema->getTable('systemtag'); + + if (!$table->hasColumn('color')) { + $table->addColumn('color', Types::STRING, [ + 'notnull' => false, + 'length' => 6, + ]); + } + } + + return $schema; + } +} diff --git a/apps/systemtags/src/components/SystemTagPicker.vue b/apps/systemtags/src/components/SystemTagPicker.vue index 8ed26ce9cb3b0..9f36dee598d07 100644 --- a/apps/systemtags/src/components/SystemTagPicker.vue +++ b/apps/systemtags/src/components/SystemTagPicker.vue @@ -31,34 +31,48 @@ -
- - {{ formatTagName(tag) }} - - - {{ input.trim() }}
- {{ t('systemtags', 'Create new tag') }} - -
-
+ :style="{'--color-primary-element': tag.color ? `#${tag.color}` : null}" + class="systemtags-picker__tag"> + + {{ formatTagName(tag) }} + + + + + + + +
  • + + {{ input.trim() }}
    + {{ t('systemtags', 'Create new tag') }} + +
    +
  • +
    @@ -104,12 +118,14 @@ import { defineComponent } from 'vue' import { emit } from '@nextcloud/event-bus' import { sanitize } from 'dompurify' import { showError, showInfo } from '@nextcloud/dialogs' +import { getCapabilities } from '@nextcloud/capabilities' import { getLanguage, n, t } from '@nextcloud/l10n' import escapeHTML from 'escape-html' import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' import NcChip from '@nextcloud/vue/dist/Components/NcChip.js' +import NcColorPicker from '@nextcloud/vue/dist/Components/NcColorPicker.js' import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js' import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js' import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js' @@ -118,11 +134,14 @@ import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' import TagIcon from 'vue-material-design-icons/Tag.vue' import CheckIcon from 'vue-material-design-icons/CheckCircle.vue' import PlusIcon from 'vue-material-design-icons/Plus.vue' +import CircleIcon from 'vue-material-design-icons/Circle.vue' import { getNodeSystemTags, setNodeSystemTags } from '../utils' import { createTag, fetchTag, fetchTags, getTagObjects, setTagObjects } from '../services/api' import logger from '../services/logger' +const primaryColor = getCapabilities()?.theming?.['color-element'] || '#00679e' + type TagListCount = { string: number } @@ -143,6 +162,7 @@ export default defineComponent({ NcCheckboxRadioSwitch, // eslint-disable-next-line vue/no-unused-components NcChip, + NcColorPicker, NcDialog, NcEmptyContent, NcLoadingIcon, @@ -150,6 +170,7 @@ export default defineComponent({ NcTextField, PlusIcon, TagIcon, + CircleIcon, }, props: { @@ -162,6 +183,7 @@ export default defineComponent({ setup() { return { emit, + primaryColor, Status, t, } @@ -345,6 +367,10 @@ export default defineComponent({ return tag.displayName }, + onColorChange(tag: TagWithId, color: string) { + tag.color = color.replace('#', '') + }, + isChecked(tag: TagWithId): boolean { return tag.displayName in this.tagList && this.tagList[tag.displayName] === this.nodes.length @@ -506,6 +532,35 @@ export default defineComponent({ gap: var(--default-grid-baseline); display: flex; flex-direction: column; + + li { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + + // Make switch full width + :deep(.checkbox-radio-switch) { + width: 100%; + + .checkbox-content { + // adjust width + max-width: none; + // recalculate padding + box-sizing: border-box; + min-height: calc(var(--default-grid-baseline) * 2 + var(--default-clickable-area)); + } + } + } + + .systemtags-picker__tag-color { + margin-inline-start: var(--default-grid-baseline); + color: var(--color-primary-element); + width: var(--default-clickable-area); + height: var(--default-clickable-area); + display: flex; + } + .systemtags-picker__tag-create { :deep(span) { text-align: start; diff --git a/apps/systemtags/src/services/api.ts b/apps/systemtags/src/services/api.ts index 3262ccd3a878f..4651c739b9488 100644 --- a/apps/systemtags/src/services/api.ts +++ b/apps/systemtags/src/services/api.ts @@ -15,7 +15,7 @@ import { formatTag, parseIdFromLocation, parseTags } from '../utils' import { logger } from '../logger.js' export const fetchTagsPayload = ` - + @@ -23,6 +23,7 @@ export const fetchTagsPayload = ` + ` diff --git a/apps/systemtags/src/types.ts b/apps/systemtags/src/types.ts index 161e4d742475a..6e4f03227e05c 100644 --- a/apps/systemtags/src/types.ts +++ b/apps/systemtags/src/types.ts @@ -8,6 +8,8 @@ export interface BaseTag { userVisible: boolean userAssignable: boolean readonly canAssign: boolean // Computed server-side + etag?: string + color?: string } export type Tag = BaseTag & { diff --git a/lib/private/SystemTag/SystemTag.php b/lib/private/SystemTag/SystemTag.php index 8c64f2389d01e..1e3e40e3218dd 100644 --- a/lib/private/SystemTag/SystemTag.php +++ b/lib/private/SystemTag/SystemTag.php @@ -17,40 +17,26 @@ public function __construct( private bool $userVisible, private bool $userAssignable, private ?string $etag = null, + private ?string $color = null ) { } - /** - * {@inheritdoc} - */ public function getId(): string { return $this->id; } - /** - * {@inheritdoc} - */ public function getName(): string { return $this->name; } - /** - * {@inheritdoc} - */ public function isUserVisible(): bool { return $this->userVisible; } - /** - * {@inheritdoc} - */ public function isUserAssignable(): bool { return $this->userAssignable; } - /** - * {@inheritdoc} - */ public function getAccessLevel(): int { if (!$this->userVisible) { return self::ACCESS_LEVEL_INVISIBLE; @@ -63,10 +49,11 @@ public function getAccessLevel(): int { return self::ACCESS_LEVEL_PUBLIC; } - /** - * {@inheritdoc} - */ public function getETag(): ?string { return $this->etag; } + + public function getColor(): ?string { + return $this->color; + } } diff --git a/lib/private/SystemTag/SystemTagManager.php b/lib/private/SystemTag/SystemTagManager.php index 70bb8e6e70b25..b72d23d2a1eb2 100644 --- a/lib/private/SystemTag/SystemTagManager.php +++ b/lib/private/SystemTag/SystemTagManager.php @@ -361,7 +361,7 @@ public function canUserSeeTag(ISystemTag $tag, ?IUser $user): bool { } private function createSystemTagFromRow($row): SystemTag { - return new SystemTag((string)$row['id'], $row['name'], (bool)$row['visibility'], (bool)$row['editable'], $row['etag']); + return new SystemTag((string)$row['id'], $row['name'], (bool)$row['visibility'], (bool)$row['editable'], $row['etag'], $row['color']); } /** diff --git a/lib/public/SystemTag/ISystemTag.php b/lib/public/SystemTag/ISystemTag.php index 593c127ba63c1..4fd93831955bf 100644 --- a/lib/public/SystemTag/ISystemTag.php +++ b/lib/public/SystemTag/ISystemTag.php @@ -89,4 +89,11 @@ public function getAccessLevel(): int; * @since 31.0.0 */ public function getETag(): ?string; + + /** + * Returns the color of the tag + * + * @since 31.0.0 + */ + public function getColor(): ?string; }