diff --git a/packages/kanban/src/components/KanbanRow.svelte b/packages/kanban/src/components/KanbanRow.svelte
index b6bf7bd5ff..efcaf7bb0c 100644
--- a/packages/kanban/src/components/KanbanRow.svelte
+++ b/packages/kanban/src/components/KanbanRow.svelte
@@ -14,7 +14,7 @@
-->
+
+
{
+ if (val) {
+ visible = true
+ dispatch('visible')
+ }
+ }}
+>
+ {#if visible}
+
+ {:else}
+
+ {#if $$slots.loading}
+
+ {:else}
+
+ {/if}
+ {/if}
+
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index 000b5e8879..efe37787f1 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -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 })
diff --git a/packages/ui/src/lazy.ts b/packages/ui/src/lazy.ts
new file mode 100644
index 0000000000..49f43d310a
--- /dev/null
+++ b/packages/ui/src/lazy.ts
@@ -0,0 +1,64 @@
+const observers = new Map()
+const entryMap = new WeakMap 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
+ }
+}
diff --git a/plugins/activity-resources/src/components/Activity.svelte b/plugins/activity-resources/src/components/Activity.svelte
index 5ba2b32c63..f91a06d112 100644
--- a/plugins/activity-resources/src/components/Activity.svelte
+++ b/plugins/activity-resources/src/components/Activity.svelte
@@ -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}
{#each filtered as tx, i}
-
+
+
+
{/each}
{/if}
diff --git a/plugins/chunter-resources/src/components/CommentPopup.svelte b/plugins/chunter-resources/src/components/CommentPopup.svelte
index 12ce9e292a..9b8b419e15 100644
--- a/plugins/chunter-resources/src/components/CommentPopup.svelte
+++ b/plugins/chunter-resources/src/components/CommentPopup.svelte
@@ -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}
-
+
+
+
{/each}
{/if}
diff --git a/plugins/tracker-resources/src/components/issues/edit/EditIssue.svelte b/plugins/tracker-resources/src/components/issues/edit/EditIssue.svelte
index 11264b34f9..0864372ba8 100644
--- a/plugins/tracker-resources/src/components/issues/edit/EditIssue.svelte
+++ b/plugins/tracker-resources/src/components/issues/edit/EditIssue.svelte
@@ -200,7 +200,10 @@
-
+
{#if saved}
{/if}
diff --git a/plugins/view-resources/src/components/Table.svelte b/plugins/view-resources/src/components/Table.svelte
index 53c8cc7b8b..f9385f7144 100644
--- a/plugins/view-resources/src/components/Table.svelte
+++ b/plugins/view-resources/src/components/Table.svelte
@@ -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, val) => {
+ acc[val] = sortOrder
+ return acc
+ }, {})
+ : { ...(options?.sort ?? {}), [sortKey]: sortOrder }
+ }
+
async function update (
_class: Ref>,
query: DocumentQuery,
@@ -111,35 +128,36 @@
limit: number,
options?: FindOptions
) {
- const sort = Array.isArray(sortKey)
- ? sortKey.reduce((acc: Record, 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 => {
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)
+ }
+ }
+ }
{#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}
{/if}
- {#each model as attribute, cell}
-
-
-
-
-
- |
- {/each}
+ {#if row < rowLimit}
+ {#each model as attribute, cell}
+
+
+
+
+
+ |
+ {/each}
+ {/if}
{/each}
diff --git a/tests/sanity/storage-dev.json b/tests/sanity/storage-dev.json
index 020d7deead..11f08b2026 100644
--- a/tests/sanity/storage-dev.json
+++ b/tests/sanity/storage-dev.json
@@ -23,6 +23,10 @@
{
"name": "#platform.notification.logging",
"value": "false"
+ },
+ {
+ "name": "#platform.lazy.loading",
+ "value": "false"
}
]
}
diff --git a/tests/sanity/storage.json b/tests/sanity/storage.json
index bfa7bf1790..53cd138d45 100644
--- a/tests/sanity/storage.json
+++ b/tests/sanity/storage.json
@@ -19,6 +19,10 @@
{
"name": "#platform.notification.logging",
"value": "false"
+ },
+ {
+ "name": "#platform.lazy.loading",
+ "value": "false"
}
]
}