mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-23 08:48:01 +00:00
264 lines
8.9 KiB
Svelte
264 lines
8.9 KiB
Svelte
<!--
|
|
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
|
// Copyright © 2021 Hardcore Engineering Inc.
|
|
//
|
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License. You may
|
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
//
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
-->
|
|
<script lang="ts">
|
|
import type { Class, Doc, DocumentQuery, FindOptions, Ref } from '@anticrm/core'
|
|
import { SortingOrder, getObjectValue } from '@anticrm/core'
|
|
import { createQuery, getClient } from '@anticrm/presentation'
|
|
import { Component, CheckBox, IconDown, IconUp, Label, Loading, showPopup, Spinner } from '@anticrm/ui'
|
|
import { BuildModelKey } from '@anticrm/view'
|
|
import { buildModel, LoadingProps } from '../utils'
|
|
import MoreV from './icons/MoreV.svelte'
|
|
import Menu from './Menu.svelte'
|
|
import notification from '@anticrm/notification'
|
|
import { createEventDispatcher } from 'svelte'
|
|
|
|
export let _class: Ref<Class<Doc>>
|
|
export let query: DocumentQuery<Doc>
|
|
export let enableChecking: boolean = false
|
|
export let showNotification: boolean = false
|
|
export let highlightRows: boolean = false
|
|
export let options: FindOptions<Doc> | undefined = undefined
|
|
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
|
|
export let config: (BuildModelKey | string)[]
|
|
|
|
// If defined, will show a number of dummy items before real data will appear.
|
|
export let loadingProps: LoadingProps | undefined = undefined
|
|
|
|
let sortKey = 'modifiedOn'
|
|
let sortOrder = SortingOrder.Descending
|
|
let selectRow: number | undefined = undefined
|
|
let loading = false
|
|
|
|
let objects: Doc[]
|
|
|
|
const q = createQuery()
|
|
|
|
const dispatch = createEventDispatcher()
|
|
|
|
$: sortingFunction = (config.find((it) => typeof it !== 'string' && it.sortingKey === sortKey) as BuildModelKey)
|
|
?.sortingFunction
|
|
|
|
let qindex = 0
|
|
async function update (
|
|
_class: Ref<Class<Doc>>,
|
|
query: DocumentQuery<Doc>,
|
|
sortKey: string,
|
|
sortOrder: SortingOrder,
|
|
options?: FindOptions<Doc>
|
|
) {
|
|
const c = ++qindex
|
|
loading = true
|
|
q.query(
|
|
_class,
|
|
query,
|
|
(result) => {
|
|
if (c !== qindex) {
|
|
return // our data is invalid.
|
|
}
|
|
objects = result
|
|
if (sortingFunction !== undefined) {
|
|
const sf = sortingFunction
|
|
objects.sort((a, b) => -1 * sortOrder * sf(a, b))
|
|
}
|
|
dispatch('content', objects)
|
|
loading = false
|
|
},
|
|
{ sort: { [sortKey]: sortOrder }, ...options, limit: 200 }
|
|
)
|
|
}
|
|
$: update(_class, query, sortKey, sortOrder, options)
|
|
|
|
const client = getClient()
|
|
|
|
const showMenu = async (ev: MouseEvent, object: Doc, row: number): Promise<void> => {
|
|
selectRow = row
|
|
showPopup(Menu, { object, baseMenuClass }, ev.target as HTMLElement, () => {
|
|
selectRow = undefined
|
|
})
|
|
}
|
|
|
|
function changeSorting (key: string): void {
|
|
if (key === '') {
|
|
return
|
|
}
|
|
if (key !== sortKey) {
|
|
sortKey = key
|
|
sortOrder = SortingOrder.Ascending
|
|
} else {
|
|
sortOrder = sortOrder === SortingOrder.Ascending ? SortingOrder.Descending : SortingOrder.Ascending
|
|
}
|
|
}
|
|
|
|
let checked: Set<Ref<Doc>> = new Set<Ref<Doc>>()
|
|
|
|
function check (id: Ref<Doc>, event: CustomEvent<boolean>) {
|
|
if (!enableChecking) return
|
|
if (event.detail) {
|
|
checked.add(id)
|
|
} else {
|
|
checked.delete(id)
|
|
}
|
|
checked = checked
|
|
}
|
|
|
|
function getLoadingLength (props: LoadingProps, options?: FindOptions<Doc>): number {
|
|
if (options?.limit !== undefined && options?.limit > 0) {
|
|
return Math.min(options?.limit, props.length)
|
|
}
|
|
return props.length
|
|
}
|
|
</script>
|
|
|
|
{#await buildModel({ client, _class, keys: config, options })}
|
|
{#if !loading}
|
|
<Loading />
|
|
{/if}
|
|
{:then model}
|
|
<table class="antiTable" class:metaColumn={enableChecking || showNotification} class:highlightRows>
|
|
<thead class="scroller-thead">
|
|
<tr class="scroller-thead__tr">
|
|
{#if enableChecking || showNotification}
|
|
<th>
|
|
{#if enableChecking && objects?.length > 0}
|
|
<div class="antiTable-cells__checkCell" class:checkall={checked.size > 0}>
|
|
<CheckBox
|
|
symbol={'minus'}
|
|
checked={objects?.length === checked.size && objects?.length > 0}
|
|
on:value={(event) => {
|
|
objects.map((object) => check(object._id, event))
|
|
}}
|
|
/>
|
|
</div>
|
|
{/if}
|
|
</th>
|
|
{/if}
|
|
{#each model as attribute}
|
|
<th
|
|
class:sortable={attribute.sortingKey}
|
|
class:sorted={attribute.sortingKey === sortKey}
|
|
on:click={() => changeSorting(attribute.sortingKey)}
|
|
>
|
|
<div class="antiTable-cells">
|
|
<Label label={attribute.label} />
|
|
{#if attribute.sortingKey === sortKey}
|
|
<div class="icon">
|
|
{#if sortOrder === SortingOrder.Ascending}
|
|
<IconUp size={'small'} />
|
|
{:else}
|
|
<IconDown size={'small'} />
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</th>
|
|
{/each}
|
|
</tr>
|
|
</thead>
|
|
{#if objects}
|
|
<tbody>
|
|
{#each objects as object, row (object._id)}
|
|
<tr class="antiTable-body__row" class:checking={checked.has(object._id)} class:fixed={row === selectRow}>
|
|
{#each model as attribute, cell}
|
|
{#if !cell}
|
|
{#if enableChecking || showNotification}
|
|
<td class="relative">
|
|
{#if showNotification}
|
|
<div class="antiTable-cells__notifyCell">
|
|
{#if enableChecking}
|
|
<div class="antiTable-cells__checkCell">
|
|
<CheckBox
|
|
checked={checked.has(object._id)}
|
|
on:value={(event) => {
|
|
check(object._id, event)
|
|
}}
|
|
/>
|
|
</div>
|
|
{/if}
|
|
<Component
|
|
is={notification.component.NotificationPresenter}
|
|
props={{ value: object, kind: enableChecking ? 'table' : 'block' }}
|
|
/>
|
|
</div>
|
|
{:else}
|
|
<div class="antiTable-cells__checkCell">
|
|
<CheckBox
|
|
checked={checked.has(object._id)}
|
|
on:value={(event) => {
|
|
check(object._id, event)
|
|
}}
|
|
/>
|
|
</div>
|
|
{/if}
|
|
</td>
|
|
{/if}
|
|
<td>
|
|
<div class="antiTable-cells__firstCell">
|
|
<svelte:component
|
|
this={attribute.presenter}
|
|
value={getObjectValue(attribute.key, object) ?? ''}
|
|
{...attribute.props}
|
|
/>
|
|
<div
|
|
id="context-menu"
|
|
class="antiTable-cells__firstCell-menuRow"
|
|
on:click={(ev) => showMenu(ev, object, row)}
|
|
>
|
|
<MoreV size={'small'} />
|
|
</div>
|
|
</div>
|
|
</td>
|
|
{:else}
|
|
<td>
|
|
<svelte:component
|
|
this={attribute.presenter}
|
|
value={getObjectValue(attribute.key, object) ?? ''}
|
|
{...attribute.props}
|
|
/>
|
|
</td>
|
|
{/if}
|
|
{/each}
|
|
</tr>
|
|
{/each}
|
|
</tbody>
|
|
{:else if loadingProps !== undefined}
|
|
<tbody>
|
|
{#each Array(getLoadingLength(loadingProps, options)) as i, row}
|
|
<tr class="antiTable-body__tr" class:fixed={row === selectRow}>
|
|
{#each model as attribute, cell}
|
|
{#if !cell}
|
|
{#if enableChecking}
|
|
<td>
|
|
<div class="antiTable-cells__checkCell">
|
|
<CheckBox checked={false} />
|
|
</div>
|
|
</td>
|
|
{/if}
|
|
<td id={`loader-${i}-${attribute.key}`}>
|
|
<Spinner size="small" />
|
|
</td>
|
|
{/if}
|
|
{/each}
|
|
</tr>
|
|
{/each}
|
|
</tbody>
|
|
{/if}
|
|
</table>
|
|
{/await}
|
|
|
|
{#if loading}
|
|
<Loading />
|
|
{/if}
|