mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-24 17:30:03 +00:00
Custom attributes (#1650)
Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
parent
342acb5bb8
commit
0be40e005e
@ -13,16 +13,11 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Class, Ref, Type } from '@anticrm/core'
|
||||
import core, { coreId } from '@anticrm/core'
|
||||
import { IntlString, mergeIds } from '@anticrm/platform'
|
||||
|
||||
export default mergeIds(coreId, core, {
|
||||
class: {
|
||||
Type: '' as Ref<Class<Type<any>>>
|
||||
},
|
||||
string: {
|
||||
Name: '' as IntlString,
|
||||
Description: '' as IntlString,
|
||||
Private: '' as IntlString,
|
||||
Archived: '' as IntlString,
|
||||
|
@ -36,7 +36,7 @@ import {
|
||||
Type,
|
||||
Version
|
||||
} from '@anticrm/core'
|
||||
import { Index, Model, Prop, TypeIntlString, TypeRef, TypeString, TypeTimestamp } from '@anticrm/model'
|
||||
import { Index, Model, Prop, TypeIntlString, TypeRef, TypeString, TypeTimestamp, UX } from '@anticrm/model'
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import core from './component'
|
||||
|
||||
@ -106,46 +106,57 @@ export class TAttribute extends TDoc implements AnyAttribute {
|
||||
name!: string
|
||||
type!: Type<any>
|
||||
label!: IntlString
|
||||
isCustom?: boolean
|
||||
}
|
||||
|
||||
@Model(core.class.Type, core.class.Obj)
|
||||
@Model(core.class.Type, core.class.Obj, DOMAIN_MODEL)
|
||||
export class TType extends TObj implements Type<any> {
|
||||
label!: IntlString
|
||||
}
|
||||
|
||||
@UX(core.string.String)
|
||||
@Model(core.class.TypeString, core.class.Type)
|
||||
export class TTypeString extends TType {}
|
||||
|
||||
@UX(core.string.IntlString)
|
||||
@Model(core.class.TypeIntlString, core.class.Type)
|
||||
export class TTypeIntlString extends TType {}
|
||||
|
||||
@UX(core.string.Number)
|
||||
@Model(core.class.TypeNumber, core.class.Type)
|
||||
export class TTypeNumber extends TType {}
|
||||
|
||||
@UX(core.string.Markup)
|
||||
@Model(core.class.TypeMarkup, core.class.Type)
|
||||
export class TTypeMarkup extends TType {}
|
||||
|
||||
@UX(core.string.Ref)
|
||||
@Model(core.class.RefTo, core.class.Type)
|
||||
export class TRefTo extends TType implements RefTo<Doc> {
|
||||
to!: Ref<Class<Doc>>
|
||||
}
|
||||
|
||||
@UX(core.string.Collection)
|
||||
@Model(core.class.Collection, core.class.Type)
|
||||
export class TCollection extends TType implements Collection<AttachedDoc> {
|
||||
of!: Ref<Class<Doc>>
|
||||
}
|
||||
|
||||
@UX(core.string.Array)
|
||||
@Model(core.class.ArrOf, core.class.Type)
|
||||
export class TArrOf extends TType implements ArrOf<Doc> {
|
||||
of!: Type<Doc>
|
||||
}
|
||||
|
||||
@UX(core.string.Boolean)
|
||||
@Model(core.class.TypeBoolean, core.class.Type)
|
||||
export class TTypeBoolean extends TType {}
|
||||
|
||||
@UX(core.string.Timestamp)
|
||||
@Model(core.class.TypeTimestamp, core.class.Type)
|
||||
export class TTypeTimestamp extends TType {}
|
||||
|
||||
@UX(core.string.Date)
|
||||
@Model(core.class.TypeDate, core.class.Type)
|
||||
export class TTypeDate extends TType {}
|
||||
|
||||
|
@ -236,6 +236,22 @@ export function createModel (builder: Builder): void {
|
||||
classPresenter(builder, core.class.TypeDate, view.component.DatePresenter, view.component.DateEditor)
|
||||
classPresenter(builder, core.class.Space, view.component.ObjectPresenter)
|
||||
|
||||
builder.mixin(core.class.TypeString, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: view.component.StringTypeEditor
|
||||
})
|
||||
|
||||
builder.mixin(core.class.TypeBoolean, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: view.component.BooleanTypeEditor
|
||||
})
|
||||
|
||||
builder.mixin(core.class.TypeDate, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: view.component.DateTypeEditor
|
||||
})
|
||||
|
||||
builder.mixin(core.class.TypeNumber, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: view.component.NumberTypeEditor
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
view.class.ActionCategory,
|
||||
core.space.Model,
|
||||
|
@ -73,7 +73,11 @@ export default mergeIds(viewId, view, {
|
||||
TableView: '' as AnyComponent,
|
||||
RolePresenter: '' as AnyComponent,
|
||||
YoutubePresenter: '' as AnyComponent,
|
||||
GithubPresenter: '' as AnyComponent
|
||||
GithubPresenter: '' as AnyComponent,
|
||||
StringTypeEditor: '' as AnyComponent,
|
||||
BooleanTypeEditor: '' as AnyComponent,
|
||||
NumberTypeEditor: '' as AnyComponent,
|
||||
DateTypeEditor: '' as AnyComponent
|
||||
},
|
||||
string: {
|
||||
Table: '' as IntlString,
|
||||
|
@ -11,6 +11,17 @@
|
||||
"Description": "Description",
|
||||
"Private": "Private",
|
||||
"Archived": "Archived",
|
||||
"ClassLabel": "Label"
|
||||
"ClassLabel": "Label",
|
||||
"String": "String",
|
||||
"Markup": "Markup",
|
||||
"Number": "Number",
|
||||
"Boolean": "Boolean",
|
||||
"Timestamp": "Timestamp",
|
||||
"Date": "Date",
|
||||
"IntlString": "IntlString",
|
||||
"Ref": "Ref",
|
||||
"Collection": "Collection",
|
||||
"Array": "Array",
|
||||
"Bag": "Bag"
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,17 @@
|
||||
"Description": "Описание",
|
||||
"Private": "Личный",
|
||||
"Archived": "Архивный",
|
||||
"ClassLabel": "Тип"
|
||||
"ClassLabel": "Тип",
|
||||
"String": "Строка",
|
||||
"Markup": "Разметка",
|
||||
"Number": "Число",
|
||||
"Boolean": "Логическое",
|
||||
"Timestamp": "Времянная отметка",
|
||||
"Date": "Дата",
|
||||
"IntlString": "Интернационализированная строка",
|
||||
"Ref": "Ссылка",
|
||||
"Collection": "Коллекция",
|
||||
"Array": "Массив",
|
||||
"Bag": "Bag"
|
||||
}
|
||||
}
|
@ -98,6 +98,7 @@ export interface Attribute<T extends PropertyType> extends Doc, UXObject {
|
||||
name: string
|
||||
type: Type<T>
|
||||
index?: IndexKind
|
||||
isCustom?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -71,6 +71,7 @@ export default plugin(coreId, {
|
||||
TxPutBag: '' as Ref<Class<TxPutBag<PropertyType>>>,
|
||||
Space: '' as Ref<Class<Space>>,
|
||||
Account: '' as Ref<Class<Account>>,
|
||||
Type: '' as Ref<Class<Type<any>>>,
|
||||
TypeString: '' as Ref<Class<Type<string>>>,
|
||||
TypeIntlString: '' as Ref<Class<Type<IntlString>>>,
|
||||
TypeNumber: '' as Ref<Class<Type<string>>>,
|
||||
@ -109,6 +110,18 @@ export default plugin(coreId, {
|
||||
ModifiedBy: '' as IntlString,
|
||||
Class: '' as IntlString,
|
||||
AttachedTo: '' as IntlString,
|
||||
AttachedToClass: '' as IntlString
|
||||
AttachedToClass: '' as IntlString,
|
||||
String: '' as IntlString,
|
||||
Markup: '' as IntlString,
|
||||
Number: '' as IntlString,
|
||||
Boolean: '' as IntlString,
|
||||
Timestamp: '' as IntlString,
|
||||
Date: '' as IntlString,
|
||||
IntlString: '' as IntlString,
|
||||
Ref: '' as IntlString,
|
||||
Collection: '' as IntlString,
|
||||
Array: '' as IntlString,
|
||||
Bag: '' as IntlString,
|
||||
Name: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -339,75 +339,75 @@ export class Builder {
|
||||
* @public
|
||||
*/
|
||||
export function TypeString (): Type<string> {
|
||||
return { _class: core.class.TypeString, label: 'TypeString' as IntlString }
|
||||
return { _class: core.class.TypeString, label: core.string.String }
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function TypeNumber (): Type<number> {
|
||||
return { _class: core.class.TypeNumber, label: 'TypeNumber' as IntlString }
|
||||
return { _class: core.class.TypeNumber, label: core.string.Number }
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function TypeMarkup (): Type<Markup> {
|
||||
return { _class: core.class.TypeMarkup, label: 'TypeMarkup' as IntlString }
|
||||
return { _class: core.class.TypeMarkup, label: core.string.Markup }
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function TypeIntlString (): Type<IntlString> {
|
||||
return { _class: core.class.TypeIntlString, label: 'TypeIntlString' as IntlString }
|
||||
return { _class: core.class.TypeIntlString, label: core.string.IntlString }
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function TypeBoolean (): Type<boolean> {
|
||||
return { _class: core.class.TypeBoolean, label: 'TypeBoolean' as IntlString }
|
||||
return { _class: core.class.TypeBoolean, label: core.string.Boolean }
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function TypeTimestamp (): Type<Timestamp> {
|
||||
return { _class: core.class.TypeTimestamp, label: 'TypeTimestamp' as IntlString }
|
||||
return { _class: core.class.TypeTimestamp, label: core.string.Timestamp }
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function TypeDate (withTime?: boolean): TypeDateType {
|
||||
return { _class: core.class.TypeDate, label: 'TypeDate' as IntlString, withTime }
|
||||
return { _class: core.class.TypeDate, label: core.string.Date, withTime }
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function TypeRef (_class: Ref<Class<Doc>>): RefTo<Doc> {
|
||||
return { _class: core.class.RefTo, to: _class, label: 'TypeRef' as IntlString }
|
||||
return { _class: core.class.RefTo, label: core.string.Ref, to: _class }
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function Collection<T extends AttachedDoc> (clazz: Ref<Class<T>>, itemLabel?: IntlString): TypeCollection<T> {
|
||||
return { _class: core.class.Collection, of: clazz, label: 'Collection' as IntlString, itemLabel }
|
||||
return { _class: core.class.Collection, label: core.string.Collection, of: clazz, itemLabel }
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function ArrOf<T extends PropertyType | Ref<Doc>> (type: Type<T>): TypeArrOf<T> {
|
||||
return { _class: core.class.ArrOf, of: type, label: 'Array' as IntlString }
|
||||
return { _class: core.class.ArrOf, label: core.string.Array, of: type }
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function Bag (): Type<Record<string, PropertyType>> {
|
||||
return { _class: core.class.Bag, label: 'Bag' as IntlString }
|
||||
return { _class: core.class.Bag, label: core.string.Bag }
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
import type { Plugin, IntlString } from './platform'
|
||||
import { Status, Severity, unknownError } from './status'
|
||||
import { _parseId } from './ident'
|
||||
import { _IdInfo, _parseId } from './ident'
|
||||
import { setPlatformStatus } from './event'
|
||||
import { IntlMessageFormat } from 'intl-messageformat'
|
||||
|
||||
@ -73,9 +73,8 @@ async function loadTranslationsForComponent (plugin: Plugin, locale: string): Pr
|
||||
}
|
||||
}
|
||||
|
||||
async function getTranslation (message: IntlString, locale: string): Promise<IntlString | Status> {
|
||||
async function getTranslation (id: _IdInfo, locale: string): Promise<IntlString | Status | undefined> {
|
||||
try {
|
||||
const id = _parseId(message)
|
||||
let messages = translations.get(id.component)
|
||||
if (messages === undefined) {
|
||||
messages = await loadTranslationsForComponent(id.component, locale)
|
||||
@ -84,11 +83,9 @@ async function getTranslation (message: IntlString, locale: string): Promise<Int
|
||||
if (messages instanceof Status) {
|
||||
return messages
|
||||
}
|
||||
return (
|
||||
(id.kind !== undefined
|
||||
? (messages[id.kind] as Record<string, IntlString>)?.[id.name]
|
||||
: (messages[id.name] as IntlString)) ?? message
|
||||
)
|
||||
return id.kind !== undefined
|
||||
? (messages[id.kind] as Record<string, IntlString>)?.[id.name]
|
||||
: (messages[id.name] as IntlString)
|
||||
} catch (err) {
|
||||
const status = unknownError(err)
|
||||
await setPlatformStatus(status)
|
||||
@ -111,13 +108,24 @@ export async function translate<P extends Record<string, any>> (message: IntlStr
|
||||
}
|
||||
return compiled.format(params)
|
||||
} else {
|
||||
const translation = await getTranslation(message, locale)
|
||||
if (translation instanceof Status) {
|
||||
cache.set(message, translation)
|
||||
try {
|
||||
const id = _parseId(message)
|
||||
if (id.component === 'embedded') {
|
||||
return id.name
|
||||
}
|
||||
const translation = (await getTranslation(id, locale)) ?? message
|
||||
if (translation instanceof Status) {
|
||||
cache.set(message, translation)
|
||||
return message
|
||||
}
|
||||
const compiled = new IntlMessageFormat(translation, locale)
|
||||
cache.set(message, compiled)
|
||||
return compiled.format(params)
|
||||
} catch (err) {
|
||||
const status = unknownError(err)
|
||||
await setPlatformStatus(status)
|
||||
cache.set(message, status)
|
||||
return message
|
||||
}
|
||||
const compiled = new IntlMessageFormat(translation, locale)
|
||||
cache.set(message, compiled)
|
||||
return compiled.format(params)
|
||||
}
|
||||
}
|
||||
|
@ -71,6 +71,11 @@ export type Namespace = Record<string, Record<string, string>>
|
||||
*/
|
||||
export const _ID_SEPARATOR = ':'
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const _EmbeddedId = 'embedded'
|
||||
|
||||
function identify (result: Record<string, any>, prefix: string, namespace: Record<string, any>): Namespace {
|
||||
for (const key in namespace) {
|
||||
const value = namespace[key]
|
||||
@ -83,6 +88,13 @@ function identify (result: Record<string, any>, prefix: string, namespace: Recor
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function getEmbeddedLabel (str: string): IntlString {
|
||||
return (_EmbeddedId + _ID_SEPARATOR + _EmbeddedId + _ID_SEPARATOR + str) as IntlString
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines plugin Ids.
|
||||
*
|
||||
|
@ -35,10 +35,10 @@
|
||||
let container: HTMLElement
|
||||
let opened: boolean = false
|
||||
|
||||
let selectedItem = items.find((x) => x.id === selected)
|
||||
$: selectedItem = items.find((x) => x.id === selected)
|
||||
$: if (selected === undefined && items[0] !== undefined) {
|
||||
selected = items[0].id
|
||||
dispatch('selected', selected)
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
@ -37,14 +37,17 @@ async function createPseudoViewlet (
|
||||
// Check if it is attached doc and collection have title override.
|
||||
const presenter = await getObjectPresenter(client, doc._class, { key: 'doc-presenter' })
|
||||
if (presenter !== undefined) {
|
||||
let collection = ''
|
||||
if (dtx.collectionAttribute?.label !== undefined) {
|
||||
collection = await translate(dtx.collectionAttribute.label, {})
|
||||
}
|
||||
return {
|
||||
display,
|
||||
icon: docClass.icon ?? activity.icon.Activity,
|
||||
label: label,
|
||||
labelParams: {
|
||||
_class: trLabel,
|
||||
collection:
|
||||
dtx.collectionAttribute?.label !== undefined ? await translate(dtx.collectionAttribute?.label, {}) : ''
|
||||
collection
|
||||
},
|
||||
component: presenter.presenter,
|
||||
pseudo: true
|
||||
|
@ -32,6 +32,10 @@
|
||||
"General": "General",
|
||||
"Navigation": "Navigation",
|
||||
"Editor": "Editor",
|
||||
"MarkdownFormatting": "Formatting"
|
||||
"MarkdownFormatting": "Formatting",
|
||||
"Type": "Type",
|
||||
"WithTime": "WithTime",
|
||||
"CreatingAttribute": "Creating an attribute",
|
||||
"CreatingAttributeConfirm": "Do you want to create an attribute? It will not be possible to change or delete it."
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,10 @@
|
||||
"General": "Общее",
|
||||
"Navigation": "Навигация",
|
||||
"Editor": "Редактор",
|
||||
"MarkdownFormatting": "Форматирование"
|
||||
"MarkdownFormatting": "Форматирование",
|
||||
"Type": "Тип",
|
||||
"WithTime": "Со временем",
|
||||
"CreatingAttribute": "Создание атрибута",
|
||||
"CreatingAttributeConfirm": "Вы хотите создать атрибут? Изменить или удалить его будет невозможно"
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@
|
||||
"svelte": "^3.47",
|
||||
"@anticrm/platform": "~0.6.6",
|
||||
"@anticrm/contact": "~0.6.5",
|
||||
"@anticrm/model": "~0.6.0",
|
||||
"@anticrm/panel": "~0.6.0",
|
||||
"@anticrm/core": "~0.6.16",
|
||||
"@anticrm/view": "~0.6.0",
|
||||
|
@ -0,0 +1,61 @@
|
||||
<!--
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Class, Doc, Ref } from '@anticrm/core'
|
||||
import presentation, { AttributesBar, getClient, KeyedAttribute } from '@anticrm/presentation'
|
||||
import { ActionIcon, IconAdd, Label, showPopup } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { collectionsFilter, getFiltredKeys } from '../utils'
|
||||
|
||||
export let object: Doc
|
||||
export let objectClass: Class<Doc>
|
||||
export let to: Ref<Class<Doc>> | undefined
|
||||
export let ignoreKeys: string[] = []
|
||||
export let vertical: boolean
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
let keys: KeyedAttribute[] = []
|
||||
|
||||
function updateKeys (ignoreKeys: string[]): void {
|
||||
const filtredKeys = getFiltredKeys(hierarchy, objectClass._id, ignoreKeys, to)
|
||||
keys = collectionsFilter(hierarchy, filtredKeys, false)
|
||||
}
|
||||
|
||||
$: updateKeys(ignoreKeys)
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
{#if vertical}
|
||||
<div class="flex-between text-sm mb-4">
|
||||
<Label label={objectClass.label} />
|
||||
<ActionIcon
|
||||
label={presentation.string.Create}
|
||||
icon={IconAdd}
|
||||
size="small"
|
||||
action={() => {
|
||||
showPopup(view.component.CreateAttribute, { _class: objectClass._id }, undefined, () => {
|
||||
updateKeys(ignoreKeys)
|
||||
dispatch('update')
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if keys.length || !vertical}
|
||||
<AttributesBar {object} {keys} {vertical} />
|
||||
{/if}
|
118
plugins/view-resources/src/components/CreateAttribute.svelte
Normal file
118
plugins/view-resources/src/components/CreateAttribute.svelte
Normal file
@ -0,0 +1,118 @@
|
||||
<!--
|
||||
// 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 core, { Class, generateId, PropertyType, Ref, Space, Type } from '@anticrm/core'
|
||||
import presentation, { getClient, MessageBox } from '@anticrm/presentation'
|
||||
import { AnyComponent, EditBox, DropdownLabelsIntl, Label, Component, Button, showPopup } from '@anticrm/ui'
|
||||
import { DropdownIntlItem } from '@anticrm/ui/src/types'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import view from '../plugin'
|
||||
import { getEmbeddedLabel } from '@anticrm/platform'
|
||||
|
||||
export let _class: Ref<Class<Space>>
|
||||
let name: string
|
||||
let type: Type<PropertyType> | undefined
|
||||
let is: AnyComponent | undefined
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function save (): Promise<void> {
|
||||
if (type === undefined) return
|
||||
showPopup(
|
||||
MessageBox,
|
||||
{
|
||||
label: view.string.CreatingAttribute,
|
||||
message: view.string.CreatingAttributeConfirm
|
||||
},
|
||||
undefined,
|
||||
async (result) => {
|
||||
if (result && type !== undefined) {
|
||||
await client.createDoc(core.class.Attribute, core.space.Model, {
|
||||
attributeOf: _class,
|
||||
name: name + generateId(),
|
||||
label: getEmbeddedLabel(name),
|
||||
isCustom: true,
|
||||
type
|
||||
})
|
||||
dispatch('close')
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function getTypes (): DropdownIntlItem[] {
|
||||
const descendants = hierarchy.getDescendants(core.class.Type)
|
||||
const res: DropdownIntlItem[] = []
|
||||
for (const descendant of descendants) {
|
||||
const _class = hierarchy.getClass(descendant)
|
||||
if (_class.label !== undefined && hierarchy.hasMixin(_class, view.mixin.ObjectEditor)) {
|
||||
res.push({
|
||||
label: _class.label,
|
||||
id: _class._id
|
||||
})
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
const items = getTypes()
|
||||
let selectedType: Ref<Class<Type<PropertyType>>>
|
||||
|
||||
$: selectedType && selectType(selectedType)
|
||||
|
||||
function selectType (type: Ref<Class<Type<PropertyType>>>): void {
|
||||
const _class = hierarchy.getClass(type)
|
||||
const editor = hierarchy.as(_class, view.mixin.ObjectEditor)
|
||||
if (editor.editor !== undefined) {
|
||||
is = editor.editor
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="antiPopup w-60 p-4 flex-col">
|
||||
<div class="mb-2"><EditBox bind:value={name} placeholder={core.string.Name} maxWidth="13rem" /></div>
|
||||
<div class="flex-between mb-2">
|
||||
<Label label={view.string.Type} />
|
||||
<DropdownLabelsIntl
|
||||
label={view.string.Type}
|
||||
{items}
|
||||
width="8rem"
|
||||
bind:selected={selectedType}
|
||||
on:selected={(e) => selectType(e.detail)}
|
||||
/>
|
||||
</div>
|
||||
{#if is}
|
||||
<Component
|
||||
{is}
|
||||
on:change={(e) => {
|
||||
type = e.detail
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
<div class="mt-2">
|
||||
<Button
|
||||
width="100%"
|
||||
disabled={type === undefined || name === undefined || name.trim().length === 0}
|
||||
label={presentation.string.Create}
|
||||
on:click={() => {
|
||||
save()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
33
plugins/view-resources/src/components/DocAttributeBar.svelte
Normal file
33
plugins/view-resources/src/components/DocAttributeBar.svelte
Normal file
@ -0,0 +1,33 @@
|
||||
<!--
|
||||
// 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 { Doc, Mixin } from '@anticrm/core'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import ClassAttributeBar from './ClassAttributeBar.svelte'
|
||||
|
||||
export let object: Doc
|
||||
export let mixins: Mixin<Doc>[]
|
||||
export let ignoreKeys: string[]
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
$: objectClass = hierarchy.getClass(object._class)
|
||||
</script>
|
||||
|
||||
<ClassAttributeBar {objectClass} {object} {ignoreKeys} to={undefined} vertical on:update />
|
||||
{#each mixins as mixin}
|
||||
<div class="bottom-divider mt-4 mb-2" />
|
||||
<ClassAttributeBar objectClass={mixin} {object} {ignoreKeys} to={objectClass._id} vertical on:update />
|
||||
{/each}
|
@ -15,7 +15,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact, { formatName } from '@anticrm/contact'
|
||||
import core, { Class, ClassifierKind, Doc, Mixin, Obj, Ref } from '@anticrm/core'
|
||||
import { Class, ClassifierKind, Doc, Mixin, Obj, Ref } from '@anticrm/core'
|
||||
import notification from '@anticrm/notification'
|
||||
import { Panel } from '@anticrm/panel'
|
||||
import { Asset, getResource, translate } from '@anticrm/platform'
|
||||
@ -29,8 +29,9 @@
|
||||
import { AnyComponent, Component } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import { getCollectionCounter } from '../utils'
|
||||
import { collectionsFilter, getCollectionCounter, getFiltredKeys } from '../utils'
|
||||
import ActionContext from './ActionContext.svelte'
|
||||
import DocAttributeBar from './DocAttributeBar.svelte'
|
||||
import UpDownNavigator from './UpDownNavigator.svelte'
|
||||
|
||||
export let _id: Ref<Doc>
|
||||
@ -45,7 +46,6 @@
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const notificationClient = getResource(notification.function.GetNotificationClient).then((res) => res())
|
||||
const docKeys: Set<string> = new Set<string>(hierarchy.getAllAttributes(core.class.AttachedDoc).keys())
|
||||
|
||||
$: read(_id)
|
||||
function read (_id: Ref<Doc>) {
|
||||
@ -90,35 +90,21 @@
|
||||
)
|
||||
}
|
||||
|
||||
function filterKeys (keys: KeyedAttribute[], ignoreKeys: string[]): KeyedAttribute[] {
|
||||
keys = keys.filter((k) => !docKeys.has(k.key))
|
||||
keys = keys.filter((k) => !ignoreKeys.includes(k.key))
|
||||
return keys
|
||||
}
|
||||
|
||||
function getFiltredKeys (objectClass: Ref<Class<Doc>>, ignoreKeys: string[], to?: Ref<Class<Doc>>): KeyedAttribute[] {
|
||||
const keys = [...hierarchy.getAllAttributes(objectClass, to).entries()]
|
||||
.filter(([, value]) => value.hidden !== true)
|
||||
.map(([key, attr]) => ({ key, attr }))
|
||||
|
||||
return filterKeys(keys, ignoreKeys)
|
||||
}
|
||||
|
||||
let ignoreKeys: string[] = []
|
||||
let ignoreMixins: Set<Ref<Mixin<Doc>>> = new Set<Ref<Mixin<Doc>>>()
|
||||
|
||||
async function updateKeys (): Promise<void> {
|
||||
const keysMap = new Map(getFiltredKeys(object._class, ignoreKeys).map((p) => [p.attr._id, p]))
|
||||
const keysMap = new Map(getFiltredKeys(hierarchy, object._class, ignoreKeys).map((p) => [p.attr._id, p]))
|
||||
for (const m of mixins) {
|
||||
const mkeys = getFiltredKeys(m._id, ignoreKeys)
|
||||
const mkeys = getFiltredKeys(hierarchy, m._id, ignoreKeys)
|
||||
for (const key of mkeys) {
|
||||
keysMap.set(key.attr._id, key)
|
||||
}
|
||||
}
|
||||
const filtredKeys = Array.from(keysMap.values())
|
||||
keys = collectionsFilter(filtredKeys, false)
|
||||
keys = collectionsFilter(hierarchy, filtredKeys, false)
|
||||
|
||||
const collectionKeys = collectionsFilter(filtredKeys, true)
|
||||
const collectionKeys = collectionsFilter(hierarchy, filtredKeys, true)
|
||||
const editors: { key: KeyedAttribute; editor: AnyComponent }[] = []
|
||||
for (const k of collectionKeys) {
|
||||
const editor = await getCollectionEditor(k)
|
||||
@ -127,18 +113,6 @@
|
||||
collectionEditors = editors
|
||||
}
|
||||
|
||||
function collectionsFilter (keys: KeyedAttribute[], get: boolean): KeyedAttribute[] {
|
||||
const result: KeyedAttribute[] = []
|
||||
for (const key of keys) {
|
||||
if (isCollectionAttr(key) === get) result.push(key)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function isCollectionAttr (key: KeyedAttribute): boolean {
|
||||
return hierarchy.isDerived(key.attr.type._class, core.class.Collection)
|
||||
}
|
||||
|
||||
async function getEditor (_class: Ref<Class<Doc>>): Promise<AnyComponent> {
|
||||
const clazz = hierarchy.getClass(_class)
|
||||
const editorMixin = hierarchy.as(clazz, view.mixin.ObjectEditor)
|
||||
@ -269,8 +243,10 @@
|
||||
{#if !headerLoading}
|
||||
{#if headerEditor !== undefined}
|
||||
<Component is={headerEditor} props={{ object, keys, vertical: dir === 'column' }} />
|
||||
{:else if dir === 'column'}
|
||||
<DocAttributeBar {object} {mixins} {ignoreKeys} on:update={updateKeys} />
|
||||
{:else}
|
||||
<AttributesBar {object} {keys} vertical={dir === 'column'} />
|
||||
<AttributesBar {object} {keys} />
|
||||
{/if}
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
@ -0,0 +1,25 @@
|
||||
<!--
|
||||
// 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 { TypeBoolean } from '@anticrm/model'
|
||||
import { onMount } from 'svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
onMount(() => {
|
||||
dispatch('change', TypeBoolean())
|
||||
})
|
||||
</script>
|
@ -0,0 +1,40 @@
|
||||
<!--
|
||||
// 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 { TypeDate } from '@anticrm/model'
|
||||
import { Label } from '@anticrm/ui'
|
||||
import { onMount } from 'svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import view from '../../plugin'
|
||||
import BooleanEditor from '../BooleanEditor.svelte'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let withTime: boolean = false
|
||||
|
||||
onMount(() => {
|
||||
dispatch('change', TypeDate(withTime))
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="flex-between">
|
||||
<Label label={view.string.WithTime} />
|
||||
<BooleanEditor
|
||||
bind:value={withTime}
|
||||
onChange={(e) => {
|
||||
dispatch('change', TypeDate(e))
|
||||
}}
|
||||
/>
|
||||
</div>
|
@ -0,0 +1,25 @@
|
||||
<!--
|
||||
// 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 { TypeNumber } from '@anticrm/model'
|
||||
import { onMount } from 'svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
onMount(() => {
|
||||
dispatch('change', TypeNumber())
|
||||
})
|
||||
</script>
|
@ -0,0 +1,25 @@
|
||||
<!--
|
||||
// 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 { TypeString } from '@anticrm/model'
|
||||
import { onMount } from 'svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
onMount(() => {
|
||||
dispatch('change', TypeString())
|
||||
})
|
||||
</script>
|
@ -38,6 +38,11 @@ import UpDownNavigator from './components/UpDownNavigator.svelte'
|
||||
import GithubPresenter from './components/linkPresenters/GithubPresenter.svelte'
|
||||
import YoutubePresenter from './components/linkPresenters/YoutubePresenter.svelte'
|
||||
import ActionsPopup from './components/ActionsPopup.svelte'
|
||||
import CreateAttribute from './components/CreateAttribute.svelte'
|
||||
import StringTypeEditor from './components/typeEditors/StringTypeEditor.svelte'
|
||||
import BooleanTypeEditor from './components/typeEditors/BooleanTypeEditor.svelte'
|
||||
import DateTypeEditor from './components/typeEditors/DateTypeEditor.svelte'
|
||||
import NumberTypeEditor from './components/typeEditors/NumberTypeEditor.svelte'
|
||||
|
||||
export { getActions } from './actions'
|
||||
export { default as ActionContext } from './components/ActionContext.svelte'
|
||||
@ -53,6 +58,11 @@ export { Table, TableView, EditDoc, ColorsPopup, Menu, SpacePresenter, UpDownNav
|
||||
export default async (): Promise<Resources> => ({
|
||||
actionImpl: actionImpl,
|
||||
component: {
|
||||
CreateAttribute,
|
||||
StringTypeEditor,
|
||||
BooleanTypeEditor,
|
||||
NumberTypeEditor,
|
||||
DateTypeEditor,
|
||||
SpacePresenter,
|
||||
StringEditor,
|
||||
StringPresenter,
|
||||
|
@ -35,6 +35,10 @@ export default mergeIds(viewId, view, {
|
||||
DeleteObjectConfirm: '' as IntlString,
|
||||
Assignees: '' as IntlString,
|
||||
Labels: '' as IntlString,
|
||||
ActionPlaceholder: '' as IntlString
|
||||
WithTime: '' as IntlString,
|
||||
Type: '' as IntlString,
|
||||
ActionPlaceholder: '' as IntlString,
|
||||
CreatingAttribute: '' as IntlString,
|
||||
CreatingAttributeConfirm: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -283,3 +283,35 @@ export function getCollectionCounter (hierarchy: Hierarchy, object: Doc, key: Ke
|
||||
}
|
||||
return (object as any)[key.key] ?? 0
|
||||
}
|
||||
|
||||
function filterKeys (hierarchy: Hierarchy, keys: KeyedAttribute[], ignoreKeys: string[]): KeyedAttribute[] {
|
||||
const docKeys: Set<string> = new Set<string>(hierarchy.getAllAttributes(core.class.AttachedDoc).keys())
|
||||
keys = keys.filter((k) => !docKeys.has(k.key))
|
||||
keys = keys.filter((k) => !ignoreKeys.includes(k.key))
|
||||
return keys
|
||||
}
|
||||
|
||||
export function getFiltredKeys (
|
||||
hierarchy: Hierarchy,
|
||||
objectClass: Ref<Class<Doc>>,
|
||||
ignoreKeys: string[],
|
||||
to?: Ref<Class<Doc>>
|
||||
): KeyedAttribute[] {
|
||||
const keys = [...hierarchy.getAllAttributes(objectClass, to).entries()]
|
||||
.filter(([, value]) => value.hidden !== true)
|
||||
.map(([key, attr]) => ({ key, attr }))
|
||||
|
||||
return filterKeys(hierarchy, keys, ignoreKeys)
|
||||
}
|
||||
|
||||
export function collectionsFilter (hierarchy: Hierarchy, keys: KeyedAttribute[], get: boolean): KeyedAttribute[] {
|
||||
const result: KeyedAttribute[] = []
|
||||
for (const key of keys) {
|
||||
if (isCollectionAttr(hierarchy, key) === get) result.push(key)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function isCollectionAttr (hierarchy: Hierarchy, key: KeyedAttribute): boolean {
|
||||
return hierarchy.isDerived(key.attr.type._class, core.class.Collection)
|
||||
}
|
||||
|
@ -311,6 +311,7 @@ const view = plugin(viewId, {
|
||||
component: {
|
||||
ObjectPresenter: '' as AnyComponent,
|
||||
EditDoc: '' as AnyComponent,
|
||||
CreateAttribute: '' as AnyComponent,
|
||||
SpacePresenter: '' as AnyComponent
|
||||
},
|
||||
icon: {
|
||||
|
Loading…
Reference in New Issue
Block a user