Allow unassign (#511)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2021-12-03 17:16:16 +07:00 committed by GitHub
parent 29371e128e
commit e4783e09f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 220 additions and 168 deletions

View File

@ -62,8 +62,8 @@ export class TAttribute extends TDoc implements AnyAttribute {
label!: IntlString
}
@Model(core.class.Type, core.class.Doc)
export class TType extends TDoc implements Type<any> {
@Model(core.class.Type, core.class.Obj)
export class TType extends TObj implements Type<any> {
label!: IntlString
}

View File

@ -15,7 +15,7 @@
import type { Account, Arr, Domain, Ref, Space, State } from '@anticrm/core'
import { DOMAIN_MODEL } from '@anticrm/core'
import { Implements, Model, Prop, TypeBoolean, TypeState, TypeString } from '@anticrm/model'
import { Implements, Model, Prop, TypeBoolean, TypeRef, TypeString } from '@anticrm/model'
import type { IntlString } from '@anticrm/platform'
import core from './component'
import { TDoc } from './core'
@ -53,7 +53,7 @@ export class TState extends TDoc implements State {
@Implements(core.interface.DocWithState)
export class TDocWithState extends TDoc {
@Prop(TypeState(), 'State' as IntlString)
@Prop(TypeRef(core.class.State), 'State' as IntlString)
state!: Ref<State>
@Prop(TypeString(), 'No.' as IntlString)

View File

@ -38,7 +38,6 @@
"@anticrm/recruit-resources": "~0.6.0",
"@anticrm/chunter": "~0.6.0",
"@anticrm/model-chunter": "~0.6.0",
"@anticrm/view": "~0.6.0",
"@anticrm/activity": "~0.6.0"
"@anticrm/view": "~0.6.0"
}
}

View File

@ -13,10 +13,9 @@
// limitations under the License.
//
import activity from '@anticrm/activity'
import type { Employee } from '@anticrm/contact'
import type { Doc, Domain, FindOptions, Ref, Timestamp } from '@anticrm/core'
import { Builder, Model, Prop, TypeBoolean, TypeDate, TypeString, UX } from '@anticrm/model'
import { Builder, Model, Prop, TypeBoolean, TypeDate, TypeRef, TypeString, UX } from '@anticrm/model'
import chunter from '@anticrm/model-chunter'
import contact, { TPerson } from '@anticrm/model-contact'
import core, { TAttachedDoc, TDocWithState, TSpace, TSpaceWithStates } from '@anticrm/model-core'
@ -80,7 +79,7 @@ export class TCandidate extends TPerson implements Candidate {
@UX('Application' as IntlString, recruit.icon.RecruitApplication, 'APP' as IntlString)
export class TApplicant extends TAttachedDoc implements Applicant {
// We need to declare, to provide property with label
@Prop(TypeString(), 'Candidate' as IntlString)
@Prop(TypeRef(recruit.class.Candidate), 'Candidate' as IntlString)
declare attachedTo: Ref<Candidate>
@Prop(TypeString(), 'Attachments' as IntlString)
@ -89,8 +88,8 @@ export class TApplicant extends TAttachedDoc implements Applicant {
@Prop(TypeString(), 'Comments' as IntlString)
comments?: number
@Prop(TypeString(), 'Assigned recruiter' as IntlString)
employee!: Ref<Employee>
@Prop(TypeRef(contact.class.Employee), 'Assigned recruiter' as IntlString)
employee!: Ref<Employee> | null
// We need this two to make typescript happy.
declare state: TDocWithState['state']
@ -228,14 +227,6 @@ export function createModel (builder: Builder): void {
attachedTo: recruit.class.Applicant,
sequence: 0
})
builder.createDoc(activity.class.TxViewlet, core.space.Model, {
objectClass: recruit.class.Applicant,
icon: recruit.icon.RecruitApplication,
txClass: core.class.TxUpdateDoc,
component: recruit.activity.TxApplicantUpdate,
display: 'inline'
}, recruit.ids.TxApplicantUpdate)
}
export { default } from './plugin'

View File

@ -13,14 +13,13 @@
// limitations under the License.
//
import { mergeIds } from '@anticrm/platform'
import type { Doc, Ref, Space } from '@anticrm/core'
import type { IntlString, Resource } from '@anticrm/platform'
import type { Ref, Space, Doc } from '@anticrm/core'
import type { AnyComponent } from '@anticrm/ui'
import type { Action } from '@anticrm/view'
import { mergeIds } from '@anticrm/platform'
import { recruitId } from '@anticrm/recruit'
import recruit from '@anticrm/recruit-resources/src/plugin'
import { TxViewlet } from '@anticrm/activity'
import type { AnyComponent } from '@anticrm/ui'
import type { Action } from '@anticrm/view'
export default mergeIds(recruitId, recruit, {
action: {
@ -48,11 +47,5 @@ export default mergeIds(recruitId, recruit, {
},
space: {
CandidatesPublic: '' as Ref<Space>
},
ids: {
TxApplicantUpdate: '' as Ref<TxViewlet>
},
activity: {
TxApplicantUpdate: '' as AnyComponent
}
})

View File

@ -16,7 +16,7 @@
import type { Employee } from '@anticrm/contact'
import contact from '@anticrm/contact'
import type { Doc, DocWithState, Domain, FindOptions, Ref } from '@anticrm/core'
import { Builder, Model, Prop, TypeString, UX } from '@anticrm/model'
import { Builder, Model, Prop, TypeRef, TypeString, UX } from '@anticrm/model'
import chunter from '@anticrm/model-chunter'
import core, { TDoc, TSpaceWithStates } from '@anticrm/model-core'
import view from '@anticrm/model-view'
@ -42,8 +42,8 @@ export class TTask extends TDoc implements Task {
@Prop(TypeString(), 'Description' as IntlString)
description!: string
@Prop(TypeString(), 'Assignee' as IntlString)
assignee!: Ref<Employee>
@Prop(TypeRef(contact.class.Employee), 'Assignee' as IntlString)
assignee!: Ref<Employee> | null
@Prop(TypeString(), 'Comments' as IntlString)
comments!: number

View File

@ -84,7 +84,7 @@ export abstract class MemDb extends TxProcessor {
const result: LookupData<T> = {}
for (const key in lookup) {
const id = (doc as any)[key] as Ref<Doc>
if (id !== undefined) {
if (id != null) {
(result as any)[key] = this.getObject(id)
}
}

View File

@ -308,8 +308,8 @@ export function TypeDate (): Type<string> {
/**
* @public
*/
export function TypeState (): Type<string> {
return { _class: core.class.State, label: 'TypeState' as IntlString }
export function TypeRef (_class: Ref<Class<Doc>>): Type<string> {
return { _class: _class, label: 'TypeRef' as IntlString }
}
/**

View File

@ -15,12 +15,13 @@
-->
<script lang="ts">
import type { Ref, Class, Doc } from '@anticrm/core'
import type { AttachedDoc, AttachedDoc, Doc } from '@anticrm/core'
import core from '@anticrm/core'
import { getResource } from '@anticrm/platform'
import type { AnySvelteComponent } from '@anticrm/ui'
import { CircleButton, Label } from '@anticrm/ui'
import { getClient } from '../utils'
import view from '@anticrm/view'
import { getClient } from '../utils'
// export let _class: Ref<Class<Doc>>
export let key: string
@ -45,8 +46,13 @@
editor = getResource(editorMixin.editor)
}
function onChange(value: any) {
client.updateDoc(_class, object.space, object._id, { [key]: value }, true).then(result => console.log('UPDATE RESULT', result))
function onChange (value: any) {
if (client.getHierarchy().isDerived(object._class, core.class.AttachedDoc)) {
const adoc = object as AttachedDoc
client.updateCollection(_class, object.space, adoc._id, adoc.attachedTo, adoc.attachedToClass, adoc.collection, { [key]: value })
} else {
client.updateDoc(_class, object.space, object._id, { [key]: value }, true)
}
}
</script>

View File

@ -29,8 +29,11 @@
export let _class: Ref<Class<Person>>
export let title: IntlString
export let caption: IntlString
export let value: Ref<Person>
export let value: Ref<Person> | null | undefined
export let show: boolean = false
export let allowDeselect = false
export let titleDeselect: IntlString | undefined = undefined
const dispatch = createEventDispatcher()
let selected: Person | undefined
@ -40,11 +43,13 @@
const client = getClient()
async function updateSelected(value: Ref<Person>) {
async function updateSelected (value: Ref<Person>) {
selected = await client.findOne(_class, { _id: value })
}
$: updateSelected(value)
$: if (value != null) {
updateSelected(value)
}
onMount(() => {
if (btn && show) {
@ -59,10 +64,19 @@
btn.focus()
if (!opened) {
opened = true
showPopup(UsersPopup, { _class, title, caption }, container, (result) => {
if (result) {
showPopup(UsersPopup, { _class, title, caption, allowDeselect, selected: value, titleDeselect }, container, (result) => {
if (result === undefined) {
// Value is not changed.
opened = false
return
}
if (result != null) {
value = result._id
dispatch('change', value)
} else {
value = null
selected = undefined
dispatch('change', null)
}
opened = false
})

View File

@ -22,17 +22,23 @@
import type { Ref, Class } from '@anticrm/core'
import type { Person } from '@anticrm/contact'
import { createQuery } from '../utils'
import { ActionIcon } from '@anticrm/ui'
import BlueCheck from './icons/BlueCheck.svelte'
export let _class: Ref<Class<Person>>
export let title: IntlString
export let caption: IntlString
export let selected: Ref<Person> | undefined
export let allowDeselect: boolean = false
export let titleDeselect: IntlString | undefined = undefined
let search: string = ''
let objects: Person[] = []
const dispatch = createEventDispatcher()
const query = createQuery()
$: query.query(_class, { name: { $like: '%'+search+'%' } }, result => { objects = result })
$: query.query(_class, { name: { $like: '%' + search + '%' } }, result => { objects = result })
afterUpdate(() => { dispatch('update', Date.now()) })
</script>
@ -46,7 +52,12 @@
<div class="flex-col box">
{#each objects as person}
<button class="menu-item" on:click={() => { dispatch('close', person) }}>
<UserInfo size={'medium'} value={person} />
<div class='flex-grow'>
<UserInfo size={'medium'} value={person} />
</div>
{#if allowDeselect && person._id === selected}
<ActionIcon direction={'top'} label={titleDeselect ?? 'Deselect'} icon={BlueCheck} action={() => { dispatch('close', null) }} size={'small'}/>
{/if}
</button>
{/each}
</div>

View File

@ -0,0 +1,22 @@
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<svg viewBox="0 0 24 24" {fill} xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_b_991_16450)">
<rect width="24" height="24" rx="12" fill="#4474F6"/>
</g>
<path d="M8.25 12L10.9989 15L15.75 9" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<defs>
<filter id="filter0_b_991_16450" x="-100" y="-100" width="224" height="224" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feGaussianBlur in="BackgroundImage" stdDeviation="50"/>
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_991_16450"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_991_16450" result="shape"/>
</filter>
</defs>
</svg>
</svg>

View File

@ -129,8 +129,15 @@
actions = result
})
function getValue (utx: TxUpdateDoc<Doc>, key: string): any {
return (utx.operations as any)[key]
async function getValue (m: AttributeModel, utx: TxUpdateDoc<Doc>): Promise<any> {
const val = (utx.operations as any)[m.key]
console.log(m._class, m.key, val, typeof val)
if (client.getHierarchy().isDerived(m._class, core.class.Doc) && typeof val === 'string') {
// We have an reference, we need to find a real object to pass for presenter
return await client.findOne(m._class, { _id: val as Ref<Doc> })
}
return val
}
const showMenu = async (ev: MouseEvent): Promise<void> => {
showPopup(
@ -207,8 +214,14 @@
{/if}
{#if viewlet === undefined && model.length > 0 && tx.updateTx}
{#each model as m}
<span>changed {m.label} to</span>
<div class="strong"><svelte:component this={m.presenter} value={getValue(tx.updateTx, m.key)} /></div>
{#await getValue(m, tx.updateTx) then value}
{#if value === null}
<span>unset {m.label}</span>
{:else}
<span>changed {m.label} to</span>
<div class="strong"><svelte:component this={m.presenter} {value} /></div>
{/if}
{/await}
{/each}
{:else if viewlet && viewlet.display === 'inline' && viewlet.component}
<div>

View File

@ -13,28 +13,43 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import contact from '@anticrm/contact'
import { AttributeBarEditor, getClient, UserBox } from '@anticrm/presentation'
import { Applicant } from '@anticrm/recruit'
import contact from '@anticrm/contact'
export let object: Applicant
const client = getClient()
function change () {
client.updateDoc(object._class, object.space, object._id, { employee: object.employee })
function change() {
client.updateCollection(
object._class,
object.space,
object._id,
object.attachedTo,
object.attachedToClass,
object.collection,
{ employee: object.employee }
)
}
</script>
<div class="flex-between header">
<UserBox _class={contact.class.Employee} title='Assigned recruiter' caption='Recruiters' bind:value={object.employee} on:change={change} />
<UserBox
_class={contact.class.Employee}
title="Assigned recruiter"
caption="Recruiters"
bind:value={object.employee}
on:change={change}
allowDeselect
titleDeselect={'Unassign recruiter'}
/>
<AttributeBarEditor key={'state'} {object} showHeader={false} />
</div>
<style lang="scss">
.header {
width: 100%;
padding: 0 .5rem;
padding: 0 0.5rem;
}
</style>
</style>

View File

@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import type { Ref, Space, SpaceWithStates } from '@anticrm/core'
@ -33,7 +32,7 @@
export let space: Ref<SpaceWithStates>
export let candidate: Ref<Candidate> // | null = null
export let employee: Ref<Employee> // | null = null
export let employee: Ref<Employee> | null = null
export let preserveCandidate = false
@ -56,18 +55,31 @@
if (sequence === undefined) {
throw new Error('sequence object not found')
}
const incResult = await client.updateDoc(view.class.Sequence, view.space.Sequence, sequence._id, {
$inc: { sequence: 1 }
}, true)
const id = await client.addCollection(recruit.class.Applicant, _space, candidate, recruit.class.Candidate, 'applications', {
state: state._id,
number: incResult.object.sequence,
employee: employee
})
const incResult = await client.updateDoc(
view.class.Sequence,
view.space.Sequence,
sequence._id,
{
$inc: { sequence: 1 }
},
true
)
const id = await client.addCollection(
recruit.class.Applicant,
_space,
candidate,
recruit.class.Candidate,
'applications',
{
state: state._id,
number: incResult.object.sequence,
employee: employee
}
)
}
async function validate (candidate: Ref<Candidate>, space: Ref<Space>) {
if (candidate === undefined) {
if (candidate == null) {
status = new Status(Severity.INFO, recruit.status.CandidateRequired, {})
} else {
if (space === undefined) {
@ -84,22 +96,32 @@
}
$: validate(candidate, _space)
</script>
<Card label={'Create Application'}
okAction={createApplication}
canSave={status.severity === Severity.OK}
spaceClass={recruit.class.Vacancy}
spaceLabel={'Vacancy'}
spacePlaceholder={'Select vacancy'}
bind:space={_space}
on:close={() => { dispatch('close') }}>
<Card
label={'Create Application'}
okAction={createApplication}
canSave={status.severity === Severity.OK}
spaceClass={recruit.class.Vacancy}
spaceLabel={'Vacancy'}
spacePlaceholder={'Select vacancy'}
bind:space={_space}
on:close={() => {
dispatch('close')
}}
>
<StatusControl slot="error" {status} />
<Grid column={1} rowGap={1.75}>
{#if !preserveCandidate}
<UserBox _class={recruit.class.Candidate} title='Candidate' caption='Candidates' bind:value={candidate} />
<UserBox _class={recruit.class.Candidate} title="Candidate" caption="Candidates" bind:value={candidate} />
{/if}
<UserBox _class={contact.class.Employee} title='Assigned recruiter' caption='Recruiters' bind:value={employee} />
<UserBox
_class={contact.class.Employee}
title="Assigned recruiter"
caption="Recruiters"
bind:value={employee}
allowDeselect
titleDeselect={'Unassign recruiter'}
/>
</Grid>
</Card>

View File

@ -1,48 +0,0 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// 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, State, TxUpdateDoc } from '@anticrm/core'
import core from '@anticrm/core'
import { getClient } from '@anticrm/presentation'
import type { Applicant } from '@anticrm/recruit'
import { Component } from '@anticrm/ui'
import view from '@anticrm/view'
export let tx: TxUpdateDoc<Applicant>
const client = getClient()
const stateClass = client.getModel().getObject(core.class.State) as Class<State>
const statePresenter = client.getHierarchy().as(stateClass, view.mixin.AttributePresenter)
</script>
{#if tx.operations.state}
<div class="flex-row-center update-container">
<span>updated State to</span>
{#if statePresenter?.presenter}
{#await client.findOne(core.class.State, { _id: tx.operations.state }) then st}
{#if st}
<Component is={statePresenter.presenter} props={{ value: st }}/>
{/if}
{/await}
{/if}
</div>
{/if}
<style lang="scss">
.update-container span { margin-right: .5rem; }
</style>

View File

@ -25,7 +25,6 @@ import KanbanCard from './components/KanbanCard.svelte'
import EditVacancy from './components/EditVacancy.svelte'
import ApplicationPresenter from './components/ApplicationPresenter.svelte'
import ApplicationsPresenter from './components/ApplicationsPresenter.svelte'
import TxApplicantUpdate from './components/activity/TxApplicantUpdate.svelte'
import { showPopup } from '@anticrm/ui'
import { Resources } from '@anticrm/platform'
@ -49,8 +48,5 @@ export default async (): Promise<Resources> => ({
ApplicationPresenter,
ApplicationsPresenter,
EditVacancy
},
activity: {
TxApplicantUpdate
}
})

View File

@ -53,7 +53,7 @@ export interface Candidate extends Person {
export interface Applicant extends DocWithState, AttachedDoc {
attachments?: number
comments?: number
employee: Ref<Employee>
employee: Ref<Employee> | null
}
/**

View File

@ -16,6 +16,7 @@
"UploadDropFilesHere": "Upload or drop files here",
"NoAttachmentsForTask": "There are no attachments for this task.",
"AssigneeRequired": "Assignee is required",
"More": "Options"
"More": "Options",
"TaskUnAssign": "Unassign"
}
}

View File

@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import contact, { Employee, EmployeeAccount } from '@anticrm/contact'
import contact, { Employee } from '@anticrm/contact'
import type { Data, Ref, Space } from '@anticrm/core'
import { generateId } from '@anticrm/core'
import { OK, Status } from '@anticrm/platform'
@ -29,12 +29,12 @@
let _space = space
const status: Status = OK
let assignee: Ref<EmployeeAccount> // | null = null
let assignee: Ref<Employee> | null = null
const object: Data<Task> = {
name: '',
description: '',
assignee: undefined as unknown as Ref<Employee>,
assignee: null,
number: 0
}
@ -79,7 +79,7 @@
<Card
label={task.string.CreateTask}
okAction={createTask}
canSave={object.name.length > 0 && assignee !== undefined}
canSave={object.name.length > 0}
spaceClass={task.class.Project}
spaceLabel={task.string.ProjectName}
spacePlaceholder={task.string.SelectProject}
@ -99,10 +99,12 @@
focus
/>
<UserBox
_class={contact.class.EmployeeAccount}
_class={contact.class.Employee}
title="Assignee *"
caption="Assign this task"
bind:value={assignee}
allowDeselect
titleDeselect={task.string.TaskUnAssign}
/>
</Grid>
</Card>

View File

@ -34,6 +34,8 @@
caption="Assignee"
bind:value={object.assignee}
on:change={change}
allowDeselect
titleDeselect={task.string.TaskUnAssign}
/>
<AttributeBarEditor key={'state'} {object} showHeader={false} />
</div>

View File

@ -30,6 +30,7 @@ export default mergeIds(taskId, task, {
SelectProject: '' as IntlString,
TaskName: '' as IntlString,
TaskAssignee: '' as IntlString,
TaskUnAssign: '' as IntlString,
TaskDescription: '' as IntlString,
NoAttachmentsForTask: '' as IntlString,
UploadDropFilesHere: '' as IntlString,

View File

@ -33,7 +33,7 @@ export interface Task extends Doc {
name: string
description: string
assignee: Ref<Employee>
assignee: Ref<Employee> | null
comments?: number
attachments?: number

View File

@ -1,36 +1,35 @@
//
// 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.
//
import type { Class, Client, Doc, FindOptions, FindResult, Obj, Ref } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform'
import { getResource } from '@anticrm/platform'
import type { Ref, Class, Obj, FindOptions, Doc, Client, FindResult } from '@anticrm/core'
import type { AnyComponent, AnySvelteComponent } from '@anticrm/ui'
import type { AnyComponent } from '@anticrm/ui'
import type { Action, ActionTarget, BuildModelOptions } from '@anticrm/view'
import view, { AttributeModel } from '@anticrm/view'
/**
* @public
*/
export async function getObjectPresenter(client: Client, _class: Ref<Class<Obj>>, preserveKey: string): Promise<AttributeModel> {
const clazz = client.getHierarchy().getClass(_class)
export async function getObjectPresenter (client: Client, _class: Ref<Class<Obj>>, preserveKey: string): Promise<AttributeModel> {
const clazz = client.getHierarchy().getClass(_class)
const presenterMixin = client.getHierarchy().as(clazz, view.mixin.AttributePresenter)
if (presenterMixin.presenter === undefined) {
if (clazz.extends !== undefined) {
return getObjectPresenter(client, clazz.extends, preserveKey)
return await getObjectPresenter(client, clazz.extends, preserveKey)
} else {
throw new Error('object presenter not found for ' + preserveKey)
}
@ -38,37 +37,48 @@ export async function getObjectPresenter(client: Client, _class: Ref<Class<Obj>>
const presenter = await getResource(presenterMixin.presenter)
return {
key: preserveKey,
_class,
label: clazz.label,
presenter
} as AttributeModel
}
}
async function getAttributePresenter(client: Client, _class: Ref<Class<Obj>>, key: string, preserveKey: string) {
async function getAttributePresenter (client: Client, _class: Ref<Class<Obj>>, key: string, preserveKey: string): Promise<AttributeModel> {
const attribute = client.getHierarchy().getAttribute(_class, key)
const clazz = client.getHierarchy().getClass(attribute.type._class)
const presenterMixin = client.getHierarchy().as(clazz, view.mixin.AttributePresenter)
let attrClass = attribute.type._class
const clazz = client.getHierarchy().getClass(attrClass)
let presenterMixin = client.getHierarchy().as(clazz, view.mixin.AttributePresenter)
let parent = clazz.extends
while (presenterMixin.presenter === undefined && parent !== undefined) {
const pclazz = client.getHierarchy().getClass(parent)
attrClass = parent
presenterMixin = client.getHierarchy().as(pclazz, view.mixin.AttributePresenter)
parent = pclazz.extends
}
if (presenterMixin.presenter === undefined) {
throw new Error('attribute presenter not found for ' + preserveKey)
}
const presenter = await getResource(presenterMixin.presenter)
return {
key: preserveKey,
_class: attrClass,
label: attribute.label,
presenter
} as AttributeModel
}
}
async function getPresenter(client: Client, _class: Ref<Class<Obj>>, key: string, preserveKey: string, options?: FindOptions<Doc>): Promise<AttributeModel> {
async function getPresenter (client: Client, _class: Ref<Class<Obj>>, key: string, preserveKey: string, options?: FindOptions<Doc>): Promise<AttributeModel> {
if (typeof key === 'object') {
const {presenter, label} = key
const { presenter, label } = key
return {
key: '',
_class,
label: label as IntlString,
presenter: await getResource(presenter as AnyComponent)
}
}
if (key.length === 0) {
return getObjectPresenter(client, _class, preserveKey)
return await getObjectPresenter(client, _class, preserveKey)
} else {
const split = key.split('.')
if (split[0] === '$lookup') {
@ -80,36 +90,37 @@ async function getPresenter(client: Client, _class: Ref<Class<Obj>>, key: string
const model = await getPresenter(client, lookupClass, lookupKey, preserveKey)
if (lookupKey === '') {
const attribute = client.getHierarchy().getAttribute(_class, split[1])
model.label = attribute.label as IntlString
model.label = attribute.label
} else {
const attribute = client.getHierarchy().getAttribute(lookupClass, lookupKey)
model.label = attribute.label as IntlString
model.label = attribute.label
}
return model
}
return getAttributePresenter(client, _class, key, preserveKey)
return await getAttributePresenter(client, _class, key, preserveKey)
}
}
export async function buildModel(options: BuildModelOptions): Promise<AttributeModel[]> {
export async function buildModel (options: BuildModelOptions): Promise<AttributeModel[]> {
console.log('building table model for', options._class)
// eslint-disable-next-line array-callback-return
const model = options.keys.map(key => {
try {
const result = getPresenter(options.client, options._class, key, key, options.options)
return result
} catch(err: any) {
} catch (err: any) {
if (!(options.ignoreMissing ?? false)) {
throw err
}
}
})
console.log(model)
return (await Promise.all(model)).filter(a => a !== undefined) as AttributeModel[]
return (await Promise.all(model)).filter(a => a !== undefined) as AttributeModel[]
}
function filterActions(client: Client, _class: Ref<Class<Obj>>, targets: ActionTarget[]): Ref<Action>[] {
const result: Ref<Action>[] = []
for (const target of targets) {
function filterActions (client: Client, _class: Ref<Class<Obj>>, targets: ActionTarget[]): Array<Ref<Action>> {
const result: Array<Ref<Action>> = []
for (const target of targets) {
if (client.getHierarchy().isDerived(_class, target.target)) {
result.push(target.action)
}
@ -117,7 +128,7 @@ function filterActions(client: Client, _class: Ref<Class<Obj>>, targets: ActionT
return result
}
export async function getActions(client: Client, _class: Ref<Class<Obj>>): Promise<FindResult<Action>> {
export async function getActions (client: Client, _class: Ref<Class<Obj>>): Promise<FindResult<Action>> {
const targets = await client.findAll(view.class.ActionTarget, {})
return await client.findAll(view.class.Action, { _id: { $in: filterActions(client, _class, targets) }})
return await client.findAll(view.class.Action, { _id: { $in: filterActions(client, _class, targets) } })
}

View File

@ -109,6 +109,7 @@ export const viewId = 'view' as Plugin
export interface AttributeModel {
key: string
label: IntlString
_class: Ref<Class<Doc>>
presenter: AnySvelteComponent
}