Vacancy configure (#1962)

Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov 2022-06-01 09:25:13 +06:00 committed by GitHub
parent a4353b0fc7
commit c6ff5f19ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 282 additions and 206 deletions

View File

@ -10,6 +10,10 @@ Platform:
- Fix skills/labels selection and show real usage counter - Fix skills/labels selection and show real usage counter
- Fix skills/labels activity - Fix skills/labels activity
HR:
- Allow to configure vacancy table
## 0.6.22 ## 0.6.22
Platform: Platform:

View File

@ -201,7 +201,7 @@ export function createModel (builder: Builder): void {
editor: contact.component.PersonEditor editor: contact.component.PersonEditor
}) })
builder.mixin(contact.class.Channel, core.class.Class, view.mixin.AttributePresenter, { builder.mixin(contact.class.Channel, core.class.Class, view.mixin.CollectionPresenter, {
presenter: contact.component.ChannelsPresenter presenter: contact.component.ChannelsPresenter
}) })

View File

@ -134,10 +134,6 @@ export function createModel (builder: Builder): void {
} }
}) })
builder.mixin(core.class.Space, core.class.Class, view.mixin.AttributePresenter, {
presenter: recruit.component.VacancyItemPresenter
})
builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.CollectionEditor, { builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.CollectionEditor, {
editor: recruit.component.Applications editor: recruit.component.Applications
}) })
@ -269,6 +265,26 @@ export function createModel (builder: Builder): void {
hiddenKeys: ['name'] hiddenKeys: ['name']
}) })
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: recruit.class.Vacancy,
descriptor: view.viewlet.Table,
config: [
'',
{
key: '@applications',
label: recruit.string.Applications
},
'$lookup.company',
'location',
'description',
{
key: '@applications.modifiedOn',
label: core.string.Modified
}
],
hiddenKeys: ['name', 'space', 'modifiedOn']
})
builder.createDoc(view.class.Viewlet, core.space.Model, { builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: recruit.class.Applicant, attachTo: recruit.class.Applicant,
descriptor: task.viewlet.StatusTable, descriptor: task.viewlet.StatusTable,

View File

@ -16,10 +16,10 @@
<script lang="ts"> <script lang="ts">
import { Doc, DocumentQuery } from '@anticrm/core' import { Doc, DocumentQuery } from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation' import { createQuery, getClient } from '@anticrm/presentation'
import { Tooltip, Button, Icon, IconAdd, Label, Loading, SearchEdit, showPopup } from '@anticrm/ui' import { Button, Icon, IconAdd, Label, Loading, SearchEdit, showPopup } from '@anticrm/ui'
import view, { Viewlet, ViewletPreference } from '@anticrm/view'
import type { Filter } from '@anticrm/view' import type { Filter } from '@anticrm/view'
import { ActionContext, TableBrowser, ViewletSetting, FilterButton } from '@anticrm/view-resources' import view, { Viewlet, ViewletPreference } from '@anticrm/view'
import { ActionContext, FilterButton, TableBrowser, ViewletSettingButton } from '@anticrm/view-resources'
import contact from '../plugin' import contact from '../plugin'
import CreateContact from './CreateContact.svelte' import CreateContact from './CreateContact.svelte'
@ -90,17 +90,7 @@
kind={'primary'} kind={'primary'}
on:click={(ev) => showCreateDialog(ev)} on:click={(ev) => showCreateDialog(ev)}
/> />
{#if viewlet} <ViewletSettingButton {viewlet} />
<Tooltip label={view.string.CustomizeView}>
<Button
icon={view.icon.Setting}
kind={'transparent'}
on:click={() => {
showPopup(ViewletSetting, { viewlet })
}}
/>
</Tooltip>
{/if}
</div> </div>
{#if viewlet} {#if viewlet}

View File

@ -14,23 +14,22 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import { createQuery, getClient } from '@anticrm/presentation'
import ui, { import ui, {
Button, Button,
EditWithIcon, EditWithIcon,
eventToHTMLElement,
Icon, Icon,
IconAdd,
IconSearch, IconSearch,
Label, Label,
showPopup,
IconAdd,
eventToHTMLElement,
Loading, Loading,
Tooltip showPopup
} from '@anticrm/ui' } from '@anticrm/ui'
import CreateProduct from './CreateProduct.svelte'
import inventory from '../plugin'
import { TableBrowser, ViewletSetting } from '@anticrm/view-resources'
import { createQuery, getClient } from '@anticrm/presentation'
import view, { Viewlet, ViewletPreference } from '@anticrm/view' import view, { Viewlet, ViewletPreference } from '@anticrm/view'
import { TableBrowser, ViewletSettingButton } from '@anticrm/view-resources'
import inventory from '../plugin'
import CreateProduct from './CreateProduct.svelte'
let search = '' let search = ''
$: resultQuery = search === '' ? {} : { $search: search } $: resultQuery = search === '' ? {} : { $search: search }
@ -89,17 +88,7 @@
kind={'primary'} kind={'primary'}
on:click={(ev) => showCreateDialog(ev)} on:click={(ev) => showCreateDialog(ev)}
/> />
{#if descr} <ViewletSettingButton viewlet={descr} />
<Tooltip label={view.string.CustomizeView}>
<Button
icon={view.icon.Setting}
kind={'transparent'}
on:click={() => {
showPopup(ViewletSetting, { viewlet: descr })
}}
/>
</Tooltip>
{/if}
</div> </div>
{#if descr} {#if descr}

View File

@ -16,9 +16,9 @@
<script lang="ts"> <script lang="ts">
import { Doc, DocumentQuery } from '@anticrm/core' import { Doc, DocumentQuery } from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation' import { createQuery, getClient } from '@anticrm/presentation'
import { Tooltip, Button, Icon, Label, Loading, showPopup, SearchEdit } from '@anticrm/ui' import { Icon, Label, Loading, SearchEdit } from '@anticrm/ui'
import view, { Viewlet, ViewletPreference } from '@anticrm/view' import view, { Viewlet, ViewletPreference } from '@anticrm/view'
import { ViewletSetting, TableBrowser } from '@anticrm/view-resources' import { TableBrowser, ViewletSettingButton } from '@anticrm/view-resources'
import lead from '../plugin' import lead from '../plugin'
let search = '' let search = ''
@ -70,17 +70,7 @@
updateResultQuery(search) updateResultQuery(search)
}} }}
/> />
{#if descr} <ViewletSettingButton viewlet={descr} />
<Tooltip label={view.string.CustomizeView}>
<Button
icon={view.icon.Setting}
kind={'transparent'}
on:click={() => {
showPopup(ViewletSetting, { viewlet: descr })
}}
/>
</Tooltip>
{/if}
</div> </div>
{#if descr} {#if descr}

View File

@ -17,10 +17,10 @@
import { createQuery, getClient } from '@anticrm/presentation' import { createQuery, getClient } from '@anticrm/presentation'
import { Applicant } from '@anticrm/recruit' import { Applicant } from '@anticrm/recruit'
import task from '@anticrm/task' import task from '@anticrm/task'
import { Tooltip, Button, Icon, IconAdd, Label, Loading, SearchEdit, showPopup } from '@anticrm/ui' import { Button, Icon, IconAdd, Label, Loading, SearchEdit, showPopup } from '@anticrm/ui'
import view, { Viewlet, ViewletPreference } from '@anticrm/view'
import type { Filter } from '@anticrm/view' import type { Filter } from '@anticrm/view'
import { TableBrowser, ViewletSetting, FilterButton } from '@anticrm/view-resources' import view, { Viewlet, ViewletPreference } from '@anticrm/view'
import { FilterButton, TableBrowser, ViewletSettingButton } from '@anticrm/view-resources'
import recruit from '../plugin' import recruit from '../plugin'
import CreateApplication from './CreateApplication.svelte' import CreateApplication from './CreateApplication.svelte'
@ -83,17 +83,7 @@
}} }}
/> />
<Button icon={IconAdd} label={recruit.string.ApplicationCreateLabel} kind={'primary'} on:click={showCreateDialog} /> <Button icon={IconAdd} label={recruit.string.ApplicationCreateLabel} kind={'primary'} on:click={showCreateDialog} />
{#if descr} <ViewletSettingButton viewlet={descr} />
<Tooltip label={view.string.CustomizeView}>
<Button
icon={view.icon.Setting}
kind={'transparent'}
on:click={() => {
showPopup(ViewletSetting, { viewlet: descr })
}}
/>
</Tooltip>
{/if}
</div> </div>
{#if descr} {#if descr}

View File

@ -17,10 +17,10 @@
import contact from '@anticrm/contact' import contact from '@anticrm/contact'
import { Doc, DocumentQuery } from '@anticrm/core' import { Doc, DocumentQuery } from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation' import { createQuery, getClient } from '@anticrm/presentation'
import { Tooltip, showPopup, Icon, Label, Loading, SearchEdit, Button, IconAdd } from '@anticrm/ui' import { Button, Icon, IconAdd, Label, Loading, SearchEdit, showPopup } from '@anticrm/ui'
import view, { Viewlet, ViewletPreference } from '@anticrm/view'
import type { Filter } from '@anticrm/view' import type { Filter } from '@anticrm/view'
import { ActionContext, TableBrowser, ViewletSetting, FilterButton } from '@anticrm/view-resources' import view, { Viewlet, ViewletPreference } from '@anticrm/view'
import { ActionContext, FilterButton, TableBrowser, ViewletSettingButton } from '@anticrm/view-resources'
import recruit from '../plugin' import recruit from '../plugin'
import CreateCandidate from './CreateCandidate.svelte' import CreateCandidate from './CreateCandidate.svelte'
@ -83,17 +83,7 @@
}} }}
/> />
<Button icon={IconAdd} label={recruit.string.CandidateCreateLabel} kind={'primary'} on:click={showCreateDialog} /> <Button icon={IconAdd} label={recruit.string.CandidateCreateLabel} kind={'primary'} on:click={showCreateDialog} />
{#if descr} <ViewletSettingButton viewlet={descr} />
<Tooltip label={view.string.CustomizeView}>
<Button
icon={view.icon.Setting}
kind={'transparent'}
on:click={() => {
showPopup(ViewletSetting, { viewlet: descr })
}}
/>
</Tooltip>
{/if}
</div> </div>
<ActionContext <ActionContext

View File

@ -14,20 +14,14 @@
--> -->
<script lang="ts"> <script lang="ts">
import core, { Doc, DocumentQuery, Ref } from '@anticrm/core' import core, { Doc, DocumentQuery, Ref } from '@anticrm/core'
import { createQuery } from '@anticrm/presentation' import { createQuery, getClient } from '@anticrm/presentation'
import { Vacancy } from '@anticrm/recruit' import { Vacancy } from '@anticrm/recruit'
import { Button, getCurrentLocation, Icon, IconAdd, Label, navigate, SearchEdit, showPopup } from '@anticrm/ui' import { Button, Icon, IconAdd, Label, Loading, SearchEdit, showPopup } from '@anticrm/ui'
import { TableBrowser } from '@anticrm/view-resources' import view, { BuildModelKey, Filter, Viewlet, ViewletPreference } from '@anticrm/view'
import { FilterButton, TableBrowser, ViewletSettingButton } from '@anticrm/view-resources'
import recruit from '../plugin' import recruit from '../plugin'
import CreateVacancy from './CreateVacancy.svelte' import CreateVacancy from './CreateVacancy.svelte'
function action (vacancy: Ref<Vacancy>): void {
const loc = getCurrentLocation()
loc.path[2] = vacancy
loc.path.length = 3
navigate(loc)
}
let search: string = '' let search: string = ''
let resultQuery: DocumentQuery<Doc> = {} let resultQuery: DocumentQuery<Doc> = {}
@ -69,42 +63,21 @@
const modifiedSorting = (a: Doc, b: Doc) => const modifiedSorting = (a: Doc, b: Doc) =>
(applications?.get(b._id as Ref<Vacancy>)?.modifiedOn ?? 0) - (applications?.get(b._id as Ref<Vacancy>)?.modifiedOn ?? 0) -
(applications?.get(a._id as Ref<Vacancy>)?.modifiedOn ?? 0) ?? 0 (applications?.get(a._id as Ref<Vacancy>)?.modifiedOn ?? 0) ?? 0
</script>
<div class="ac-header full"> const replacedKeys: Map<string, BuildModelKey> = new Map<string, BuildModelKey>([
<div class="ac-header__wrap-title"> [
<div class="ac-header__icon"><Icon icon={recruit.icon.Vacancy} size={'small'} /></div> '@applications',
<span class="ac-header__title"><Label label={recruit.string.Vacancies} /></span>
</div>
<SearchEdit
bind:value={search}
on:change={(e) => {
search = e.detail
}}
/>
<Button icon={IconAdd} label={recruit.string.VacancyCreateLabel} kind={'primary'} on:click={showCreateDialog} />
</div>
<TableBrowser
_class={recruit.class.Vacancy}
config={[
{ {
key: '', key: '@applications',
presenter: recruit.component.VacancyItemPresenter,
label: recruit.string.Vacancy,
sortingKey: 'name',
props: { action }
},
{
key: '',
presenter: recruit.component.VacancyCountPresenter, presenter: recruit.component.VacancyCountPresenter,
label: recruit.string.Applications, label: recruit.string.Applications,
props: { applications }, props: { applications },
sortingKey: '@applications', sortingKey: '@applications',
sortingFunction: applicationSorting sortingFunction: applicationSorting
}, }
'$lookup.company', ],
'location', [
'description', '@applications.modifiedOn',
{ {
key: '', key: '',
presenter: recruit.component.VacancyModifiedPresenter, presenter: recruit.component.VacancyModifiedPresenter,
@ -113,10 +86,83 @@
sortingKey: 'modifiedOn', sortingKey: 'modifiedOn',
sortingFunction: modifiedSorting sortingFunction: modifiedSorting
} }
]} ]
])
const client = getClient()
let filters: Filter[] = []
let descr: Viewlet | undefined
let loading = true
const preferenceQuery = createQuery()
let preference: ViewletPreference | undefined
client
.findOne<Viewlet>(view.class.Viewlet, {
attachTo: recruit.class.Vacancy,
descriptor: view.viewlet.Table
})
.then((res) => {
descr = res
if (res !== undefined) {
preferenceQuery.query(
view.class.ViewletPreference,
{
attachedTo: res._id
},
(res) => {
preference = res[0]
loading = false
},
{ limit: 1 }
)
}
})
function createConfig (descr: Viewlet, preference: ViewletPreference | undefined): (string | BuildModelKey)[] {
const base = preference?.config ?? descr.config
const result: (string | BuildModelKey)[] = []
for (const key of base) {
if (typeof key === 'string') {
result.push(replacedKeys.get(key) ?? key)
} else {
result.push(replacedKeys.get(key.key) ?? key)
}
}
return result
}
</script>
<div class="ac-header full">
<div class="ac-header__wrap-title">
<div class="ac-header__icon"><Icon icon={recruit.icon.Vacancy} size={'small'} /></div>
<span class="ac-header__title"><Label label={recruit.string.Vacancies} /></span>
<div class="ml-4"><FilterButton _class={recruit.mixin.Candidate} bind:filters /></div>
</div>
<SearchEdit
bind:value={search}
on:change={(e) => {
search = e.detail
}}
/>
<Button icon={IconAdd} label={recruit.string.VacancyCreateLabel} kind={'primary'} on:click={showCreateDialog} />
<ViewletSettingButton viewlet={descr} />
</div>
{#if descr}
{#if loading}
<Loading />
{:else}
<TableBrowser
_class={recruit.class.Vacancy}
config={createConfig(descr, preference)}
options={descr.options}
query={{ query={{
...resultQuery, ...resultQuery,
archived: false archived: false
}} }}
bind:filters
showNotification showNotification
/> />
{/if}
{/if}

View File

@ -15,23 +15,54 @@
--> -->
<script lang="ts"> <script lang="ts">
import type { Vacancy } from '@anticrm/recruit' import type { Vacancy } from '@anticrm/recruit'
import { Icon } from '@anticrm/ui' import {
import { getPanelURI } from '@anticrm/ui/src/panelup' ActionIcon,
getCurrentLocation,
Icon,
IconEdit,
Location,
locationToUrl,
navigate,
showPanel
} from '@anticrm/ui'
import recruit from '../plugin' import recruit from '../plugin'
export let value: Vacancy export let value: Vacancy
export let inline: boolean = false export let inline: boolean = false
function editVacancy (): void {
showPanel(recruit.component.EditVacancy, value._id, value._class, 'content')
}
function getLoc (): Location {
const loc = getCurrentLocation()
loc.path[2] = value._id
loc.path.length = 3
return loc
}
function getLink (): string {
const loc = getLoc()
return document.location.origin + locationToUrl(loc)
}
</script> </script>
{#if value} {#if value}
<a <div class="flex-presenter" class:inline-presenter={inline}>
class="flex-presenter"
class:inline-presenter={inline}
href="#{getPanelURI(recruit.component.EditVacancy, value._id, value._class, 'content')}"
>
<div class="icon"> <div class="icon">
<Icon icon={recruit.icon.Vacancy} size={'small'} /> <Icon icon={recruit.icon.Vacancy} size={'small'} />
</div> </div>
<a
on:click|preventDefault={(e) => {
navigate(getLoc())
e.preventDefault()
}}
href={getLink()}
>
<span class="label">{value.name}</span> <span class="label">{value.name}</span>
</a> </a>
<div class="action">
<ActionIcon label={recruit.string.Edit} size={'small'} icon={IconEdit} action={editVacancy} />
</div>
</div>
{/if} {/if}

View File

@ -43,7 +43,7 @@
const client = getClient() const client = getClient()
const hierarchy = client.getHierarchy() const hierarchy = client.getHierarchy()
$: lookup = buildConfigLookup(hierarchy, _class, config) $: lookup = options?.lookup ?? buildConfigLookup(hierarchy, _class, config)
let sortKey = 'modifiedOn' let sortKey = 'modifiedOn'
let sortOrder = SortingOrder.Descending let sortOrder = SortingOrder.Descending

View File

@ -102,11 +102,11 @@
const allAttributes = hierarchy.getAllAttributes(viewlet.attachTo) const allAttributes = hierarchy.getAllAttributes(viewlet.attachTo)
for (const [, attribute] of allAttributes) { for (const [, attribute] of allAttributes) {
if (attribute.hidden === true || attribute.label === undefined) continue
if (viewlet.hiddenKeys?.includes(attribute.name)) continue
if (hierarchy.isDerived(attribute.type._class, core.class.Collection)) continue
const value = getValue(attribute.name, attribute.type) const value = getValue(attribute.name, attribute.type)
if (result.findIndex((p) => p.value === value) !== -1) continue if (result.findIndex((p) => p.value === value) !== -1) continue
if (hierarchy.isDerived(attribute.type._class, core.class.Collection)) continue
if (viewlet.hiddenKeys?.includes(value)) continue
if (attribute.hidden !== true && attribute.label !== undefined) {
const typeClassId = getAttributePresenterClass(attribute) const typeClassId = getAttributePresenterClass(attribute)
const typeClass = hierarchy.getClass(typeClassId) const typeClass = hierarchy.getClass(typeClassId)
let presenter = hierarchy.as(typeClass, view.mixin.AttributePresenter).presenter let presenter = hierarchy.as(typeClass, view.mixin.AttributePresenter).presenter
@ -123,7 +123,6 @@
enabled: false enabled: false
}) })
} }
}
return preference === undefined ? result : setStatus(result, preference) return preference === undefined ? result : setStatus(result, preference)
} }

View File

@ -0,0 +1,34 @@
<!--
// 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 { Button, showPopup, Tooltip } from '@anticrm/ui'
import { Viewlet } from '@anticrm/view'
import ViewletSetting from './ViewletSetting.svelte'
import view from '../plugin'
export let viewlet: Viewlet | undefined
</script>
{#if viewlet}
<Tooltip label={view.string.CustomizeView}>
<Button
icon={view.icon.Setting}
kind={'transparent'}
on:click={() => {
showPopup(ViewletSetting, { viewlet })
}}
/>
</Tooltip>
{/if}

View File

@ -16,40 +16,40 @@
import { Resources } from '@anticrm/platform' import { Resources } from '@anticrm/platform'
import { getEventPopupPositionElement, PopupAlignment } from '@anticrm/ui' import { getEventPopupPositionElement, PopupAlignment } from '@anticrm/ui'
import { actionImpl } from './actionImpl' import { actionImpl } from './actionImpl'
import ActionsPopup from './components/ActionsPopup.svelte'
import BooleanEditor from './components/BooleanEditor.svelte' import BooleanEditor from './components/BooleanEditor.svelte'
import BooleanPresenter from './components/BooleanPresenter.svelte' import BooleanPresenter from './components/BooleanPresenter.svelte'
import BooleanTruePresenter from './components/BooleanTruePresenter.svelte'
import ClassAttributeBar from './components/ClassAttributeBar.svelte'
import ClassPresenter from './components/ClassPresenter.svelte'
import ColorsPopup from './components/ColorsPopup.svelte' import ColorsPopup from './components/ColorsPopup.svelte'
import DateEditor from './components/DateEditor.svelte' import DateEditor from './components/DateEditor.svelte'
import DatePresenter from './components/DatePresenter.svelte' import DatePresenter from './components/DatePresenter.svelte'
import SpacePresenter from './components/SpacePresenter.svelte' import DocAttributeBar from './components/DocAttributeBar.svelte'
import StringEditor from './components/StringEditor.svelte' import EditBoxPopup from './components/EditBoxPopup.svelte'
import StringPresenter from './components/StringPresenter.svelte'
import EditDoc from './components/EditDoc.svelte' import EditDoc from './components/EditDoc.svelte'
import EnumEditor from './components/EnumEditor.svelte'
import FilterBar from './components/filter/FilterBar.svelte'
import ObjectFilter from './components/filter/ObjectFilter.svelte'
import TimestampFilter from './components/filter/TimestampFilter.svelte'
import ValueFilter from './components/filter/ValueFilter.svelte'
import HTMLPresenter from './components/HTMLPresenter.svelte' import HTMLPresenter from './components/HTMLPresenter.svelte'
import IntlStringPresenter from './components/IntlStringPresenter.svelte' import IntlStringPresenter from './components/IntlStringPresenter.svelte'
import GithubPresenter from './components/linkPresenters/GithubPresenter.svelte'
import YoutubePresenter from './components/linkPresenters/YoutubePresenter.svelte'
import Menu from './components/Menu.svelte' import Menu from './components/Menu.svelte'
import NumberEditor from './components/NumberEditor.svelte' import NumberEditor from './components/NumberEditor.svelte'
import NumberPresenter from './components/NumberPresenter.svelte' import NumberPresenter from './components/NumberPresenter.svelte'
import ObjectPresenter from './components/ObjectPresenter.svelte' import ObjectPresenter from './components/ObjectPresenter.svelte'
import RolePresenter from './components/RolePresenter.svelte' import RolePresenter from './components/RolePresenter.svelte'
import SpacePresenter from './components/SpacePresenter.svelte'
import StringEditor from './components/StringEditor.svelte'
import StringPresenter from './components/StringPresenter.svelte'
import Table from './components/Table.svelte' import Table from './components/Table.svelte'
import TableBrowser from './components/TableBrowser.svelte'
import TimestampPresenter from './components/TimestampPresenter.svelte' import TimestampPresenter from './components/TimestampPresenter.svelte'
import UpDownNavigator from './components/UpDownNavigator.svelte' import UpDownNavigator from './components/UpDownNavigator.svelte'
import GithubPresenter from './components/linkPresenters/GithubPresenter.svelte' import ViewletSettingButton from './components/ViewletSettingButton.svelte'
import YoutubePresenter from './components/linkPresenters/YoutubePresenter.svelte'
import ActionsPopup from './components/ActionsPopup.svelte'
import DocAttributeBar from './components/DocAttributeBar.svelte'
import ViewletSetting from './components/ViewletSetting.svelte'
import TableBrowser from './components/TableBrowser.svelte'
import ValueFilter from './components/filter/ValueFilter.svelte'
import ObjectFilter from './components/filter/ObjectFilter.svelte'
import TimestampFilter from './components/filter/TimestampFilter.svelte'
import ClassPresenter from './components/ClassPresenter.svelte'
import FilterBar from './components/filter/FilterBar.svelte'
import EditBoxPopup from './components/EditBoxPopup.svelte'
import BooleanTruePresenter from './components/BooleanTruePresenter.svelte'
import EnumEditor from './components/EnumEditor.svelte'
import ClassAttributeBar from './components/ClassAttributeBar.svelte'
function PositionElementAlignment (e?: Event): PopupAlignment | undefined { function PositionElementAlignment (e?: Event): PopupAlignment | undefined {
return getEventPopupPositionElement(e) return getEventPopupPositionElement(e)
@ -58,10 +58,10 @@ function PositionElementAlignment (e?: Event): PopupAlignment | undefined {
export { getActions, invokeAction } from './actions' export { getActions, invokeAction } from './actions'
export { default as ActionContext } from './components/ActionContext.svelte' export { default as ActionContext } from './components/ActionContext.svelte'
export { default as ActionHandler } from './components/ActionHandler.svelte' export { default as ActionHandler } from './components/ActionHandler.svelte'
export { default as FilterButton } from './components/filter/FilterButton.svelte'
export { default as LinkPresenter } from './components/LinkPresenter.svelte'
export { default as ContextMenu } from './components/Menu.svelte' export { default as ContextMenu } from './components/Menu.svelte'
export { default as TableBrowser } from './components/TableBrowser.svelte' export { default as TableBrowser } from './components/TableBrowser.svelte'
export { default as LinkPresenter } from './components/LinkPresenter.svelte'
export { default as FilterButton } from './components/filter/FilterButton.svelte'
export * from './context' export * from './context'
export * from './selection' export * from './selection'
export { buildModel, getCollectionCounter, getObjectPresenter, LoadingProps } from './utils' export { buildModel, getCollectionCounter, getObjectPresenter, LoadingProps } from './utils'
@ -75,7 +75,7 @@ export {
Menu, Menu,
SpacePresenter, SpacePresenter,
UpDownNavigator, UpDownNavigator,
ViewletSetting, ViewletSettingButton,
FilterBar, FilterBar,
ClassAttributeBar ClassAttributeBar
} }
@ -88,7 +88,6 @@ export default async (): Promise<Resources> => ({
ValueFilter, ValueFilter,
TimestampFilter, TimestampFilter,
TableBrowser, TableBrowser,
ViewletSetting,
SpacePresenter, SpacePresenter,
StringEditor, StringEditor,
StringPresenter, StringPresenter,

View File

@ -48,16 +48,20 @@ export interface LoadingProps {
export async function getObjectPresenter ( export async function getObjectPresenter (
client: Client, client: Client,
_class: Ref<Class<Obj>>, _class: Ref<Class<Obj>>,
preserveKey: BuildModelKey preserveKey: BuildModelKey,
isCollectionAttr: boolean = false
): Promise<AttributeModel> { ): Promise<AttributeModel> {
const clazz = client.getHierarchy().getClass(_class) const hierarchy = client.getHierarchy()
const presenterMixin = client.getHierarchy().as(clazz, view.mixin.AttributePresenter) const mixin = isCollectionAttr ? view.mixin.CollectionPresenter : view.mixin.AttributePresenter
if (presenterMixin.presenter === undefined) { const clazz = hierarchy.getClass(_class)
if (clazz.extends !== undefined) { let mixinClazz = hierarchy.getClass(_class)
return await getObjectPresenter(client, clazz.extends, preserveKey) let presenterMixin = hierarchy.as(clazz, mixin)
} else { while (presenterMixin.presenter === undefined && mixinClazz.extends !== undefined) {
throw new Error('object presenter not found for ' + JSON.stringify(preserveKey)) presenterMixin = hierarchy.as(mixinClazz, mixin)
mixinClazz = hierarchy.getClass(mixinClazz.extends)
} }
if (presenterMixin.presenter === undefined) {
throw new Error('object presenter not found for ' + JSON.stringify(preserveKey))
} }
const presenter = await getResource(presenterMixin.presenter) const presenter = await getResource(presenterMixin.presenter)
const key = preserveKey.sortingKey ?? preserveKey.key const key = preserveKey.sortingKey ?? preserveKey.key
@ -132,7 +136,8 @@ export async function getPresenter<T extends Doc> (
_class: Ref<Class<T>>, _class: Ref<Class<T>>,
key: BuildModelKey, key: BuildModelKey,
preserveKey: BuildModelKey, preserveKey: BuildModelKey,
lookup?: Lookup<T> lookup?: Lookup<T>,
isCollectionAttr: boolean = false
): Promise<AttributeModel> { ): Promise<AttributeModel> {
if (key.presenter !== undefined) { if (key.presenter !== undefined) {
const { presenter, label, sortingKey } = key const { presenter, label, sortingKey } = key
@ -146,7 +151,7 @@ export async function getPresenter<T extends Doc> (
} }
} }
if (key.key.length === 0) { if (key.key.length === 0) {
return await getObjectPresenter(client, _class, preserveKey) return await getObjectPresenter(client, _class, preserveKey, isCollectionAttr)
} else { } else {
if (key.key.startsWith('$lookup')) { if (key.key.startsWith('$lookup')) {
if (lookup === undefined) { if (lookup === undefined) {
@ -262,7 +267,7 @@ async function getLookupPresenter<T extends Doc> (
const lookupClass = getLookupClass(key.key, lookup, _class) const lookupClass = getLookupClass(key.key, lookup, _class)
const lookupProperty = getLookupProperty(key.key) const lookupProperty = getLookupProperty(key.key)
const lookupKey = { ...key, key: lookupProperty[0] } const lookupKey = { ...key, key: lookupProperty[0] }
const model = await getPresenter(client, lookupClass[0], lookupKey, preserveKey) const model = await getPresenter(client, lookupClass[0], lookupKey, preserveKey, undefined, lookupClass[2])
model.label = getLookupLabel(client, lookupClass[1], lookupClass[0], lookupKey, lookupProperty[1]) model.label = getLookupLabel(client, lookupClass[1], lookupClass[0], lookupKey, lookupProperty[1])
return model return model
} }
@ -292,7 +297,7 @@ export function getLookupClass<T extends Doc> (
key: string, key: string,
lookup: Lookup<T>, lookup: Lookup<T>,
parent: Ref<Class<T>> parent: Ref<Class<T>>
): [Ref<Class<Doc>>, Ref<Class<Doc>>] { ): [Ref<Class<Doc>>, Ref<Class<Doc>>, boolean] {
const _class = getLookup(key, lookup, parent) const _class = getLookup(key, lookup, parent)
if (_class === undefined) { if (_class === undefined) {
throw new Error('lookup class does not provided for ' + key) throw new Error('lookup class does not provided for ' + key)
@ -313,7 +318,7 @@ function getLookup (
key: string, key: string,
lookup: Lookup<any>, lookup: Lookup<any>,
parent: Ref<Class<Doc>> parent: Ref<Class<Doc>>
): [Ref<Class<Doc>>, Ref<Class<Doc>>] | undefined { ): [Ref<Class<Doc>>, Ref<Class<Doc>>, boolean] | undefined {
const parts = key.split('$lookup.').filter((p) => p.length > 0) const parts = key.split('$lookup.').filter((p) => p.length > 0)
const currentKey = parts[0].split('.').filter((p) => p.length > 0)[0] const currentKey = parts[0].split('.').filter((p) => p.length > 0)[0]
const current = (lookup as any)[currentKey] const current = (lookup as any)[currentKey]
@ -325,13 +330,17 @@ function getLookup (
return getLookup(nestedKey, current[1], current[0]) return getLookup(nestedKey, current[1], current[0])
} }
if (Array.isArray(current)) { if (Array.isArray(current)) {
return [current[0], parent] return [current[0], parent, false]
} }
if (current === undefined && lookup._id !== undefined) { if (current === undefined && lookup._id !== undefined) {
const reverse = (lookup._id as any)[currentKey] const reverse = (lookup._id as any)[currentKey]
return reverse !== undefined ? [reverse, parent] : undefined return reverse !== undefined
? Array.isArray(reverse)
? [reverse[0], parent, true]
: [reverse, parent, true]
: undefined
} }
return current !== undefined ? [current, parent] : undefined return current !== undefined ? [current, parent, false] : undefined
} }
export function getBooleanLabel (value: boolean | undefined): IntlString { export function getBooleanLabel (value: boolean | undefined): IntlString {

View File

@ -391,7 +391,6 @@ const view = plugin(viewId, {
component: { component: {
ObjectPresenter: '' as AnyComponent, ObjectPresenter: '' as AnyComponent,
EditDoc: '' as AnyComponent, EditDoc: '' as AnyComponent,
ViewletSetting: '' as AnyComponent,
SpacePresenter: '' as AnyComponent, SpacePresenter: '' as AnyComponent,
BooleanTruePresenter: '' as AnyComponent BooleanTruePresenter: '' as AnyComponent
}, },

View File

@ -17,14 +17,14 @@
import core, { WithLookup } from '@anticrm/core' import core, { WithLookup } from '@anticrm/core'
import { IntlString } from '@anticrm/platform' import { IntlString } from '@anticrm/platform'
import presentation, { createQuery, getClient } from '@anticrm/presentation' import presentation, { createQuery, getClient } from '@anticrm/presentation'
import { AnyComponent, showPanel, Button, Icon, SearchEdit, showPopup, Tooltip, IconAdd } from '@anticrm/ui' import { AnyComponent, Button, Icon, IconAdd, SearchEdit, showPanel, showPopup, Tooltip } from '@anticrm/ui'
import type { Filter } from '@anticrm/view'
import view, { Viewlet } from '@anticrm/view' import view, { Viewlet } from '@anticrm/view'
import { ViewletSetting } from '@anticrm/view-resources' import { ViewletSettingButton } from '@anticrm/view-resources'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import plugin from '../plugin' import plugin from '../plugin'
import { classIcon } from '../utils' import { classIcon } from '../utils'
import Header from './Header.svelte' import Header from './Header.svelte'
import type { Filter } from '@anticrm/view'
export let spaceId: Ref<Space> | undefined export let spaceId: Ref<Space> | undefined
export let createItemDialog: AnyComponent | undefined export let createItemDialog: AnyComponent | undefined
@ -115,16 +115,6 @@
{#if createItemDialog} {#if createItemDialog}
<Button icon={IconAdd} label={createItemLabel} kind={'primary'} on:click={(ev) => showCreateDialog(ev)} /> <Button icon={IconAdd} label={createItemLabel} kind={'primary'} on:click={(ev) => showCreateDialog(ev)} />
{/if} {/if}
{#if viewlet} <ViewletSettingButton {viewlet} />
<Tooltip label={view.string.CustomizeView}>
<Button
icon={view.icon.Setting}
kind={'transparent'}
on:click={() => {
showPopup(ViewletSetting, { viewlet })
}}
/>
</Tooltip>
{/if}
{/if} {/if}
</div> </div>