mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-29 19:56:18 +00:00
Security improvments (#5595)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
b60067cc92
commit
2d92b9aa46
@ -66,7 +66,7 @@ import core, { TAccount, TAttachedDoc, TDoc } from '@hcengineering/model-core'
|
||||
import { createPublicLinkAction } from '@hcengineering/model-guest'
|
||||
import { generateClassNotificationTypes } from '@hcengineering/model-notification'
|
||||
import presentation from '@hcengineering/model-presentation'
|
||||
import view, { createAction, type Viewlet } from '@hcengineering/model-view'
|
||||
import view, { createAction, createAttributePresenter, type Viewlet } from '@hcengineering/model-view'
|
||||
import workbench from '@hcengineering/model-workbench'
|
||||
import notification from '@hcengineering/notification'
|
||||
import type { Asset, IntlString, Resource } from '@hcengineering/platform'
|
||||
@ -1142,4 +1142,6 @@ export function createModel (builder: Builder): void {
|
||||
domain: DOMAIN_CONTACT,
|
||||
disabled: [{ attachedToClass: 1 }, { modifiedBy: 1 }, { createdBy: 1 }, { createdOn: -1 }, { attachedTo: 1 }]
|
||||
})
|
||||
|
||||
createAttributePresenter(builder, contact.component.SpaceMembersEditor, core.class.Space, 'members', 'array')
|
||||
}
|
||||
|
@ -211,6 +211,10 @@ export function createModel (builder: Builder): void {
|
||||
inlineEditor: hr.component.DepartmentEditor
|
||||
})
|
||||
|
||||
builder.mixin(hr.class.Department, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: hr.component.DepartmentRefPresenter
|
||||
})
|
||||
|
||||
builder.mixin(hr.class.Department, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: hr.component.EditDepartment
|
||||
})
|
||||
|
@ -50,7 +50,8 @@ export default mergeIds(hrId, hr, {
|
||||
TzDatePresenter: '' as AnyComponent,
|
||||
TzDateEditor: '' as AnyComponent,
|
||||
RequestPresenter: '' as AnyComponent,
|
||||
DepartmentPresenter: '' as AnyComponent
|
||||
DepartmentPresenter: '' as AnyComponent,
|
||||
DepartmentRefPresenter: '' as AnyComponent
|
||||
},
|
||||
category: {
|
||||
HR: '' as Ref<ActionCategory>
|
||||
|
@ -18,9 +18,7 @@ import activity, { type ActivityMessage } from '@hcengineering/activity'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import {
|
||||
DOMAIN_MODEL,
|
||||
Hierarchy,
|
||||
IndexKind,
|
||||
type Space,
|
||||
type Account,
|
||||
type AttachedDoc,
|
||||
type Class,
|
||||
@ -31,6 +29,7 @@ import {
|
||||
type Domain,
|
||||
type Markup,
|
||||
type Ref,
|
||||
type Space,
|
||||
type Timestamp,
|
||||
type Tx
|
||||
} from '@hcengineering/core'
|
||||
@ -700,11 +699,7 @@ export function generateClassNotificationTypes (
|
||||
ignoreKeys: string[] = [],
|
||||
defaultEnabled: string[] = []
|
||||
): void {
|
||||
const txes = builder.getTxes()
|
||||
const hierarchy = new Hierarchy()
|
||||
for (const tx of txes) {
|
||||
hierarchy.tx(tx)
|
||||
}
|
||||
const hierarchy = builder.hierarchy
|
||||
const attributes = hierarchy.getAllAttributes(
|
||||
_class,
|
||||
hierarchy.isDerived(_class, core.class.AttachedDoc) ? core.class.AttachedDoc : core.class.Doc
|
||||
|
@ -23,7 +23,8 @@ import {
|
||||
type DocumentQuery,
|
||||
type Domain,
|
||||
type Ref,
|
||||
type Space
|
||||
type Space,
|
||||
type AnyAttribute
|
||||
} from '@hcengineering/core'
|
||||
import { type Builder, Mixin, Model, UX } from '@hcengineering/model'
|
||||
import core, { TClass, TDoc } from '@hcengineering/model-core'
|
||||
@ -87,10 +88,13 @@ import {
|
||||
type ViewletPreference,
|
||||
type ObjectIdentifier,
|
||||
type ObjectIcon,
|
||||
type ObjectTooltip
|
||||
type ObjectTooltip,
|
||||
type AttrPresenter,
|
||||
type AttributeCategory
|
||||
} from '@hcengineering/view'
|
||||
|
||||
import view from './plugin'
|
||||
import { classPresenter, createAction } from './utils'
|
||||
|
||||
export { viewId } from '@hcengineering/view'
|
||||
export { viewOperation } from './migration'
|
||||
@ -98,38 +102,7 @@ export type { ViewAction, Viewlet }
|
||||
|
||||
export const DOMAIN_VIEW = 'view' as Domain
|
||||
|
||||
export function createAction<T extends Doc = Doc, P = Record<string, any>> (
|
||||
builder: Builder,
|
||||
data: Data<Action<T, P>>,
|
||||
id?: Ref<Action<T, P>>
|
||||
): void {
|
||||
const { label, ...adata } = data
|
||||
builder.createDoc<Action<T, P>>(view.class.Action, core.space.Model, { label, ...adata }, id)
|
||||
}
|
||||
|
||||
export function classPresenter (
|
||||
builder: Builder,
|
||||
_class: Ref<Class<Doc>>,
|
||||
presenter: AnyComponent,
|
||||
editor?: AnyComponent,
|
||||
popup?: AnyComponent,
|
||||
activity?: AnyComponent
|
||||
): void {
|
||||
builder.mixin(_class, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter
|
||||
})
|
||||
if (editor !== undefined) {
|
||||
builder.mixin(_class, core.class.Class, view.mixin.AttributeEditor, {
|
||||
inlineEditor: editor,
|
||||
popup
|
||||
})
|
||||
}
|
||||
if (activity !== undefined) {
|
||||
builder.mixin(_class, core.class.Class, view.mixin.ActivityAttributePresenter, {
|
||||
presenter: activity
|
||||
})
|
||||
}
|
||||
}
|
||||
export * from './utils'
|
||||
|
||||
@Model(view.class.FilteredView, core.class.Doc, DOMAIN_VIEW)
|
||||
@UX(view.string.FilteredViews)
|
||||
@ -383,6 +356,14 @@ export class TObjectPanel extends TClass implements ObjectPanel {
|
||||
component!: AnyComponent
|
||||
}
|
||||
|
||||
@Model(view.class.AttrPresenter, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TAttrPresenter extends TDoc implements AttrPresenter {
|
||||
category!: AttributeCategory
|
||||
objectClass!: Ref<Class<Doc<Space>>>
|
||||
attribute!: Ref<AnyAttribute>
|
||||
component!: AnyComponent
|
||||
}
|
||||
|
||||
export type ActionTemplate = Partial<Data<Action>>
|
||||
|
||||
/**
|
||||
@ -467,7 +448,8 @@ export function createModel (builder: Builder): void {
|
||||
TGroupping,
|
||||
TObjectIdentifier,
|
||||
TObjectTooltip,
|
||||
TObjectIcon
|
||||
TObjectIcon,
|
||||
TAttrPresenter
|
||||
)
|
||||
|
||||
classPresenter(
|
||||
|
70
models/view/src/utils.ts
Normal file
70
models/view/src/utils.ts
Normal file
@ -0,0 +1,70 @@
|
||||
//
|
||||
// Copyright © 2024 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, type Data, type Doc, type Ref } from '@hcengineering/core'
|
||||
import { type Builder } from '@hcengineering/model'
|
||||
import core from '@hcengineering/model-core'
|
||||
import { type AnyComponent } from '@hcengineering/ui'
|
||||
import { type Action, type AttributeCategory } from '@hcengineering/view'
|
||||
import view from '.'
|
||||
|
||||
export function createAction<T extends Doc = Doc, P = Record<string, any>> (
|
||||
builder: Builder,
|
||||
data: Data<Action<T, P>>,
|
||||
id?: Ref<Action<T, P>>
|
||||
): void {
|
||||
const { label, ...adata } = data
|
||||
builder.createDoc<Action<T, P>>(view.class.Action, core.space.Model, { label, ...adata }, id)
|
||||
}
|
||||
|
||||
export function classPresenter (
|
||||
builder: Builder,
|
||||
_class: Ref<Class<Doc>>,
|
||||
presenter: AnyComponent,
|
||||
editor?: AnyComponent,
|
||||
popup?: AnyComponent,
|
||||
activity?: AnyComponent
|
||||
): void {
|
||||
builder.mixin(_class, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter
|
||||
})
|
||||
if (editor !== undefined) {
|
||||
builder.mixin(_class, core.class.Class, view.mixin.AttributeEditor, {
|
||||
inlineEditor: editor,
|
||||
popup
|
||||
})
|
||||
}
|
||||
if (activity !== undefined) {
|
||||
builder.mixin(_class, core.class.Class, view.mixin.ActivityAttributePresenter, {
|
||||
presenter: activity
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function createAttributePresenter<T extends Doc> (
|
||||
builder: Builder,
|
||||
component: AnyComponent,
|
||||
_class: Ref<Class<T>>,
|
||||
key: keyof T,
|
||||
category: AttributeCategory
|
||||
): void {
|
||||
const attr = builder.hierarchy.getAttribute(_class, key as string)
|
||||
builder.createDoc(view.class.AttrPresenter, core.space.Model, {
|
||||
component,
|
||||
attribute: attr._id,
|
||||
objectClass: _class,
|
||||
category
|
||||
})
|
||||
}
|
@ -27,6 +27,7 @@ import core, {
|
||||
Domain,
|
||||
Enum,
|
||||
EnumOf,
|
||||
Hierarchy,
|
||||
Hyperlink,
|
||||
Mixin as IMixin,
|
||||
IndexKind,
|
||||
@ -290,6 +291,7 @@ function _generateTx (tx: ClassTxes): Tx[] {
|
||||
*/
|
||||
export class Builder {
|
||||
private readonly txes: Tx[] = []
|
||||
readonly hierarchy = new Hierarchy()
|
||||
|
||||
onTx?: (tx: Tx) => void
|
||||
|
||||
@ -322,7 +324,7 @@ export class Builder {
|
||||
for (const tx of generated) {
|
||||
this.txes.push(tx)
|
||||
this.onTx?.(tx)
|
||||
// this.hierarchy.tx(tx)
|
||||
this.hierarchy.tx(tx)
|
||||
}
|
||||
}
|
||||
|
||||
@ -352,6 +354,7 @@ export class Builder {
|
||||
}
|
||||
this.txes.push(tx)
|
||||
this.onTx?.(tx)
|
||||
this.hierarchy.tx(tx)
|
||||
return TxProcessor.createDoc2Doc(tx)
|
||||
}
|
||||
|
||||
@ -364,6 +367,7 @@ export class Builder {
|
||||
const tx = txFactory.createTxMixin(objectId, objectClass, core.space.Model, mixin, attributes)
|
||||
this.txes.push(tx)
|
||||
this.onTx?.(tx)
|
||||
this.hierarchy.tx(tx)
|
||||
}
|
||||
|
||||
getTxes (): Tx[] {
|
||||
|
@ -61,6 +61,12 @@
|
||||
return true
|
||||
}
|
||||
|
||||
export let sort: <T extends Doc>(a: T, b: T) => number = (a, b) => {
|
||||
const aval: string = `${getObjectValue(groupBy, a as any)}`
|
||||
const bval: string = `${getObjectValue(groupBy, b as any)}`
|
||||
return aval.localeCompare(bval)
|
||||
}
|
||||
|
||||
const created: Doc[] = []
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@ -84,11 +90,7 @@
|
||||
_id: { $nin: ignoreObjects, ..._idExtra }
|
||||
},
|
||||
(result) => {
|
||||
result.sort((a, b) => {
|
||||
const aval: string = `${getObjectValue(groupBy, a as any)}`
|
||||
const bval: string = `${getObjectValue(groupBy, b as any)}`
|
||||
return aval.localeCompare(bval)
|
||||
})
|
||||
result.sort(sort)
|
||||
if (created.length > 0) {
|
||||
const cmap = new Set(created.map((it) => it._id))
|
||||
objects = [...created, ...result.filter((d) => !cmap.has(d._id))].filter(filter)
|
||||
|
@ -48,7 +48,7 @@ import core, {
|
||||
import { getMetadata, getResource } from '@hcengineering/platform'
|
||||
import { LiveQuery as LQ } from '@hcengineering/query'
|
||||
import { type AnyComponent, type AnySvelteComponent, type IconSize } from '@hcengineering/ui'
|
||||
import view, { type AttributeEditor } from '@hcengineering/view'
|
||||
import view, { type AttributeCategory, type AttributeEditor } from '@hcengineering/view'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { onDestroy } from 'svelte'
|
||||
import { type KeyedAttribute } from '..'
|
||||
@ -407,16 +407,6 @@ export async function copyTextToClipboard (text: string): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type AttributeCategory = 'object' | 'attribute' | 'inplace' | 'collection' | 'array'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const AttributeCategoryOrder = { attribute: 0, inplace: 1, collection: 2, array: 2, object: 3 }
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
@ -13,8 +13,8 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Employee, PersonAccount } from '@hcengineering/contact'
|
||||
import core, { Account, Ref } from '@hcengineering/core'
|
||||
import { Contact, Employee, PersonAccount, getName } from '@hcengineering/contact'
|
||||
import core, { Account, Ref, getCurrentAccount } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { ButtonKind, ButtonSize } from '@hcengineering/ui'
|
||||
@ -33,7 +33,7 @@
|
||||
export let includeItems: Ref<Account>[] | undefined = undefined
|
||||
export let excludeItems: Ref<Account>[] | undefined = undefined
|
||||
export let emptyLabel: IntlString | undefined = undefined
|
||||
export let allowGuests: boolean = true
|
||||
export let allowGuests: boolean = false
|
||||
|
||||
let timer: any = null
|
||||
const client = getClient()
|
||||
@ -103,6 +103,29 @@
|
||||
: {})
|
||||
}
|
||||
}
|
||||
|
||||
const hierarchy = client.getHierarchy()
|
||||
const me = getCurrentAccount() as PersonAccount
|
||||
|
||||
function sort (a: Contact, b: Contact): number {
|
||||
if (me.person === a._id) {
|
||||
return -1
|
||||
}
|
||||
if (me.person === b._id) {
|
||||
return 1
|
||||
}
|
||||
const aIncludes = employees.includes(a._id as Ref<Employee>)
|
||||
const bIncludes = employees.includes(b._id as Ref<Employee>)
|
||||
if (aIncludes && !bIncludes) {
|
||||
return -1
|
||||
}
|
||||
if (!aIncludes && bIncludes) {
|
||||
return 1
|
||||
}
|
||||
const aName = getName(hierarchy, a)
|
||||
const bName = getName(hierarchy, b)
|
||||
return aName.localeCompare(bName)
|
||||
}
|
||||
</script>
|
||||
|
||||
<UserBoxList
|
||||
@ -114,6 +137,7 @@
|
||||
{docQuery}
|
||||
on:update={onUpdate}
|
||||
{size}
|
||||
{sort}
|
||||
justify={'left'}
|
||||
width={width ?? 'min-content'}
|
||||
{kind}
|
||||
|
@ -4,14 +4,9 @@
|
||||
import notification from '@hcengineering/notification'
|
||||
import { Panel } from '@hcengineering/panel'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import presentation, {
|
||||
type AttributeCategory,
|
||||
createQuery,
|
||||
getClient,
|
||||
type KeyedAttribute
|
||||
} from '@hcengineering/presentation'
|
||||
import presentation, { createQuery, getClient, type KeyedAttribute } from '@hcengineering/presentation'
|
||||
import { type AnyComponent, Button, Component, IconMixin, IconMoreH, Label } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import view, { AttributeCategory } from '@hcengineering/view'
|
||||
import {
|
||||
DocAttributeBar,
|
||||
DocNavLink,
|
||||
|
@ -0,0 +1,45 @@
|
||||
<!--
|
||||
// Copyright © 2024 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 { Account, Ref, getCurrentAccount } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { Button, ButtonKind, ButtonSize } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import AccountArrayEditor from './AccountArrayEditor.svelte'
|
||||
|
||||
export let label: IntlString
|
||||
export let value: Ref<Account>[]
|
||||
export let onChange: ((refs: Ref<Account>[]) => void) | undefined
|
||||
export let readonly = false
|
||||
export let kind: ButtonKind = 'link'
|
||||
export let size: ButtonSize = 'large'
|
||||
export let width: string | undefined = undefined
|
||||
|
||||
const me = getCurrentAccount()._id
|
||||
|
||||
$: joined = value.includes(me)
|
||||
|
||||
function join (): void {
|
||||
if (value.includes(me)) return
|
||||
if (onChange === undefined) return
|
||||
onChange([...value, me])
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if !joined && onChange !== undefined}
|
||||
<Button label={view.string.Join} {size} {width} kind={'primary'} on:click={join} />
|
||||
{:else}
|
||||
<AccountArrayEditor {label} {value} {onChange} {readonly} {kind} {size} {width} allowGuests />
|
||||
{/if}
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact, { Employee, Person } from '@hcengineering/contact'
|
||||
import contact, { Contact, Employee, Person } from '@hcengineering/contact'
|
||||
import type { Class, Doc, DocumentQuery, Ref } from '@hcengineering/core'
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import { ObjectCreate, getClient } from '@hcengineering/presentation'
|
||||
@ -27,9 +27,9 @@
|
||||
import UsersPopup from './UsersPopup.svelte'
|
||||
import Members from './icons/Members.svelte'
|
||||
|
||||
export let items: Ref<Employee>[] = []
|
||||
export let _class: Ref<Class<Employee>> = contact.mixin.Employee
|
||||
export let docQuery: DocumentQuery<Employee> | undefined = {}
|
||||
export let items: Ref<Person>[] = []
|
||||
export let _class: Ref<Class<Person>> = contact.mixin.Employee
|
||||
export let docQuery: DocumentQuery<Person> | undefined = {}
|
||||
|
||||
export let label: IntlString | undefined = undefined
|
||||
export let kind: ButtonKind = 'no-border'
|
||||
@ -41,16 +41,18 @@
|
||||
export let readonly: boolean = false
|
||||
export let create: ObjectCreate | undefined = undefined
|
||||
|
||||
function filter (items: Ref<Employee>[]): Ref<Employee>[] {
|
||||
export let sort: ((a: Person, b: Person) => number) | undefined = undefined
|
||||
|
||||
function filter (items: Ref<Person>[]): Ref<Person>[] {
|
||||
return items.filter((it, idx, arr) => arr.indexOf(it) === idx)
|
||||
}
|
||||
|
||||
let persons: Person[] = filter(items)
|
||||
.map((p) => $personByIdStore.get(p))
|
||||
.filter((p) => p !== undefined) as Employee[]
|
||||
.filter((p) => p !== undefined) as Person[]
|
||||
$: persons = filter(items)
|
||||
.map((p) => $personByIdStore.get(p))
|
||||
.filter((p) => p !== undefined) as Employee[]
|
||||
.filter((p) => p !== undefined) as Person[]
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
@ -62,9 +64,7 @@
|
||||
.findAllSync(contact.class.PersonAccount, {})
|
||||
.map((p) => p.person)
|
||||
)
|
||||
showPopup(
|
||||
UsersPopup,
|
||||
{
|
||||
const popupProps: any = {
|
||||
_class,
|
||||
label,
|
||||
docQuery,
|
||||
@ -82,16 +82,16 @@
|
||||
},
|
||||
readonly,
|
||||
create
|
||||
},
|
||||
evt.target as HTMLElement,
|
||||
undefined,
|
||||
(result) => {
|
||||
}
|
||||
if (sort !== undefined) {
|
||||
popupProps.sort = sort
|
||||
}
|
||||
showPopup(UsersPopup, popupProps, evt.target as HTMLElement, undefined, (result) => {
|
||||
if (result != null) {
|
||||
items = filter(result)
|
||||
dispatch('update', items)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact, { Contact, getFirstName, getLastName, Person } from '@hcengineering/contact'
|
||||
import contact, { Contact, getFirstName, getLastName, getName, Person } from '@hcengineering/contact'
|
||||
import type { Class, Doc, DocumentQuery, FindOptions, Ref } from '@hcengineering/core'
|
||||
import type { Asset, IntlString } from '@hcengineering/platform'
|
||||
import presentation, { getClient, ObjectCreate, ObjectPopup } from '@hcengineering/presentation'
|
||||
@ -35,6 +35,14 @@
|
||||
return true
|
||||
}
|
||||
|
||||
const hierarchy = getClient().getHierarchy()
|
||||
|
||||
export let sort: (a: Doc, b: Doc) => number = (a, b) => {
|
||||
const aName = getName(hierarchy, a as Contact)
|
||||
const bName = getName(hierarchy, b as Contact)
|
||||
return aName.localeCompare(bName)
|
||||
}
|
||||
|
||||
export let multiSelect: boolean = false
|
||||
export let allowDeselect: boolean = false
|
||||
export let titleDeselect: IntlString | undefined = undefined
|
||||
@ -46,7 +54,6 @@
|
||||
export let create: ObjectCreate | undefined = undefined
|
||||
export let readonly = false
|
||||
|
||||
const hierarchy = getClient().getHierarchy()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: _create =
|
||||
@ -72,6 +79,7 @@
|
||||
type={'object'}
|
||||
docQuery={readonly ? { ...docQuery, _id: { $in: selectedUsers } } : docQuery}
|
||||
{filter}
|
||||
{sort}
|
||||
groupBy={'_class'}
|
||||
bind:selectedObjects={selectedUsers}
|
||||
bind:ignoreObjects={ignoreUsers}
|
||||
|
@ -107,6 +107,7 @@ import UserDetails from './components/UserDetails.svelte'
|
||||
import EditOrganizationPanel from './components/EditOrganizationPanel.svelte'
|
||||
import ChannelIcon from './components/ChannelIcon.svelte'
|
||||
import CreateGuest from './components/CreateGuest.svelte'
|
||||
import SpaceMembersEditor from './components/SpaceMembersEditor.svelte'
|
||||
|
||||
import contact from './plugin'
|
||||
import {
|
||||
@ -342,7 +343,8 @@ export default async (): Promise<Resources> => ({
|
||||
PersonAccountRefPresenter,
|
||||
PersonIcon,
|
||||
EditOrganizationPanel,
|
||||
ChannelIcon
|
||||
ChannelIcon,
|
||||
SpaceMembersEditor
|
||||
},
|
||||
completion: {
|
||||
EmployeeQuery: async (
|
||||
|
@ -188,7 +188,8 @@ export const contactPlugin = plugin(contactId, {
|
||||
PersonIcon: '' as AnyComponent,
|
||||
EditOrganizationPanel: '' as AnyComponent,
|
||||
CollaborationUserAvatar: '' as AnyComponent,
|
||||
CreateGuest: '' as AnyComponent
|
||||
CreateGuest: '' as AnyComponent,
|
||||
SpaceMembersEditor: '' as AnyComponent
|
||||
},
|
||||
channelProvider: {
|
||||
Email: '' as Ref<ChannelProvider>,
|
||||
|
@ -0,0 +1,33 @@
|
||||
<!--
|
||||
// Copyright © 2024 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 { Ref } from '@hcengineering/core'
|
||||
import hr, { Department } from '@hcengineering/hr'
|
||||
import DepartmentPresenter from './DepartmentPresenter.svelte'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
|
||||
export let value: Ref<Department>
|
||||
|
||||
let department: Department | undefined
|
||||
|
||||
const query = createQuery()
|
||||
query.query(hr.class.Department, { _id: value }, (result) => {
|
||||
department = result[0]
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if department}
|
||||
<DepartmentPresenter value={department} />
|
||||
{/if}
|
@ -28,6 +28,7 @@ import RequestPresenter from './components/RequestPresenter.svelte'
|
||||
import { showPopup } from '@hcengineering/ui'
|
||||
import { type Request } from '@hcengineering/hr'
|
||||
import EditRequestType from './components/EditRequestType.svelte'
|
||||
import DepartmentRefPresenter from './components/DepartmentRefPresenter.svelte'
|
||||
|
||||
async function editRequestType (object: Request): Promise<void> {
|
||||
showPopup(EditRequestType, { object })
|
||||
@ -46,7 +47,8 @@ export default async (): Promise<Resources> => ({
|
||||
TzDateEditor,
|
||||
RequestPresenter,
|
||||
EditRequestType,
|
||||
DepartmentPresenter
|
||||
DepartmentPresenter,
|
||||
DepartmentRefPresenter
|
||||
},
|
||||
actionImpl: {
|
||||
EditRequestType: editRequestType
|
||||
|
@ -20,7 +20,6 @@
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import {
|
||||
ActionContext,
|
||||
AttributeCategory,
|
||||
AttributesBar,
|
||||
KeyedAttribute,
|
||||
createQuery,
|
||||
@ -29,7 +28,7 @@
|
||||
reduceCalls
|
||||
} from '@hcengineering/presentation'
|
||||
import { AnyComponent, Button, Component, IconMixin, IconMoreH } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import view, { AttributeCategory } from '@hcengineering/view'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
|
||||
import { DocNavLink, ParentsNavigator, getDocAttrsInfo, getDocLabel, getDocMixins, showMenu } from '..'
|
||||
|
@ -152,9 +152,9 @@
|
||||
if (hierarchy.isDerived(type._class, core.class.RefTo)) {
|
||||
return '$lookup.' + name
|
||||
}
|
||||
if (hierarchy.isDerived(type._class, core.class.ArrOf)) {
|
||||
return getValue(name, (type as ArrOf<any>).of)
|
||||
}
|
||||
// if (hierarchy.isDerived(type._class, core.class.ArrOf)) {
|
||||
// return getValue(name, (type as ArrOf<any>).of)
|
||||
// }
|
||||
return name
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@
|
||||
|
||||
const client = getClient()
|
||||
const key = { key: filter.key.key }
|
||||
const promise = getPresenter(client, filter.key._class, key, key)
|
||||
const promise = getPresenter(client, filter.key._class, key, key, undefined, false, 'attribute')
|
||||
|
||||
let values = new Set<any>()
|
||||
let selectedValues: Set<any> = new Set<any>(filter.value.map((p) => p[0]))
|
||||
|
@ -36,6 +36,7 @@ import core, {
|
||||
type Lookup,
|
||||
type Mixin,
|
||||
type Obj,
|
||||
type Permission,
|
||||
type Ref,
|
||||
type RefTo,
|
||||
type ReverseLookup,
|
||||
@ -48,22 +49,19 @@ import core, {
|
||||
type TxOperations,
|
||||
type TxUpdateDoc,
|
||||
type TypeAny,
|
||||
type TypedSpace,
|
||||
type Permission
|
||||
type TypedSpace
|
||||
} from '@hcengineering/core'
|
||||
import { type Restrictions } from '@hcengineering/guest'
|
||||
import type { Asset, IntlString } from '@hcengineering/platform'
|
||||
import { getResource, translate } from '@hcengineering/platform'
|
||||
import {
|
||||
type AttributeCategory,
|
||||
AttributeCategoryOrder,
|
||||
createQuery,
|
||||
getAttributePresenterClass,
|
||||
getClient,
|
||||
hasResource,
|
||||
type KeyedAttribute,
|
||||
getFiltredKeys,
|
||||
hasResource,
|
||||
isAdminUser,
|
||||
createQuery
|
||||
type KeyedAttribute
|
||||
} from '@hcengineering/presentation'
|
||||
import { type CollaborationUser } from '@hcengineering/text-editor'
|
||||
import {
|
||||
@ -81,8 +79,10 @@ import {
|
||||
type Location
|
||||
} from '@hcengineering/ui'
|
||||
import view, {
|
||||
type AttributePresenter,
|
||||
AttributeCategoryOrder,
|
||||
type AttributeCategory,
|
||||
type AttributeModel,
|
||||
type AttributePresenter,
|
||||
type BuildModelKey,
|
||||
type BuildModelOptions,
|
||||
type CollectionPresenter,
|
||||
@ -179,37 +179,58 @@ export async function getAttributePresenter (
|
||||
_class: Ref<Class<Obj>>,
|
||||
key: string,
|
||||
preserveKey: BuildModelKey,
|
||||
mixinClass?: Ref<Mixin<CollectionPresenter>>
|
||||
mixinClass?: Ref<Mixin<CollectionPresenter>>,
|
||||
_category?: AttributeCategory
|
||||
): Promise<AttributeModel> {
|
||||
const actualMixinClass = mixinClass ?? view.mixin.AttributePresenter
|
||||
|
||||
const hierarchy = client.getHierarchy()
|
||||
const attribute = hierarchy.getAttribute(_class, key)
|
||||
let { attrClass, category } = getAttributePresenterClass(hierarchy, attribute)
|
||||
if (_category !== undefined) {
|
||||
category = _category
|
||||
}
|
||||
|
||||
const presenterClass = getAttributePresenterClass(hierarchy, attribute)
|
||||
const isCollectionAttr = presenterClass.category === 'collection'
|
||||
let overridedPresenter = await client
|
||||
.getModel()
|
||||
.findOne(view.class.AttrPresenter, { objectClass: _class, attribute: attribute._id, category })
|
||||
if (overridedPresenter === undefined) {
|
||||
overridedPresenter = await client
|
||||
.getModel()
|
||||
.findOne(view.class.AttrPresenter, { attribute: attribute._id, category })
|
||||
}
|
||||
|
||||
const isCollectionAttr = category === 'collection'
|
||||
const mixin = isCollectionAttr ? view.mixin.CollectionPresenter : actualMixinClass
|
||||
|
||||
let presenterMixin: AttributePresenter | CollectionPresenter | undefined = hierarchy.classHierarchyMixin(
|
||||
presenterClass.attrClass,
|
||||
attrClass,
|
||||
mixin
|
||||
)
|
||||
|
||||
if (presenterMixin?.presenter === undefined && mixinClass != null && mixin === mixinClass) {
|
||||
presenterMixin = hierarchy.classHierarchyMixin(presenterClass.attrClass, view.mixin.AttributePresenter)
|
||||
presenterMixin = hierarchy.classHierarchyMixin(attrClass, view.mixin.AttributePresenter)
|
||||
}
|
||||
|
||||
let presenter: AnySvelteComponent
|
||||
let presenter: AnySvelteComponent | undefined
|
||||
|
||||
if (overridedPresenter !== undefined) {
|
||||
presenter = await getResource(overridedPresenter.component)
|
||||
}
|
||||
|
||||
if (presenter === undefined) {
|
||||
const attributePresenter = presenterMixin as AttributePresenter
|
||||
if (presenterClass.category === 'array' && attributePresenter.arrayPresenter !== undefined) {
|
||||
if (category === 'array' && attributePresenter.arrayPresenter !== undefined) {
|
||||
presenter = await getResource(attributePresenter.arrayPresenter)
|
||||
} else if (presenterMixin?.presenter !== undefined) {
|
||||
presenter = await getResource(presenterMixin.presenter)
|
||||
} else if (presenterClass.attrClass === core.class.TypeAny) {
|
||||
} else if (attrClass === core.class.TypeAny) {
|
||||
const typeAny = attribute.type as TypeAny
|
||||
presenter = await getResource(typeAny.presenter)
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
if (presenter === undefined) {
|
||||
throw new Error('attribute presenter not found for ' + JSON.stringify(preserveKey))
|
||||
}
|
||||
|
||||
@ -223,7 +244,7 @@ export async function getAttributePresenter (
|
||||
return {
|
||||
key: preserveKey.key,
|
||||
sortingKey,
|
||||
_class: presenterClass.attrClass,
|
||||
_class: attrClass,
|
||||
label: preserveKey.label ?? attribute.shortLabel ?? attribute.label,
|
||||
presenter,
|
||||
props: preserveKey.props,
|
||||
@ -265,7 +286,8 @@ export async function getPresenter<T extends Doc> (
|
||||
key: BuildModelKey,
|
||||
preserveKey: BuildModelKey,
|
||||
lookup?: Lookup<T>,
|
||||
isCollectionAttr: boolean = false
|
||||
isCollectionAttr: boolean = false,
|
||||
_category?: AttributeCategory
|
||||
): Promise<AttributeModel> {
|
||||
if (key.presenter !== undefined) {
|
||||
const { presenter, label, sortingKey } = key
|
||||
@ -294,7 +316,7 @@ export async function getPresenter<T extends Doc> (
|
||||
}
|
||||
return await getLookupPresenter(client, _class, key, preserveKey, lookup)
|
||||
}
|
||||
return await getAttributePresenter(client, _class, key.key, preserveKey)
|
||||
return await getAttributePresenter(client, _class, key.key, preserveKey, undefined, _category)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,6 +52,7 @@ import {
|
||||
ObjectTitle,
|
||||
ObjectTooltip,
|
||||
ObjectValidator,
|
||||
AttrPresenter,
|
||||
PreviewPresenter,
|
||||
SpaceHeader,
|
||||
SpaceName,
|
||||
@ -116,7 +117,8 @@ const view = plugin(viewId, {
|
||||
ActionCategory: '' as Ref<Class<ActionCategory>>,
|
||||
LinkPresenter: '' as Ref<Class<LinkPresenter>>,
|
||||
FilterMode: '' as Ref<Class<FilterMode>>,
|
||||
FilteredView: '' as Ref<Class<FilteredView>>
|
||||
FilteredView: '' as Ref<Class<FilteredView>>,
|
||||
AttrPresenter: '' as Ref<Class<AttrPresenter>>
|
||||
},
|
||||
action: {
|
||||
Delete: '' as Ref<Action>,
|
||||
|
@ -780,4 +780,21 @@ export interface IconProps {
|
||||
color?: number
|
||||
}
|
||||
|
||||
export type AttributeCategory = 'attribute' | 'inplace' | 'collection' | 'array' | 'object'
|
||||
|
||||
export const AttributeCategoryOrder: Record<AttributeCategory, number> = {
|
||||
attribute: 0,
|
||||
inplace: 1,
|
||||
collection: 2,
|
||||
array: 3,
|
||||
object: 4
|
||||
}
|
||||
|
||||
export type ObjectPresenterType = 'link' | 'text'
|
||||
|
||||
export interface AttrPresenter extends Doc {
|
||||
attribute: Ref<AnyAttribute>
|
||||
category: AttributeCategory
|
||||
objectClass: Ref<Class<Doc>>
|
||||
component: AnyComponent
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user