mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-03 08:18:52 +02:00
Board: Add card labels picker popup (#1434)
Signed-off-by: Anna No <anna.no@xored.com>
This commit is contained in:
@@ -136,7 +136,7 @@ p:last-child { margin-block-end: 0; }
|
||||
flex-wrap: nowrap;
|
||||
min-width: 0;
|
||||
}
|
||||
.flex-row-streach {
|
||||
.flex-row-stretch {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-wrap: nowrap;
|
||||
@@ -171,6 +171,7 @@ p:last-child { margin-block-end: 0; }
|
||||
}
|
||||
.justify-between { justify-content: space-between; }
|
||||
.justify-end { justify-content: flex-end; }
|
||||
.justify-center { justify-content: center; }
|
||||
.items-baseline { align-items: baseline; }
|
||||
|
||||
.flex-gap-3 {gap: .75rem;}
|
||||
@@ -345,6 +346,7 @@ p:last-child { margin-block-end: 0; }
|
||||
/* --------- */
|
||||
.no-word-wrap { word-wrap: none; }
|
||||
.relative { position: relative; }
|
||||
.absolute { position: absolute; }
|
||||
.abs-lt-content {
|
||||
position: absolute;
|
||||
top: var(--modal-padding);
|
||||
@@ -381,10 +383,12 @@ p:last-child { margin-block-end: 0; }
|
||||
|
||||
.h-full { height: 100%; }
|
||||
.h-2 { height: .5rem; }
|
||||
.h-8 { height: 2rem; }
|
||||
.h-9 { height: 2.25rem; }
|
||||
.h-16 { height: 4rem; }
|
||||
.h-18 { height: 4.5rem; }
|
||||
.w-full { width: 100%; }
|
||||
.w-2 { width: .5rem; }
|
||||
.w-9 { width: 2.25rem; }
|
||||
.w-85 {width: 21.25rem; }
|
||||
.w-165 {width: 41.25rem; }
|
||||
|
||||
@@ -74,3 +74,15 @@ export function hexColorToNumber (hexColor: string): number {
|
||||
export function numberToHexColor (color: number): string {
|
||||
return `#${color.toString(16)}`
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function numberToRGB (color: number, alpha?: number): string {
|
||||
const hex = color.toString(16)
|
||||
const r = parseInt(hex.slice(0, 2), 16)
|
||||
const g = parseInt(hex.slice(2, 4), 16)
|
||||
const b = parseInt(hex.slice(4, 6), 16)
|
||||
|
||||
return `rgba(${r}, ${g}, ${b}, ${alpha === undefined ? '1' : alpha})`
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
"Suggested": "Suggested",
|
||||
"AddToCard": "Add to card",
|
||||
"Labels": "Labels",
|
||||
"CreateLabel": "Create a new label",
|
||||
"SearchLabels": "Search labels...",
|
||||
"Checklist": "Checklist",
|
||||
"Dates": "Dates",
|
||||
"Attachments": "Attachments",
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
"Suggested": "Предложенное",
|
||||
"AddToCard": "Добавить",
|
||||
"Labels": "Метки",
|
||||
"CreateLabel": "Создать",
|
||||
"SearchLabels": "Поиск...",
|
||||
"Checklist": "Списки",
|
||||
"Dates": "Дата",
|
||||
"Attachments": "Прикрепленное",
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
<Scroller>
|
||||
<div class="flex-col-stretch h-full w-165 p-6">
|
||||
<!-- TODO cover -->
|
||||
<div class="flex-row-streach">
|
||||
<div class="flex-row-stretch">
|
||||
<div class="w-9">
|
||||
<Icon icon={board.icon.Card} size="large" />
|
||||
</div>
|
||||
@@ -91,20 +91,20 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-row-streach">
|
||||
<div class="flex-row-stretch">
|
||||
<div class="w-9" />
|
||||
<div>
|
||||
<Label label={board.string.InList} />
|
||||
<span class="state-name ml-1" on:click={handleMove}>{state?.title}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-row-streach">
|
||||
<div class="flex-row-stretch">
|
||||
<div class="flex-grow mr-4">
|
||||
<div class="flex-row-streach">
|
||||
<div class="flex-row-stretch">
|
||||
<div class="w-9" />
|
||||
<CardDetails bind:value={object} />
|
||||
</div>
|
||||
<div class="flex-row-streach mt-4 mb-2">
|
||||
<div class="flex-row-stretch mt-4 mb-2">
|
||||
<div class="w-9">
|
||||
<Icon icon={board.icon.Card} size="large" />
|
||||
</div>
|
||||
@@ -112,7 +112,7 @@
|
||||
<Label label={board.string.Description} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-row-streach">
|
||||
<div class="flex-row-stretch">
|
||||
<div class="w-9" />
|
||||
<div class="background-bg-accent border-bg-accent border-radius-3 p-2 w-full">
|
||||
<StyledTextBox
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
{#if value !== undefined}
|
||||
<div class="flex-col-stretch h-full w-full">
|
||||
<div class="flex-row-streach mt-4 mb-2">
|
||||
<div class="flex-row-stretch mt-4 mb-2">
|
||||
<div class="w-9">
|
||||
<Icon icon={IconActivity} size="large" />
|
||||
</div>
|
||||
@@ -42,7 +42,7 @@
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-row-streach">
|
||||
<div class="flex-row-stretch">
|
||||
<div class="w-9" />
|
||||
<div class="w-full">
|
||||
<Component is={chunter.component.CommentInput} props={{ object: value }} />
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
{#if value !== undefined && value.attachments !== undefined && value.attachments > 0}
|
||||
<div class="flex-col w-full">
|
||||
<div class="flex-row-streach mt-4 mb-2">
|
||||
<div class="flex-row-stretch mt-4 mb-2">
|
||||
<div class="w-9">
|
||||
<Icon icon={IconAttachment} size="large" />
|
||||
</div>
|
||||
@@ -46,7 +46,7 @@
|
||||
<Label label={board.string.Attachments} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-row-streach">
|
||||
<div class="flex-row-stretch">
|
||||
<div class="w-9" />
|
||||
<div class="flex-col flex-gap-1 w-full">
|
||||
{#each attachments as attach}
|
||||
|
||||
@@ -37,17 +37,21 @@
|
||||
let labelsHandler: () => void
|
||||
let dateHandler: () => void
|
||||
|
||||
$: value.members &&
|
||||
value.members.length > 0 &&
|
||||
$: if (value.members && value.members.length > 0) {
|
||||
query.query(contact.class.Employee, { _id: { $in: value.members } }, (result) => {
|
||||
members = result
|
||||
})
|
||||
} else {
|
||||
members = []
|
||||
}
|
||||
|
||||
$: value.labels &&
|
||||
value.labels.length > 0 &&
|
||||
$: if (value.labels && value.labels.length > 0) {
|
||||
query.query(board.class.CardLabel, { _id: { $in: value.labels } }, (result) => {
|
||||
labels = result
|
||||
})
|
||||
} else {
|
||||
labels = []
|
||||
}
|
||||
|
||||
getCardActions(client, {
|
||||
_id: { $in: [board.cardAction.Dates, board.cardAction.Labels, board.cardAction.Members] }
|
||||
@@ -65,6 +69,7 @@
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
@@ -82,7 +87,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
{#if labels && labels.length > 0}
|
||||
<div class="flex-col mt-4 mr6">
|
||||
<div class="flex-col mt-4 mr-6">
|
||||
<div class="text-md font-medium">
|
||||
<Label label={board.string.Labels} />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
<script lang="ts">
|
||||
import { Card, CardLabel } from '@anticrm/board'
|
||||
import { Ref } from '@anticrm/core'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { Button, EditBox, Icon, IconEdit, IconCheck, Label, numberToHexColor, numberToRGB } from '@anticrm/ui'
|
||||
|
||||
import board from '../../plugin'
|
||||
import { getBoardLabels } from '../../utils/BoardUtils'
|
||||
import { updateCard } from '../../utils/CardUtils'
|
||||
|
||||
export let object: Card
|
||||
|
||||
const client = getClient()
|
||||
let search: string | undefined = undefined
|
||||
let labels = object.labels ?? []
|
||||
let boardCardLabels: CardLabel[] = []
|
||||
let filteredLabels: CardLabel[] = []
|
||||
let hovered: Ref<CardLabel> | undefined = undefined
|
||||
|
||||
function applySearch() {
|
||||
if (!search || search.trim().length <= 0) {
|
||||
filteredLabels = boardCardLabels
|
||||
return
|
||||
}
|
||||
|
||||
const text = search!.toUpperCase()
|
||||
filteredLabels = boardCardLabels.filter((l) => l.title?.toUpperCase().includes(text) ?? false)
|
||||
}
|
||||
|
||||
async function fetchBoardLabels() {
|
||||
if (object.space) {
|
||||
boardCardLabels = await getBoardLabels(client, object.space)
|
||||
applySearch()
|
||||
}
|
||||
}
|
||||
|
||||
function toggle(label: CardLabel) {
|
||||
if (!object) {
|
||||
return
|
||||
}
|
||||
|
||||
if (labels.includes(label._id)) {
|
||||
labels = labels.filter((l) => l !== label._id)
|
||||
} else {
|
||||
labels = [...labels, label._id]
|
||||
}
|
||||
|
||||
updateCard(client, object, 'labels', labels)
|
||||
}
|
||||
|
||||
$: object.space && fetchBoardLabels()
|
||||
|
||||
</script>
|
||||
|
||||
<div class="antiPopup antiPopup-withHeader antiPopup-withTitle w-85 pb-2">
|
||||
<div class="ap-space" />
|
||||
<div class="fs-title ap-header flex-row-center">
|
||||
<Label label={board.string.Labels} />
|
||||
</div>
|
||||
<div class="ap-space bottom-divider" />
|
||||
<div class="flex-col ml-4 mt-1 mb-1 mr-2 flex-gap-1">
|
||||
<div class="p-2 mt-1 mb-1 border-bg-accent border-radius-1">
|
||||
<EditBox
|
||||
bind:value={search}
|
||||
maxWidth="100%"
|
||||
placeholder={board.string.SearchLabels}
|
||||
on:change={() => applySearch()} />
|
||||
</div>
|
||||
<div class="text-md font-medium">
|
||||
<Label label={board.string.Labels} />
|
||||
</div>
|
||||
{#each filteredLabels as label}
|
||||
<div
|
||||
class="flex-row-stretch"
|
||||
on:mouseover={() => {
|
||||
hovered = label._id
|
||||
}}
|
||||
on:focus={() => {
|
||||
hovered = label._id
|
||||
}}
|
||||
on:mouseout={() => {
|
||||
hovered = undefined
|
||||
}}
|
||||
on:blur={() => {
|
||||
hovered = undefined
|
||||
}}>
|
||||
<div
|
||||
class="relative flex-row-center justify-center border-radius-1 fs-title w-full h-8 mr-2"
|
||||
style:background-color={numberToHexColor(label.color)}
|
||||
style:box-shadow={hovered === label._id ? `-0.5rem 0 ${numberToRGB(label.color, 0.6)}` : ''}
|
||||
on:click={() => toggle(label)}>
|
||||
{label.title ?? ''}
|
||||
{#if labels.includes(label._id)}
|
||||
<div class="absolute flex-center h-full mr-2" style:top="0" style:right="0">
|
||||
<Icon icon={IconCheck} size="small" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<Button icon={IconEdit} kind="transparent" />
|
||||
</div>
|
||||
{/each}
|
||||
<div class="mt-3" />
|
||||
<Button label={board.string.CreateLabel} kind="no-border" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -25,6 +25,7 @@ import EditCard from './components/EditCard.svelte'
|
||||
import KanbanCard from './components/KanbanCard.svelte'
|
||||
import TemplatesIcon from './components/TemplatesIcon.svelte'
|
||||
import KanbanView from './components/KanbanView.svelte'
|
||||
import CardLabelsPicker from './components/popups/CardLabelsPicker.svelte'
|
||||
import MoveView from './components/popups/MoveCard.svelte'
|
||||
import DateRangePicker from './components/popups/DateRangePicker.svelte'
|
||||
import { addCurrentUser, canAddCurrentUser, isArchived, isUnarchived } from './utils/CardUtils'
|
||||
@@ -36,6 +37,11 @@ async function showMoveCardPopup (object: Card): Promise<void> {
|
||||
async function showDatePickerPopup (object: Card): Promise<void> {
|
||||
showPopup(DateRangePicker, { object })
|
||||
}
|
||||
|
||||
async function showCardLabelsPopup (object: Card): Promise<void> {
|
||||
showPopup(CardLabelsPicker, { object })
|
||||
}
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
CreateBoard,
|
||||
@@ -50,7 +56,8 @@ export default async (): Promise<Resources> => ({
|
||||
cardActionHandler: {
|
||||
Join: addCurrentUser,
|
||||
Move: showMoveCardPopup,
|
||||
Dates: showDatePickerPopup
|
||||
Dates: showDatePickerPopup,
|
||||
Labels: showCardLabelsPopup
|
||||
},
|
||||
cardActionSupportedHandler: {
|
||||
Join: canAddCurrentUser,
|
||||
|
||||
@@ -51,6 +51,8 @@ export default mergeIds(boardId, board, {
|
||||
Suggested: '' as IntlString,
|
||||
AddToCard: '' as IntlString,
|
||||
Labels: '' as IntlString,
|
||||
CreateLabel: '' as IntlString,
|
||||
SearchLabels: '' as IntlString,
|
||||
Checklist: '' as IntlString,
|
||||
Dates: '' as IntlString,
|
||||
Attachments: '' as IntlString,
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
</script>
|
||||
|
||||
{#if object !== undefined}
|
||||
<div class="flex-row-streach flex-grow">
|
||||
<div class="flex-row-stretch flex-grow">
|
||||
<div class="mr-8">
|
||||
{#if editable}
|
||||
<EditableAvatar avatar={object.avatar} size={'x-large'} on:done={onAvatarDone} on:remove={removeAvatar} />
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
<div class="ac-header__icon"><Icon icon={notification.icon.Notifications} size={'medium'} /></div>
|
||||
<div class="ac-header__title"><Label label={notification.string.Notifications}/></div>
|
||||
</div>
|
||||
<div class="flex-row-streach flex-grow container">
|
||||
<div class="flex-row-stretch flex-grow container">
|
||||
<div class="flex-col flex-grow">
|
||||
<div class="flex-grow">
|
||||
<Grid {column} columnGap={5} rowGap={1.5} >
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<div class="ac-header__icon"><Icon icon={setting.icon.Password} size={'medium'} /></div>
|
||||
<div class="ac-header__title"><Label label={setting.string.ChangePassword} /></div>
|
||||
</div>
|
||||
<div class="flex-row-streach flex-grow p-10">
|
||||
<div class="flex-row-stretch flex-grow p-10">
|
||||
<div class="flex-grow flex-col">
|
||||
<div class="flex-grow flex-col">
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user