mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 19:11:33 +03:00
Tracker: Issues list view (#1313)
Signed-off-by: Artyom Grigorovich <grigorovichartyom@gmail.com>
This commit is contained in:
parent
207bcf73bb
commit
de0a5fcdd8
19
.vscode/settings.json
vendored
19
.vscode/settings.json
vendored
@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"files.exclude": {
|
|
||||||
"**/.git": true,
|
|
||||||
"**/.svn": true,
|
|
||||||
"**/.hg": true,
|
|
||||||
"**/CVS": true,
|
|
||||||
"**/.DS_Store": true,
|
|
||||||
"**/Thumbs.db": true,
|
|
||||||
"**/.rush": true,
|
|
||||||
"**/lib": true,
|
|
||||||
"**/.heft": true,
|
|
||||||
"**/_api-extractor-temp": true,
|
|
||||||
"**/svelte.config.js": true,
|
|
||||||
"**/postcss.config.js": true,
|
|
||||||
"**/node_modules": true,
|
|
||||||
"**/temp": true
|
|
||||||
},
|
|
||||||
"explorerExclude.backup": null
|
|
||||||
}
|
|
@ -0,0 +1,56 @@
|
|||||||
|
<!--
|
||||||
|
// 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 { formatName, Person } from '@anticrm/contact'
|
||||||
|
import { Hierarchy } from '@anticrm/core'
|
||||||
|
import { Avatar } from '@anticrm/presentation'
|
||||||
|
import { showPanel } from '@anticrm/ui'
|
||||||
|
import view from '@anticrm/view'
|
||||||
|
|
||||||
|
export let value: Person | undefined
|
||||||
|
export let inline: boolean = false
|
||||||
|
export let shouldShowName = true
|
||||||
|
export let shouldShowPlaceholder = false
|
||||||
|
|
||||||
|
const avatarSize = 'x-small'
|
||||||
|
|
||||||
|
const onClick = async () => {
|
||||||
|
if (value) {
|
||||||
|
showPanel(view.component.EditDoc, value._id, Hierarchy.mixinOrClass(value), 'full')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if value || shouldShowPlaceholder}
|
||||||
|
{#if value}
|
||||||
|
<a
|
||||||
|
class="flex-presenter"
|
||||||
|
class:inline-presenter={inline}
|
||||||
|
href="#{encodeURIComponent([view.component.EditDoc, value._id, Hierarchy.mixinOrClass(value)].join('|'))}"
|
||||||
|
on:click={onClick}
|
||||||
|
>
|
||||||
|
<div class="icon">
|
||||||
|
<Avatar size={avatarSize} avatar={value?.avatar} />
|
||||||
|
</div>
|
||||||
|
{#if shouldShowName}
|
||||||
|
<span class="label">{formatName(value.name)}</span>
|
||||||
|
{/if}
|
||||||
|
</a>
|
||||||
|
{:else}
|
||||||
|
<div class="icon">
|
||||||
|
<Avatar size={avatarSize} avatar={undefined} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
@ -15,33 +15,26 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { formatName, Person } from '@anticrm/contact'
|
import { formatName, Person } from '@anticrm/contact'
|
||||||
import { Hierarchy } from '@anticrm/core'
|
import { IntlString } from '@anticrm/platform'
|
||||||
import { Avatar } from '@anticrm/presentation'
|
import { Tooltip } from '@anticrm/ui'
|
||||||
import { showPanel } from '@anticrm/ui'
|
import PersonContent from './PersonContent.svelte'
|
||||||
import view from '@anticrm/view'
|
|
||||||
|
|
||||||
export let value: Person
|
export let value: Person
|
||||||
export let inline: boolean = false
|
export let inline: boolean = false
|
||||||
export let showLabel = true
|
export let shouldShowName = true
|
||||||
|
export let shouldShowPlaceholder = false
|
||||||
async function onClick () {
|
export let tooltipLabels: { personLabel: IntlString; placeholderLabel?: IntlString } | undefined = undefined
|
||||||
showPanel(view.component.EditDoc, value._id, Hierarchy.mixinOrClass(value), 'full')
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if value}
|
{#if value || shouldShowPlaceholder}
|
||||||
|
{#if tooltipLabels}
|
||||||
<a
|
<Tooltip
|
||||||
class="flex-presenter"
|
label={value ? tooltipLabels.personLabel : tooltipLabels.placeholderLabel}
|
||||||
class:inline-presenter={inline}
|
props={{ value: formatName(value?.name) }}
|
||||||
href="#{encodeURIComponent([view.component.EditDoc, value._id, Hierarchy.mixinOrClass(value)].join('|'))}"
|
|
||||||
on:click={onClick}
|
|
||||||
>
|
>
|
||||||
<div class="icon">
|
<PersonContent {inline} {value} {shouldShowName} {shouldShowPlaceholder} />
|
||||||
<Avatar size={'x-small'} avatar={value.avatar} />
|
</Tooltip>
|
||||||
</div>
|
{:else}
|
||||||
{#if showLabel}
|
<PersonContent {inline} {value} {shouldShowName} {shouldShowPlaceholder} />
|
||||||
<span class="label">{formatName(value.name)}</span>
|
|
||||||
{/if}
|
{/if}
|
||||||
</a>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Doc, Ref } from '@anticrm/core'
|
import { Doc, Ref, getObjectValue } from '@anticrm/core'
|
||||||
import { IconMoreV, showPopup } from '@anticrm/ui'
|
import { IconMoreV, showPopup } from '@anticrm/ui'
|
||||||
import { AttributeModel } from '@anticrm/view'
|
import { AttributeModel } from '@anticrm/view'
|
||||||
import inventory, { Category } from '@anticrm/inventory'
|
import inventory, { Category } from '@anticrm/inventory'
|
||||||
@ -29,19 +29,6 @@
|
|||||||
export let parent: Ref<Doc> = inventory.global.Category
|
export let parent: Ref<Doc> = inventory.global.Category
|
||||||
let expanded: Set<Ref<Category>> = new Set<Ref<Category>>()
|
let expanded: Set<Ref<Category>> = new Set<Ref<Category>>()
|
||||||
|
|
||||||
function getValue (doc: Category, key: string): any {
|
|
||||||
if (key.length === 0) {
|
|
||||||
return doc
|
|
||||||
}
|
|
||||||
const path = key.split('.')
|
|
||||||
const len = path.length
|
|
||||||
let obj = doc as any
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
obj = obj?.[path[i]]
|
|
||||||
}
|
|
||||||
return obj ?? ''
|
|
||||||
}
|
|
||||||
|
|
||||||
const showMenu = async (ev: MouseEvent, object: Category): Promise<void> => {
|
const showMenu = async (ev: MouseEvent, object: Category): Promise<void> => {
|
||||||
showPopup(ContextMenu, { object }, ev.target as HTMLElement)
|
showPopup(ContextMenu, { object }, ev.target as HTMLElement)
|
||||||
}
|
}
|
||||||
@ -74,13 +61,21 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<svelte:component this={attribute.presenter} value={getValue(object, attribute.key)} {...attribute.props} />
|
<svelte:component
|
||||||
|
this={attribute.presenter}
|
||||||
|
value={getObjectValue(attribute.key, object) ?? ''}
|
||||||
|
{...attribute.props}
|
||||||
|
/>
|
||||||
<div class="menuRow" on:click={(ev) => showMenu(ev, object)}><IconMoreV size={'small'} /></div>
|
<div class="menuRow" on:click={(ev) => showMenu(ev, object)}><IconMoreV size={'small'} /></div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
{:else}
|
{:else}
|
||||||
<td>
|
<td>
|
||||||
<svelte:component this={attribute.presenter} value={getValue(object, attribute.key)} {...attribute.props} />
|
<svelte:component
|
||||||
|
this={attribute.presenter}
|
||||||
|
value={getObjectValue(attribute.key, object)}
|
||||||
|
{...attribute.props}
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -11,10 +11,14 @@
|
|||||||
"Issues": "Issues",
|
"Issues": "Issues",
|
||||||
"Views": "Views",
|
"Views": "Views",
|
||||||
"Active": "Active",
|
"Active": "Active",
|
||||||
|
"AllIssues": "All issues {value}",
|
||||||
|
"ActiveIssues": "Active issues {value}",
|
||||||
|
"BacklogIssues": "Backlog {value}",
|
||||||
"Backlog": "Backlog",
|
"Backlog": "Backlog",
|
||||||
"Board": "Board",
|
"Board": "Board",
|
||||||
"Projects": "Projects",
|
"Projects": "Projects",
|
||||||
"CreateTeam": "Create team",
|
"CreateTeam": "Create team",
|
||||||
|
"AddIssue": "Add Issue",
|
||||||
"NewIssue": "New issue",
|
"NewIssue": "New issue",
|
||||||
"SaveIssue": "Save issue",
|
"SaveIssue": "Save issue",
|
||||||
"CreateMore": "Create more",
|
"CreateMore": "Create more",
|
||||||
@ -35,6 +39,8 @@
|
|||||||
"Status": "",
|
"Status": "",
|
||||||
"Number": "Number",
|
"Number": "Number",
|
||||||
"Assignee": "Assignee",
|
"Assignee": "Assignee",
|
||||||
|
"AssignTo": "Assign to",
|
||||||
|
"AssignedTo": "Assigned to {value}",
|
||||||
"Parent": "Set parent issue\u2026",
|
"Parent": "Set parent issue\u2026",
|
||||||
"BlockedBy": "",
|
"BlockedBy": "",
|
||||||
"RelatedTo": "",
|
"RelatedTo": "",
|
||||||
@ -44,6 +50,7 @@
|
|||||||
"Project": "Project",
|
"Project": "Project",
|
||||||
"Space": "",
|
"Space": "",
|
||||||
"DueDate": "Set due date\u2026",
|
"DueDate": "Set due date\u2026",
|
||||||
|
"ModificationDate": "Last Modified: {value}",
|
||||||
"Team": "",
|
"Team": "",
|
||||||
"Issue": "",
|
"Issue": "",
|
||||||
"Document": "",
|
"Document": "",
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
<script lang='ts'>
|
<script lang="ts">
|
||||||
import { Ref } from '@anticrm/core'
|
import { Ref } from '@anticrm/core'
|
||||||
import { IssueStatus, Team } from '@anticrm/tracker'
|
import { IssueStatus, Team } from '@anticrm/tracker'
|
||||||
import Issues from './Issues.svelte'
|
import Issues from './Issues.svelte'
|
||||||
export let currentSpace: Ref<Team>
|
import tracker from '../../plugin'
|
||||||
|
|
||||||
let todalIssues = 0
|
export let currentSpace: Ref<Team>
|
||||||
</script>
|
</script>
|
||||||
<div class='fs-title'>
|
|
||||||
Active issues {todalIssues}
|
<Issues {currentSpace} categories={[IssueStatus.InProgress, IssueStatus.Todo]} title={tracker.string.ActiveIssues} />
|
||||||
</div>
|
|
||||||
<Issues currentSpace={currentSpace} categories={[IssueStatus.InProgress, IssueStatus.Todo]} on:content={(evt) => { todalIssues = evt.detail.length } }></Issues>
|
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
<script lang='ts'>
|
<script lang="ts">
|
||||||
import { Ref } from '@anticrm/core'
|
import { Ref } from '@anticrm/core'
|
||||||
import { IssueStatus, Team } from '@anticrm/tracker'
|
import { IssueStatus, Team } from '@anticrm/tracker'
|
||||||
import Issues from './Issues.svelte'
|
import Issues from './Issues.svelte'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
|
||||||
export let currentSpace: Ref<Team>
|
export let currentSpace: Ref<Team>
|
||||||
</script>
|
</script>
|
||||||
<Issues {currentSpace} categories={[IssueStatus.Backlog]}></Issues>
|
|
||||||
|
<Issues title={tracker.string.BacklogIssues} {currentSpace} categories={[IssueStatus.Backlog]} />
|
||||||
|
@ -105,7 +105,7 @@
|
|||||||
<div class='flex-between mb-2'>
|
<div class='flex-between mb-2'>
|
||||||
<IssuePresenter value={object} {currentTeam}/>
|
<IssuePresenter value={object} {currentTeam}/>
|
||||||
{#if issue.$lookup?.assignee }
|
{#if issue.$lookup?.assignee }
|
||||||
<Component is={view.component.ObjectPresenter} props={{ value: issue.$lookup.assignee, props: { showLabel: false } }}/>
|
<Component is={view.component.ObjectPresenter} props={{ value: issue.$lookup.assignee, props: { shouldShowName: false } }}/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<span class='fs-bold title'>
|
<span class='fs-bold title'>
|
||||||
|
@ -1,45 +1,75 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { DocumentQuery, FindOptions } from '@anticrm/core'
|
|
||||||
import { Issue, IssueStatus, Team } from '@anticrm/tracker'
|
|
||||||
import { Scroller } from '@anticrm/ui'
|
|
||||||
import { Table } from '@anticrm/view-resources'
|
|
||||||
import tracker from '../../plugin'
|
|
||||||
import contact from '@anticrm/contact'
|
import contact from '@anticrm/contact'
|
||||||
|
import { DocumentQuery, FindOptions, Ref } from '@anticrm/core'
|
||||||
|
import { Issue, IssueStatus, Team } from '@anticrm/tracker'
|
||||||
|
import { Icon, IconAdd, Scroller, Tooltip, Button, showPopup, Label } from '@anticrm/ui'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
import IssuesList from './IssuesList.svelte'
|
||||||
|
import { issueStatuses } from '../../utils'
|
||||||
|
import CreateIssue from '../CreateIssue.svelte'
|
||||||
|
|
||||||
export let query: DocumentQuery<Issue>
|
export let query: DocumentQuery<Issue>
|
||||||
export let category: IssueStatus
|
export let category: IssueStatus
|
||||||
|
export let currentSpace: Ref<Team> | undefined = undefined
|
||||||
export let currentTeam: Team
|
export let currentTeam: Team
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
const options: FindOptions<Issue> = {
|
const options: FindOptions<Issue> = {
|
||||||
lookup: {
|
lookup: {
|
||||||
assignee: contact.class.Employee
|
assignee: contact.class.Employee
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let visible = false
|
let issuesAmount = 0
|
||||||
|
|
||||||
|
const handleNewIssueAdded = (event: Event) => {
|
||||||
|
if (!currentSpace) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
showPopup(CreateIssue, { space: currentSpace, issueStatus: category }, event.target)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class='category' class:visible={visible}>
|
<div class="category" class:visible={issuesAmount > 0}>
|
||||||
<div class='fs-title'>
|
<div class="header categoryHeader flex-between label">
|
||||||
{IssueStatus[category]}
|
<div class="flex-row-center gap-2">
|
||||||
|
<Icon icon={issueStatuses[category].icon} size={'small'} />
|
||||||
|
<span class="lines-limit-2"><Label label={issueStatuses[category].label} /></span>
|
||||||
|
<span class="eLabelCounter ml-2">{issuesAmount}</span>
|
||||||
</div>
|
</div>
|
||||||
<Scroller>
|
<div class="flex mr-1">
|
||||||
<Table
|
<Tooltip label={tracker.string.AddIssueTooltip} direction={'left'}>
|
||||||
|
<Button icon={IconAdd} kind={'transparent'} on:click={handleNewIssueAdded} />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Scroller>
|
||||||
|
<IssuesList
|
||||||
_class={tracker.class.Issue}
|
_class={tracker.class.Issue}
|
||||||
config={[
|
config={[
|
||||||
{ key: '', presenter: tracker.component.IssuePresenter, props: { currentTeam }, label: tracker.string.Issue },
|
{ key: '', presenter: tracker.component.IssuePresenter, props: { currentTeam } },
|
||||||
'title',
|
{ key: '', presenter: tracker.component.TitlePresenter },
|
||||||
// 'status',
|
{ key: 'modifiedOn', presenter: tracker.component.ModificationDatePresenter },
|
||||||
'$lookup.assignee',
|
{
|
||||||
'modifiedOn'
|
key: '$lookup.assignee',
|
||||||
|
props: {
|
||||||
|
shouldShowName: false,
|
||||||
|
shouldShowPlaceholder: true,
|
||||||
|
tooltipLabels: { personLabel: tracker.string.AssignedTo, placeholderLabel: tracker.string.AssignTo }
|
||||||
|
}
|
||||||
|
}
|
||||||
]}
|
]}
|
||||||
options={options}
|
{options}
|
||||||
query={{ ...query, status: category }}
|
query={{ ...query, status: category }}
|
||||||
showNotification
|
on:content={(evt) => {
|
||||||
highlightRows
|
issuesAmount = evt.detail.length
|
||||||
on:content={(evt) => { visible = evt.detail.length > 0 }}
|
dispatch('content', issuesAmount)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Scroller>
|
</Scroller>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@ -49,4 +79,19 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.categoryHeader {
|
||||||
|
height: 2.5rem;
|
||||||
|
background-color: var(--theme-table-bg-hover);
|
||||||
|
padding-left: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--theme-caption-color);
|
||||||
|
.eLabelCounter {
|
||||||
|
opacity: 0.8;
|
||||||
|
font-weight: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
import type { Issue, Team } from '@anticrm/tracker'
|
import type { Issue, Team } from '@anticrm/tracker'
|
||||||
import { Icon, showPanel } from '@anticrm/ui'
|
import { Icon, showPanel } from '@anticrm/ui'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
|
import { issuePriorities } from '../../utils'
|
||||||
|
|
||||||
export let value: Issue
|
export let value: Issue
|
||||||
export let currentTeam: Team
|
export let currentTeam: Team
|
||||||
@ -37,6 +38,9 @@
|
|||||||
class:inline-presenter={inline}
|
class:inline-presenter={inline}
|
||||||
on:click={show}
|
on:click={show}
|
||||||
>
|
>
|
||||||
|
<div class="icon">
|
||||||
|
<Icon icon={issuePriorities[value.priority].icon} size={'small'} />
|
||||||
|
</div>
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<Icon icon={tracker.icon.Issue} size={'small'} />
|
<Icon icon={tracker.icon.Issue} size={'small'} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,9 +16,10 @@
|
|||||||
import type { DocumentQuery, Ref } from '@anticrm/core'
|
import type { DocumentQuery, Ref } from '@anticrm/core'
|
||||||
import { createQuery } from '@anticrm/presentation'
|
import { createQuery } from '@anticrm/presentation'
|
||||||
import { Issue, IssueStatus, Team } from '@anticrm/tracker'
|
import { Issue, IssueStatus, Team } from '@anticrm/tracker'
|
||||||
import { ScrollBox } from '@anticrm/ui'
|
import { Label, ScrollBox } from '@anticrm/ui'
|
||||||
import CategoryPresenter from './CategoryPresenter.svelte'
|
import CategoryPresenter from './CategoryPresenter.svelte'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
|
import { IntlString } from '@anticrm/platform'
|
||||||
|
|
||||||
export let currentSpace: Ref<Team>
|
export let currentSpace: Ref<Team>
|
||||||
export let categories = [
|
export let categories = [
|
||||||
@ -28,15 +29,26 @@
|
|||||||
IssueStatus.Done,
|
IssueStatus.Done,
|
||||||
IssueStatus.Canceled
|
IssueStatus.Canceled
|
||||||
]
|
]
|
||||||
|
export let title: IntlString = tracker.string.AllIssues
|
||||||
export let query: DocumentQuery<Issue> = {}
|
export let query: DocumentQuery<Issue> = {}
|
||||||
export let search: string = ''
|
export let search: string = ''
|
||||||
|
|
||||||
|
const spaceQuery = createQuery()
|
||||||
|
const issuesMap: { [status: string]: number } = {}
|
||||||
|
|
||||||
|
$: getTotalIssues = () => {
|
||||||
|
let total = 0
|
||||||
|
|
||||||
|
for (const issuesAmount of Object.values(issuesMap)) {
|
||||||
|
total += issuesAmount
|
||||||
|
}
|
||||||
|
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
$: resultQuery =
|
$: resultQuery =
|
||||||
search === '' ? { space: currentSpace, ...query } : { $search: search, space: currentSpace, ...query }
|
search === '' ? { space: currentSpace, ...query } : { $search: search, space: currentSpace, ...query }
|
||||||
|
|
||||||
const spaceQuery = createQuery()
|
|
||||||
|
|
||||||
let currentTeam: Team | undefined
|
let currentTeam: Team | undefined
|
||||||
|
|
||||||
$: spaceQuery.query(tracker.class.Team, { _id: currentSpace }, (res) => {
|
$: spaceQuery.query(tracker.class.Team, { _id: currentSpace }, (res) => {
|
||||||
@ -46,9 +58,21 @@
|
|||||||
|
|
||||||
{#if currentTeam}
|
{#if currentTeam}
|
||||||
<ScrollBox vertical stretch>
|
<ScrollBox vertical stretch>
|
||||||
|
<div class="fs-title">
|
||||||
|
<Label label={title} params={{ value: getTotalIssues() }} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="ml-4 mt-4">
|
<div class="ml-4 mt-4">
|
||||||
{#each categories as category}
|
{#each categories as category}
|
||||||
<CategoryPresenter {category} query={resultQuery} {currentTeam} />
|
<CategoryPresenter
|
||||||
|
{category}
|
||||||
|
query={resultQuery}
|
||||||
|
{currentSpace}
|
||||||
|
{currentTeam}
|
||||||
|
on:content={(event) => {
|
||||||
|
issuesMap[category] = event.detail
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</ScrollBox>
|
</ScrollBox>
|
||||||
|
@ -0,0 +1,232 @@
|
|||||||
|
<!--
|
||||||
|
// 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 { Class, Doc, DocumentQuery, FindOptions, Ref, getObjectValue } from '@anticrm/core'
|
||||||
|
import { SortingOrder } from '@anticrm/core'
|
||||||
|
import { createQuery, getClient } from '@anticrm/presentation'
|
||||||
|
import { CheckBox, Loading, showPopup, Spinner, IconMoreV } from '@anticrm/ui'
|
||||||
|
import { BuildModelKey } from '@anticrm/view'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import { buildModel, LoadingProps, Menu } from '@anticrm/view-resources'
|
||||||
|
|
||||||
|
export let _class: Ref<Class<Doc>>
|
||||||
|
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
|
||||||
|
export let config: (BuildModelKey | string)[]
|
||||||
|
export let options: FindOptions<Doc> | undefined = undefined
|
||||||
|
export let query: DocumentQuery<Doc>
|
||||||
|
|
||||||
|
// If defined, will show a number of dummy items before real data will appear.
|
||||||
|
export let loadingProps: LoadingProps | undefined = undefined
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
const DOCS_MAX_AMOUNT = 200
|
||||||
|
const liveQuery = createQuery()
|
||||||
|
const sort = { modifiedOn: SortingOrder.Descending }
|
||||||
|
|
||||||
|
let selectedIssueIds = new Set<Ref<Doc>>()
|
||||||
|
let selectedRowIndex: number | undefined
|
||||||
|
let isLoading = false
|
||||||
|
let docObjects: Doc[] | undefined
|
||||||
|
let queryIndex = 0
|
||||||
|
|
||||||
|
const updateData = async (_class: Ref<Class<Doc>>, query: DocumentQuery<Doc>, options?: FindOptions<Doc>) => {
|
||||||
|
const i = ++queryIndex
|
||||||
|
|
||||||
|
isLoading = true
|
||||||
|
|
||||||
|
liveQuery.query(
|
||||||
|
_class,
|
||||||
|
query,
|
||||||
|
(result) => {
|
||||||
|
if (i !== queryIndex) {
|
||||||
|
return // our data is invalid.
|
||||||
|
}
|
||||||
|
|
||||||
|
docObjects = result
|
||||||
|
dispatch('content', docObjects)
|
||||||
|
isLoading = false
|
||||||
|
},
|
||||||
|
{ sort, ...options, limit: DOCS_MAX_AMOUNT }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
$: updateData(_class, query, options)
|
||||||
|
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
const showMenu = async (event: MouseEvent, docObject: Doc, rowIndex: number) => {
|
||||||
|
selectedRowIndex = rowIndex
|
||||||
|
|
||||||
|
showPopup(Menu, { object: docObject, baseMenuClass }, event.target as HTMLElement, () => {
|
||||||
|
selectedRowIndex = undefined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleIssueSelected = (id: Ref<Doc>, event: Event) => {
|
||||||
|
const eventTarget = event.target as HTMLInputElement
|
||||||
|
const isChecked = eventTarget.checked
|
||||||
|
|
||||||
|
if (isChecked) {
|
||||||
|
selectedIssueIds.add(id)
|
||||||
|
} else {
|
||||||
|
selectedIssueIds.delete(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedIssueIds = selectedIssueIds
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLoadingElementsLength = (props: LoadingProps, options?: FindOptions<Doc>) => {
|
||||||
|
if (options?.limit && options?.limit > 0) {
|
||||||
|
return Math.min(options.limit, props.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.length
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#await buildModel({ client, _class, keys: config, options })}
|
||||||
|
{#if !isLoading}
|
||||||
|
<Loading />
|
||||||
|
{/if}
|
||||||
|
{:then attributeModels}
|
||||||
|
<div class="listRoot">
|
||||||
|
{#if docObjects}
|
||||||
|
{#each docObjects as docObject, rowIndex (docObject._id)}
|
||||||
|
<div
|
||||||
|
class="listGrid"
|
||||||
|
class:mListGridChecked={selectedIssueIds.has(docObject._id)}
|
||||||
|
class:mListGridFixed={rowIndex === selectedRowIndex}
|
||||||
|
>
|
||||||
|
{#each attributeModels as attributeModel, attributeModelIndex}
|
||||||
|
{#if attributeModelIndex === 0}
|
||||||
|
<div class="gridElement">
|
||||||
|
<div class="antiTable-cells__checkCell">
|
||||||
|
<CheckBox
|
||||||
|
checked={selectedIssueIds.has(docObject._id)}
|
||||||
|
on:change={(event) => {
|
||||||
|
handleIssueSelected(docObject._id, event)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="issuePresenter">
|
||||||
|
<svelte:component
|
||||||
|
this={attributeModel.presenter}
|
||||||
|
value={getObjectValue(attributeModel.key, docObject) ?? ''}
|
||||||
|
{...attributeModel.props}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
id="context-menu"
|
||||||
|
class="eIssuePresenterContextMenu"
|
||||||
|
on:click={(event) => showMenu(event, docObject, rowIndex)}
|
||||||
|
>
|
||||||
|
<IconMoreV size={'small'} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="gridElement">
|
||||||
|
<svelte:component
|
||||||
|
this={attributeModel.presenter}
|
||||||
|
value={getObjectValue(attributeModel.key, docObject) ?? ''}
|
||||||
|
{...attributeModel.props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{:else if loadingProps !== undefined}
|
||||||
|
{#each Array(getLoadingElementsLength(loadingProps, options)) as _, rowIndex}
|
||||||
|
<div class="listGrid mListGridIsLoading" class:fixed={rowIndex === selectedRowIndex}>
|
||||||
|
{#each attributeModels as _, attributeModelIndex}
|
||||||
|
{#if attributeModelIndex === 0}
|
||||||
|
<div class="gridElement">
|
||||||
|
<CheckBox checked={false} />
|
||||||
|
<div class="ml-4">
|
||||||
|
<Spinner size="small" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/await}
|
||||||
|
|
||||||
|
{#if isLoading}
|
||||||
|
<Loading />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.listRoot {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.listGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 9rem auto 4rem 2rem;
|
||||||
|
height: 3.25rem;
|
||||||
|
color: var(--theme-caption-color);
|
||||||
|
border-bottom: 1px solid var(--theme-button-border-hovered);
|
||||||
|
|
||||||
|
&.mListGridChecked {
|
||||||
|
background-color: var(--theme-table-bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mListGridFixed {
|
||||||
|
.eIssuePresenterContextMenu {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mListGridIsLoading {
|
||||||
|
grid-template-columns: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--theme-table-bg-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gridElement {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.issuePresenter {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 1rem;
|
||||||
|
|
||||||
|
.eIssuePresenterContextMenu {
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.eIssuePresenterContextMenu {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,33 @@
|
|||||||
|
<!--
|
||||||
|
// 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 { Tooltip } from '@anticrm/ui'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
|
||||||
|
export let value: number
|
||||||
|
|
||||||
|
$: shortModificationDate = new Date(value).toLocaleString('default', { month: 'short', day: 'numeric' })
|
||||||
|
$: fullModificationDate = new Date(value).toLocaleString('default', {
|
||||||
|
minute: '2-digit',
|
||||||
|
hour: 'numeric',
|
||||||
|
day: '2-digit',
|
||||||
|
month: 'short',
|
||||||
|
year: 'numeric'
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Tooltip label={tracker.string.ModificationDate} props={{ value: fullModificationDate }}>
|
||||||
|
<span class="nowrap">{shortModificationDate}</span>
|
||||||
|
</Tooltip>
|
@ -0,0 +1,41 @@
|
|||||||
|
<!--
|
||||||
|
// 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 { Issue } from '@anticrm/tracker'
|
||||||
|
import { Icon } from '@anticrm/ui'
|
||||||
|
import { issueStatuses } from '../../utils'
|
||||||
|
|
||||||
|
export let value: Issue
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if value}
|
||||||
|
<div class="titlePresenter">
|
||||||
|
<div class="icon">
|
||||||
|
<Icon icon={issueStatuses[value.status].icon} size={'small'} />
|
||||||
|
</div>
|
||||||
|
<span class="label nowrap" title={value.title}>{value.title}</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.titlePresenter {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -26,6 +26,8 @@ import MyIssues from './components/myissues/MyIssues.svelte'
|
|||||||
import Projects from './components/projects/Projects.svelte'
|
import Projects from './components/projects/Projects.svelte'
|
||||||
import Views from './components/views/Views.svelte'
|
import Views from './components/views/Views.svelte'
|
||||||
import IssuePresenter from './components/issues/IssuePresenter.svelte'
|
import IssuePresenter from './components/issues/IssuePresenter.svelte'
|
||||||
|
import TitlePresenter from './components/issues/TitlePresenter.svelte'
|
||||||
|
import ModificationDatePresenter from './components/issues/ModificationDatePresenter.svelte'
|
||||||
import EditIssue from './components/issues/EditIssue.svelte'
|
import EditIssue from './components/issues/EditIssue.svelte'
|
||||||
import NewIssueHeader from './components/NewIssueHeader.svelte'
|
import NewIssueHeader from './components/NewIssueHeader.svelte'
|
||||||
|
|
||||||
@ -41,6 +43,8 @@ export default async (): Promise<Resources> => ({
|
|||||||
Projects,
|
Projects,
|
||||||
Views,
|
Views,
|
||||||
IssuePresenter,
|
IssuePresenter,
|
||||||
|
TitlePresenter,
|
||||||
|
ModificationDatePresenter,
|
||||||
EditIssue,
|
EditIssue,
|
||||||
NewIssueHeader
|
NewIssueHeader
|
||||||
}
|
}
|
||||||
|
@ -29,11 +29,15 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
Issues: '' as IntlString,
|
Issues: '' as IntlString,
|
||||||
Views: '' as IntlString,
|
Views: '' as IntlString,
|
||||||
Active: '' as IntlString,
|
Active: '' as IntlString,
|
||||||
|
AllIssues: '' as IntlString,
|
||||||
|
ActiveIssues: '' as IntlString,
|
||||||
|
BacklogIssues: '' as IntlString,
|
||||||
Backlog: '' as IntlString,
|
Backlog: '' as IntlString,
|
||||||
Board: '' as IntlString,
|
Board: '' as IntlString,
|
||||||
Project: '' as IntlString,
|
Project: '' as IntlString,
|
||||||
Projects: '' as IntlString,
|
Projects: '' as IntlString,
|
||||||
CreateTeam: '' as IntlString,
|
CreateTeam: '' as IntlString,
|
||||||
|
AddIssue: '' as IntlString,
|
||||||
NewIssue: '' as IntlString,
|
NewIssue: '' as IntlString,
|
||||||
Team: '' as IntlString,
|
Team: '' as IntlString,
|
||||||
SelectTeam: '' as IntlString,
|
SelectTeam: '' as IntlString,
|
||||||
@ -56,6 +60,8 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
Status: '' as IntlString,
|
Status: '' as IntlString,
|
||||||
Number: '' as IntlString,
|
Number: '' as IntlString,
|
||||||
Assignee: '' as IntlString,
|
Assignee: '' as IntlString,
|
||||||
|
AssignTo: '' as IntlString,
|
||||||
|
AssignedTo: '' as IntlString,
|
||||||
Parent: '' as IntlString,
|
Parent: '' as IntlString,
|
||||||
BlockedBy: '' as IntlString,
|
BlockedBy: '' as IntlString,
|
||||||
RelatedTo: '' as IntlString,
|
RelatedTo: '' as IntlString,
|
||||||
@ -64,6 +70,7 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
Labels: '' as IntlString,
|
Labels: '' as IntlString,
|
||||||
Space: '' as IntlString,
|
Space: '' as IntlString,
|
||||||
DueDate: '' as IntlString,
|
DueDate: '' as IntlString,
|
||||||
|
ModificationDate: '' as IntlString,
|
||||||
Issue: '' as IntlString,
|
Issue: '' as IntlString,
|
||||||
Document: '' as IntlString,
|
Document: '' as IntlString,
|
||||||
DocumentIcon: '' as IntlString,
|
DocumentIcon: '' as IntlString,
|
||||||
@ -86,6 +93,8 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
Board: '' as AnyComponent,
|
Board: '' as AnyComponent,
|
||||||
Projects: '' as AnyComponent,
|
Projects: '' as AnyComponent,
|
||||||
IssuePresenter: '' as AnyComponent,
|
IssuePresenter: '' as AnyComponent,
|
||||||
|
TitlePresenter: '' as AnyComponent,
|
||||||
|
ModificationDatePresenter: '' as AnyComponent,
|
||||||
EditIssue: '' as AnyComponent,
|
EditIssue: '' as AnyComponent,
|
||||||
CreateTeam: '' as AnyComponent,
|
CreateTeam: '' as AnyComponent,
|
||||||
NewIssueHeader: '' as AnyComponent
|
NewIssueHeader: '' as AnyComponent
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Class, Doc, DocumentQuery, FindOptions, Ref } from '@anticrm/core'
|
import type { Class, Doc, DocumentQuery, FindOptions, Ref } from '@anticrm/core'
|
||||||
import { SortingOrder } from '@anticrm/core'
|
import { SortingOrder, getObjectValue } from '@anticrm/core'
|
||||||
import { createQuery, getClient } from '@anticrm/presentation'
|
import { createQuery, getClient } from '@anticrm/presentation'
|
||||||
import { Component, CheckBox, IconDown, IconUp, Label, Loading, showPopup, Spinner } from '@anticrm/ui'
|
import { Component, CheckBox, IconDown, IconUp, Label, Loading, showPopup, Spinner } from '@anticrm/ui'
|
||||||
import { BuildModelKey } from '@anticrm/view'
|
import { BuildModelKey } from '@anticrm/view'
|
||||||
@ -80,19 +80,6 @@
|
|||||||
}
|
}
|
||||||
$: update(_class, query, sortKey, sortOrder, options)
|
$: update(_class, query, sortKey, sortOrder, options)
|
||||||
|
|
||||||
function getValue (doc: Doc, key: string): any {
|
|
||||||
if (key.length === 0) {
|
|
||||||
return doc
|
|
||||||
}
|
|
||||||
const path = key.split('.')
|
|
||||||
const len = path.length
|
|
||||||
let obj = doc as any
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
obj = obj?.[path[i]]
|
|
||||||
}
|
|
||||||
return obj ?? ''
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
const showMenu = async (ev: MouseEvent, object: Doc, row: number): Promise<void> => {
|
const showMenu = async (ev: MouseEvent, object: Doc, row: number): Promise<void> => {
|
||||||
@ -219,7 +206,7 @@
|
|||||||
<div class="antiTable-cells__firstCell">
|
<div class="antiTable-cells__firstCell">
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={attribute.presenter}
|
this={attribute.presenter}
|
||||||
value={getValue(object, attribute.key)}
|
value={getObjectValue(attribute.key, object) ?? ''}
|
||||||
{...attribute.props}
|
{...attribute.props}
|
||||||
/>
|
/>
|
||||||
<div id='context-menu' class="antiTable-cells__firstCell-menuRow" on:click={(ev) => showMenu(ev, object, row)}>
|
<div id='context-menu' class="antiTable-cells__firstCell-menuRow" on:click={(ev) => showMenu(ev, object, row)}>
|
||||||
@ -231,7 +218,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={attribute.presenter}
|
this={attribute.presenter}
|
||||||
value={getValue(object, attribute.key)}
|
value={getObjectValue(attribute.key, object) ?? ''}
|
||||||
{...attribute.props}
|
{...attribute.props}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
@ -27,6 +27,7 @@ import IntlStringPresenter from './components/IntlStringPresenter.svelte'
|
|||||||
import NumberEditor from './components/NumberEditor.svelte'
|
import NumberEditor from './components/NumberEditor.svelte'
|
||||||
import NumberPresenter from './components/NumberPresenter.svelte'
|
import NumberPresenter from './components/NumberPresenter.svelte'
|
||||||
import Table from './components/Table.svelte'
|
import Table from './components/Table.svelte'
|
||||||
|
import Menu from './components/Menu.svelte'
|
||||||
import TableView from './components/TableView.svelte'
|
import TableView from './components/TableView.svelte'
|
||||||
import TimestampPresenter from './components/TimestampPresenter.svelte'
|
import TimestampPresenter from './components/TimestampPresenter.svelte'
|
||||||
import { deleteObject } from './utils'
|
import { deleteObject } from './utils'
|
||||||
@ -40,7 +41,7 @@ import view from './plugin'
|
|||||||
|
|
||||||
export { default as ContextMenu } from './components/Menu.svelte'
|
export { default as ContextMenu } from './components/Menu.svelte'
|
||||||
export { buildModel, getActions, getObjectPresenter, LoadingProps, getCollectionCounter } from './utils'
|
export { buildModel, getActions, getObjectPresenter, LoadingProps, getCollectionCounter } from './utils'
|
||||||
export { Table, TableView, EditDoc, ColorsPopup }
|
export { Table, TableView, EditDoc, ColorsPopup, Menu }
|
||||||
|
|
||||||
function Delete (object: Doc): void {
|
function Delete (object: Doc): void {
|
||||||
showPopup(
|
showPopup(
|
||||||
|
@ -71,6 +71,7 @@ export async function getObjectPresenter (
|
|||||||
_class,
|
_class,
|
||||||
label: preserveKey.label ?? clazz.label,
|
label: preserveKey.label ?? clazz.label,
|
||||||
presenter,
|
presenter,
|
||||||
|
props: preserveKey.props,
|
||||||
sortingKey
|
sortingKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user