mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-29 04:55:03 +02:00
UBER-939: Speedup table/kanban (#3764)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
​
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
@@ -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 })
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
{
|
||||
"name": "#platform.notification.logging",
|
||||
"value": "false"
|
||||
},
|
||||
{
|
||||
"name": "#platform.lazy.loading",
|
||||
"value": "false"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
{
|
||||
"name": "#platform.notification.logging",
|
||||
"value": "false"
|
||||
},
|
||||
{
|
||||
"name": "#platform.lazy.loading",
|
||||
"value": "false"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user