Skip to content

Commit

Permalink
fixup! fixup! feat(systemtags): add colors in bulk tagging action
Browse files Browse the repository at this point in the history
Signed-off-by: skjnldsv <[email protected]>
  • Loading branch information
skjnldsv committed Dec 3, 2024
1 parent e02f597 commit 98ff185
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 11 deletions.
2 changes: 1 addition & 1 deletion apps/systemtags/appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<summary>Collaborative tagging functionality which shares tags among people.</summary>
<description>Collaborative tagging functionality which shares tags among people. Great for teams.
(If you are a provider with a multi-tenancy installation, it is advised to deactivate this app as tags are shared.)</description>
<version>1.21.0</version>
<version>1.21.1</version>
<licence>agpl</licence>
<author>Vincent Petry</author>
<author>Joas Schilling</author>
Expand Down
28 changes: 19 additions & 9 deletions apps/systemtags/src/components/SystemTagPicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,13 @@

<!-- Color picker -->
<NcColorPicker :data-cy-systemtags-picker-tag-color="tag.id"
:value="`#${tag.color || primaryColor}`"
:value="`#${tag.color}`"
class="systemtags-picker__tag-color"
@update:value="onColorChange(tag, $event)">
<NcButton :aria-label="t('systemtags', 'Change tag color')" type="tertiary">
<template #icon>
<CircleIcon :size="24" />
<CircleIcon v-if="tag.color" :size="24" fill-color="var(--color-circle-icon)" />
<CircleOutlineIcon v-else :size="24" fill-color="var(--color-circle-icon)" />
<PencilIcon />
</template>
</NcButton>
Expand Down Expand Up @@ -138,6 +139,7 @@ import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
import CheckIcon from 'vue-material-design-icons/CheckCircle.vue'
import CircleIcon from 'vue-material-design-icons/Circle.vue'
import CircleOutlineIcon from 'vue-material-design-icons/CircleOutline.vue'
import PencilIcon from 'vue-material-design-icons/Pencil.vue'
import PlusIcon from 'vue-material-design-icons/Plus.vue'
import TagIcon from 'vue-material-design-icons/Tag.vue'
Expand All @@ -147,9 +149,6 @@ import { getNodeSystemTags, setNodeSystemTags } from '../utils'
import { elementColor, invertTextColor, isDarkModeEnabled } from '../utils/colorUtils'
import logger from '../services/logger'

const primaryColor = getComputedStyle(document.body)
.getPropertyValue('--color-primary-element')
.replace('#', '') || '0069c3'
const mainBackgroundColor = getComputedStyle(document.body)
.getPropertyValue('--color-main-background')
.replace('#', '') || (isDarkModeEnabled() ? '000000' : 'ffffff')
Expand All @@ -171,6 +170,7 @@ export default defineComponent({
components: {
CheckIcon,
CircleIcon,
CircleOutlineIcon,
NcButton,
NcCheckboxRadioSwitch,
// eslint-disable-next-line vue/no-unused-components
Expand All @@ -196,7 +196,6 @@ export default defineComponent({
setup() {
return {
emit,
primaryColor,
Status,
t,
}
Expand Down Expand Up @@ -529,9 +528,20 @@ export default defineComponent({
},

tagListStyle(tag: TagWithId): Record<string, string> {
const primaryElement = elementColor(`#${tag.color || primaryColor}`, `#${mainBackgroundColor}`)
// No color, no style
if (!tag.color) {
return {
// See inline system tag color
'--color-circle-icon': 'var(--color-text-maxcontrast)',
}
}

// Make the checkbox color the same as the tag color
// as well as the circle icon color picker
const primaryElement = elementColor(`#${tag.color}`, `#${mainBackgroundColor}`)
const textColor = invertTextColor(primaryElement) ? '#000000' : '#ffffff'
return {
'--color-circle-icon': 'var(--color-primary-element)',
'--color-primary': primaryElement,
'--color-primary-text': textColor,
'--color-primary-element': primaryElement,
Expand Down Expand Up @@ -587,7 +597,6 @@ export default defineComponent({

.systemtags-picker__tag-color button {
margin-inline-start: calc(var(--default-grid-baseline) * 2);
color: var(--color-primary-element);

span.pencil-icon {
display: none;
Expand All @@ -600,7 +609,8 @@ export default defineComponent({
.pencil-icon {
display: block;
}
.circle-icon {
.circle-icon,
.circle-outline-icon {
display: none;
}
}
Expand Down
10 changes: 9 additions & 1 deletion apps/systemtags/src/css/fileEntryInlineSystemTags.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 22px; // min-size - 2 * 5px padding
line-height: 20px; // min-size - 2 * 5px padding - 2 * 1px border
text-align: center;

&--more {
Expand All @@ -34,6 +34,14 @@
& + .files-list__system-tag {
margin-inline-start: 5px;
}

// With color
&[data-systemtag-color] {
border-color: var(--systemtag-color);
color: var(--systemtag-color);
border-width: 2px;
line-height: 18px; // min-size - 2 * 5px padding - 2 * 2px border
}
}

@media (min-width: 512px) {
Expand Down
4 changes: 4 additions & 0 deletions apps/systemtags/src/event-bus.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Node } from '@nextcloud/files'
import type { TagWithId } from './types'

declare module '@nextcloud/event-bus' {
interface NextcloudEvents {
'systemtags:node:updated': Node
'systemtags:tag:deleted': TagWithId
'systemtags:tag:updated': TagWithId
'systemtags:tag:created': TagWithId
}
}

Expand Down
48 changes: 48 additions & 0 deletions apps/systemtags/src/files_actions/inlineSystemTagsAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,40 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Node } from '@nextcloud/files'
import type { TagWithId } from '../types'
import { FileAction } from '@nextcloud/files'
import { subscribe } from '@nextcloud/event-bus'
import { t } from '@nextcloud/l10n'

import '../css/fileEntryInlineSystemTags.scss'
import { elementColor, isDarkModeEnabled } from '../utils/colorUtils'
import { fetchTags } from '../services/api'
import { getNodeSystemTags } from '../utils'

// Init tag cache
const tags: TagWithId[] = []
fetchTags().then((fetchedTags) => {
tags.push(...fetchedTags)
})

const renderTag = function(tag: string, isMore = false): HTMLElement {
const tagElement = document.createElement('li')
tagElement.classList.add('files-list__system-tag')
tagElement.setAttribute('data-systemtag-name', tag)
tagElement.textContent = tag

// Set the color if it exists
const cachedTag = tags.find((t) => t.displayName === tag)
if (cachedTag?.color) {
// Make sure contrast is good and follow WCAG guidelines
const mainBackgroundColor = getComputedStyle(document.body)
.getPropertyValue('--color-main-background')
.replace('#', '') || (isDarkModeEnabled() ? '000000' : 'ffffff')
const primaryElement = elementColor(`#${cachedTag.color}`, `#${mainBackgroundColor}`)
tagElement.style.setProperty('--systemtag-color', primaryElement)
tagElement.setAttribute('data-systemtag-color', 'true')
}

if (isMore) {
tagElement.classList.add('files-list__system-tag--more')
}
Expand Down Expand Up @@ -84,6 +106,7 @@ export const action = new FileAction({
order: 0,
})

// Update the system tags html when the node is updated
const updateSystemTagsHtml = function(node: Node) {
renderInline(node).then((systemTagsHtml) => {
document.querySelectorAll(`[data-systemtags-fileid="${node.fileid}"]`).forEach((element) => {
Expand All @@ -92,4 +115,29 @@ const updateSystemTagsHtml = function(node: Node) {
})
}

// Add and remove tags from the cache
const addTag = function(tag: TagWithId) {
tags.push(tag)
}
const removeTag = function(tag: TagWithId) {
tags.splice(tags.findIndex((t) => t.id === tag.id), 1)
}
const updateTag = function(tag: TagWithId) {
const index = tags.findIndex((t) => t.id === tag.id)
if (index !== -1) {
tags[index] = tag
}
updateSystemTagsColorAttribute(tag)
}
// Update the color attribute of the system tags
const updateSystemTagsColorAttribute = function(tag: TagWithId) {
document.querySelectorAll(`[data-systemtag-name="${tag.displayName}"]`).forEach((element) => {
(element as HTMLElement).style.setProperty('--systemtag-color', `#${tag.color}`)
})
}

// Subscribe to the events
subscribe('systemtags:node:updated', updateSystemTagsHtml)
subscribe('systemtags:tag:created', addTag)
subscribe('systemtags:tag:deleted', removeTag)
subscribe('systemtags:tag:updated', updateTag)
4 changes: 4 additions & 0 deletions apps/systemtags/src/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { t } from '@nextcloud/l10n'
import { davClient } from './davClient.js'
import { formatTag, parseIdFromLocation, parseTags } from '../utils'
import { logger } from '../logger.js'
import { emit } from '@nextcloud/event-bus'

export const fetchTagsPayload = `<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
Expand Down Expand Up @@ -82,6 +83,7 @@ export const createTag = async (tag: Tag | ServerTag): Promise<number> => {
})
const contentLocation = headers.get('content-location')
if (contentLocation) {
emit('systemtags:tag:created', tag)
return parseIdFromLocation(contentLocation)
}
logger.error(t('systemtags', 'Missing "Content-Location" header'))
Expand Down Expand Up @@ -115,6 +117,7 @@ export const updateTag = async (tag: TagWithId): Promise<void> => {
method: 'PROPPATCH',
data,
})
emit('systemtags:tag:updated', tag)
} catch (error) {
logger.error(t('systemtags', 'Failed to update tag'), { error })
throw new Error(t('systemtags', 'Failed to update tag'))
Expand All @@ -125,6 +128,7 @@ export const deleteTag = async (tag: TagWithId): Promise<void> => {
const path = '/systemtags/' + tag.id
try {
await davClient.deleteFile(path)
emit('systemtags:tag:deleted', tag)
} catch (error) {
logger.error(t('systemtags', 'Failed to delete tag'), { error })
throw new Error(t('systemtags', 'Failed to delete tag'))
Expand Down

0 comments on commit 98ff185

Please sign in to comment.