Files
huly-platform/plugins/tracker-resources/src/components/issues/IssuesList.svelte
T
2022-04-13 00:04:29 +07:00

263 lines
7.3 KiB
Svelte

<!--
// Copyright © 2022 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 { Class, Doc, DocumentQuery, FindOptions, Ref, getObjectValue } from '@anticrm/core'
import { SortingOrder } from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation'
import { CheckBox, Loading, showPopup, Spinner, IconMoreV } from '@anticrm/ui'
import { BuildModelKey } from '@anticrm/view'
import { createEventDispatcher } from 'svelte'
import { buildModel, LoadingProps, Menu } from '@anticrm/view-resources'
export let _class: Ref<Class<Doc>>
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
export let config: (BuildModelKey | string)[]
export let options: FindOptions<Doc> | undefined = undefined
export let query: DocumentQuery<Doc>
// If defined, will show a number of dummy items before real data will appear.
export let loadingProps: LoadingProps | undefined = undefined
const dispatch = createEventDispatcher()
const DOCS_MAX_AMOUNT = 200
const liveQuery = createQuery()
const sort = { modifiedOn: SortingOrder.Descending }
let selectedIssueIds = new Set<Ref<Doc>>()
let selectedRowIndex: number | undefined
let isLoading = false
let docObjects: Doc[] | undefined
let queryIndex = 0
const updateData = async (_class: Ref<Class<Doc>>, query: DocumentQuery<Doc>, options?: FindOptions<Doc>) => {
const i = ++queryIndex
isLoading = true
liveQuery.query(
_class,
query,
(result) => {
if (i !== queryIndex) {
return // our data is invalid.
}
docObjects = result
dispatch('content', docObjects)
isLoading = false
},
{ sort, ...options, limit: DOCS_MAX_AMOUNT }
)
}
$: updateData(_class, query, options)
const client = getClient()
const showMenu = async (event: MouseEvent, docObject: Doc, rowIndex: number) => {
selectedRowIndex = rowIndex
showPopup(Menu, { object: docObject, baseMenuClass }, event.target as HTMLElement, () => {
selectedRowIndex = undefined
})
}
const handleIssueSelected = (id: Ref<Doc>, event: Event) => {
const eventTarget = event.target as HTMLInputElement
const isChecked = eventTarget.checked
if (isChecked) {
selectedIssueIds.add(id)
} else {
selectedIssueIds.delete(id)
}
selectedIssueIds = selectedIssueIds
}
const getLoadingElementsLength = (props: LoadingProps, options?: FindOptions<Doc>) => {
if (options?.limit && options?.limit > 0) {
return Math.min(options.limit, props.length)
}
return props.length
}
</script>
{#await buildModel({ client, _class, keys: config, options })}
{#if !isLoading}
<Loading />
{/if}
{:then attributeModels}
<div class="listRoot">
{#if docObjects}
{#each docObjects as docObject, rowIndex (docObject._id)}
<div
class="listGrid"
class:mListGridChecked={selectedIssueIds.has(docObject._id)}
class:mListGridFixed={rowIndex === selectedRowIndex}
>
{#each attributeModels as attributeModel, attributeModelIndex}
{#if attributeModelIndex === 0}
<div class="gridElement">
<div class="eListGridCheckBox ml-2">
<CheckBox
checked={selectedIssueIds.has(docObject._id)}
on:change={(event) => {
handleIssueSelected(docObject._id, event)
}}
/>
</div>
<div class="priorityPresenter">
<svelte:component
this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, docObject) ?? ''}
{...attributeModel.props}
/>
</div>
</div>
{:else if attributeModelIndex === 1}
<div class="issuePresenter">
<svelte:component
this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, docObject) ?? ''}
{...attributeModel.props}
/>
<div
id="context-menu"
class="eIssuePresenterContextMenu"
on:click={(event) => showMenu(event, docObject, rowIndex)}
>
<IconMoreV size={'small'} />
</div>
</div>
{:else}
<div class="gridElement">
<svelte:component
this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, docObject) ?? ''}
{...attributeModel.props}
/>
</div>
{/if}
{/each}
</div>
{/each}
{:else if loadingProps !== undefined}
{#each Array(getLoadingElementsLength(loadingProps, options)) as _, rowIndex}
<div class="listGrid mListGridIsLoading" class:fixed={rowIndex === selectedRowIndex}>
{#each attributeModels as _, attributeModelIndex}
{#if attributeModelIndex === 0}
<div class="gridElement">
<CheckBox checked={false} />
<div class="ml-4">
<Spinner size="small" />
</div>
</div>
{/if}
{/each}
</div>
{/each}
{/if}
</div>
{/await}
{#if isLoading}
<Loading />
{/if}
<style lang="scss">
.listRoot {
width: 100%;
}
.listGrid {
display: grid;
grid-template-columns: 4rem 6rem auto 4rem 2rem;
height: 3.25rem;
color: var(--theme-caption-color);
border-bottom: 1px solid var(--theme-button-border-hovered);
&.mListGridChecked {
background-color: var(--theme-table-bg-hover);
.eListGridCheckBox {
opacity: 1;
}
}
&.mListGridFixed {
.eIssuePresenterContextMenu {
visibility: visible;
}
}
&.mListGridIsLoading {
grid-template-columns: auto;
}
&:hover {
background-color: var(--theme-table-bg-hover);
}
.eListGridCheckBox {
display: flex;
align-items: center;
justify-content: center;
padding: 0.03rem;
border-radius: 0.25rem;
background-color: rgba(247, 248, 248, 0.5);
opacity: 0;
&:hover {
opacity: 1;
}
}
}
.gridElement {
display: flex;
align-items: center;
justify-content: start;
}
.priorityPresenter {
padding-left: 0.5rem;
}
.issuePresenter {
display: flex;
align-items: center;
padding-right: 1rem;
.eIssuePresenterContextMenu {
visibility: hidden;
opacity: 0.6;
cursor: pointer;
&:hover {
opacity: 1;
}
}
&:hover {
.eIssuePresenterContextMenu {
visibility: visible;
}
}
}
</style>