diff --git a/models/board/src/index.ts b/models/board/src/index.ts index ca95ebf9f4..bf99ebb539 100644 --- a/models/board/src/index.ts +++ b/models/board/src/index.ts @@ -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 { + 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 }) diff --git a/models/board/src/plugin.ts b/models/board/src/plugin.ts index 09d4385169..3eac1af135 100644 --- a/models/board/src/plugin.ts +++ b/models/board/src/plugin.ts @@ -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 diff --git a/packages/theme/styles/_layouts.scss b/packages/theme/styles/_layouts.scss index e7356e791f..effed695cb 100644 --- a/packages/theme/styles/_layouts.scss +++ b/packages/theme/styles/_layouts.scss @@ -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; } diff --git a/packages/ui/lang/en.json b/packages/ui/lang/en.json index 9e14b168aa..16db726c01 100644 --- a/packages/ui/lang/en.json +++ b/packages/ui/lang/en.json @@ -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}}", diff --git a/packages/ui/src/colors.ts b/packages/ui/src/colors.ts index ac4991fe26..3e20c8e5f5 100644 --- a/packages/ui/src/colors.ts +++ b/packages/ui/src/colors.ts @@ -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})` } diff --git a/plugins/board-assets/lang/en.json b/plugins/board-assets/lang/en.json index 894c3da014..c6a96e5882 100644 --- a/plugins/board-assets/lang/en.json +++ b/plugins/board-assets/lang/en.json @@ -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", diff --git a/plugins/board-assets/lang/ru.json b/plugins/board-assets/lang/ru.json index 439702081a..ed11b294c4 100644 --- a/plugins/board-assets/lang/ru.json +++ b/plugins/board-assets/lang/ru.json @@ -1,5 +1,6 @@ { "string": { + "Name": "Название", "CreateBoard": "Создать", "CreateCard": "Создать", "CardName": "Название", @@ -32,6 +33,9 @@ "Labels": "Метки", "CreateLabel": "Создать", "SearchLabels": "Поиск...", + "SelectColor": "Выберите цвет", + "NoColor": "Без цвета.", + "NoColorInfo": "Не будет показываться на доске.", "Checklist": "Списки", "Dates": "Дата", "Attachments": "Прикрепленное", diff --git a/plugins/board-resources/src/components/popups/CardLabelsEditor.svelte b/plugins/board-resources/src/components/popups/CardLabelsEditor.svelte new file mode 100644 index 0000000000..863ad8e2b1 --- /dev/null +++ b/plugins/board-resources/src/components/popups/CardLabelsEditor.svelte @@ -0,0 +1,157 @@ + + +
+
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ {#each colorGroups as colorGroup} +
+ {#each colorGroup as color} +
+ selectColor(color)}> + {#if selected.color === color} +
+ +
+ {/if} +
+
+ {/each} +
+ {/each} +
+
+ selectColor(hiddenColor, true)}> + {#if selected.isHidden} +
+ +
+ {/if} +
+
+
+
+
+
+
+
+
+ +
diff --git a/plugins/board-resources/src/components/popups/CardLabelsPicker.svelte b/plugins/board-resources/src/components/popups/CardLabelsPicker.svelte index 760df0eec9..268674fa5e 100644 --- a/plugins/board-resources/src/components/popups/CardLabelsPicker.svelte +++ b/plugins/board-resources/src/components/popups/CardLabelsPicker.svelte @@ -1,21 +1,34 @@ -
-
-
+
+
-
+
toggle(label)}> {label.title ?? ''} - {#if labels.includes(label._id)} + {#if object?.labels?.includes(label._id)}
{/if}
-
{/each}
-
diff --git a/plugins/board-resources/src/components/popups/CardLabelsPopup.svelte b/plugins/board-resources/src/components/popups/CardLabelsPopup.svelte new file mode 100644 index 0000000000..a0ab6ad045 --- /dev/null +++ b/plugins/board-resources/src/components/popups/CardLabelsPopup.svelte @@ -0,0 +1,41 @@ + + +{#if editMode.isEdit} + setEditMode(false, undefined)} + /> +{:else} + setEditMode(true, undefined)} + onEdit={(o) => setEditMode(true, o)} + /> +{/if} diff --git a/plugins/board-resources/src/components/popups/DateRangePicker.svelte b/plugins/board-resources/src/components/popups/DateRangePicker.svelte index d47793a2b4..f128a50cec 100644 --- a/plugins/board-resources/src/components/popups/DateRangePicker.svelte +++ b/plugins/board-resources/src/components/popups/DateRangePicker.svelte @@ -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') }} /> diff --git a/plugins/board-resources/src/components/presenters/ColorPresenter.svelte b/plugins/board-resources/src/components/presenters/ColorPresenter.svelte new file mode 100644 index 0000000000..624793a01f --- /dev/null +++ b/plugins/board-resources/src/components/presenters/ColorPresenter.svelte @@ -0,0 +1,31 @@ + + +{#if value} +
+ +
+{/if} + + diff --git a/plugins/board-resources/src/components/presenters/DatePresenter.svelte b/plugins/board-resources/src/components/presenters/DatePresenter.svelte index 67c64774c2..c8a195c5be 100644 --- a/plugins/board-resources/src/components/presenters/DatePresenter.svelte +++ b/plugins/board-resources/src/components/presenters/DatePresenter.svelte @@ -10,7 +10,7 @@ const dispatch = createEventDispatcher() function check () { - if (isInline || isChecked === undefined) return + if (isInline || isChecked === undefined || !value) return dispatch('update', { ...value, isChecked }) } diff --git a/plugins/board-resources/src/components/presenters/LabelPresenter.svelte b/plugins/board-resources/src/components/presenters/LabelPresenter.svelte index cffe0cf669..5e5de6ea55 100644 --- a/plugins/board-resources/src/components/presenters/LabelPresenter.svelte +++ b/plugins/board-resources/src/components/presenters/LabelPresenter.svelte @@ -1,18 +1,13 @@ {#if value} -
- -
+ +
{value.title ?? ''}
+
{/if} diff --git a/plugins/board-resources/src/components/presenters/MemberPresenter.svelte b/plugins/board-resources/src/components/presenters/MemberPresenter.svelte index 8d0f5e82e0..762a656287 100644 --- a/plugins/board-resources/src/components/presenters/MemberPresenter.svelte +++ b/plugins/board-resources/src/components/presenters/MemberPresenter.svelte @@ -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' diff --git a/plugins/board-resources/src/index.ts b/plugins/board-resources/src/index.ts index d8abf04e4c..68e92969bb 100644 --- a/plugins/board-resources/src/index.ts +++ b/plugins/board-resources/src/index.ts @@ -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 { showPopup(MoveView, { object }) @@ -39,7 +49,7 @@ async function showDatePickerPopup (object: Card): Promise { } async function showCardLabelsPopup (object: Card): Promise { - showPopup(CardLabelsPicker, { object }) + showPopup(CardLabelsPopup, { object }) } export default async (): Promise => ({ @@ -49,6 +59,8 @@ export default async (): Promise => ({ EditCard, KanbanCard, CardPresenter, + CardDatePresenter, + CardLabelPresenter, TemplatesIcon, KanbanView, BoardPresenter diff --git a/plugins/board-resources/src/plugin.ts b/plugins/board-resources/src/plugin.ts index 5e92fc36bb..38ab9fc2b2 100644 --- a/plugins/board-resources/src/plugin.ts +++ b/plugins/board-resources/src/plugin.ts @@ -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, diff --git a/plugins/board-resources/src/utils/BoardUtils.ts b/plugins/board-resources/src/utils/BoardUtils.ts index 6c0210488b..0e3c72143e 100644 --- a/plugins/board-resources/src/utils/BoardUtils.ts +++ b/plugins/board-resources/src/utils/BoardUtils.ts @@ -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 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): Promise { 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, - color: string, - title?: string + color: number, + title?: string, + isHidden?: boolean ): Promise { 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 }) } diff --git a/plugins/board/src/index.ts b/plugins/board/src/index.ts index b44cddb399..122d2266f4 100644 --- a/plugins/board/src/index.ts +++ b/plugins/board/src/index.ts @@ -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>, Card: '' as Ref>, CardAction: '' as Ref>, + CardDate: '' as Ref>, CardLabel: '' as Ref> }, icon: {