Tracker: labels on the card. (#2221)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov 2022-07-07 05:21:30 +03:00 committed by GitHub
parent bbe6c12474
commit 2450fd0fa9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 231 additions and 34 deletions

View File

@ -253,6 +253,7 @@
display: flex;
flex-direction: column;
padding: 0.5rem;
max-width: 50vw;
color: var(--caption-color);
background-color: var(--accent-bg-color);
border: 1px solid var(--divider-color);

View File

@ -0,0 +1,90 @@
<script lang="ts">
import { Doc } from '@anticrm/core'
import { createQuery } from '@anticrm/presentation'
import type { TagReference } from '@anticrm/tags'
import tags from '@anticrm/tags'
import { getEventPopupPositionElement, showPopup, resizeObserver } from '@anticrm/ui'
import TagReferencePresenter from './TagReferencePresenter.svelte'
import TagsEditorPopup from './TagsEditorPopup.svelte'
import { createEventDispatcher, afterUpdate } from 'svelte'
export let object: Doc
export let full: boolean
export let ckeckFilled: boolean = false
export let kind: 'short' | 'full' = 'short'
export let isEditable: boolean = false
export let action: (evt: MouseEvent) => Promise<void> | void = async () => {}
const dispatch = createEventDispatcher()
let items: TagReference[] = []
const query = createQuery()
$: query.query(tags.class.TagReference, { attachedTo: object._id }, (result) => {
items = result
})
async function tagsHandler (evt: MouseEvent): Promise<void> {
showPopup(TagsEditorPopup, { object }, getEventPopupPositionElement(evt))
}
let allWidth: number
const widths: number[] = []
afterUpdate(() => {
let count: number = 0
widths.forEach((i) => (count += i))
full = count > allWidth
dispatch('change', { full, ckeckFilled })
})
</script>
<div
class="labels-container"
style:justify-content={kind === 'short' ? 'space-between' : 'flex-start'}
style:flex-wrap={kind === 'short' ? 'nowrap' : 'wrap'}
use:resizeObserver={(element) => {
allWidth = element.clientWidth
}}
on:click|stopPropagation={(evt) => {
if (isEditable) tagsHandler(evt)
else action(evt)
}}
>
{#each items as value, i}
<div class="label-box wrap-{kind}">
<TagReferencePresenter {value} kind={'kanban-labels'} bind:realWidth={widths[i]} />
</div>
{/each}
</div>
<style lang="scss">
.labels-container {
overflow: hidden;
display: flex;
align-items: center;
flex-shrink: 0;
width: 100%;
min-width: 0;
border-radius: 0.25rem;
}
.label-box {
display: flex;
align-items: center;
flex-shrink: 10;
width: auto;
min-width: 0;
border-radius: 0.25rem;
transition: box-shadow 0.15s ease-in-out;
&:last-child {
flex-shrink: 0;
}
}
.wrap-short:not(:last-child) {
margin-right: 0.375rem;
}
.wrap-full {
margin: 0.125rem;
}
</style>

View File

@ -14,13 +14,14 @@
-->
<script lang="ts">
import type { TagReference } from '@anticrm/tags'
import { getPlatformColor, IconClose, Icon } from '@anticrm/ui'
import { getPlatformColor, IconClose, Icon, resizeObserver } from '@anticrm/ui'
import TagItem from './TagItem.svelte'
import { createEventDispatcher } from 'svelte'
export let value: TagReference
export let isEditable: boolean = false
export let kind: 'labels' | 'skills' = 'skills'
export let kind: 'labels' | 'kanban-labels' | 'skills' = 'skills'
export let realWidth: number | undefined = undefined
const dispatch = createEventDispatcher()
</script>
@ -28,8 +29,24 @@
{#if value}
{#if kind === 'skills'}
<TagItem tag={value} />
{:else if kind === 'kanban-labels'}
<button
class="label-container"
use:resizeObserver={(element) => {
realWidth = element.clientWidth
}}
>
<div class="color" style:background-color={getPlatformColor(value.color ?? 0)} />
<span class="overflow-label ml-1 text-sm caption-color">{value.title}</span>
</button>
{:else if kind === 'labels'}
<div class="tag-container" style:padding-right={isEditable ? '0' : '0.5rem'}>
<div
class="tag-container"
style:padding-right={isEditable ? '0' : '0.5rem'}
use:resizeObserver={(element) => {
realWidth = element.clientWidth
}}
>
<div class="color" style:background-color={getPlatformColor(value.color ?? 0)} />
<span class="overflow-label ml-1-5 caption-color">{value.title}</span>
{#if isEditable}
@ -54,12 +71,6 @@
border: 1px solid var(--divider-color);
border-radius: 0.75rem;
.color {
flex-shrink: 0;
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
}
.btn-close {
flex-shrink: 0;
margin-left: 0.125rem;
@ -74,4 +85,45 @@
}
}
}
.label-container {
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
padding: 0 0.375rem;
height: 1.375rem;
min-width: 1.375rem;
font-weight: 500;
font-size: 0.75rem;
line-height: 0.75rem;
white-space: nowrap;
color: var(--accent-color);
background-color: var(--board-card-bg-color);
border: 1px solid var(--divider-color);
border-radius: 0.25rem;
transition-property: border, background-color, color, box-shadow;
transition-duration: 0.15s;
&:hover {
color: var(--accent-color);
background-color: var(--button-bg-hover);
border-color: var(--button-border-hover);
transition-duration: 0;
}
&:focus {
border-color: var(--primary-edit-border-color) !important;
}
&:disabled {
color: rgb(var(--caption-color) / 40%);
cursor: not-allowed;
}
}
.color {
flex-shrink: 0;
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
}
</style>

View File

@ -30,7 +30,7 @@
</script>
{#if items.length}
<div class="flex-row-center flex-wrap" on:click={tagsHandler}>
<div class="flex-row-center flex-wrap">
{#each items as value}
<div class="step-container">
<TagReferencePresenter {value} {isEditable} kind={'labels'} on:remove={(res) => removeTag(res.detail)} />
@ -38,7 +38,7 @@
{/each}
{#if isEditable}
<div class="step-container">
<button class="tag-button" on:click={tagsHandler}>
<button class="tag-button" on:click|stopPropagation={tagsHandler}>
<div class="icon"><Icon icon={IconAdd} size={'full'} /></div>
<span class="overflow-label label"><Label {label} /></span>
</button>
@ -46,7 +46,7 @@
{/if}
</div>
{:else if isEditable}
<button class="tag-button" style="width: min-content" on:click={tagsHandler}>
<button class="tag-button" style="width: min-content" on:click|stopPropagation={tagsHandler}>
<div class="icon"><Icon icon={IconAdd} size={'full'} /></div>
<span class="overflow-label label"><Label {label} /></span>
</button>

View File

@ -29,6 +29,7 @@ import TagElementCountPresenter from './components/TagElementCountPresenter.svel
import TagsFilter from './components/TagsFilter.svelte'
import TagsAttributeEditor from './components/TagsAttributeEditor.svelte'
import TagsEditorPopup from './components/TagsEditorPopup.svelte'
import LabelsPresenter from './components/LabelsPresenter.svelte'
import { ObjQueryType } from '@anticrm/core'
import { getRefs } from './utils'
import { Filter } from '@anticrm/view'
@ -58,7 +59,8 @@ export default async (): Promise<Resources> => ({
TagsCategoryBar,
TagElementCountPresenter,
TagsAttributeEditor,
TagsEditorPopup
TagsEditorPopup,
LabelsPresenter
},
actionImpl: {
Open: (value: TagElement, evt: MouseEvent) => {

View File

@ -84,7 +84,8 @@ const tagsPlugin = plugin(tagsId, {
TagsDropdownEditor: '' as AnyComponent,
TagsCategoryBar: '' as AnyComponent,
TagsAttributeEditor: '' as AnyComponent,
TagsPresenter: '' as AnyComponent
TagsPresenter: '' as AnyComponent,
LabelsPresenter: '' as AnyComponent
},
category: {
NoCategory: '' as Ref<TagCategory>

View File

@ -31,6 +31,7 @@
export let shape: ButtonShape = undefined
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = 'min-content'
export let onlyIcon: boolean = false
let selectedProject: Project | undefined
let defaultProjectLabel = ''
@ -88,19 +89,32 @@
}
</script>
<Button
{kind}
{size}
{shape}
{width}
{justify}
icon={projectIcon}
disabled={!isEditable}
on:click={handleProjectEditorOpened}
>
<svelte:fragment slot="content">
{#if projectText}
<span class="overflow-label disabled">{projectText}</span>
{/if}
</svelte:fragment>
</Button>
{#if onlyIcon}
<Button
{kind}
{size}
{shape}
{width}
{justify}
icon={projectIcon}
disabled={!isEditable}
on:click={handleProjectEditorOpened}
/>
{:else}
<Button
{kind}
{size}
{shape}
{width}
{justify}
icon={projectIcon}
disabled={!isEditable}
on:click={handleProjectEditorOpened}
>
<svelte:fragment slot="content">
{#if projectText}
<span class="overflow-label disabled">{projectText}</span>
{/if}
</svelte:fragment>
</Button>
{/if}

View File

@ -19,7 +19,17 @@
import notification from '@anticrm/notification'
import { createQuery } from '@anticrm/presentation'
import { Issue, IssuesGrouping, IssuesOrdering, IssueStatus, Team, ViewOptions } from '@anticrm/tracker'
import { Button, Component, Icon, IconAdd, showPanel, showPopup, getPlatformColor, Loading } from '@anticrm/ui'
import {
Button,
Component,
Icon,
IconAdd,
showPanel,
showPopup,
getPlatformColor,
Loading,
tooltip
} from '@anticrm/ui'
import { focusStore, ListSelectionProvider, SelectDirection, selectionStore } from '@anticrm/view-resources'
import ActionContext from '@anticrm/view-resources/src/components/ActionContext.svelte'
import Menu from '@anticrm/view-resources/src/components/Menu.svelte'
@ -40,6 +50,7 @@
import ParentNamesPresenter from './ParentNamesPresenter.svelte'
import PriorityEditor from './PriorityEditor.svelte'
import StatusEditor from './StatusEditor.svelte'
import tags from '@anticrm/tags'
export let currentSpace: Ref<Team> = tracker.team.DefaultTeam
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
@ -146,6 +157,8 @@
return []
}
$: states = getIssueStates(groupBy, shouldShowEmptyGroups, issueStates, issueStatusStates, priorityStates)
const fullFilled: { [key: string]: boolean } = {}
</script>
{#if !states?.length}
@ -203,13 +216,14 @@
</svelte:fragment>
<svelte:fragment slot="card" let:object>
{@const issue = toIssue(object)}
{@const issueId = object._id}
<div
class="tracker-card"
on:click={() => {
showPanel(tracker.component.EditIssue, object._id, object._class, 'content')
}}
>
<div class="flex-col mr-8">
<div class="flex-col ml-4 mr-8">
<div class="flex clear-mins names">
<IssuePresenter value={issue} />
<ParentNamesPresenter value={issue} />
@ -235,7 +249,7 @@
<Component is={notification.component.NotificationPresenter} props={{ value: object }} />
</div>
</div>
<div class="buttons-group xsmall-gap mt-10px">
<div class="buttons-group xsmall-gap states-bar">
{#if issue && issueStatuses && issue.subIssues > 0}
<SubIssuesSelector {issue} {currentTeam} {issueStatuses} />
{/if}
@ -247,7 +261,23 @@
size={'inline'}
justify={'center'}
width={''}
bind:onlyIcon={fullFilled[issueId]}
/>
<div
class="clear-mins"
use:tooltip={{
component: fullFilled[issueId] ? tags.component.LabelsPresenter : undefined,
props: { object: issue, kind: 'full' }
}}
>
<Component
is={tags.component.LabelsPresenter}
props={{ object: issue, ckeckFilled: fullFilled[issueId] }}
on:change={(res) => {
if (res.detail.full) fullFilled[issueId] = true
}}
/>
</div>
</div>
</div>
</svelte:fragment>
@ -275,7 +305,12 @@
display: flex;
flex-direction: column;
justify-content: center;
padding: 0.5rem 1rem;
// padding: 0.5rem 1rem;
min-height: 6.5rem;
}
.states-bar {
flex-shrink: 10;
width: fit-content;
margin: 0.625rem 1rem 0;
}
</style>

View File

@ -31,6 +31,7 @@
export let shape: ButtonShape = undefined
export let justify: 'left' | 'center' = 'left'
export let width: string | undefined = '100%'
export let onlyIcon: boolean = false
const client = getClient()
@ -65,6 +66,7 @@
{isEditable}
{shouldShowLabel}
{popupPlaceholder}
{onlyIcon}
value={value.project}
onProjectIdChange={handleProjectIdChanged}
/>