mirror of
https://github.com/hcengineering/platform.git
synced 2025-01-24 20:40:59 +00:00
Tracker: labels on the card. (#2221)
Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
parent
bbe6c12474
commit
2450fd0fa9
@ -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);
|
||||
|
90
plugins/tags-resources/src/components/LabelsPresenter.svelte
Normal file
90
plugins/tags-resources/src/components/LabelsPresenter.svelte
Normal 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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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) => {
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
/>
|
||||
|
Loading…
Reference in New Issue
Block a user