From f7e495b609010fbe371fdd7a88f96fa45f4da02a Mon Sep 17 00:00:00 2001 From: Denis Bykhov Date: Fri, 10 Feb 2023 22:45:14 +0600 Subject: [PATCH] Sortable list (#2618) Signed-off-by: Denis Bykhov --- models/lead/src/index.ts | 8 +- models/tracker/src/index.ts | 50 ++- packages/kanban/src/components/Kanban.svelte | 7 +- packages/ui/src/components/Component.svelte | 10 +- .../components/issues/IssuesContent.svelte | 8 +- .../components/issues/IssuesListItem.svelte | 232 ------------ .../src/components/issues/IssuesView.svelte | 37 +- .../issues/edit/SubIssuesSelector.svelte | 18 +- .../issues/timereport/EstimationEditor.svelte | 2 +- .../scrums/ScrumRecordTimeSpend.svelte | 4 +- .../src/components/ViewOptions.svelte | 7 +- .../src/components/list/List.svelte | 8 +- .../src/components/list/ListCategories.svelte | 17 +- .../src/components/list/ListCategory.svelte | 355 +++++++++++++----- .../src/components/list/ListItem.svelte | 70 +++- plugins/view/src/index.ts | 2 - 16 files changed, 420 insertions(+), 415 deletions(-) delete mode 100644 plugins/tracker-resources/src/components/issues/IssuesListItem.svelte diff --git a/models/lead/src/index.ts b/models/lead/src/index.ts index 98daba93b8..07f56cc749 100644 --- a/models/lead/src/index.ts +++ b/models/lead/src/index.ts @@ -234,10 +234,10 @@ export function createModel (builder: Builder): void { attachTo: lead.class.Lead, descriptor: view.viewlet.List, config: [ - { key: '', props: { fixed: 'left' } }, - { key: 'title', props: { fixed: 'left' } }, - { key: 'state', props: { fixed: 'left' } }, - { key: 'doneState', props: { fixed: 'left' } }, + { key: '', props: { listProps: { fixed: 'left' } } }, + { key: 'title', props: { listProps: { fixed: 'left' } } }, + { key: 'state', props: { listProps: { fixed: 'left' } } }, + { key: 'doneState', props: { listProps: { fixed: 'left' } } }, { key: '', presenter: view.component.GrowPresenter }, 'attachments', 'comments', diff --git a/models/tracker/src/index.ts b/models/tracker/src/index.ts index 9ca51b288e..74ab1f591d 100644 --- a/models/tracker/src/index.ts +++ b/models/tracker/src/index.ts @@ -540,7 +540,7 @@ export function createModel (builder: Builder): void { presenter: tracker.component.PriorityEditor, props: { type: 'priority', kind: 'list', size: 'small' } }, - { key: '', presenter: tracker.component.IssuePresenter, props: { type: 'issue', fixed: 'left' } }, + { key: '', presenter: tracker.component.IssuePresenter, props: { type: 'issue', listProps: { fixed: 'left' } } }, { key: '', presenter: tracker.component.StatusEditor, @@ -558,8 +558,10 @@ export function createModel (builder: Builder): void { size: 'small', shape: 'round', shouldShowPlaceholder: false, - excludeByKey: 'project', - optional: true + listProps: { + excludeByKey: 'project', + optional: true + } } }, { @@ -570,24 +572,26 @@ export function createModel (builder: Builder): void { size: 'small', shape: 'round', shouldShowPlaceholder: false, - excludeByKey: 'sprint', - optional: true + listProps: { + excludeByKey: 'sprint', + optional: true + } } }, { key: '', presenter: tracker.component.EstimationEditor, - props: { kind: 'list', size: 'small', optional: true } + props: { kind: 'list', size: 'small', listProps: { optional: true } } }, { key: 'modifiedOn', presenter: tracker.component.ModificationDatePresenter, - props: { fixed: 'right', optional: true } + props: { listProps: { fixed: 'right', optional: true } } }, { key: '$lookup.assignee', presenter: tracker.component.AssigneePresenter, - props: { issueClass: tracker.class.Issue, defaultClass: contact.class.Employee, shouldShowLabel: false } + props: { defaultClass: contact.class.Employee, shouldShowLabel: false } } ] }) @@ -595,11 +599,11 @@ export function createModel (builder: Builder): void { const subIssuesOptions: ViewOptionsModel = { groupBy: ['status', 'assignee', 'priority', 'sprint'], orderBy: [ + ['rank', SortingOrder.Ascending], ['status', SortingOrder.Ascending], ['priority', SortingOrder.Ascending], ['modifiedOn', SortingOrder.Descending], - ['dueDate', SortingOrder.Descending], - ['rank', SortingOrder.Ascending] + ['dueDate', SortingOrder.Descending] ], groupDepth: 1, other: [] @@ -619,7 +623,11 @@ export function createModel (builder: Builder): void { presenter: tracker.component.PriorityEditor, props: { type: 'priority', kind: 'list', size: 'small' } }, - { key: '', presenter: tracker.component.IssuePresenter, props: { type: 'issue', fixed: 'left' } }, + { + key: '', + presenter: tracker.component.IssuePresenter, + props: { type: 'issue', listProps: { fixed: 'left' } } + }, { key: '', presenter: tracker.component.StatusEditor, @@ -637,24 +645,26 @@ export function createModel (builder: Builder): void { size: 'small', shape: 'round', shouldShowPlaceholder: false, - excludeByKey: 'sprint', - optional: true + listProps: { + excludeByKey: 'sprint', + optional: true + } } }, { key: '', presenter: tracker.component.EstimationEditor, - props: { kind: 'list', size: 'small', optional: true } + props: { kind: 'list', size: 'small', listProps: { optional: true } } }, { key: 'modifiedOn', presenter: tracker.component.ModificationDatePresenter, - props: { fixed: 'right', optional: true } + props: { listProps: { fixed: 'right', optional: true } } }, { key: '$lookup.assignee', presenter: tracker.component.AssigneePresenter, - props: { issueClass: tracker.class.Issue, defaultClass: contact.class.Employee, shouldShowLabel: false } + props: { defaultClass: contact.class.Employee, shouldShowLabel: false } } ] }, @@ -690,11 +700,15 @@ export function createModel (builder: Builder): void { props: { kind: 'list', size: 'small', shape: 'round', shouldShowPlaceholder: false } }, { key: '', presenter: tracker.component.TemplateEstimationEditor, props: { kind: 'list', size: 'small' } }, - { key: 'modifiedOn', presenter: tracker.component.ModificationDatePresenter, props: { fixed: 'right' } }, + { + key: 'modifiedOn', + presenter: tracker.component.ModificationDatePresenter, + props: { listProps: { fixed: 'right' } } + }, { key: '$lookup.assignee', presenter: tracker.component.AssigneePresenter, - props: { issueClass: tracker.class.IssueTemplate, defaultClass: contact.class.Employee, shouldShowLabel: false } + props: { defaultClass: contact.class.Employee, shouldShowLabel: false } } ] }) diff --git a/packages/kanban/src/components/Kanban.svelte b/packages/kanban/src/components/Kanban.svelte index aa2cb89a5b..19322112c8 100644 --- a/packages/kanban/src/components/Kanban.svelte +++ b/packages/kanban/src/components/Kanban.svelte @@ -128,11 +128,8 @@ } } function cardDragOver (evt: CardDragEvent, object: ExtItem): void { - if (dragCard !== undefined) { - ;(dragCard as any)[fieldName] = (dragCard as any)[fieldName] - if (!dontUpdateRank) { - dragCard.rank = doCalcRank(object, evt) - } + if (dragCard !== undefined && !dontUpdateRank) { + dragCard.rank = doCalcRank(object, evt) } } function cardDrop (evt: CardDragEvent, object: ExtItem): void { diff --git a/packages/ui/src/components/Component.svelte b/packages/ui/src/components/Component.svelte index d609a78eae..5da4e00a39 100644 --- a/packages/ui/src/components/Component.svelte +++ b/packages/ui/src/components/Component.svelte @@ -35,11 +35,13 @@ {/if} {:then Ctor} - - {#if $$slots.default} + {#if $$slots.default !== undefined} + - {/if} - + + {:else} + + {/if} {:catch err}
diff --git a/plugins/tracker-resources/src/components/issues/IssuesContent.svelte b/plugins/tracker-resources/src/components/issues/IssuesContent.svelte
index 4605780390..0b42f36791 100644
--- a/plugins/tracker-resources/src/components/issues/IssuesContent.svelte
+++ b/plugins/tracker-resources/src/components/issues/IssuesContent.svelte
@@ -1,6 +1,6 @@
 
-
-
-
-
-
- { - dispatch('check', { docs: [docObject], value: event.detail }) - }} - /> -
- -
-
- {#each model as attributeModel} - {#if attributeModel.props?.type === 'grow'} - - {:else if (!groupByKey || attributeModel.props?.excludeByKey !== groupByKey) && !(attributeModel.props?.optional && compactMode)} - {#if attributeModel.props?.fixed} - - - - {:else} - - {/if} - {/if} - {/each} - {#if compactMode} -
- -
- -
-
-
- -
- -
-
- {#each model as attributeModel} - {@const value = getObjectValue(attributeModel.key, docObject)} - {#if attributeModel.props?.optional && attributeModel.props?.excludeByKey !== groupByKey && value !== undefined} - - {/if} - {/each} -
-
- {/if} -
- - diff --git a/plugins/tracker-resources/src/components/issues/IssuesView.svelte b/plugins/tracker-resources/src/components/issues/IssuesView.svelte index b366faa69a..5847d6ed76 100644 --- a/plugins/tracker-resources/src/components/issues/IssuesView.svelte +++ b/plugins/tracker-resources/src/components/issues/IssuesView.svelte @@ -1,8 +1,8 @@ @@ -120,8 +93,8 @@ (resultQuery = e.detail)} />
- {#if viewlet && _teams && issueStatuses} - + {#if viewlet} + {/if} {#if $$slots.aside !== undefined && asideShown}
diff --git a/plugins/tracker-resources/src/components/issues/edit/SubIssuesSelector.svelte b/plugins/tracker-resources/src/components/issues/edit/SubIssuesSelector.svelte index da03fc18aa..7c8504addf 100644 --- a/plugins/tracker-resources/src/components/issues/edit/SubIssuesSelector.svelte +++ b/plugins/tracker-resources/src/components/issues/edit/SubIssuesSelector.svelte @@ -32,7 +32,7 @@ import { subIssueListProvider } from '../../../utils' export let value: WithLookup - export let currentTeam: Team | undefined + export let currentTeam: Team | undefined = undefined export let kind: ButtonKind = 'link-bordered' export let size: ButtonSize = 'inline' @@ -41,9 +41,23 @@ let btn: HTMLElement + $: team = currentTeam + let subIssues: Issue[] = [] let countComplate: number = 0 + const teamQuery = createQuery() + $: if (currentTeam === undefined) { + teamQuery.query( + tracker.class.Team, + { + _id: value.space + }, + (res) => ([team] = res) + ) + } else { + teamQuery.unsubscribe() + } const query = createQuery() const statusesQuery = createQuery() @@ -103,7 +117,7 @@ SelectPopup, { value: subIssues.map((iss) => { - const text = currentTeam ? `${getIssueId(currentTeam, iss)} ${iss.title}` : iss.title + const text = team ? `${getIssueId(team, iss)} ${iss.title}` : iss.title return { id: iss._id, text, isSelected: iss._id === value._id, ...getIssueStatusIcon(iss, statuses) } }), diff --git a/plugins/tracker-resources/src/components/issues/timereport/EstimationEditor.svelte b/plugins/tracker-resources/src/components/issues/timereport/EstimationEditor.svelte index f73199e4ce..35297c55a7 100644 --- a/plugins/tracker-resources/src/components/issues/timereport/EstimationEditor.svelte +++ b/plugins/tracker-resources/src/components/issues/timereport/EstimationEditor.svelte @@ -31,7 +31,7 @@ export let size: ButtonSize = 'large' export let justify: 'left' | 'center' = 'left' export let width: string | undefined = undefined - export let currentTeam: Team | undefined + export let currentTeam: Team | undefined = undefined const client = getClient() const dispatch = createEventDispatcher() diff --git a/plugins/tracker-resources/src/components/scrums/ScrumRecordTimeSpend.svelte b/plugins/tracker-resources/src/components/scrums/ScrumRecordTimeSpend.svelte index ffa8c47d1e..7718cd5789 100644 --- a/plugins/tracker-resources/src/components/scrums/ScrumRecordTimeSpend.svelte +++ b/plugins/tracker-resources/src/components/scrums/ScrumRecordTimeSpend.svelte @@ -175,7 +175,9 @@ size: 'small', shape: 'round', shouldShowPlaceholder: false, - excludeByKey: 'sprint', + listProps: { + excludeByKey: 'sprint' + }, isEditable: false } }, diff --git a/plugins/view-resources/src/components/ViewOptions.svelte b/plugins/view-resources/src/components/ViewOptions.svelte index 79ddacad68..0d9ac5fcfb 100644 --- a/plugins/view-resources/src/components/ViewOptions.svelte +++ b/plugins/view-resources/src/components/ViewOptions.svelte @@ -13,6 +13,11 @@ const dispatch = createEventDispatcher() + $: grops = viewOptions.groupBy = + viewOptions.groupBy[viewOptions.groupBy.length - 1] === noCategory + ? viewOptions.groupBy + : [...viewOptions.groupBy, noCategory] + const client = getClient() const hierarchy = client.getHierarchy() const lookup = buildConfigLookup(hierarchy, viewlet.attachTo, viewlet.config, viewlet.options?.lookup) @@ -57,7 +62,7 @@
- {#each viewOptions.groupBy as group, i} + {#each grops as group, i}
-
+
diff --git a/plugins/view-resources/src/components/list/ListCategories.svelte b/plugins/view-resources/src/components/list/ListCategories.svelte index f073e7884c..e140020bca 100644 --- a/plugins/view-resources/src/components/list/ListCategories.svelte +++ b/plugins/view-resources/src/components/list/ListCategories.svelte @@ -18,7 +18,7 @@ import { getClient } from '@hcengineering/presentation' import { AnyComponent } from '@hcengineering/ui' import { AttributeModel, BuildModelKey, CategoryOption, ViewOptionModel, ViewOptions } from '@hcengineering/view' - import { onDestroy } from 'svelte' + import { createEventDispatcher, onDestroy } from 'svelte' import { buildModel, getAdditionalHeader, getCategories, getPresenter, groupBy } from '../../utils' import { CategoryQuery, noCategory } from '../../viewOptions' import ListCategory from './ListCategory.svelte' @@ -44,6 +44,8 @@ export let newObjectProps: Record export let docByIndex: Map export let viewOptionsConfig: ViewOptionModel[] | undefined + export let dragItem: Doc | undefined + export let listDiv: HTMLDivElement $: groupByKey = viewOptions.groupBy[level] ?? noCategory $: groupedDocs = groupBy(docs, groupByKey) @@ -110,12 +112,14 @@ let res = initIndex for (let index = 0; index < i; index++) { const cat = categories[index] - res += groupedDocs[cat]?.length + res += groupedDocs[cat]?.length ?? 0 } return res } $: extraHeaders = getAdditionalHeader(client, _class) + + const dispatch = createEventDispatcher() {#each categories as category, i} @@ -133,6 +137,7 @@ {level} {viewOptions} {groupByKey} + {lookup} {config} {docByIndex} {itemModels} @@ -147,9 +152,17 @@ on:check on:uncheckAll on:row-focus + on:dragstart={(e) => { + dispatch('dragstart', { + target: e.detail.target, + index: e.detail.index + getInitIndex(categories, i) + }) + }} {flatHeaders} {disableHeader} {props} + {listDiv} + bind:dragItem /> {/key} {/each} diff --git a/plugins/view-resources/src/components/list/ListCategory.svelte b/plugins/view-resources/src/components/list/ListCategory.svelte index bfa3af58a7..c322d428e2 100644 --- a/plugins/view-resources/src/components/list/ListCategory.svelte +++ b/plugins/view-resources/src/components/list/ListCategory.svelte @@ -13,8 +13,10 @@ // limitations under the License. --> -{#if !disableHeader} - { - limit += 20 - }} - on:collapse={() => { - collapsed = !collapsed - }} - /> -{/if} - - {#if !lastLevel} -
- -
- {:else if itemModels} - {#if limited} - {#each limited as docObject, i (docObject._id)} - + {#if !disableHeader} + { + if (limit !== undefined) limit += 20 + }} + on:collapse={() => { + collapsed = !collapsed + }} + /> + {/if} + + {#if !lastLevel} +
+ dispatch('check', { docs: ev.detail.docs, value: ev.detail.value })} - on:contextmenu={(event) => handleMenuOpened(event, docObject, initIndex + i)} - on:focus={() => {}} - on:mouseover={() => handleRowFocused(docObject)} + docs={items} + {_class} + {space} + {lookup} + {loadingPropsLength} + {baseMenuClass} + {config} + {selectedObjectIds} + {createItemDialog} + {createItemLabel} + {viewOptions} + {newObjectProps} + {flatHeaders} {props} + level={level + 1} + {initIndex} + {docByIndex} + {viewOptionsConfig} + {listDiv} + bind:dragItem + on:check + on:uncheckAll + on:row-focus + on:dragstart={dragStartHandler} /> - {/each} - {/if} - {:else if loadingPropsLength !== undefined} - {#each Array(Math.max(loadingPropsLength, limit)) as _, rowIndex} -
-
-
- -
- +
+ {:else if itemModels} + {#if limited} + {#each limited as docObject, i (docObject._id)} + dragStart(e, docObject, i)} + on:dragenter={(e) => { + if (dragItemIndex !== undefined) { + e.stopPropagation() + e.preventDefault() + } + }} + on:dragleave={(e) => dragItemLeave(e, i)} + on:dragover={(e) => dragover(e, i)} + on:drop={dropItemHandle} + on:check={(ev) => dispatch('check', { docs: ev.detail.docs, value: ev.detail.value })} + on:contextmenu={(event) => handleMenuOpened(event, docObject, initIndex + i)} + on:focus={() => {}} + on:mouseover={() => handleRowFocused(docObject)} + {props} + /> + {/each} + {/if} + {:else if loadingPropsLength !== undefined} + {#each Array(Math.max(loadingPropsLength, limit ?? 0)) as _, rowIndex} +
+
+
+ +
+ +
-
- {/each} - {/if} - + {/each} + {/if} + +