mirror of
https://github.com/hcengineering/platform.git
synced 2025-01-26 05:14:39 +00:00
Merge pull request #1443 from hcengineering/ano/create-edit-label
Board: Add create / edit card label popup
This commit is contained in:
commit
4286a6cd1a
@ -16,7 +16,7 @@
|
||||
// To help typescript locate view plugin properly
|
||||
import type { Board, Card, CardAction, CardDate, CardLabel } from '@anticrm/board'
|
||||
import type { Employee } from '@anticrm/contact'
|
||||
import { TxOperations as Client, Doc, DOMAIN_MODEL, FindOptions, IndexKind, Ref } from '@anticrm/core'
|
||||
import { TxOperations as Client, Doc, DOMAIN_MODEL, FindOptions, IndexKind, Ref, Type, Timestamp } from '@anticrm/core'
|
||||
import {
|
||||
Builder,
|
||||
Collection,
|
||||
@ -32,7 +32,7 @@ import {
|
||||
import attachment from '@anticrm/model-attachment'
|
||||
import chunter from '@anticrm/model-chunter'
|
||||
import contact from '@anticrm/model-contact'
|
||||
import core, { TAttachedDoc, TDoc } from '@anticrm/model-core'
|
||||
import core, { TAttachedDoc, TDoc, TObj } from '@anticrm/model-core'
|
||||
import task, { TSpaceWithStates, TTask } from '@anticrm/model-task'
|
||||
import view from '@anticrm/model-view'
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
@ -40,6 +40,13 @@ import { Asset, IntlString, Resource } from '@anticrm/platform'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import board from './plugin'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function TypeCardDate (): Type<CardDate> {
|
||||
return { _class: board.class.CardDate, label: board.string.Dates }
|
||||
}
|
||||
|
||||
@Model(board.class.Board, task.class.SpaceWithStates)
|
||||
@UX(board.string.Board, board.icon.Board)
|
||||
export class TBoard extends TSpaceWithStates implements Board {
|
||||
@ -47,11 +54,20 @@ export class TBoard extends TSpaceWithStates implements Board {
|
||||
background!: string
|
||||
}
|
||||
|
||||
@Model(board.class.CardDate, core.class.Obj, DOMAIN_MODEL)
|
||||
@UX(board.string.Dates)
|
||||
export class TCardDate extends TObj implements CardDate {
|
||||
dueDate?: Timestamp
|
||||
isChecked?: boolean
|
||||
startDate?: Timestamp
|
||||
}
|
||||
|
||||
@Model(board.class.CardLabel, core.class.AttachedDoc, DOMAIN_MODEL)
|
||||
@UX(board.string.Labels)
|
||||
export class TCardLabel extends TAttachedDoc implements CardLabel {
|
||||
title!: string;
|
||||
color!: number;
|
||||
title!: string
|
||||
color!: number
|
||||
isHidden?: boolean
|
||||
}
|
||||
|
||||
@Model(board.class.Card, task.class.Task)
|
||||
@ -64,6 +80,7 @@ export class TCard extends TTask implements Card {
|
||||
@Prop(TypeBoolean(), board.string.IsArchived)
|
||||
isArchived?: boolean
|
||||
|
||||
@Prop(TypeCardDate(), board.string.Dates)
|
||||
date?: CardDate
|
||||
|
||||
@Prop(TypeMarkup(), board.string.Description)
|
||||
@ -105,7 +122,7 @@ export class TCardAction extends TDoc implements CardAction {
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TBoard, TCard, TCardLabel, TCardAction)
|
||||
builder.createModel(TBoard, TCard, TCardLabel, TCardDate, TCardAction)
|
||||
|
||||
builder.mixin(board.class.Board, core.class.Class, workbench.mixin.SpaceView, {
|
||||
view: {
|
||||
@ -206,6 +223,14 @@ export function createModel (builder: Builder): void {
|
||||
presenter: board.component.CardPresenter
|
||||
})
|
||||
|
||||
builder.mixin(board.class.CardLabel, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: board.component.CardLabelPresenter
|
||||
})
|
||||
|
||||
builder.mixin(board.class.CardDate, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: board.component.CardDatePresenter
|
||||
})
|
||||
|
||||
builder.mixin(board.class.Board, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: board.component.BoardPresenter
|
||||
})
|
||||
|
@ -28,6 +28,9 @@ export default mergeIds(boardId, board, {
|
||||
CreateCard: '' as AnyComponent,
|
||||
KanbanCard: '' as AnyComponent,
|
||||
CardPresenter: '' as AnyComponent,
|
||||
CardLabelPresenter: '' as AnyComponent,
|
||||
CardDatePresenter: '' as AnyComponent,
|
||||
BoardPresenter: '' as AnyComponent,
|
||||
TemplatesIcon: '' as AnyComponent,
|
||||
Cards: '' as AnyComponent,
|
||||
KanbanView: '' as AnyComponent
|
||||
|
@ -174,8 +174,9 @@ p:last-child { margin-block-end: 0; }
|
||||
.justify-center { justify-content: center; }
|
||||
.items-baseline { align-items: baseline; }
|
||||
|
||||
.flex-gap-3 {gap: .75rem;}
|
||||
.flex-gap-1 {gap: .25rem;}
|
||||
.flex-gap-3 { gap: .75rem; }
|
||||
.flex-gap-2 { gap: .5rem; }
|
||||
.flex-gap-1 { gap: .25rem; }
|
||||
|
||||
.flex-presenter, .inline-presenter {
|
||||
flex-wrap: nowrap;
|
||||
@ -325,6 +326,7 @@ p:last-child { margin-block-end: 0; }
|
||||
.mx-3 { margin: 0 .75rem; }
|
||||
.my-4 { margin: 1rem 0; }
|
||||
|
||||
.pl-1 { padding-left: .25rem; }
|
||||
.pl-2 { padding-left: .5rem; }
|
||||
.pl-4 { padding-left: 1rem; }
|
||||
.pl-8 { padding-left: 2rem; }
|
||||
@ -383,6 +385,8 @@ p:last-child { margin-block-end: 0; }
|
||||
|
||||
.h-full { height: 100%; }
|
||||
.h-2 { height: .5rem; }
|
||||
.h-6 { height: 1.5rem; }
|
||||
.h-7 { height: 1.75rem; }
|
||||
.h-8 { height: 2rem; }
|
||||
.h-9 { height: 2.25rem; }
|
||||
.h-16 { height: 4rem; }
|
||||
@ -390,8 +394,10 @@ p:last-child { margin-block-end: 0; }
|
||||
.w-full { width: 100%; }
|
||||
.w-2 { width: .5rem; }
|
||||
.w-9 { width: 2.25rem; }
|
||||
.w-85 {width: 21.25rem; }
|
||||
.w-165 {width: 41.25rem; }
|
||||
.w-14 { width: 3.5rem; }
|
||||
.w-16 { width: 4rem; }
|
||||
.w-85 { width: 21.25rem; }
|
||||
.w-165 { width: 41.25rem; }
|
||||
.min-w-0 { min-width: 0; }
|
||||
.min-w-4 { min-width: 1rem; }
|
||||
.min-w-9 { min-width: 2.25rem; }
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"string": {
|
||||
"EditBoxPlaceholder": "placeholder",
|
||||
"EditBoxPlaceholder": "Type text...",
|
||||
"Ok": "Ok",
|
||||
"Cancel": "Cancel",
|
||||
"Minutes": "{minutes, plural, =0 {less than a minute ago} =1 {a minute ago} other {# minutes ago}}",
|
||||
|
@ -12,6 +12,7 @@ export const ChetwodeBlueColor = '#6F7BC5' // dark blue
|
||||
export const SalmonColor = '#F28469' // salmon
|
||||
export const SeaBuckthornColor = '#F2994A' // orange (warning)
|
||||
export const FlamingoColor = '#EB5757' // red (error)
|
||||
export const LinkWaterColor = '#C9CBCD'
|
||||
|
||||
const blackColors = Object.freeze([
|
||||
FeijoaColor,
|
||||
@ -65,6 +66,9 @@ export function getColorNumberByText (str: string): number {
|
||||
* @public
|
||||
*/
|
||||
export function hexColorToNumber (hexColor: string): number {
|
||||
if (hexColor === undefined || hexColor === null) {
|
||||
return 0
|
||||
}
|
||||
return parseInt(hexColor.replace('#', ''), 16)
|
||||
}
|
||||
|
||||
@ -72,6 +76,9 @@ export function hexColorToNumber (hexColor: string): number {
|
||||
* @public
|
||||
*/
|
||||
export function numberToHexColor (color: number): string {
|
||||
if (color === undefined || color === null) {
|
||||
return ''
|
||||
}
|
||||
return `#${color.toString(16)}`
|
||||
}
|
||||
|
||||
@ -79,10 +86,13 @@ export function numberToHexColor (color: number): string {
|
||||
* @public
|
||||
*/
|
||||
export function numberToRGB (color: number, alpha?: number): string {
|
||||
if (color === undefined || color === null) {
|
||||
return ''
|
||||
}
|
||||
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)
|
||||
const r = hex.length >= 2 ? parseInt(hex.slice(0, 2), 16) : 0
|
||||
const g = hex.length >= 4 ? parseInt(hex.slice(2, 4), 16) : 0
|
||||
const b = hex.length >= 6 ? parseInt(hex.slice(4, 6), 16) : 0
|
||||
|
||||
return `rgba(${r}, ${g}, ${b}, ${alpha === undefined ? '1' : alpha})`
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"string": {
|
||||
"Name": "Name",
|
||||
"CreateBoard": "Create board",
|
||||
"CreateCard": "Create card",
|
||||
"CardName": "Card name",
|
||||
@ -32,6 +33,9 @@
|
||||
"Labels": "Labels",
|
||||
"CreateLabel": "Create a new label",
|
||||
"SearchLabels": "Search labels...",
|
||||
"SelectColor": "Select a color",
|
||||
"NoColor": "No color.",
|
||||
"NoColorInfo": "This won't show up on the front of cards.",
|
||||
"Checklist": "Checklist",
|
||||
"Dates": "Dates",
|
||||
"Attachments": "Attachments",
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"string": {
|
||||
"Name": "Название",
|
||||
"CreateBoard": "Создать",
|
||||
"CreateCard": "Создать",
|
||||
"CardName": "Название",
|
||||
@ -32,6 +33,9 @@
|
||||
"Labels": "Метки",
|
||||
"CreateLabel": "Создать",
|
||||
"SearchLabels": "Поиск...",
|
||||
"SelectColor": "Выберите цвет",
|
||||
"NoColor": "Без цвета.",
|
||||
"NoColorInfo": "Не будет показываться на доске.",
|
||||
"Checklist": "Списки",
|
||||
"Dates": "Дата",
|
||||
"Attachments": "Прикрепленное",
|
||||
|
@ -0,0 +1,157 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import { Board, CardLabel } from '@anticrm/board'
|
||||
import { Ref } from '@anticrm/core'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import {
|
||||
Label,
|
||||
Button,
|
||||
EditBox,
|
||||
Icon,
|
||||
IconBack,
|
||||
IconCheck,
|
||||
IconClose,
|
||||
LinkWaterColor,
|
||||
hexColorToNumber
|
||||
} from '@anticrm/ui'
|
||||
|
||||
import board from '../../plugin'
|
||||
import { createCardLabel, getBoardAvailableColors } from '../../utils/BoardUtils'
|
||||
import ColorPresenter from '../presenters/ColorPresenter.svelte'
|
||||
|
||||
export let object: CardLabel | undefined
|
||||
export let boardRef: Ref<Board>
|
||||
export let onBack: () => void
|
||||
|
||||
let selected: {
|
||||
color?: number
|
||||
isHidden?: boolean
|
||||
} = { color: object?.color }
|
||||
|
||||
let title = object?.title
|
||||
const hiddenColor = hexColorToNumber(LinkWaterColor)
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
const colorGroups: number[][] = getBoardAvailableColors().reduce(
|
||||
(result: number[][], currentValue: string) => {
|
||||
const last = result[result.length - 1]
|
||||
if (last.length >= 5) {
|
||||
result.push([hexColorToNumber(currentValue)])
|
||||
} else {
|
||||
last.push(hexColorToNumber(currentValue))
|
||||
}
|
||||
return result
|
||||
},
|
||||
[[]]
|
||||
)
|
||||
|
||||
function selectColor (color: number, isHidden?: boolean) {
|
||||
selected = { color, isHidden }
|
||||
}
|
||||
|
||||
async function save () {
|
||||
const { color, isHidden } = selected
|
||||
if (!color) {
|
||||
return
|
||||
}
|
||||
|
||||
if (object?._id) {
|
||||
await client.update(object, {
|
||||
color,
|
||||
title: title ?? '',
|
||||
isHidden: isHidden ?? false
|
||||
})
|
||||
} else {
|
||||
await createCardLabel(client, boardRef, color, title, isHidden)
|
||||
}
|
||||
|
||||
onBack()
|
||||
}
|
||||
|
||||
async function remove () {
|
||||
if (!object?._id) {
|
||||
return
|
||||
}
|
||||
|
||||
await client.removeDoc(object._class, object.space, object._id)
|
||||
|
||||
onBack()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="antiPopup w-85">
|
||||
<div class="relative fs-title flex-center h-9">
|
||||
<div class="absolute flex-center ml-2 h-full" style:top="0" style:left="0">
|
||||
<Button icon={IconBack} kind="transparent" size="small" on:click={onBack} />
|
||||
</div>
|
||||
<div>
|
||||
<Label label={board.string.Labels} />
|
||||
</div>
|
||||
|
||||
<div class="absolute flex-center mr-2 h-full" style:top="0" style:right="0">
|
||||
<Button
|
||||
icon={IconClose}
|
||||
kind="transparent"
|
||||
size="small"
|
||||
on:click={() => {
|
||||
dispatch('close')
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="ap-space bottom-divider" />
|
||||
<div class="flex-col ml-4 mt-4 mr-4 flex-gap-1">
|
||||
<div class="text-md font-medium">
|
||||
<Label label={board.string.Name} />
|
||||
</div>
|
||||
|
||||
<div class="p-2 mt-1 mb-1 border-bg-accent border-radius-1">
|
||||
<EditBox bind:value={title} maxWidth="100%" focus={true} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-col ml-4 mt-4 mb-4 mr-4 flex-gap-1">
|
||||
<div class="text-md font-medium">
|
||||
<Label label={board.string.SelectColor} />
|
||||
</div>
|
||||
|
||||
<div class="flex-col mt-1 mb-1 flex-gap-2">
|
||||
{#each colorGroups as colorGroup}
|
||||
<div class="flex-row-stretch flex-gap-2">
|
||||
{#each colorGroup as color}
|
||||
<div class="w-14">
|
||||
<ColorPresenter value={color} size="large" on:click={() => selectColor(color)}>
|
||||
{#if selected.color === color}
|
||||
<div class="flex-center flex-grow fs-title h-full">
|
||||
<Icon icon={IconCheck} size="small" />
|
||||
</div>
|
||||
{/if}
|
||||
</ColorPresenter>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
<div class="flex-row-stretch flex-gap-2">
|
||||
<div class="w-14">
|
||||
<ColorPresenter value={hiddenColor} size="large" on:click={() => selectColor(hiddenColor, true)}>
|
||||
{#if selected.isHidden}
|
||||
<div class="flex-center flex-grow fs-title h-full">
|
||||
<Icon icon={IconCheck} size="small" />
|
||||
</div>
|
||||
{/if}
|
||||
</ColorPresenter>
|
||||
</div>
|
||||
<div class="flex-col text-md">
|
||||
<div class="fs-bold"><Label label={board.string.NoColor} /></div>
|
||||
<div><Label label={board.string.NoColorInfo} /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ap-footer">
|
||||
{#if object?._id}
|
||||
<Button size="small" kind="dangerous" label={board.string.Delete} on:click={remove} />
|
||||
{/if}
|
||||
<Button label={board.string.Save} size="small" kind="primary" on:click={save} disabled={!selected.color} />
|
||||
</div>
|
||||
</div>
|
@ -1,21 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { Card, CardLabel } from '@anticrm/board'
|
||||
import { Ref } from '@anticrm/core'
|
||||
import type { Ref } from '@anticrm/core'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { Button, EditBox, Icon, IconEdit, IconCheck, Label, numberToHexColor, numberToRGB } from '@anticrm/ui'
|
||||
import {
|
||||
Button,
|
||||
EditBox,
|
||||
Icon,
|
||||
IconEdit,
|
||||
IconCheck,
|
||||
IconClose,
|
||||
Label,
|
||||
numberToHexColor,
|
||||
numberToRGB
|
||||
} from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import board from '../../plugin'
|
||||
import { getBoardLabels } from '../../utils/BoardUtils'
|
||||
import { updateCard } from '../../utils/CardUtils'
|
||||
|
||||
export let object: Card
|
||||
export let search: string | undefined = undefined
|
||||
export let onEdit: (label: CardLabel) => void
|
||||
export let onCreate: () => void
|
||||
|
||||
const client = getClient()
|
||||
let search: string | undefined = undefined
|
||||
let labels = object.labels ?? []
|
||||
|
||||
let boardCardLabels: CardLabel[] = []
|
||||
let filteredLabels: CardLabel[] = []
|
||||
let hovered: Ref<CardLabel> | undefined = undefined
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function applySearch () {
|
||||
if (!search || search.trim().length <= 0) {
|
||||
@ -34,31 +47,51 @@
|
||||
}
|
||||
}
|
||||
|
||||
function toggle (label: CardLabel) {
|
||||
async function fetch () {
|
||||
if (!object) {
|
||||
return
|
||||
}
|
||||
|
||||
if (labels.includes(label._id)) {
|
||||
labels = labels.filter((l) => l !== label._id)
|
||||
} else {
|
||||
labels = [...labels, label._id]
|
||||
object = await client.findOne(object._class, { _id: object._id }) ?? object
|
||||
}
|
||||
|
||||
async function toggle (label: CardLabel) {
|
||||
if (!object) {
|
||||
return
|
||||
}
|
||||
|
||||
updateCard(client, object, 'labels', labels)
|
||||
if (object?.labels?.includes(label._id)) {
|
||||
await client.update(object, {
|
||||
$pull: { labels: label._id as any } // TODO: fix as any
|
||||
})
|
||||
} else {
|
||||
await client.update(object, {
|
||||
$push: { labels: label._id }
|
||||
})
|
||||
}
|
||||
|
||||
fetch()
|
||||
}
|
||||
|
||||
$: 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">
|
||||
<div class="antiPopup w-85 pb-2">
|
||||
<div class="relative fs-title flex-center h-9">
|
||||
<Label label={board.string.Labels} />
|
||||
<div class="absolute flex-center mr-2 h-full" style:top="0" style:right="0">
|
||||
<Button
|
||||
icon={IconClose}
|
||||
kind="transparent"
|
||||
size="small"
|
||||
on:click={() => {
|
||||
dispatch('close')
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="ap-space bottom-divider" />
|
||||
<div class="flex-col ml-4 mt-1 mb-1 mr-2 flex-gap-1">
|
||||
<div class="flex-col ml-4 mt-2 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}
|
||||
@ -87,19 +120,19 @@
|
||||
<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)}` : ''}
|
||||
style:box-shadow={hovered === label._id ? `-0.4rem 0 ${numberToRGB(label.color, 0.6)}` : ''}
|
||||
on:click={() => toggle(label)}>
|
||||
{label.title ?? ''}
|
||||
{#if labels.includes(label._id)}
|
||||
{#if object?.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" />
|
||||
<Button icon={IconEdit} kind="transparent" on:click={() => onEdit(label)} />
|
||||
</div>
|
||||
{/each}
|
||||
<div class="mt-3" />
|
||||
<Button label={board.string.CreateLabel} kind="no-border" />
|
||||
<Button label={board.string.CreateLabel} kind="no-border" on:click={() => onCreate()} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -0,0 +1,41 @@
|
||||
<script lang="ts">
|
||||
import { Card, CardLabel } from '@anticrm/board'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import CardLabelsEditor from './CardLabelsEditor.svelte'
|
||||
import CardLabelsPicker from './CardLabelsPicker.svelte'
|
||||
|
||||
export let object: Card
|
||||
|
||||
let editMode: {
|
||||
isEdit?: boolean
|
||||
object?: CardLabel
|
||||
} = {}
|
||||
let search: string | undefined = undefined
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function setEditMode (isEdit: boolean, object?: CardLabel) {
|
||||
editMode = { isEdit, object }
|
||||
}
|
||||
|
||||
function close () {
|
||||
dispatch('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if editMode.isEdit}
|
||||
<CardLabelsEditor
|
||||
on:close
|
||||
boardRef={object.space}
|
||||
object={editMode.object}
|
||||
onBack={() => setEditMode(false, undefined)}
|
||||
/>
|
||||
{:else}
|
||||
<CardLabelsPicker
|
||||
bind:search
|
||||
on:close
|
||||
{object}
|
||||
onCreate={() => setEditMode(true, undefined)}
|
||||
onEdit={(o) => setEditMode(true, o)}
|
||||
/>
|
||||
{/if}
|
@ -20,8 +20,12 @@
|
||||
let dueDateEnabled = dueDate !== undefined
|
||||
$: dueDate && (savedDueDate = dueDate)
|
||||
|
||||
function getEmptyDate (): CardDate {
|
||||
return { _class: object.date?._class ?? board.class.CardDate }
|
||||
}
|
||||
|
||||
function update () {
|
||||
const date: CardDate = {}
|
||||
const date: CardDate = getEmptyDate()
|
||||
if (startDate !== undefined) date.startDate = startDate
|
||||
if (dueDate !== undefined) date.dueDate = dueDate
|
||||
client.update(object, { date })
|
||||
@ -78,7 +82,7 @@
|
||||
label={board.string.Remove}
|
||||
size={'small'}
|
||||
on:click={() => {
|
||||
client.update(object, { date: {} })
|
||||
client.update(object, { date: getEmptyDate() })
|
||||
dispatch('close')
|
||||
}}
|
||||
/>
|
||||
|
@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { numberToHexColor, numberToRGB } from '@anticrm/ui'
|
||||
|
||||
export let value: number
|
||||
export let size: 'small' | 'medium' | 'large' = 'medium'
|
||||
|
||||
const hoverColor = numberToRGB(value, 0.6)
|
||||
const color = numberToHexColor(value)
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
<div
|
||||
class="color-presenter border-radius-1 min-w-9"
|
||||
class:h-8={size === 'large'}
|
||||
class:h-7={size === 'medium'}
|
||||
class:h-6={size === 'small'}
|
||||
style="--color-presenter-color: {color}; --color-presenter-hoverColor: {hoverColor}"
|
||||
on:click
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.color-presenter {
|
||||
background-color: var(--color-presenter-color);
|
||||
&:hover {
|
||||
background-color: var(--color-presenter-hoverColor);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -10,7 +10,7 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function check () {
|
||||
if (isInline || isChecked === undefined) return
|
||||
if (isInline || isChecked === undefined || !value) return
|
||||
dispatch('update', { ...value, isChecked })
|
||||
}
|
||||
</script>
|
||||
|
@ -1,18 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { Button, numberToHexColor } from '@anticrm/ui'
|
||||
import type { CardLabel } from '@anticrm/board'
|
||||
import ColorPresenter from './ColorPresenter.svelte'
|
||||
|
||||
export let value: CardLabel
|
||||
export let size: 'large' | 'medium'
|
||||
|
||||
const background = numberToHexColor(value.color)
|
||||
|
||||
export let size: 'small' | 'medium' | 'large' = 'medium'
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
<div style:background class="border-radius-1">
|
||||
<Button {size} kind="transparent" on:click>
|
||||
<div class="flex-center h-full w-full min-w-4" slot="content">{value.title ?? ''}</div>
|
||||
</Button>
|
||||
</div>
|
||||
<ColorPresenter value={value.color} {size} on:click>
|
||||
<div class="flex-center h-full w-full fs-title text-sm pr-1 pl-1">{value.title ?? ''}</div>
|
||||
</ColorPresenter>
|
||||
{/if}
|
||||
|
@ -3,7 +3,7 @@
|
||||
import { getFirstName, getLastName } from '@anticrm/contact'
|
||||
import { Button, showPopup } from '@anticrm/ui'
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import EditMember from '../popups/EditMember.svelte';
|
||||
import EditMember from '../popups/EditMember.svelte'
|
||||
|
||||
export let value: Employee
|
||||
export let size: 'large' | 'medium'
|
||||
|
@ -25,10 +25,20 @@ 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 CardLabelsPopup from './components/popups/CardLabelsPopup.svelte'
|
||||
import MoveView from './components/popups/MoveCard.svelte'
|
||||
import DateRangePicker from './components/popups/DateRangePicker.svelte'
|
||||
import { addCurrentUser, canAddCurrentUser, isArchived, isUnarchived, archiveCard, unarchiveCard, deleteCard } from './utils/CardUtils'
|
||||
import CardLabelPresenter from './components/presenters/LabelPresenter.svelte'
|
||||
import CardDatePresenter from './components/presenters/DatePresenter.svelte'
|
||||
import {
|
||||
addCurrentUser,
|
||||
canAddCurrentUser,
|
||||
isArchived,
|
||||
isUnarchived,
|
||||
archiveCard,
|
||||
unarchiveCard,
|
||||
deleteCard
|
||||
} from './utils/CardUtils'
|
||||
|
||||
async function showMoveCardPopup (object: Card): Promise<void> {
|
||||
showPopup(MoveView, { object })
|
||||
@ -39,7 +49,7 @@ async function showDatePickerPopup (object: Card): Promise<void> {
|
||||
}
|
||||
|
||||
async function showCardLabelsPopup (object: Card): Promise<void> {
|
||||
showPopup(CardLabelsPicker, { object })
|
||||
showPopup(CardLabelsPopup, { object })
|
||||
}
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
@ -49,6 +59,8 @@ export default async (): Promise<Resources> => ({
|
||||
EditCard,
|
||||
KanbanCard,
|
||||
CardPresenter,
|
||||
CardDatePresenter,
|
||||
CardLabelPresenter,
|
||||
TemplatesIcon,
|
||||
KanbanView,
|
||||
BoardPresenter
|
||||
|
@ -19,6 +19,7 @@ import type { AnyComponent } from '@anticrm/ui'
|
||||
|
||||
export default mergeIds(boardId, board, {
|
||||
string: {
|
||||
Name: '' as IntlString,
|
||||
BoardName: '' as IntlString,
|
||||
MakePrivate: '' as IntlString,
|
||||
MakePrivateDescription: '' as IntlString,
|
||||
@ -53,6 +54,9 @@ export default mergeIds(boardId, board, {
|
||||
Labels: '' as IntlString,
|
||||
CreateLabel: '' as IntlString,
|
||||
SearchLabels: '' as IntlString,
|
||||
SelectColor: '' as IntlString,
|
||||
NoColor: '' as IntlString,
|
||||
NoColorInfo: '' as IntlString,
|
||||
Checklist: '' as IntlString,
|
||||
Dates: '' as IntlString,
|
||||
Attachments: '' as IntlString,
|
||||
@ -98,9 +102,6 @@ export default mergeIds(boardId, board, {
|
||||
RemoveFromCard: '' as IntlString
|
||||
},
|
||||
component: {
|
||||
CreateCustomer: '' as AnyComponent,
|
||||
CardsPresenter: '' as AnyComponent,
|
||||
BoardPresenter: '' as AnyComponent,
|
||||
Boards: '' as AnyComponent,
|
||||
EditCard: '' as AnyComponent,
|
||||
Members: '' as AnyComponent,
|
||||
|
@ -2,7 +2,19 @@ import core, { Ref, TxOperations } from '@anticrm/core'
|
||||
import board, { Board, CardLabel } from '@anticrm/board'
|
||||
import type { KanbanTemplate } from '@anticrm/task'
|
||||
import { createKanban } from '@anticrm/task'
|
||||
import { hexColorToNumber, FernColor, FlamingoColor, MalibuColor, MoodyBlueColor, SeaBuckthornColor } from '@anticrm/ui'
|
||||
import {
|
||||
hexColorToNumber,
|
||||
FernColor,
|
||||
FlamingoColor,
|
||||
MalibuColor,
|
||||
MediumTurquoiseColor,
|
||||
MoodyBlueColor,
|
||||
SeaBuckthornColor,
|
||||
FeijoaColor,
|
||||
EastSideColor,
|
||||
SalmonColor,
|
||||
SeagullColor
|
||||
} from '@anticrm/ui'
|
||||
|
||||
export async function createBoard (
|
||||
client: TxOperations,
|
||||
@ -26,27 +38,44 @@ export async function getBoardLabels (client: TxOperations, boardRef: Ref<Board>
|
||||
return await client.findAll(board.class.CardLabel, { attachedTo: boardRef })
|
||||
}
|
||||
|
||||
export function getBoardAvailableColors (): string[] {
|
||||
return [
|
||||
FernColor,
|
||||
SeaBuckthornColor,
|
||||
FlamingoColor,
|
||||
MalibuColor,
|
||||
MoodyBlueColor,
|
||||
FeijoaColor,
|
||||
EastSideColor,
|
||||
MediumTurquoiseColor,
|
||||
SalmonColor,
|
||||
SeagullColor
|
||||
]
|
||||
}
|
||||
|
||||
export async function createBoardLabels (client: TxOperations, boardRef: Ref<Board>): Promise<void> {
|
||||
await Promise.all([
|
||||
createCardLabel(client, boardRef, FernColor),
|
||||
createCardLabel(client, boardRef, SeaBuckthornColor),
|
||||
createCardLabel(client, boardRef, FlamingoColor),
|
||||
createCardLabel(client, boardRef, MalibuColor),
|
||||
createCardLabel(client, boardRef, MoodyBlueColor)
|
||||
createCardLabel(client, boardRef, hexColorToNumber(FernColor)),
|
||||
createCardLabel(client, boardRef, hexColorToNumber(SeaBuckthornColor)),
|
||||
createCardLabel(client, boardRef, hexColorToNumber(FlamingoColor)),
|
||||
createCardLabel(client, boardRef, hexColorToNumber(MalibuColor)),
|
||||
createCardLabel(client, boardRef, hexColorToNumber(MoodyBlueColor))
|
||||
])
|
||||
}
|
||||
|
||||
export async function createCardLabel (
|
||||
client: TxOperations,
|
||||
boardRef: Ref<Board>,
|
||||
color: string,
|
||||
title?: string
|
||||
color: number,
|
||||
title?: string,
|
||||
isHidden?: boolean
|
||||
): Promise<void> {
|
||||
await client.createDoc(board.class.CardLabel, core.space.Model, {
|
||||
attachedTo: boardRef,
|
||||
attachedToClass: board.class.Board,
|
||||
collection: '',
|
||||
color: hexColorToNumber(color),
|
||||
title: title ?? ''
|
||||
collection: 'labels',
|
||||
color,
|
||||
title: title ?? '',
|
||||
isHidden: isHidden ?? false
|
||||
})
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
//
|
||||
|
||||
import { Employee } from '@anticrm/contact'
|
||||
import type { AttachedDoc, Class, TxOperations as Client, Doc, Markup, Ref, Timestamp } from '@anticrm/core'
|
||||
import type { AttachedDoc, Class, TxOperations as Client, Doc, Markup, Ref, Timestamp, Obj } from '@anticrm/core'
|
||||
import type { Asset, IntlString, Plugin, Resource } from '@anticrm/platform'
|
||||
import { plugin } from '@anticrm/platform'
|
||||
import type { KanbanTemplateSpace, SpaceWithStates, Task } from '@anticrm/task'
|
||||
@ -44,12 +44,13 @@ export interface BoardView extends SpaceWithStates {
|
||||
export interface CardLabel extends AttachedDoc {
|
||||
title: string
|
||||
color: number
|
||||
isHidden?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface CardDate {
|
||||
export interface CardDate extends Obj {
|
||||
dueDate?: Timestamp
|
||||
isChecked?: boolean
|
||||
startDate?: Timestamp
|
||||
@ -110,6 +111,7 @@ const boards = plugin(boardId, {
|
||||
Board: '' as Ref<Class<Board>>,
|
||||
Card: '' as Ref<Class<Card>>,
|
||||
CardAction: '' as Ref<Class<CardAction>>,
|
||||
CardDate: '' as Ref<Class<CardDate>>,
|
||||
CardLabel: '' as Ref<Class<CardLabel>>
|
||||
},
|
||||
icon: {
|
||||
|
Loading…
Reference in New Issue
Block a user