TSK-1309, TSK-1310, TSK-571 (#3048)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2023-04-23 10:50:41 +07:00 committed by GitHub
parent 7aa6ff437e
commit f0a4edee49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 199 additions and 101 deletions

View File

@ -210,7 +210,7 @@
}
export function select (offset: 1 | -1 | 0, of?: Doc, dir?: 'vertical' | 'horizontal'): void {
let pos = (of !== undefined ? objects.findIndex((it) => it._id === of._id) : selection) ?? -1
let pos = (of != null ? objects.findIndex((it) => it._id === of._id) : selection) ?? -1
if (pos === -1) {
for (const st of categories) {
const stateObjs = getGroupByValues(groupByDocs, st) ?? []
@ -237,7 +237,7 @@
if (objState === -1) {
return
}
const stateObjs = getGroupByValues(groupByDocs, categories.indexOf(objState)) ?? []
const stateObjs = getGroupByValues(groupByDocs, categories[objState]) ?? []
const statePos = stateObjs.findIndex((it) => it._id === obj._id)
if (statePos === undefined) {
return

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import { CategoryType, Doc, Ref } from '@hcengineering/core'
import ui, { Button, IconMoreH } from '@hcengineering/ui'
import ui, { Button, IconMoreH, mouseAttractor } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import { slide } from 'svelte/transition'
import { CardDragEvent, Item } from '../types'
@ -69,7 +69,7 @@
class="card-container"
class:selection={selection !== undefined ? objects[selection]?._id === object._id : false}
class:checked={checkedSet.has(object._id)}
on:mouseover={() => dispatch('obj-focus', object)}
on:mouseover={mouseAttractor(() => dispatch('obj-focus', object))}
on:focus={() => {}}
on:contextmenu={(evt) => showMenu(evt, object)}
draggable={true}

View File

@ -50,14 +50,17 @@
closePanel()
}
$: props = $panelstore.panel
$: if (props !== undefined) {
component = undefined
getResource(props.component).then((r) => {
component = r
})
$: if ($panelstore.panel !== undefined) {
if ($panelstore.panel.component === undefined) {
props = $panelstore.panel
} else {
getResource($panelstore.panel.component).then((r) => {
component = r
props = $panelstore.panel
})
}
} else {
props = undefined
}
function escapeClose () {

View File

@ -105,3 +105,21 @@ export function tableToCSV (tableId: string, separator = ','): string {
* @public
*/
export const networkStatus = writable<number>(0)
let attractorMx = 0
let attractorMy = 0
/**
* perform mouse movement checks and call method if they was
*/
export function mouseAttractor (op: () => void, diff = 5): (evt: MouseEvent) => void {
return (evt: MouseEvent) => {
const dx = evt.clientX - attractorMx
const dy = evt.clientY - attractorMy
attractorMx = evt.clientX
attractorMy = evt.clientY
if (Math.sqrt(dx * dx + dy * dy) > diff) {
op()
}
}
}

View File

@ -95,7 +95,7 @@
let kanbanUI: KanbanUI
const listProvider = new ListSelectionProvider((offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => {
kanbanUI.select(offset, of, dir)
kanbanUI?.select(offset, of, dir)
})
onMount(() => {
;(document.activeElement as HTMLElement)?.blur()
@ -125,6 +125,7 @@
...options
}
)
$: listProvider.update(cards)
$: groupByDocs = groupBy(cards, 'state')
const getUpdateProps = (doc: Doc, category: CategoryType): DocumentUpdate<DocWithRank> | undefined => {
@ -151,11 +152,8 @@
getGroupByValues={(groupByDocs, category) => getGroupByValues(groupByDocs, category)}
{setGroupByValues}
categories={states.map((it) => it._id)}
on:content={(evt) => {
listProvider.update(evt.detail)
}}
on:obj-focus={(evt) => {
listProvider.updateFocus(evt.detail)
listProvider.updateFocus(evt.detail.object)
}}
{groupByDocs}
{getUpdateProps}

View File

@ -106,7 +106,7 @@
let kanbanUI: KanbanUI
const listProvider = new ListSelectionProvider((offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => {
kanbanUI.select(offset, of, dir)
kanbanUI?.select(offset, of, dir)
})
onMount(() => {
;(document.activeElement as HTMLElement)?.blur()
@ -144,6 +144,7 @@
}
}
)
$: listProvider.update(tasks)
let categories: CategoryType[] = []
@ -247,9 +248,6 @@
{setGroupByValues}
{getUpdateProps}
{groupByDocs}
on:content={(evt) => {
listProvider.update(evt.detail)
}}
on:obj-focus={(evt) => {
listProvider.updateFocus(evt.detail)
}}

View File

@ -143,7 +143,7 @@
let kanbanUI: Kanban
const listProvider = new ListSelectionProvider((offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => {
kanbanUI.select(offset, of, dir)
kanbanUI?.select(offset, of, dir)
})
onMount(() => {
;(document.activeElement as HTMLElement)?.blur()
@ -172,6 +172,8 @@
}
)
$: listProvider.update(issues)
let categories: CategoryType[] = []
const queryId = generateId()
@ -265,9 +267,6 @@
{setGroupByValues}
{getUpdateProps}
{groupByDocs}
on:content={(evt) => {
listProvider.update(evt.detail)
}}
on:obj-focus={(evt) => {
listProvider.updateFocus(evt.detail)
}}

View File

@ -72,7 +72,7 @@
<div
class="{twoRows ? 'flex-col' : 'flex-between'} p-text-2"
on:contextmenu|preventDefault={(ev) => showContextMenu(ev, report)}
on:mouseover={() => {
on:mouseenter={() => {
listProvider.updateFocus(report)
}}
on:focus={() => {

View File

@ -282,7 +282,7 @@
class:checking={checkedSet.has(object._id)}
class:fixed={row === selection}
class:selected={row === selection}
on:mouseover={() => onRow(object)}
on:mouseenter={() => onRow(object)}
on:focus={() => {}}
bind:this={refs[row]}
on:contextmenu|preventDefault={(ev) => {

View File

@ -37,13 +37,10 @@
export let flatHeaders = false
export let disableHeader = false
export let props: Record<string, any> = {}
export let selection: number | undefined = undefined
export let documents: Doc[] | undefined = undefined
const elementByIndex: Map<number, HTMLDivElement> = new Map()
const docByIndex: Map<number, Doc> = new Map()
const indexById: Map<Ref<Doc>, number> = new Map()
let docs: Doc[] = []
$: orderBy = viewOptions.orderBy
@ -63,16 +60,16 @@
resultQuery,
(res) => {
docs = res
dispatch('content', docs)
},
resultOptions
)
} else {
docsQuery.unsubscribe()
docs = documents
dispatch('content', docs)
}
$: dispatch('content', docs)
const dispatch = createEventDispatcher()
const client = getClient()
@ -100,26 +97,7 @@
}
export function select (offset: 1 | -1 | 0, of?: Doc): void {
let pos = (of !== undefined ? indexById.get(of._id) : -1) ?? -1
pos += offset
if (pos < 0) {
pos = 0
}
if (pos >= docs.length) {
pos = docs.length - 1
}
const target = docByIndex.get(pos)
if (target !== undefined) {
onRow(target)
}
const r = elementByIndex.get(pos)
if (r !== undefined) {
r.scrollIntoView({ behavior: 'auto', block: 'nearest' })
}
}
function onRow (object: Doc): void {
dispatch('row-focus', object)
listCategories?.select(offset, of)
}
const getLoadingElementsLength = (props: LoadingProps | undefined, options?: FindOptions<Doc>) => {
@ -137,23 +115,23 @@
} = {}
let listDiv: HTMLDivElement
let listCategories: ListCategories
</script>
<div class="list-container" bind:this={listDiv}>
<ListCategories
bind:this={listCategories}
newObjectProps={() => (space ? { space } : {})}
{elementByIndex}
{indexById}
{docs}
{_class}
{space}
{selection}
query={resultQuery}
{lookup}
loadingPropsLength={getLoadingElementsLength(loadingProps, options)}
{baseMenuClass}
{config}
{viewOptions}
{docByIndex}
{viewOptionsConfig}
{selectedObjectIds}
level={0}
@ -167,6 +145,9 @@
{props}
{listDiv}
bind:dragItem
on:select={(evt) => {
select(0, evt.detail)
}}
/>
</div>

View File

@ -18,7 +18,7 @@
import { getClient, statusStore } from '@hcengineering/presentation'
import { AnyComponent } from '@hcengineering/ui'
import { AttributeModel, BuildModelKey, CategoryOption, ViewOptionModel, ViewOptions } from '@hcengineering/view'
import { createEventDispatcher, onDestroy } from 'svelte'
import { createEventDispatcher, onDestroy, SvelteComponentTyped } from 'svelte'
import {
buildModel,
concatCategories,
@ -31,8 +31,6 @@
import { CategoryQuery, noCategory } from '../../viewOptions'
import ListCategory from './ListCategory.svelte'
export let elementByIndex: Map<number, HTMLDivElement>
export let indexById: Map<Ref<Doc>, number>
export let docs: Doc[]
export let _class: Ref<Class<Doc>>
export let space: Ref<Space> | undefined
@ -51,13 +49,13 @@
export let level: number
export let initIndex = 0
export let newObjectProps: (doc: Doc) => Record<string, any> | undefined
export let docByIndex: Map<number, Doc>
export let viewOptionsConfig: ViewOptionModel[] | undefined
export let dragItem: {
doc?: Doc
revert?: () => void
}
export let listDiv: HTMLDivElement
export let selection: number | undefined = undefined
$: groupByKey = viewOptions.groupBy[level] ?? noCategory
let categories: CategoryType[] = []
@ -129,25 +127,118 @@
$: extraHeaders = getAdditionalHeader(client, _class)
const dispatch = createEventDispatcher()
function getState (doc: Doc): number {
let pos = 0
for (const st of categories) {
const stateObjs = getGroupByValues(groupByDocs, st) ?? []
if (stateObjs.findIndex((it) => it._id === doc._id) !== -1) {
return pos
}
pos++
}
return -1
}
export function select (offset: 1 | -1 | 0, of?: Doc, dir?: 'vertical' | 'horizontal'): void {
let pos = (of != null ? docs.findIndex((it) => it._id === of._id) : selection) ?? -1
if (pos === -1) {
for (const st of categories) {
const stateObjs = getGroupByValues(groupByDocs, st) ?? []
if (stateObjs.length > 0) {
pos = docs.findIndex((it) => it._id === stateObjs[0]._id)
break
}
}
}
if (pos < 0) {
pos = 0
}
if (pos >= docs.length) {
pos = docs.length - 1
}
const obj = docs[pos]
if (obj === undefined) {
return
}
// We found group
const objState = getState(obj)
if (objState === -1) {
return
}
if (level + 1 >= viewOptions.groupBy.length) {
const stateObjs = getGroupByValues(groupByDocs, categories[objState]) ?? []
const statePos = stateObjs.findIndex((it) => it._id === obj._id)
if (statePos === undefined) {
return
}
console.log(statePos, objState, offset)
if (offset === -1) {
if (dir === undefined || dir === 'vertical') {
if (statePos - 1 < 0 && objState > 0) {
const pstateObjs = getGroupByValues(groupByDocs, categories[objState - 1]) ?? []
dispatch('select', pstateObjs[pstateObjs.length - 1])
} else {
const obj = stateObjs[statePos - 1] ?? stateObjs[0]
scrollInto(objState, obj)
dispatch('row-focus', obj)
}
return
}
}
if (offset === 1) {
if (dir === undefined || dir === 'vertical') {
if (statePos + 1 >= stateObjs.length && objState < categories.length) {
const pstateObjs = getGroupByValues(groupByDocs, categories[objState + 1]) ?? []
if (pstateObjs[0] !== undefined) {
dispatch('select', pstateObjs[0])
}
} else {
const obj = stateObjs[statePos + 1] ?? stateObjs[stateObjs.length - 1]
scrollInto(objState, obj)
dispatch('row-focus', obj)
}
return
}
}
if (offset === 0) {
// scrollInto(objState, obj)
dispatch('row-focus', obj)
}
} else {
listCategory[objState]?.select(offset, of, dir)
}
}
function scrollInto (statePos: number, obj: Doc): void {
// listCategory[statePos]?.scrollIntoView({ behavior: 'auto', block: 'nearest' })
listListCategory[statePos]?.scroll(obj)
listCategory[statePos]?.scroll(obj)
}
const listCategory: SvelteComponentTyped[] = []
const listListCategory: ListCategory[] = []
</script>
{#each categories as category, i (typeof category === 'object' ? category.name : category)}
{@const items = groupByKey === noCategory ? docs : getGroupByValues(groupByDocs, category)}
<ListCategory
{elementByIndex}
{indexById}
bind:this={listListCategory[i]}
{extraHeaders}
{space}
{selectedObjectIds}
{headerComponent}
initIndex={getInitIndex(categories, i)}
{baseMenuClass}
{level}
{viewOptions}
{groupByKey}
{lookup}
{config}
{docByIndex}
{itemModels}
{_class}
singleCat={level === 0 && categories.length === 1}
@ -175,8 +266,6 @@
>
<svelte:fragment
slot="category"
let:elementByIndex
let:indexById
let:docs
let:_class
let:space
@ -192,16 +281,13 @@
let:flatHeaders
let:props
let:level
let:initIndex
let:docByIndex
let:viewOptionsConfig
let:listDiv
let:dragstart
>
<svelte:self
{elementByIndex}
{indexById}
{docs}
bind:this={listCategory[i]}
{_class}
{space}
{lookup}
@ -217,7 +303,6 @@
{props}
{level}
{initIndex}
{docByIndex}
{viewOptionsConfig}
{listDiv}
on:dragItem
@ -225,6 +310,9 @@
on:uncheckAll
on:row-focus
on:dragstart={dragstart}
on:select={(evt) => {
select(0, evt.detail)
}}
/>
</svelte:fragment>
</ListCategory>

View File

@ -16,17 +16,18 @@
import { Class, Doc, DocumentUpdate, Lookup, PrimitiveType, Ref, Space, StatusValue } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import { calcRank, DocWithRank } from '@hcengineering/task'
import { DocWithRank, calcRank } from '@hcengineering/task'
import {
AnyComponent,
CheckBox,
ExpandCollapse,
Spinner,
getEventPositionElement,
showPopup,
Spinner
mouseAttractor,
showPopup
} from '@hcengineering/ui'
import { AttributeModel, BuildModelKey, ViewOptionModel, ViewOptions } from '@hcengineering/view'
import { createEventDispatcher } from 'svelte'
import { createEventDispatcher, tick } from 'svelte'
import { FocusSelection, focusStore } from '../../selection'
import Menu from '../Menu.svelte'
import ListHeader from './ListHeader.svelte'
@ -39,7 +40,6 @@
export let space: Ref<Space> | undefined
export let baseMenuClass: Ref<Class<Doc>> | undefined
export let items: Doc[]
export let initIndex: number
export let createItemDialog: AnyComponent | undefined
export let createItemLabel: IntlString | undefined
export let loadingPropsLength: number | undefined
@ -50,14 +50,11 @@
export let disableHeader = false
export let props: Record<string, any> = {}
export let level: number
export let elementByIndex: Map<number, HTMLDivElement>
export let indexById: Map<Ref<Doc>, number>
export let lookup: Lookup<Doc>
export let _class: Ref<Class<Doc>>
export let config: (string | BuildModelKey)[]
export let viewOptions: ViewOptions
export let newObjectProps: (doc: Doc) => Record<string, any> | undefined
export let docByIndex: Map<number, Doc>
export let viewOptionsConfig: ViewOptionModel[] | undefined
export let dragItem: {
doc?: Doc
@ -92,7 +89,7 @@
dispatch('row-focus', object)
}
const handleMenuOpened = async (event: MouseEvent, object: Doc, rowIndex: number) => {
const handleMenuOpened = async (event: MouseEvent, object: Doc) => {
event.preventDefault()
handleRowFocused(object)
@ -300,6 +297,24 @@
index: i
})
}
export function scroll (item: Doc): void {
const pos = items.findIndex((it) => it._id === item._id)
if (pos >= 0) {
if (collapsed) {
collapsed = false
tick().then(() => scroll(item))
return
}
if (pos >= limited.length) {
limit = (limit ?? 0) + 20
tick().then(() => scroll(item))
} else {
listItems[pos]?.scroll()
}
}
}
const listItems: ListItem[] = []
</script>
<div
@ -337,8 +352,6 @@
<div class="p-2">
<slot
name="category"
{elementByIndex}
{indexById}
docs={items}
{_class}
{space}
@ -354,8 +367,6 @@
{flatHeaders}
{props}
level={level + 1}
{initIndex}
{docByIndex}
{viewOptionsConfig}
{listDiv}
dragItem
@ -366,12 +377,9 @@
{#if limited}
{#each limited as docObject, i (docObject._id)}
<ListItem
bind:this={listItems[i]}
{docObject}
{elementByIndex}
{docByIndex}
{indexById}
model={itemModels}
index={initIndex + i}
{groupByKey}
selected={isSelected(docObject, $focusStore)}
checked={selectedObjectIdsSet.has(docObject._id)}
@ -386,9 +394,9 @@
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:contextmenu={(event) => handleMenuOpened(event, docObject)}
on:focus={() => {}}
on:mouseover={() => handleRowFocused(docObject)}
on:mouseover={mouseAttractor(() => handleRowFocused(docObject))}
{props}
/>
{/each}

View File

@ -13,25 +13,25 @@
// limitations under the License.
-->
<script lang="ts">
import core, { AnyAttribute, Doc, getObjectValue, Ref } from '@hcengineering/core'
import core, { AnyAttribute, Doc, getObjectValue } from '@hcengineering/core'
import notification from '@hcengineering/notification'
import { getClient, updateAttribute } from '@hcengineering/presentation'
import { CheckBox, Component, deviceOptionsStore as deviceInfo, tooltip, IconCircles } from '@hcengineering/ui'
import { CheckBox, Component, deviceOptionsStore as deviceInfo, IconCircles, tooltip } from '@hcengineering/ui'
import { AttributeModel } from '@hcengineering/view'
import { createEventDispatcher } from 'svelte'
import { FixedColumn } from '../..'
import view from '../../plugin'
export let docObject: Doc
export let index: number
export let model: AttributeModel[]
export let groupByKey: string | undefined
export let checked: boolean
export let selected: boolean
export let props: Record<string, any> = {}
export let elementByIndex: Map<number, HTMLDivElement>
export let indexById: Map<Ref<Doc>, number>
export let docByIndex: Map<number, Doc>
export function scroll () {
elem?.scrollIntoView({ behavior: 'auto', block: 'nearest' })
}
let elem: HTMLDivElement
@ -47,10 +47,6 @@
$: compactMode = $deviceInfo.twoRows
$: elem && elementByIndex.set(index, elem)
$: indexById.set(docObject._id, index)
$: docByIndex.set(index, docObject)
const client = getClient()
function onChange (value: any, doc: Doc, key: string, attribute: AnyAttribute) {
@ -94,6 +90,7 @@
draggable={true}
on:contextmenu
on:focus
on:mouseenter
on:mouseover
on:dragover
on:dragenter

View File

@ -4,7 +4,14 @@
import { AnyComponent, issueSP, Scroller } from '@hcengineering/ui'
import { BuildModelKey, Viewlet, ViewOptions } from '@hcengineering/view'
import { onMount } from 'svelte'
import { ActionContext, ListSelectionProvider, LoadingProps, SelectDirection, selectionStore } from '../..'
import {
ActionContext,
ListSelectionProvider,
LoadingProps,
SelectDirection,
focusStore,
selectionStore
} from '../..'
import List from './List.svelte'
@ -56,6 +63,7 @@
{props}
viewOptionsConfig={viewlet.viewOptions?.other}
selectedObjectIds={$selectionStore ?? []}
selection={listProvider.current($focusStore)}
on:row-focus={(event) => {
listProvider.updateFocus(event.detail ?? undefined)
}}