UBER-939: Speedup table/kanban (#3764)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2023-10-01 22:55:30 +07:00
committed by GitHub
parent 0c5041f0b3
commit 2e2be22ee5
10 changed files with 187 additions and 47 deletions
@@ -14,7 +14,7 @@
-->
<script lang="ts">
import { CategoryType, Doc, Ref } from '@hcengineering/core'
import ui, { Button, IconMoreH, mouseAttractor } from '@hcengineering/ui'
import ui, { Button, IconMoreH, Lazy, mouseAttractor } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import { slide } from 'svelte/transition'
import { CardDragEvent, DocWithRank, Item } from '../types'
@@ -102,7 +102,9 @@
isDragging = false
}}
>
<slot name="card" object={toAny(object)} {dragged} />
<Lazy>
<slot name="card" object={toAny(object)} {dragged} />
</Lazy>
</div>
</div>
{/each}
+28
View File
@@ -0,0 +1,28 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
import { lazyObserver } from '../lazy'
let visible = false
</script>
<div
use:lazyObserver={(val) => {
if (val) {
visible = true
dispatch('visible')
}
}}
>
{#if visible}
<slot />
{:else}
<!-- Zero-width space character -->
{#if $$slots.loading}
<slot name="loading" />
{:else}
&#8203;
{/if}
{/if}
</div>
+2
View File
@@ -197,6 +197,7 @@ export { default as IconWithEmoji } from './components/IconWithEmoji.svelte'
export { default as ModeSelector } from './components/ModeSelector.svelte'
export { default as SimpleTimePopup } from './components/calendar/SimpleTimePopup.svelte'
export { default as NumberInput } from './components/NumberInput.svelte'
export { default as Lazy } from './components/Lazy.svelte'
export * from './types'
export * from './location'
@@ -208,6 +209,7 @@ export * from './components/calendar/internal/DateUtils'
export * from './colors'
export * from './focus'
export * from './resize'
export * from './lazy'
export function createApp (target: HTMLElement): SvelteComponent {
return new Root({ target })
+64
View File
@@ -0,0 +1,64 @@
const observers = new Map<string, IntersectionObserver>()
const entryMap = new WeakMap<Element, { callback: (entry: IntersectionObserverEntry) => void }>()
function makeObserver (rootMargin: string): IntersectionObserver {
return new IntersectionObserver(
(entries, observer) => {
for (const entry of entries) {
const entryData = entryMap.get(entry.target)
if (entryData == null) {
observer.unobserve(entry.target)
continue
}
entryData.callback(entry)
if (entry.isIntersecting) {
entryMap.delete(entry.target)
observer.unobserve(entry.target)
}
}
},
{ rootMargin }
)
}
function listen (
rootMargin: string,
element: Element,
callback: (entry: IntersectionObserverEntry) => void
): () => void {
let observer = observers.get(rootMargin)
if (observer == null) {
observer = makeObserver(rootMargin)
observers.set(rootMargin, observer)
}
entryMap.set(element, { callback })
observer.observe(element)
return () => {
observer?.unobserve(element)
entryMap.delete(element)
}
}
export function lazyObserver (node: Element, onVisible: (value: boolean) => void): any {
let visible = false
const lazyEnabled = (localStorage.getItem('#platform.lazy.loading') ?? 'true') === 'true'
if (!lazyEnabled) {
visible = true
onVisible(visible)
}
if (visible) {
onVisible(visible)
return {}
}
const destroy = listen('20%', node, ({ isIntersecting }) => {
visible = isIntersecting
onVisible(visible)
})
return {
destroy
}
}
@@ -19,7 +19,7 @@
import notification, { DocUpdateTx, DocUpdates, Writable } from '@hcengineering/notification'
import { getResource } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Component, Grid, Label, Spinner } from '@hcengineering/ui'
import { Component, Grid, Label, Lazy, Spinner } from '@hcengineering/ui'
import { ActivityKey, activityKey, newActivity } from '../activity'
import { filterCollectionTxes } from '../utils'
import ActivityFilter from './ActivityFilter.svelte'
@@ -149,13 +149,15 @@
{#if filtered}
<Grid column={1} rowGap={0.75}>
{#each filtered as tx, i}
<TxView
{tx}
{viewlets}
isNew={newTxIndexes.includes(i)}
isNextNew={newTxIndexes.includes(i + 1)}
shouldScroll={i === scrollIndex}
/>
<Lazy>
<TxView
{tx}
{viewlets}
isNew={newTxIndexes.includes(i)}
isNextNew={newTxIndexes.includes(i + 1)}
shouldScroll={i === scrollIndex}
/>
</Lazy>
{/each}
</Grid>
{/if}
@@ -18,7 +18,7 @@
import chunter, { Comment } from '@hcengineering/chunter'
import { createQuery } from '@hcengineering/presentation'
import { Label, resizeObserver, Spinner, closeTooltip } from '@hcengineering/ui'
import { Label, resizeObserver, Spinner, closeTooltip, Lazy } from '@hcengineering/ui'
import { DocNavLink, ObjectPresenter } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
import CommentInput from './CommentInput.svelte'
@@ -79,7 +79,9 @@
{:else}
{#each comments as comment}
<div class="item">
<CommentPresenter value={comment} />
<Lazy>
<CommentPresenter value={comment} />
</Lazy>
</div>
{/each}
{/if}
@@ -200,7 +200,10 @@
</span>
</svelte:fragment>
<svelte:fragment slot="pre-utils">
<ComponentExtensions extension={tracker.extensions.EditIssueHeader} />
<ComponentExtensions
extension={tracker.extensions.EditIssueHeader}
props={{ size: 'medium', kind: 'ghost', space: issue.space }}
/>
{#if saved}
<Label label={presentation.string.Saved} />
{/if}
@@ -39,7 +39,8 @@
mouseAttractor,
resizeObserver,
showPopup,
Spinner
Spinner,
lazyObserver
} from '@hcengineering/ui'
import { AttributeModel, BuildModelKey, BuildModelOptions } from '@hcengineering/view'
import view from '../plugin'
@@ -93,6 +94,13 @@
let objectsRecieved = false
const refs: HTMLElement[] = []
let rowLimit = 1
const oldClass = _class
$: if (oldClass !== _class) {
rowLimit = 1 // delayed show
}
$: refs.length = objects.length
const q = createQuery()
@@ -102,6 +110,15 @@
$: sortingFunction = (config.find((it) => typeof it !== 'string' && it.sortingKey === _sortKey) as BuildModelKey)
?.sortingFunction
function getSort (sortKey: string | string[]) {
return Array.isArray(sortKey)
? sortKey.reduce((acc: Record<string, SortingOrder>, val) => {
acc[val] = sortOrder
return acc
}, {})
: { ...(options?.sort ?? {}), [sortKey]: sortOrder }
}
async function update (
_class: Ref<Class<Doc>>,
query: DocumentQuery<Doc>,
@@ -111,35 +128,36 @@
limit: number,
options?: FindOptions<Doc>
) {
const sort = Array.isArray(sortKey)
? sortKey.reduce((acc: Record<string, SortingOrder>, val) => {
acc[val] = sortOrder
return acc
}, {})
: { ...(options?.sort ?? {}), [sortKey]: sortOrder }
const update = q.query(
q.query(
_class,
query,
(result) => {
objects = result
total = result.total === -1 ? 0 : result.total
objectsRecieved = true
if (sortingFunction !== undefined) {
const sf = sortingFunction
objects.sort((a, b) => -1 * sortOrder * sf(a, b))
objects = result.sort((a, b) => -1 * sortOrder * sf(a, b))
} else {
objects = result
}
dispatch('content', objects)
objectsRecieved = true
loading = loading === 1 ? 0 : -1
},
{ sort, limit, ...options, lookup, total: true }
{ sort: getSort(sortKey), limit, ...options, lookup, total: false }
)
if (update && ++loading > 0) {
objects = []
}
}
$: update(_class, query, _sortKey, sortOrder, lookup, limit, options)
$: dispatch('content', objects)
const qSlow = createQuery()
$: qSlow.query(
_class,
query,
(result) => {
total = result.total
},
{ sort: getSort(_sortKey), limit: 1, ...options, lookup, total: true }
)
const showMenu = async (ev: MouseEvent, object: Doc, row: number): Promise<void> => {
selection = row
if (!checkedSet.has(object._id)) {
@@ -268,6 +286,14 @@
model = await buildModel(modelOptions)
isBuildingModel = false
}
function contextHandler (object: Doc, row: number): (ev: MouseEvent) => void {
return (ev) => {
if (!readonly) {
showMenu(ev, object, row)
}
}
}
</script>
{#if !model || isBuildingModel}
@@ -333,9 +359,10 @@
on:mouseenter={mouseAttractor(() => onRow(object))}
on:focus={() => {}}
bind:this={refs[row]}
on:contextmenu|preventDefault={(ev) => {
if (!readonly) {
showMenu(ev, object, row)
on:contextmenu|preventDefault={contextHandler(object, row)}
use:lazyObserver={(val) => {
if (val && row >= rowLimit) {
rowLimit = row + 10
}
}}
>
@@ -370,19 +397,21 @@
{/if}
</td>
{/if}
{#each model as attribute, cell}
<td>
<div class:antiTable-cells__firstCell={!cell}>
<!-- {getOnChange(object, attribute) !== undefined} -->
<svelte:component
this={attribute.presenter}
value={getValue(attribute, object)}
onChange={getOnChange(object, attribute)}
{...joinProps(attribute, object)}
/>
</div>
</td>
{/each}
{#if row < rowLimit}
{#each model as attribute, cell}
<td>
<div class:antiTable-cells__firstCell={!cell}>
<!-- {getOnChange(object, attribute) !== undefined} -->
<svelte:component
this={attribute.presenter}
value={getValue(attribute, object)}
onChange={getOnChange(object, attribute)}
{...joinProps(attribute, object)}
/>
</div>
</td>
{/each}
{/if}
</tr>
{/each}
</tbody>
+4
View File
@@ -23,6 +23,10 @@
{
"name": "#platform.notification.logging",
"value": "false"
},
{
"name": "#platform.lazy.loading",
"value": "false"
}
]
}
+4
View File
@@ -19,6 +19,10 @@
{
"name": "#platform.notification.logging",
"value": "false"
},
{
"name": "#platform.lazy.loading",
"value": "false"
}
]
}