Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2022-01-11 16:07:29 +07:00 committed by GitHub
parent aabef475a6
commit decf27ef0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 95 additions and 53 deletions

View File

@ -21,6 +21,7 @@
import attachment from '@anticrm/attachment' import attachment from '@anticrm/attachment'
export let objectId: Ref<Doc> export let objectId: Ref<Doc>
export let attachments: number
</script> </script>
@ -29,4 +30,5 @@
config={['', 'lastModified']} config={['', 'lastModified']}
options={ {} } options={ {} }
query={ { attachedTo: objectId } } query={ { attachedTo: objectId } }
loadingProps={{ length: attachments }}
/> />

View File

@ -14,26 +14,20 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import attachment from '../plugin' import { Class, Doc, Ref, Space } from '@anticrm/core'
import type { Attachment } from '@anticrm/attachment'
import type { Class, Doc, Ref, Space } from '@anticrm/core'
import { setPlatformStatus, unknownError } from '@anticrm/platform' import { setPlatformStatus, unknownError } from '@anticrm/platform'
import { createQuery, getClient } from '@anticrm/presentation' import { getClient } from '@anticrm/presentation'
import { CircleButton, IconAdd, Label, Spinner } from '@anticrm/ui' import { CircleButton, IconAdd, Label, Spinner } from '@anticrm/ui'
import { Table } from '@anticrm/view-resources' import { Table } from '@anticrm/view-resources'
import attachment from '../plugin'
import { uploadFile } from '../utils' import { uploadFile } from '../utils'
import UploadDuo from './icons/UploadDuo.svelte' import UploadDuo from './icons/UploadDuo.svelte'
export let objectId: Ref<Doc> export let objectId: Ref<Doc>
export let space: Ref<Space> export let space: Ref<Space>
export let _class: Ref<Class<Doc>> export let _class: Ref<Class<Doc>>
let attachments: Attachment[] = [] export let attachments: number
const query = createQuery()
$: query.query(attachment.class.Attachment, { attachedTo: objectId }, (result) => {
attachments = result
})
let inputFile: HTMLInputElement let inputFile: HTMLInputElement
let loading = 0 let loading = 0
@ -108,7 +102,7 @@
/> />
</div> </div>
{#if attachments.length === 0 && !loading} {#if (attachments === 0) && !loading}
<div <div
class="flex-col-center mt-5 zone-container" class="flex-col-center mt-5 zone-container"
class:solid={dragover} class:solid={dragover}
@ -137,6 +131,7 @@
config={['', 'lastModified']} config={['', 'lastModified']}
options={{}} options={{}}
query={{ attachedTo: objectId }} query={{ attachedTo: objectId }}
loadingProps={ { length: attachments } }
/> />
{/if} {/if}
</div> </div>

View File

@ -24,7 +24,7 @@
</script> </script>
{#if value && value.attachments && value.attachments > 0} {#if value && value.attachments && value.attachments > 0}
<Tooltip label={'Attachments (' + value.attachments + ')'} component={AttachmentPopup} props={{ objectId: value._id }}> <Tooltip label={'Attachments (' + value.attachments + ')'} component={AttachmentPopup} props={{ objectId: value._id, attachments: value.attachments }}>
<div class="sm-tool-icon"> <div class="sm-tool-icon">
<span class="icon"><IconAttachment size="small"/></span>&nbsp;{value.attachments} <span class="icon"><IconAttachment size="small"/></span>&nbsp;{value.attachments}
</div> </div>

View File

@ -150,6 +150,13 @@
onDestroy(() => { onDestroy(() => {
observer.disconnect() observer.disconnect()
}) })
function getCollectionCounter (object: Doc, key: KeyedAttribute): number {
if (client.getHierarchy().isMixin(key.attr.attributeOf)) {
return (client.getHierarchy().as(object, key.attr.attributeOf) as any)[key.key]
}
return (object as any)[key.key] ?? 0
}
</script> </script>
{#if object !== undefined} {#if object !== undefined}
@ -202,7 +209,7 @@
{#each collectionKeys as collection} {#each collectionKeys as collection}
<div class="mt-14"> <div class="mt-14">
{#await getCollectionEditor(collection) then is} {#await getCollectionEditor(collection) then is}
<Component {is} props={{ objectId: object._id, _class: object._class, space: object.space }} /> <Component {is} props={{ objectId: object._id, _class: object._class, space: object.space, [collection.key]: getCollectionCounter(object, collection) }} />
{/await} {/await}
</div> </div>
{/each} {/each}

View File

@ -15,8 +15,7 @@
<script lang="ts"> <script lang="ts">
import type { Ref } from '@anticrm/core' import type { Ref } from '@anticrm/core'
import type { Customer, Lead } from '@anticrm/lead' import type { Customer } from '@anticrm/lead'
import { createQuery } from '@anticrm/presentation'
import task from '@anticrm/task' import task from '@anticrm/task'
import { CircleButton, IconAdd, Label, showPopup } from '@anticrm/ui' import { CircleButton, IconAdd, Label, showPopup } from '@anticrm/ui'
import { Table } from '@anticrm/view-resources' import { Table } from '@anticrm/view-resources'
@ -24,11 +23,8 @@
import CreateLead from './CreateLead.svelte' import CreateLead from './CreateLead.svelte'
export let objectId: Ref<Customer> export let objectId: Ref<Customer>
export let leads: number | undefined = undefined
let leads: Lead[] = [] $: loadingProps = leads !== undefined ? { length: leads } : undefined
const query = createQuery()
$: query.query(lead.class.Lead, { attachedTo: objectId }, result => { leads = result })
const createLead = (ev: MouseEvent): void => const createLead = (ev: MouseEvent): void =>
showPopup(CreateLead, { candidate: objectId, preserveCandidate: true }, ev.target as HTMLElement) showPopup(CreateLead, { candidate: objectId, preserveCandidate: true }, ev.target as HTMLElement)
@ -39,7 +35,7 @@
<div class="title">Leads</div> <div class="title">Leads</div>
<CircleButton icon={IconAdd} size={'small'} selected on:click={createLead} /> <CircleButton icon={IconAdd} size={'small'} selected on:click={createLead} />
</div> </div>
{#if leads.length > 0} {#if leads !== undefined && leads > 0}
<Table <Table
_class={lead.class.Lead} _class={lead.class.Lead}
config={['', '$lookup.state']} config={['', '$lookup.state']}
@ -51,6 +47,7 @@
} }
} }
query={ { attachedTo: objectId } } query={ { attachedTo: objectId } }
{loadingProps}
/> />
{:else} {:else}
<div class="flex-col-center mt-5 createapp-container"> <div class="flex-col-center mt-5 createapp-container">

View File

@ -14,26 +14,20 @@
--> -->
<script lang="ts"> <script lang="ts">
import type { Ref, Space, Doc, Class } from '@anticrm/core' import type { Class, Doc, Ref, Space } from '@anticrm/core'
import core from '@anticrm/core' import core from '@anticrm/core'
import type { Applicant } from '@anticrm/recruit' import task from '@anticrm/task'
import { createQuery } from '@anticrm/presentation' import { CircleButton, IconAdd, Label, showPopup } from '@anticrm/ui'
import { CircleButton, IconAdd, showPopup, Label } from '@anticrm/ui' import { Table } from '@anticrm/view-resources'
import recruit from '../plugin'
import CreateApplication from './CreateApplication.svelte' import CreateApplication from './CreateApplication.svelte'
import FileDuo from './icons/FileDuo.svelte' import FileDuo from './icons/FileDuo.svelte'
import { Table } from '@anticrm/view-resources'
import task from '@anticrm/task'
import recruit from '../plugin'
export let objectId: Ref<Doc> export let objectId: Ref<Doc>
export let space: Ref<Space> export let space: Ref<Space>
export let _class: Ref<Class<Doc>> export let _class: Ref<Class<Doc>>
let applications: Applicant[] = [] export let applications: number
const query = createQuery()
$: query.query(recruit.class.Applicant, { attachedTo: objectId }, result => { applications = result })
const createApp = (ev: MouseEvent): void => const createApp = (ev: MouseEvent): void =>
showPopup(CreateApplication, { candidate: objectId, preserveCandidate: true }, ev.target as HTMLElement) showPopup(CreateApplication, { candidate: objectId, preserveCandidate: true }, ev.target as HTMLElement)
@ -44,7 +38,7 @@
<div class="title">Applications</div> <div class="title">Applications</div>
<CircleButton icon={IconAdd} size={'small'} selected on:click={createApp} /> <CircleButton icon={IconAdd} size={'small'} selected on:click={createApp} />
</div> </div>
{#if applications.length > 0} {#if applications > 0}
<Table <Table
_class={recruit.class.Applicant} _class={recruit.class.Applicant}
config={['', '$lookup.space.name', '$lookup.state']} config={['', '$lookup.space.name', '$lookup.state']}
@ -57,6 +51,7 @@
} }
} }
query={ { attachedTo: objectId } } query={ { attachedTo: objectId } }
loadingProps={ { length: applications } }
/> />
{:else} {:else}
<div class="flex-col-center mt-5 createapp-container"> <div class="flex-col-center mt-5 createapp-container">

View File

@ -38,4 +38,5 @@
} }
} }
query={ { attachedTo: value._id } } query={ { attachedTo: value._id } }
loadingProps={{ length: value.applications ?? 0 }}
/> />

View File

@ -17,9 +17,9 @@
import type { Class, Doc, DocumentQuery, FindOptions, Ref } from '@anticrm/core' import type { Class, Doc, DocumentQuery, FindOptions, Ref } from '@anticrm/core'
import { SortingOrder } from '@anticrm/core' import { SortingOrder } from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation' import { createQuery, getClient } from '@anticrm/presentation'
import { CheckBox, IconDown, IconUp, Label, Loading, showPopup } from '@anticrm/ui' import { CheckBox, IconDown, IconUp, Label, Loading, showPopup, Spinner } from '@anticrm/ui'
import { BuildModelKey } from '@anticrm/view' import { BuildModelKey } from '@anticrm/view'
import { buildModel } from '../utils' import { buildModel, LoadingProps } from '../utils'
import MoreV from './icons/MoreV.svelte' import MoreV from './icons/MoreV.svelte'
import Menu from './Menu.svelte' import Menu from './Menu.svelte'
@ -28,7 +28,10 @@
export let enableChecking: boolean = false export let enableChecking: boolean = false
export let options: FindOptions<Doc> | undefined = undefined export let options: FindOptions<Doc> | undefined = undefined
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
export let config: (BuildModelKey|string)[] 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 sortKey = 'modifiedOn'
let sortOrder = SortingOrder.Descending let sortOrder = SortingOrder.Descending
@ -38,7 +41,13 @@
const q = createQuery() const q = createQuery()
async function update(_class: Ref<Class<Doc>>, query: DocumentQuery<Doc>, sortKey: string, sortOrder: SortingOrder, options?: FindOptions<Doc>) { async function update (
_class: Ref<Class<Doc>>,
query: DocumentQuery<Doc>,
sortKey: string,
sortOrder: SortingOrder,
options?: FindOptions<Doc>
) {
q.query( q.query(
_class, _class,
query, query,
@ -97,6 +106,13 @@
} }
checked = checked 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> </script>
{#await buildModel({ client, _class, keys: config, options })} {#await buildModel({ client, _class, keys: config, options })}
@ -158,24 +174,46 @@
</div> </div>
</td> </td>
{/if} {/if}
<td <td>
><div class="firstCell"> <div class="firstCell">
<svelte:component <svelte:component
this={attribute.presenter} this={attribute.presenter}
value={getValue(object, attribute.key)} value={getValue(object, attribute.key)}
{...attribute.props} {...attribute.props}/>
/>
<div class="menuRow" on:click={(ev) => showMenu(ev, object, row)}><MoreV size={'small'} /></div> <div class="menuRow" on:click={(ev) => showMenu(ev, object, row)}><MoreV size={'small'} /></div>
</div></td </div>
> </td>
{:else} {:else}
<td <td>
><svelte:component <svelte:component
this={attribute.presenter} this={attribute.presenter}
value={getValue(object, attribute.key)} value={getValue(object, attribute.key)}
{...attribute.props} {...attribute.props}
/></td />
> </td>
{/if}
{/each}
</tr>
{/each}
</tbody>
{:else if loadingProps !== undefined}
<tbody>
{#each Array(getLoadingLength(loadingProps, options)) as i, row}
<tr class="tr-body" class:fixed={row === selectRow}>
{#each model as attribute, cell}
{#if !cell}
{#if enableChecking}
<td>
<div class="checkCell">
<CheckBox
checked={false}
/>
</div>
</td>
{/if}
<td>
<Spinner size="small" />
</td>
{/if} {/if}
{/each} {/each}
</tr> </tr>

View File

@ -30,7 +30,7 @@ import { deleteObject } from './utils'
import MoveView from './components/Move.svelte' import MoveView from './components/Move.svelte'
export { default as ContextMenu } from './components/Menu.svelte' export { default as ContextMenu } from './components/Menu.svelte'
export { buildModel, getActions, getObjectPresenter } from './utils' export { buildModel, getActions, getObjectPresenter, LoadingProps } from './utils'
export { Table, TableView } export { Table, TableView }
function Delete (object: Doc): void { function Delete (object: Doc): void {

View File

@ -22,6 +22,13 @@ import type { Action, ActionTarget, BuildModelOptions } from '@anticrm/view'
import view, { AttributeModel, BuildModelKey } from '@anticrm/view' import view, { AttributeModel, BuildModelKey } from '@anticrm/view'
import { ErrorPresenter } from '@anticrm/ui' import { ErrorPresenter } from '@anticrm/ui'
/**
* Define some properties to be used to show component until data is properly loaded.
*/
export interface LoadingProps {
length: number
}
/** /**
* @public * @public
*/ */
@ -37,9 +44,9 @@ export async function getObjectPresenter (client: Client, _class: Ref<Class<Obj>
} }
const presenter = await getResource(presenterMixin.presenter) const presenter = await getResource(presenterMixin.presenter)
const key = preserveKey.sortingKey ?? preserveKey.key const key = preserveKey.sortingKey ?? preserveKey.key
const sortingKey = clazz.sortingKey ? const sortingKey = clazz.sortingKey !== undefined
(key.length > 0 ? key + '.' + clazz.sortingKey : clazz.sortingKey) ? (key.length > 0 ? key + '.' + clazz.sortingKey : clazz.sortingKey)
: key : key
return { return {
key: preserveKey.key, key: preserveKey.key,
_class, _class,