mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-15 12:55:59 +00:00
Adjust label editors (#1955)
Signed-off-by: Dvinyanin Alexandr <dvinyanin.alexandr@gmail.com>
This commit is contained in:
parent
a12d5a3347
commit
7fe382daf0
@ -1,10 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## 0.6.23
|
||||
## 0.6.23 (upcoming)
|
||||
|
||||
Platform:
|
||||
|
||||
- Fix first filter disappear
|
||||
- Adjust label editors design
|
||||
|
||||
## 0.6.22
|
||||
|
||||
|
@ -40,6 +40,7 @@ import workbench, { Application } from '@anticrm/model-workbench'
|
||||
import { IntlString } from '@anticrm/platform'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import preference, { TPreference } from '@anticrm/model-preference'
|
||||
import tags from '@anticrm/model-tags'
|
||||
import board from './plugin'
|
||||
|
||||
@Model(board.class.Board, task.class.SpaceWithStates)
|
||||
@ -64,8 +65,6 @@ export class TCardCover extends TType implements CardCover {
|
||||
export class TCommonBoardPreference extends TPreference implements CommonBoardPreference {
|
||||
@Prop(TypeRef(workbench.class.Application), board.string.CommonBoardPreference)
|
||||
attachedTo!: Ref<Application>
|
||||
|
||||
cardLabelsCompactMode!: boolean
|
||||
}
|
||||
|
||||
@Model(board.class.Card, task.class.Task)
|
||||
@ -282,8 +281,9 @@ export function createModel (builder: Builder): void {
|
||||
{
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: board.component.LabelsActionPopup,
|
||||
element: view.popup.PositionElementAlignment
|
||||
component: tags.component.TagsEditorPopup,
|
||||
element: view.popup.PositionElementAlignment,
|
||||
value: 'object'
|
||||
},
|
||||
label: board.string.Labels,
|
||||
icon: board.icon.Card,
|
||||
|
@ -28,13 +28,11 @@ export default mergeIds(boardId, board, {
|
||||
CreateCard: '' as AnyComponent,
|
||||
KanbanCard: '' as AnyComponent,
|
||||
CardPresenter: '' as AnyComponent,
|
||||
CardLabelPresenter: '' as AnyComponent,
|
||||
BoardPresenter: '' as AnyComponent,
|
||||
TemplatesIcon: '' as AnyComponent,
|
||||
Cards: '' as AnyComponent,
|
||||
KanbanView: '' as AnyComponent,
|
||||
TableView: '' as AnyComponent,
|
||||
LabelsActionPopup: '' as AnyComponent,
|
||||
DatesActionPopup: '' as AnyComponent,
|
||||
CoverActionPopup: '' as AnyComponent,
|
||||
MoveActionPopup: '' as AnyComponent,
|
||||
|
@ -90,6 +90,9 @@ export function createModel (builder: Builder): void {
|
||||
builder.mixin(tags.class.TagReference, core.class.Class, view.mixin.CollectionEditor, {
|
||||
editor: tags.component.Tags
|
||||
})
|
||||
builder.mixin(tags.class.TagReference, core.class.Class, view.mixin.AttributeEditor, {
|
||||
editor: tags.component.TagsAttributeEditor
|
||||
})
|
||||
|
||||
builder.mixin(tags.class.TagReference, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: tags.component.TagReferencePresenter
|
||||
|
@ -24,9 +24,9 @@ export default mergeIds(tagsId, tags, {
|
||||
component: {
|
||||
Tags: '' as AnyComponent,
|
||||
TagReferencePresenter: '' as AnyComponent,
|
||||
TagsPresenter: '' as AnyComponent,
|
||||
TagsItemPresenter: '' as AnyComponent,
|
||||
TagsFilter: '' as AnyComponent
|
||||
TagsFilter: '' as AnyComponent,
|
||||
TagsEditorPopup: '' as AnyComponent
|
||||
},
|
||||
string: {
|
||||
TagElementLabel: '' as IntlString,
|
||||
|
@ -81,6 +81,7 @@
|
||||
space={object.space}
|
||||
{onChange}
|
||||
{focus}
|
||||
{object}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -104,6 +105,7 @@
|
||||
space={object.space}
|
||||
{onChange}
|
||||
{focus}
|
||||
{object}
|
||||
/>
|
||||
{/if}
|
||||
{:else if showHeader}
|
||||
@ -121,6 +123,7 @@
|
||||
space={object.space}
|
||||
{onChange}
|
||||
{focus}
|
||||
{object}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -141,6 +144,7 @@
|
||||
space={object.space}
|
||||
{onChange}
|
||||
{focus}
|
||||
{object}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
@ -154,6 +158,7 @@
|
||||
space={object.space}
|
||||
{onChange}
|
||||
{focus}
|
||||
{object}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -56,6 +56,7 @@
|
||||
let handleMove: (e: Event) => void
|
||||
let checklists: TodoItem[] = []
|
||||
const mixins: Mixin<Doc>[] = []
|
||||
const allowedCollections = ['labels']
|
||||
const ignoreKeys = [
|
||||
'isArchived',
|
||||
'location',
|
||||
@ -184,7 +185,7 @@
|
||||
</div>
|
||||
<svelte:fragment slot="custom-attributes" let:direction>
|
||||
{#if direction === 'column'}
|
||||
<DocAttributeBar {object} {mixins} {ignoreKeys} />
|
||||
<DocAttributeBar {object} {mixins} {ignoreKeys} {allowedCollections} />
|
||||
<!-- TODO: adjust rest actions -->
|
||||
<CardActions bind:value={object} />
|
||||
{:else}
|
||||
|
@ -21,6 +21,7 @@
|
||||
import type { Ref, WithLookup } from '@anticrm/core'
|
||||
import notification from '@anticrm/notification'
|
||||
import view from '@anticrm/view'
|
||||
import tags from '@anticrm/tags'
|
||||
import { getClient, UserBoxList } from '@anticrm/presentation'
|
||||
import {
|
||||
Button,
|
||||
@ -35,7 +36,6 @@
|
||||
} from '@anticrm/ui'
|
||||
import { ContextMenu } from '@anticrm/view-resources'
|
||||
import board from '../plugin'
|
||||
import CardLabels from './editor/CardLabels.svelte'
|
||||
import DatePresenter from './presenters/DatePresenter.svelte'
|
||||
import { hasDate, openCardPanel, updateCard, updateCardMembers } from '../utils/CardUtils'
|
||||
import CheckListsPresenter from './presenters/ChecklistsPresenter.svelte'
|
||||
@ -94,9 +94,6 @@
|
||||
class="abs-full-content background-theme-content-accent h-full w-full flex-center fs-title"
|
||||
/>
|
||||
{/if}
|
||||
<div class="ml-1">
|
||||
<CardLabels bind:value={object} isInline={true} />
|
||||
</div>
|
||||
{#if !isEditMode}
|
||||
<div class="absolute mr-1 mt-1" style:top="0" style:right="0">
|
||||
<Button icon={IconEdit} kind="transparent" on:click={enterEditMode} />
|
||||
@ -147,9 +144,6 @@
|
||||
class="abs-full-content background-theme-content-accent h-full w-full flex-center fs-title"
|
||||
/>
|
||||
{/if}
|
||||
<div class="ml-1">
|
||||
<CardLabels bind:value={object} isInline={true} />
|
||||
</div>
|
||||
{#if !isEditMode && !object.cover}
|
||||
<div class="absolute mr-1 mt-1" style:top="0" style:right="0">
|
||||
<Button icon={IconEdit} kind="transparent" on:click={enterEditMode} />
|
||||
@ -211,6 +205,14 @@
|
||||
<CheckListsPresenter value={object} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if (object.labels ?? 0) > 0}
|
||||
<div class="float-left">
|
||||
<Component
|
||||
is={tags.component.TagsPresenter}
|
||||
props={{ value: object, _class: object._class, key: 'labels' }}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if (object.members?.length ?? 0) > 0}
|
||||
|
@ -3,6 +3,7 @@
|
||||
import { Class, FindOptions, Ref } from '@anticrm/core'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import task, { SpaceWithStates, State } from '@anticrm/task'
|
||||
import tags from '@anticrm/tags'
|
||||
import { TableBrowser } from '@anticrm/view-resources'
|
||||
import board from '../plugin'
|
||||
|
||||
@ -23,7 +24,12 @@
|
||||
config={[
|
||||
'title',
|
||||
'$lookup.state',
|
||||
{ key: '', presenter: board.component.CardLabels, label: board.string.Labels },
|
||||
{
|
||||
key: '',
|
||||
presenter: tags.component.TagsAttributeEditor,
|
||||
props: { isEditable: false },
|
||||
label: board.string.Labels
|
||||
},
|
||||
'startDate',
|
||||
'dueDate',
|
||||
{ key: 'members', presenter: board.component.UserBoxList, label: board.string.Members, sortingKey: '' },
|
||||
|
@ -24,7 +24,6 @@
|
||||
import plugin from '../../plugin'
|
||||
import { updateCardMembers } from '../../utils/CardUtils'
|
||||
import UserBoxList from '../UserBoxList.svelte'
|
||||
import CardLabels from './CardLabels.svelte'
|
||||
|
||||
export let value: Card
|
||||
const client = getClient()
|
||||
@ -55,10 +54,6 @@
|
||||
<div class="ml-4">
|
||||
<UserBoxList value={value.members ?? []} on:update={updateMembers} />
|
||||
</div>
|
||||
<div class="label fs-bold">
|
||||
<Label label={plugin.string.Labels} />
|
||||
</div>
|
||||
<CardLabels {value} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
@ -1,94 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Card } from '@anticrm/board'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import tags, { TagReference } from '@anticrm/tags'
|
||||
import { Button, Icon, IconAdd } from '@anticrm/ui'
|
||||
import { invokeAction } from '@anticrm/view-resources'
|
||||
|
||||
import board from '../../plugin'
|
||||
import { commonBoardPreference } from '../../utils/BoardUtils'
|
||||
import { getCardActions } from '../../utils/CardActionUtils'
|
||||
import LabelPresenter from '../presenters/LabelPresenter.svelte'
|
||||
|
||||
export let value: Card
|
||||
export let isInline: boolean = false
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let labelsHandler: (e: Event) => void
|
||||
let isHovered: boolean = false
|
||||
|
||||
let labels: TagReference[] = []
|
||||
const query = createQuery()
|
||||
$: query.query(tags.class.TagReference, { attachedTo: value._id }, (result) => {
|
||||
labels = result
|
||||
})
|
||||
|
||||
$: isCompact = $commonBoardPreference?.cardLabelsCompactMode
|
||||
|
||||
if (!isInline) {
|
||||
getCardActions(client, {
|
||||
_id: board.action.Labels
|
||||
}).then(async (result) => {
|
||||
if (result?.[0]) {
|
||||
labelsHandler = (e: Event) => invokeAction(value, e, result[0].action, result[0].actionProps)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function toggleCompact () {
|
||||
if (!isInline) return
|
||||
client.update($commonBoardPreference, { cardLabelsCompactMode: !isCompact })
|
||||
}
|
||||
|
||||
function hoverIn () {
|
||||
if (isInline) {
|
||||
isHovered = true
|
||||
}
|
||||
}
|
||||
|
||||
function hoverOut () {
|
||||
isHovered = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex mb-1"
|
||||
class:labels-inline-container={isInline}
|
||||
on:click={toggleCompact}
|
||||
on:mouseover={hoverIn}
|
||||
on:focus={hoverIn}
|
||||
on:mouseout={hoverOut}
|
||||
on:blur={hoverOut}
|
||||
>
|
||||
{#if labels && labels.length > 0}
|
||||
<div class="flex-row-center flex-wrap flex-gap-1 ml-4">
|
||||
{#each labels as label}
|
||||
<LabelPresenter
|
||||
value={label}
|
||||
size={isInline ? (isCompact ? 'tiny' : 'x-small') : undefined}
|
||||
{isHovered}
|
||||
on:click={labelsHandler}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{:else if !isInline}
|
||||
<Button kind="link" size="large" on:click={labelsHandler}>
|
||||
<Icon slot="content" icon={IconAdd} size="small" />
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
@ -1,106 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { Label, Button, EditBox, Icon, IconBack, IconCheck, IconClose, hexColorToNumber } from '@anticrm/ui'
|
||||
|
||||
import board from '../../plugin'
|
||||
import { createCardLabel, getBoardAvailableColors } from '../../utils/BoardUtils'
|
||||
import ColorPresenter from '../presenters/ColorPresenter.svelte'
|
||||
import { TagElement } from '@anticrm/tags'
|
||||
|
||||
export let object: TagElement | undefined
|
||||
export let onBack: () => void
|
||||
|
||||
let { title, color } = object ?? {}
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
const colorGroups = (function chunk (colors: number[]): number[][] {
|
||||
return colors.length ? [colors.slice(0, 5), ...chunk(colors.slice(5))] : []
|
||||
})(getBoardAvailableColors().map(hexColorToNumber))
|
||||
|
||||
async function save () {
|
||||
if (!title || !color) {
|
||||
return
|
||||
}
|
||||
if (object) {
|
||||
await client.update(object, { title, color })
|
||||
} else {
|
||||
await createCardLabel(client, { title, color })
|
||||
}
|
||||
onBack()
|
||||
}
|
||||
|
||||
async function remove () {
|
||||
if (!object) return
|
||||
await client.remove(object)
|
||||
onBack()
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="antiPopup w-85">
|
||||
<div class="relative flex-row-center w-full ">
|
||||
<div class="absolute ml-1 mt-1 mb-1" style:top="0" style:left="0">
|
||||
<Button icon={IconBack} kind="transparent" size="small" on:click={onBack} />
|
||||
</div>
|
||||
<div class="flex-center flex-grow fs-title mt-1 mb-1">
|
||||
<Label label={board.string.Labels} />
|
||||
</div>
|
||||
|
||||
<div class="absolute mr-1 mt-1 mb-1" 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 c}
|
||||
<div class="w-14">
|
||||
<ColorPresenter
|
||||
value={c}
|
||||
size="large"
|
||||
on:click={() => {
|
||||
color = c
|
||||
}}
|
||||
>
|
||||
{#if c === color}
|
||||
<div class="flex-center flex-grow fs-title h-full">
|
||||
<Icon icon={IconCheck} size="small" />
|
||||
</div>
|
||||
{/if}
|
||||
</ColorPresenter>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ap-footer">
|
||||
{#if object}
|
||||
<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={!color || !title} />
|
||||
</div>
|
||||
</div>
|
@ -1,119 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Card } from '@anticrm/board'
|
||||
import type { Ref } from '@anticrm/core'
|
||||
import tags, { TagElement, TagReference } from '@anticrm/tags'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import {
|
||||
Button,
|
||||
EditBox,
|
||||
Icon,
|
||||
IconEdit,
|
||||
IconCheck,
|
||||
IconClose,
|
||||
Label,
|
||||
numberToHexColor,
|
||||
numberToRGB
|
||||
} from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import board from '../../plugin'
|
||||
import { addCardLabel } from '../../utils/BoardUtils'
|
||||
|
||||
export let object: Card
|
||||
export let search: string = ''
|
||||
export let onEdit: (label: TagElement) => void
|
||||
export let onCreate: () => void
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let hovered: Ref<TagElement> | undefined = undefined
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let labels: TagElement[] = []
|
||||
const labelsQuery = createQuery()
|
||||
$: labelsQuery.query(
|
||||
tags.class.TagElement,
|
||||
{ title: { $like: '%' + search + '%' }, targetClass: board.class.Card },
|
||||
(result) => {
|
||||
labels = result
|
||||
}
|
||||
)
|
||||
|
||||
let cardLabels: TagReference[] = []
|
||||
let cardLabelRefs: Ref<TagElement>[] = []
|
||||
const cardLabelsQuery = createQuery()
|
||||
$: cardLabelsQuery.query(tags.class.TagReference, { attachedTo: object._id }, (result) => {
|
||||
cardLabels = result
|
||||
cardLabelRefs = result.map(({ tag }) => tag)
|
||||
})
|
||||
|
||||
async function toggle (label: TagElement) {
|
||||
const cardLabel = cardLabels.find(({ tag }) => tag === label._id)
|
||||
if (cardLabel) {
|
||||
await client.remove(cardLabel)
|
||||
return
|
||||
}
|
||||
addCardLabel(client, object, label)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="antiPopup w-85 pb-2">
|
||||
<div class="relative flex-row-center w-full">
|
||||
<div class="flex-center flex-grow fs-title mt-1 mb-1">
|
||||
<Label label={board.string.Labels} />
|
||||
</div>
|
||||
<div class="absolute mr-1 mt-1 mb-1" 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-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} maxWidth="100%" placeholder={board.string.SearchLabels} />
|
||||
</div>
|
||||
<div class="text-md font-medium">
|
||||
<Label label={board.string.Labels} />
|
||||
</div>
|
||||
{#each labels 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.4rem 0 ${numberToRGB(label.color, 0.6)}` : ''}
|
||||
on:click={() => toggle(label)}
|
||||
>
|
||||
{label.title}
|
||||
{#if cardLabelRefs.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" on:click={() => onEdit(label)} />
|
||||
</div>
|
||||
{/each}
|
||||
<div class="mt-3" />
|
||||
<Button label={board.string.CreateLabel} kind="no-border" on:click={() => onCreate()} />
|
||||
</div>
|
||||
</div>
|
@ -1,30 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Card } from '@anticrm/board'
|
||||
import { TagElement } from '@anticrm/tags'
|
||||
import CardLabelsEditor from './CardLabelsEditor.svelte'
|
||||
import CardLabelsPicker from './CardLabelsPicker.svelte'
|
||||
|
||||
export let value: Card
|
||||
|
||||
let editMode: {
|
||||
isEdit?: boolean
|
||||
object?: TagElement
|
||||
} = {}
|
||||
let search: string | undefined = undefined
|
||||
|
||||
function setEditMode (isEdit: boolean, object?: TagElement) {
|
||||
editMode = { isEdit, object }
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if editMode.isEdit}
|
||||
<CardLabelsEditor on:close object={editMode.object} onBack={() => setEditMode(false, undefined)} />
|
||||
{:else}
|
||||
<CardLabelsPicker
|
||||
bind:search
|
||||
on:close
|
||||
object={value}
|
||||
onCreate={() => setEditMode(true, undefined)}
|
||||
onEdit={(o) => setEditMode(true, o)}
|
||||
/>
|
||||
{/if}
|
@ -24,11 +24,9 @@ import CreateCard from './components/CreateCard.svelte'
|
||||
import EditCard from './components/EditCard.svelte'
|
||||
import KanbanCard from './components/KanbanCard.svelte'
|
||||
import KanbanView from './components/KanbanView.svelte'
|
||||
import CardLabelsPopup from './components/popups/CardLabelsPopup.svelte'
|
||||
import MoveCard from './components/popups/MoveCard.svelte'
|
||||
import CopyCard from './components/popups/CopyCard.svelte'
|
||||
import DateRangePicker from './components/popups/DateRangePicker.svelte'
|
||||
import CardLabelPresenter from './components/presenters/LabelPresenter.svelte'
|
||||
import TemplatesIcon from './components/TemplatesIcon.svelte'
|
||||
import BoardHeader from './components/BoardHeader.svelte'
|
||||
import BoardMenu from './components/BoardMenu.svelte'
|
||||
@ -36,7 +34,6 @@ import MenuMainPage from './components/MenuMainPage.svelte'
|
||||
import Archive from './components/Archive.svelte'
|
||||
import TableView from './components/TableView.svelte'
|
||||
import UserBoxList from './components/UserBoxList.svelte'
|
||||
import CardLabels from './components/editor/CardLabels.svelte'
|
||||
import CardCoverEditor from './components/editor/CardCoverEditor.svelte'
|
||||
import CardCoverPresenter from './components/presenters/CardCoverPresenter.svelte'
|
||||
import CardCoverPicker from './components/popups/CardCoverPicker.svelte'
|
||||
@ -62,7 +59,6 @@ export default async (): Promise<Resources> => ({
|
||||
EditCard,
|
||||
KanbanCard,
|
||||
CardPresenter,
|
||||
CardLabelPresenter,
|
||||
TemplatesIcon,
|
||||
KanbanView,
|
||||
BoardPresenter,
|
||||
@ -72,11 +68,9 @@ export default async (): Promise<Resources> => ({
|
||||
MenuMainPage,
|
||||
TableView,
|
||||
UserBoxList,
|
||||
CardLabels,
|
||||
CardCoverEditor,
|
||||
CardCoverPresenter,
|
||||
// action popups
|
||||
LabelsActionPopup: CardLabelsPopup,
|
||||
DatesActionPopup: DateRangePicker,
|
||||
CoverActionPopup: CardCoverPicker,
|
||||
MoveActionPopup: MoveCard,
|
||||
|
@ -133,7 +133,6 @@ export default mergeIds(boardId, board, {
|
||||
BoardMenu: '' as AnyComponent,
|
||||
Archive: '' as AnyComponent,
|
||||
MenuMainPage: '' as AnyComponent,
|
||||
UserBoxList: '' as AnyComponent,
|
||||
CardLabels: '' as AnyComponent
|
||||
UserBoxList: '' as AnyComponent
|
||||
}
|
||||
})
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { readable } from 'svelte/store'
|
||||
import board, { Board, Card, CommonBoardPreference } from '@anticrm/board'
|
||||
import board, { Board, CommonBoardPreference } from '@anticrm/board'
|
||||
import core, { Ref, TxOperations } from '@anticrm/core'
|
||||
import type { KanbanTemplate, TodoItem } from '@anticrm/task'
|
||||
import preference from '@anticrm/preference'
|
||||
import tags, { TagElement } from '@anticrm/tags'
|
||||
import { createKanban } from '@anticrm/task'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import {
|
||||
@ -53,28 +52,6 @@ export function getBoardAvailableColors (): string[] {
|
||||
]
|
||||
}
|
||||
|
||||
export async function createCardLabel (
|
||||
client: TxOperations,
|
||||
{ title, color }: { title: string, color: number }
|
||||
): Promise<void> {
|
||||
await client.createDoc(tags.class.TagElement, tags.space.Tags, {
|
||||
title,
|
||||
color,
|
||||
targetClass: board.class.Card,
|
||||
description: '',
|
||||
category: board.category.Other
|
||||
})
|
||||
}
|
||||
|
||||
export async function addCardLabel (client: TxOperations, card: Card, label: TagElement): Promise<void> {
|
||||
const { title, color, _id: tag } = label
|
||||
await client.addCollection(tags.class.TagReference, card.space, card._id, card._class, 'labels', {
|
||||
title,
|
||||
color,
|
||||
tag
|
||||
})
|
||||
}
|
||||
|
||||
export function getDateIcon (item: TodoItem): 'normal' | 'warning' | 'overdue' {
|
||||
if (item.dueTo === null) return 'normal'
|
||||
const date = new Date()
|
||||
@ -86,8 +63,7 @@ export const commonBoardPreference = readable<CommonBoardPreference>(undefined,
|
||||
createQuery().query(board.class.CommonBoardPreference, { attachedTo: board.app.Board }, (result) => {
|
||||
if (result.total > 0) return set(result[0])
|
||||
void getClient().createDoc(board.class.CommonBoardPreference, preference.space.Preference, {
|
||||
attachedTo: board.app.Board,
|
||||
cardLabelsCompactMode: false
|
||||
attachedTo: board.app.Board
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -80,9 +80,7 @@ export interface MenuPage extends Doc {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface CommonBoardPreference extends Preference {
|
||||
cardLabelsCompactMode: boolean
|
||||
}
|
||||
export interface CommonBoardPreference extends Preference {}
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
@ -0,0 +1,36 @@
|
||||
<script lang="ts">
|
||||
import { Doc } from '@anticrm/core'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import tags, { TagReference } from '@anticrm/tags'
|
||||
|
||||
import { getEventPopupPositionElement, Icon, IconAdd, showPopup } from '@anticrm/ui'
|
||||
|
||||
import Button from '@anticrm/ui/src/components/Button.svelte'
|
||||
import TagReferencePresenter from './TagReferencePresenter.svelte'
|
||||
import TagsEditorPopup from './TagsEditorPopup.svelte'
|
||||
|
||||
export let object: Doc
|
||||
export let isEditable: boolean = true
|
||||
|
||||
let items: TagReference[] = []
|
||||
const query = createQuery()
|
||||
$: query.query(tags.class.TagReference, { attachedTo: object._id }, (result) => {
|
||||
items = result
|
||||
})
|
||||
async function tagsHandler (evt: MouseEvent): Promise<void> {
|
||||
if (!isEditable) return
|
||||
showPopup(TagsEditorPopup, { object }, getEventPopupPositionElement(evt))
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if items.length}
|
||||
<div class="flex-row-center flex-wrap flex-gap-1 ml-4" on:click={tagsHandler}>
|
||||
{#each items as value}
|
||||
<TagReferencePresenter {value} />
|
||||
{/each}
|
||||
</div>
|
||||
{:else if isEditable}
|
||||
<Button kind="link" on:click={tagsHandler}>
|
||||
<Icon icon={IconAdd} slot="content" size="small" />
|
||||
</Button>
|
||||
{/if}
|
34
plugins/tags-resources/src/components/TagsEditorPopup.svelte
Normal file
34
plugins/tags-resources/src/components/TagsEditorPopup.svelte
Normal file
@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { Doc, Ref } from '@anticrm/core'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import tags, { TagElement } from '@anticrm/tags'
|
||||
import TagsPopup from './TagsPopup.svelte'
|
||||
|
||||
export let object: Doc
|
||||
|
||||
let selected: Ref<TagElement>[] = []
|
||||
const query = createQuery()
|
||||
$: query.query(tags.class.TagReference, { attachedTo: object._id }, (result) => {
|
||||
selected = result.map(({ tag }) => tag)
|
||||
})
|
||||
const client = getClient()
|
||||
async function addRef ({ title, color, _id: tag }: TagElement): Promise<void> {
|
||||
await client.addCollection(tags.class.TagReference, object.space, object._id, object._class, 'labels', {
|
||||
title,
|
||||
color,
|
||||
tag
|
||||
})
|
||||
}
|
||||
async function removeTag (tag: TagElement): Promise<void> {
|
||||
const tagRef = await client.findOne(tags.class.TagReference, { tag: tag._id })
|
||||
if (tagRef) await client.remove(tagRef)
|
||||
}
|
||||
async function onUpdate (event: CustomEvent<{ action: string; tag: TagElement }>) {
|
||||
const result = event.detail
|
||||
if (result === undefined) return
|
||||
if (result.action === 'add') addRef(result.tag)
|
||||
else if (result.action === 'remove') removeTag(result.tag)
|
||||
}
|
||||
</script>
|
||||
|
||||
<TagsPopup targetClass={object._class} {selected} on:update={onUpdate} />
|
@ -27,6 +27,8 @@ import TagsPresenter from './components/TagsPresenter.svelte'
|
||||
import TagsView from './components/TagsView.svelte'
|
||||
import TagElementCountPresenter from './components/TagElementCountPresenter.svelte'
|
||||
import TagsFilter from './components/TagsFilter.svelte'
|
||||
import TagsAttributeEditor from './components/TagsAttributeEditor.svelte'
|
||||
import TagsEditorPopup from './components/TagsEditorPopup.svelte'
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
@ -41,7 +43,9 @@ export default async (): Promise<Resources> => ({
|
||||
TagsItemPresenter,
|
||||
CategoryPresenter,
|
||||
TagsCategoryBar,
|
||||
TagElementCountPresenter
|
||||
TagElementCountPresenter,
|
||||
TagsAttributeEditor,
|
||||
TagsEditorPopup
|
||||
},
|
||||
actionImpl: {
|
||||
Open: (value: TagElement, evt: MouseEvent) => {
|
||||
|
@ -81,7 +81,9 @@ const tagsPlugin = plugin(tagsId, {
|
||||
TagsView: '' as AnyComponent,
|
||||
TagsEditor: '' as AnyComponent,
|
||||
TagsDropdownEditor: '' as AnyComponent,
|
||||
TagsCategoryBar: '' as AnyComponent
|
||||
TagsCategoryBar: '' as AnyComponent,
|
||||
TagsAttributeEditor: '' as AnyComponent,
|
||||
TagsPresenter: '' as AnyComponent
|
||||
},
|
||||
category: {
|
||||
NoCategory: '' as Ref<TagCategory>
|
||||
|
@ -17,12 +17,13 @@
|
||||
import { AttributesBar, getClient, KeyedAttribute } from '@anticrm/presentation'
|
||||
import setting from '@anticrm/setting'
|
||||
import { Button, getCurrentLocation, Label, navigate, Tooltip } from '@anticrm/ui'
|
||||
import { collectionsFilter, getFiltredKeys } from '../utils'
|
||||
import { getFiltredKeys, isCollectionAttr } from '../utils'
|
||||
|
||||
export let object: Doc
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let to: Ref<Class<Doc>> | undefined
|
||||
export let ignoreKeys: string[] = []
|
||||
export let allowedCollections: string[] = []
|
||||
export let vertical: boolean
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
@ -32,7 +33,7 @@
|
||||
|
||||
function updateKeys (ignoreKeys: string[]): void {
|
||||
const filtredKeys = getFiltredKeys(hierarchy, _class, ignoreKeys, to)
|
||||
keys = collectionsFilter(hierarchy, filtredKeys, false)
|
||||
keys = filtredKeys.filter((key) => !isCollectionAttr(hierarchy, key) || allowedCollections.includes(key.key))
|
||||
}
|
||||
|
||||
$: updateKeys(ignoreKeys)
|
||||
|
@ -19,9 +19,26 @@
|
||||
export let object: Doc
|
||||
export let mixins: Mixin<Doc>[]
|
||||
export let ignoreKeys: string[]
|
||||
export let allowedCollections: string[] = []
|
||||
</script>
|
||||
|
||||
<ClassAttributeBar _class={object._class} {object} {ignoreKeys} to={undefined} vertical on:update />
|
||||
<ClassAttributeBar
|
||||
_class={object._class}
|
||||
{object}
|
||||
{ignoreKeys}
|
||||
to={undefined}
|
||||
{allowedCollections}
|
||||
vertical
|
||||
on:update
|
||||
/>
|
||||
{#each mixins as mixin}
|
||||
<ClassAttributeBar _class={mixin._id} {object} {ignoreKeys} to={object._class} vertical on:update />
|
||||
<ClassAttributeBar
|
||||
_class={mixin._id}
|
||||
{object}
|
||||
{ignoreKeys}
|
||||
to={object._class}
|
||||
{allowedCollections}
|
||||
vertical
|
||||
on:update
|
||||
/>
|
||||
{/each}
|
||||
|
@ -374,6 +374,6 @@ export function collectionsFilter (hierarchy: Hierarchy, keys: KeyedAttribute[],
|
||||
return result
|
||||
}
|
||||
|
||||
function isCollectionAttr (hierarchy: Hierarchy, key: KeyedAttribute): boolean {
|
||||
export function isCollectionAttr (hierarchy: Hierarchy, key: KeyedAttribute): boolean {
|
||||
return hierarchy.isDerived(key.attr.type._class, core.class.Collection)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user