Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov 2022-05-29 10:56:06 +06:00 committed by GitHub
parent c2fd75e3ce
commit 4270887dec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 608 additions and 22 deletions

View File

@ -36,7 +36,9 @@ import {
Timestamp,
Type,
Version,
BlobData
BlobData,
EnumOf,
Enum
} from '@anticrm/core'
import { Hidden, Index, Model, Prop, TypeIntlString, TypeRef, TypeString, TypeTimestamp, UX } from '@anticrm/model'
import type { IntlString } from '@anticrm/platform'
@ -103,6 +105,13 @@ export class TInterface extends TDoc implements Interface<Doc> {
extends?: Ref<Interface<Doc>>[]
}
@UX(core.string.Enum)
@Model(core.class.Enum, core.class.Doc, DOMAIN_MODEL)
export class TEnum extends TDoc implements Enum {
name!: string
enumValues!: string[]
}
@Model(core.class.Attribute, core.class.Doc, DOMAIN_MODEL)
export class TAttribute extends TDoc implements AnyAttribute {
attributeOf!: Ref<Class<Obj>>
@ -163,6 +172,12 @@ export class TTypeTimestamp extends TType {}
@Model(core.class.TypeDate, core.class.Type)
export class TTypeDate extends TType {}
@UX(core.string.Enum)
@Model(core.class.EnumOf, core.class.Type)
export class TEnumOf extends TType implements EnumOf {
of!: Ref<Enum>
}
@Model(core.class.Version, core.class.Doc, DOMAIN_MODEL)
export class TVersion extends TDoc implements Version {
major!: number

View File

@ -36,7 +36,9 @@ import {
TTypeNumber,
TTypeString,
TTypeTimestamp,
TVersion
TEnumOf,
TVersion,
TEnum
} from './core'
import { TAccount, TSpace } from './security'
import { TUserStatus } from './transient'
@ -79,19 +81,21 @@ export function createModel (builder: Builder): void {
TAccount,
TAttribute,
TType,
TTypeString,
TEnumOf,
TTypeMarkup,
TTypeBoolean,
TTypeTimestamp,
TRefTo,
TCollection,
TTypeDate,
TArrOf,
TVersion,
TRefTo,
TTypeDate,
TTypeTimestamp,
TTypeNumber,
TTypeBoolean,
TTypeString,
TCollection,
TVersion,
TTypeIntlString,
TPluginConfiguration,
TUserStatus,
TEnum,
TBlobData
)
}

View File

@ -131,6 +131,18 @@ export function createModel (builder: Builder): void {
},
setting.ids.ClassSetting
)
builder.createDoc(
setting.class.SettingsCategory,
core.space.Model,
{
name: 'enums',
label: setting.string.Enums,
icon: setting.icon.Setting,
component: setting.component.EnumSetting,
order: 4600
},
setting.ids.EnumSetting
)
builder.createDoc(
setting.class.SettingsCategory,
core.space.Model,
@ -216,6 +228,10 @@ export function createModel (builder: Builder): void {
builder.mixin(core.class.RefTo, core.class.Class, view.mixin.ObjectEditor, {
editor: setting.component.RefEditor
})
builder.mixin(core.class.EnumOf, core.class.Class, view.mixin.ObjectEditor, {
editor: setting.component.EnumTypeEditor
})
}
export { settingOperation } from './migration'

View File

@ -13,7 +13,7 @@
// limitations under the License.
//
import { Ref } from '@anticrm/core'
import { Doc, Ref } from '@anticrm/core'
import { mergeIds } from '@anticrm/platform'
import { settingId } from '@anticrm/setting'
import setting from '@anticrm/setting-resources/src/plugin'
@ -26,13 +26,16 @@ export default mergeIds(settingId, setting, {
TxIntegrationDisableReconnect: '' as AnyComponent
},
ids: {
TxIntegrationDisable: '' as Ref<TxViewlet>
TxIntegrationDisable: '' as Ref<TxViewlet>,
EnumSetting: '' as Ref<Doc>
},
component: {
EnumSetting: '' as AnyComponent,
StringTypeEditor: '' as AnyComponent,
BooleanTypeEditor: '' as AnyComponent,
NumberTypeEditor: '' as AnyComponent,
DateTypeEditor: '' as AnyComponent,
RefEditor: '' as AnyComponent
RefEditor: '' as AnyComponent,
EnumTypeEditor: '' as AnyComponent
}
})

View File

@ -509,6 +509,8 @@ export function createModel (builder: Builder): void {
builder.mixin(core.class.TypeTimestamp, core.class.Class, view.mixin.AttributeFilter, {
component: view.component.TimestampFilter
})
classPresenter(builder, core.class.EnumOf, view.component.StringPresenter, view.component.EnumEditor)
}
export default view

View File

@ -79,7 +79,8 @@ export default mergeIds(viewId, view, {
RolePresenter: '' as AnyComponent,
YoutubePresenter: '' as AnyComponent,
GithubPresenter: '' as AnyComponent,
ClassPresenter: '' as AnyComponent
ClassPresenter: '' as AnyComponent,
EnumEditor: '' as AnyComponent
},
string: {
Table: '' as IntlString,

View File

@ -23,6 +23,7 @@
"Ref": "Ref",
"Collection": "Collection",
"Array": "Array",
"Bag": "Bag"
"Bag": "Bag",
"Enum": "Enum"
}
}

View File

@ -23,6 +23,7 @@
"Ref": "Ссылка",
"Collection": "Коллекция",
"Array": "Массив",
"Bag": "Bag"
"Bag": "Bag",
"Enum": "Сравочник"
}
}

View File

@ -90,6 +90,14 @@ export enum IndexKind {
Indexed
}
/**
* @public
*/
export interface Enum extends Doc {
name: string
enumValues: string[]
}
/**
* @public
*/
@ -224,6 +232,13 @@ export interface ArrOf<T extends PropertyType> extends Type<T[]> {
of: Type<T>
}
/**
* @public
*/
export interface EnumOf extends Type<string> {
of: Ref<Enum>
}
/**
* @public
*/

View File

@ -24,6 +24,8 @@ import type {
Class,
Collection,
Doc,
Enum,
EnumOf,
Interface,
Obj,
PluginConfiguration,
@ -82,6 +84,8 @@ export default plugin(coreId, {
TypeDate: '' as Ref<Class<Type<Timestamp | Date>>>,
RefTo: '' as Ref<Class<RefTo<Doc>>>,
ArrOf: '' as Ref<Class<ArrOf<Doc>>>,
Enum: '' as Ref<Class<Enum>>,
EnumOf: '' as Ref<Class<EnumOf>>,
Collection: '' as Ref<Class<Collection<AttachedDoc>>>,
Bag: '' as Ref<Class<Type<Record<string, PropertyType>>>>,
Version: '' as Ref<Class<Version>>,
@ -124,6 +128,7 @@ export default plugin(coreId, {
Collection: '' as IntlString,
Array: '' as IntlString,
Bag: '' as IntlString,
Name: '' as IntlString
Name: '' as IntlString,
Enum: '' as IntlString
}
})

View File

@ -25,6 +25,8 @@ import core, {
Data,
Doc,
Domain,
Enum,
EnumOf,
generateId,
IndexKind,
Interface,
@ -392,6 +394,13 @@ export function TypeRef (_class: Ref<Class<Doc>>): RefTo<Doc> {
return { _class: core.class.RefTo, label: core.string.Ref, to: _class }
}
/**
* @public
*/
export function TypeEnum (of: Ref<Enum>): EnumOf {
return { _class: core.class.EnumOf, label: core.string.Enum, of }
}
/**
* @public
*/

View File

@ -27,6 +27,7 @@ export { default as EditableAvatar } from './components/EditableAvatar.svelte'
export { default as Members } from './components/Members.svelte'
export { default as MessageBox } from './components/MessageBox.svelte'
export { default as MessageViewer } from './components/MessageViewer.svelte'
export { default as ObjectPopup } from './components/ObjectPopup.svelte'
export { default as PDFViewer } from './components/PDFViewer.svelte'
export { default as SpaceCreateCard } from './components/SpaceCreateCard.svelte'
export { default as SpaceMultiBoxList } from './components/SpaceMultiBoxList.svelte'

View File

@ -33,6 +33,7 @@
export let width: string | undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined
export let focusIndex = -1
export let autoSelect: boolean = true
let container: HTMLElement
let opened: boolean = false
@ -41,7 +42,7 @@
let selectedItem = items.find((x) => x.id === selected)
$: selectedItem = items.find((x) => x.id === selected)
$: if (selected === undefined && items[0] !== undefined) {
$: if (autoSelect && selected === undefined && items[0] !== undefined) {
selected = items[0].id
}

View File

@ -40,6 +40,9 @@
"Type": "Type",
"WithTime": "WithTime",
"CreatingAttribute": "Creating an attribute",
"EditAttribute": "Edit attribute"
"EditAttribute": "Edit attribute",
"CreateEnum": "Create enum",
"Enums": "Enums",
"NewValue": "New value"
}
}

View File

@ -40,6 +40,9 @@
"Type": "Тип",
"WithTime": "Со временем",
"CreatingAttribute": "Создание атрибута",
"EditAttribute": "Редактирование атрибута"
"EditAttribute": "Редактирование атрибута",
"CreateEnum": "Создать справочник",
"Enums": "Справочники",
"NewValue": "Новое значение"
}
}

View File

@ -0,0 +1,89 @@
<!--
// 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, { Enum } from '@anticrm/core'
import presentation, { Card, getClient } from '@anticrm/presentation'
import { ActionIcon, EditBox, IconAdd, IconDelete } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import setting from '../plugin'
export let value: Enum | undefined
let name: string = value?.name ?? ''
let values: string[] = value?.enumValues ?? []
const client = getClient()
const dispatch = createEventDispatcher()
async function save (): Promise<void> {
if (value === undefined) {
await client.createDoc(core.class.Enum, core.space.Model, {
name,
enumValues: values
})
} else {
await client.update(value, {
name,
enumValues: values
})
}
dispatch('close')
}
function add () {
if (newValue.trim().length === 0) return
values.push(newValue)
values = values
newValue = ''
}
function remove (value: string) {
values = values.filter((p) => p !== value)
}
let newValue = ''
</script>
<Card
label={core.string.Enum}
okLabel={presentation.string.Save}
okAction={save}
canSave={name.trim().length > 0 && values.length > 0}
on:close={() => {
dispatch('close')
}}
>
<div class="mb-2"><EditBox bind:value={name} placeholder={core.string.Name} maxWidth="13rem" /></div>
<div class="flex-between mb-4">
<EditBox placeholder={setting.string.NewValue} bind:value={newValue} maxWidth="13rem" /><ActionIcon
icon={IconAdd}
label={presentation.string.Add}
action={add}
size={'small'}
/>
</div>
<div class="flex-row">
{#each values as value}
<div class="flex-between mb-2">
{value}<ActionIcon
icon={IconDelete}
label={setting.string.Delete}
action={() => {
remove(value)
}}
size={'small'}
/>
</div>
{/each}
</div>
</Card>

View File

@ -0,0 +1,67 @@
<!--
// 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, { Enum } from '@anticrm/core'
import { createQuery } from '@anticrm/presentation'
import { CircleButton, Icon, IconAdd, Label, showPopup } from '@anticrm/ui'
import setting from '../plugin'
import EnumValues from './EnumValues.svelte'
const query = createQuery()
let enums: Enum[] = []
let selected: Enum | undefined
query.query(core.class.Enum, {}, (res) => {
enums = res
})
function create () {
showPopup(setting.component.EditEnum, 'top')
}
</script>
<div class="antiComponent">
<div class="ac-header short divide">
<div class="ac-header__icon"><Icon icon={setting.icon.Setting} size={'medium'} /></div>
<div class="ac-header__title"><Label label={setting.string.Enums} /></div>
</div>
<div class="ac-body columns hScroll">
<div class="ac-column">
<div class="flex-between trans-title mb-3">
<Label label={setting.string.Enums} />
<CircleButton icon={IconAdd} size="medium" on:click={create} />
</div>
<div class="overflow-y-auto">
{#each enums as value}
<div
class="ac-column__list-item"
class:selected={selected === value}
on:click={() => {
selected = value
}}
>
{value.name}
</div>
{/each}
</div>
</div>
<div class="ac-column max">
{#if selected !== undefined}
<EnumValues value={selected} />
{/if}
</div>
</div>
</div>

View File

@ -0,0 +1,78 @@
<!--
// 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 { Enum } from '@anticrm/core'
import { ActionIcon, Button, EditBox, IconAdd, IconDelete } from '@anticrm/ui'
import presentation, { getClient } from '@anticrm/presentation'
import setting from '../plugin'
export let value: Enum
const client = getClient()
$: values = value.enumValues
let newValue = ''
function add () {
if (newValue.trim().length === 0) return
values.push(newValue)
values = values
newValue = ''
}
function remove (value: string) {
values = values.filter((p) => p !== value)
}
async function save () {
await client.update(value, {
enumValues: values
})
}
</script>
<div class="flex-grow">
<div class="flex-between mb-4">
<EditBox placeholder={setting.string.NewValue} bind:value={newValue} maxWidth="20rem" /><ActionIcon
label={presentation.string.Add}
icon={IconAdd}
action={add}
size={'small'}
/>
</div>
<div class="overflow-y-auto flex-row">
{#each values as value}
<div class="flex-between mb-2">
{value}<ActionIcon
icon={IconDelete}
label={setting.string.Delete}
action={() => {
remove(value)
}}
size={'small'}
/>
</div>
{/each}
</div>
</div>
<div class="flex-row-reverse">
<Button
label={presentation.string.Save}
kind={'primary'}
on:click={() => {
save()
}}
/>
</div>

View File

@ -0,0 +1,54 @@
<!--
// 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 type { Class, Doc, DocumentQuery, Enum, Ref } from '@anticrm/core'
import { IntlString } from '@anticrm/platform'
import { ObjectPopup } from '@anticrm/presentation'
import { AnyComponent } from '@anticrm/ui'
export let _class: Ref<Class<Enum>>
export let selected: Ref<Enum> | undefined
export let query: DocumentQuery<Enum> | undefined
export let create:
| {
component: AnyComponent
label: IntlString
}
| undefined = undefined
$: _create =
create !== undefined
? {
...create,
update: (doc: Doc) => (doc as Enum).name
}
: undefined
</script>
<ObjectPopup
{_class}
{selected}
bind:docQuery={query}
multiSelect={false}
allowDeselect={false}
shadows={true}
create={_create}
on:update
on:close
>
<svelte:fragment slot="item" let:item>
{item.name}
</svelte:fragment>
</ObjectPopup>

View File

@ -0,0 +1,82 @@
<!--
// 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 type { IntlString } from '@anticrm/platform'
import {
Label,
showPopup,
IconFolder,
Button,
eventToHTMLElement,
getFocusManager,
AnyComponent,
Tooltip,
TooltipAlignment
} from '@anticrm/ui'
import EnumPopup from './EnumPopup.svelte'
import core, { Ref, Class, DocumentQuery, Enum } from '@anticrm/core'
export let label: IntlString
export let value: Enum | undefined
export let focusIndex = -1
export let focus = false
export let create:
| {
component: AnyComponent
label: IntlString
}
| undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined
const _class: Ref<Class<Enum>> = core.class.Enum
const query: DocumentQuery<Enum> = {}
const mgr = getFocusManager()
</script>
<Tooltip {label} fill={false} direction={labelDirection}>
<Button
{focus}
{focusIndex}
icon={IconFolder}
size={'small'}
kind={'no-border'}
on:click={(ev) => {
showPopup(
EnumPopup,
{
_class,
label,
options: { sort: { modifiedOn: -1 } },
selected: value?._id,
spaceQuery: query,
create
},
eventToHTMLElement(ev),
(result) => {
if (result) {
value = result
mgr?.setFocusPos(focusIndex)
}
}
)
}}
>
<span slot="content" class="text-sm">
{#if value}{value.name}{:else}<Label {label} />{/if}
</span>
</Button>
</Tooltip>

View File

@ -0,0 +1,68 @@
<!--
// 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, { Enum, EnumOf, Ref } from '@anticrm/core'
import { TypeEnum } from '@anticrm/model'
import presentation, { getClient } from '@anticrm/presentation'
import { Button, Tooltip, Label, showPopup } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import setting from '../../plugin'
import EnumSelect from './EnumSelect.svelte'
export let type: EnumOf | undefined
export let editable: boolean = true
const client = getClient()
const dispatch = createEventDispatcher()
let value: Enum | undefined
$: value && dispatch('change', { type: TypeEnum(value._id) })
$: ref = type?.of
const create = {
label: setting.string.CreateEnum,
component: setting.component.EditEnum
}
async function updateSelected (ref: Ref<Enum> | undefined) {
value = ref !== undefined ? await client.findOne(core.class.Enum, { _id: ref }) : undefined
}
$: updateSelected(ref)
async function edit () {
if (value === undefined) return
showPopup(setting.component.EditEnum, { value }, 'top')
}
</script>
<div class="flex-row-center flex-grow">
<Label label={core.string.Enum} />
<div class="ml-4">
{#if editable}
<EnumSelect label={core.string.Enum} bind:value {create} />
{:else if value}
{value.name}
{/if}
</div>
{#if value}
<div class="ml-2">
<Tooltip label={presentation.string.Edit}>
<Button icon={setting.icon.Setting} kind={'no-border'} on:click={edit} size={'small'} />
</Tooltip>
</div>
{/if}
</div>

View File

@ -31,6 +31,9 @@ import BooleanTypeEditor from './components/typeEditors/BooleanTypeEditor.svelte
import DateTypeEditor from './components/typeEditors/DateTypeEditor.svelte'
import NumberTypeEditor from './components/typeEditors/NumberTypeEditor.svelte'
import RefEditor from './components/typeEditors/RefEditor.svelte'
import EnumTypeEditor from './components/typeEditors/EnumTypeEditor.svelte'
import EditEnum from './components/EditEnum.svelte'
import EnumSetting from './components/EnumSetting.svelte'
export default async (): Promise<Resources> => ({
activity: {
@ -52,6 +55,9 @@ export default async (): Promise<Resources> => ({
BooleanTypeEditor,
NumberTypeEditor,
RefEditor,
DateTypeEditor
DateTypeEditor,
EnumTypeEditor,
EditEnum,
EnumSetting
}
})

View File

@ -16,8 +16,12 @@
import type { IntlString } from '@anticrm/platform'
import { mergeIds } from '@anticrm/platform'
import setting, { settingId } from '@anticrm/setting'
import { AnyComponent } from '@anticrm/ui'
export default mergeIds(settingId, setting, {
component: {
EditEnum: '' as AnyComponent
},
string: {
IntegrationDisabled: '' as IntlString,
IntegrationWith: '' as IntlString,
@ -32,6 +36,9 @@ export default mergeIds(settingId, setting, {
WithTime: '' as IntlString,
Type: '' as IntlString,
CreatingAttribute: '' as IntlString,
EditAttribute: '' as IntlString
EditAttribute: '' as IntlString,
CreateEnum: '' as IntlString,
Enums: '' as IntlString,
NewValue: '' as IntlString
}
})

View File

@ -0,0 +1,53 @@
<!--
// 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, { EnumOf } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform'
import { createQuery } from '@anticrm/presentation'
import { DropdownLabels, DropdownTextItem } from '@anticrm/ui'
export let label: IntlString
export let value: string
export let type: EnumOf
export let focus: boolean
export let onChange: (value: string) => void
let items: DropdownTextItem[] = []
const query = createQuery()
query.query(
core.class.Enum,
{
_id: type.of
},
(res) => {
items = res[0].enumValues.map((p) => {
return { id: p, label: p }
})
},
{ limit: 1 }
)
</script>
<DropdownLabels
bind:selected={value}
{items}
{label}
autoSelect={false}
on:selected={(e) => {
onChange(e.detail)
}}
/>

View File

@ -47,6 +47,7 @@ import TimestampFilter from './components/filter/TimestampFilter.svelte'
import ClassPresenter from './components/ClassPresenter.svelte'
import EditBoxPopup from './components/EditBoxPopup.svelte'
import BooleanTruePresenter from './components/BooleanTruePresenter.svelte'
import EnumEditor from './components/EnumEditor.svelte'
function PositionElementAlignment (e?: Event): PopupAlignment | undefined {
return getEventPopupPositionElement(e)
@ -103,7 +104,8 @@ export default async (): Promise<Resources> => ({
YoutubePresenter,
ActionsPopup,
StringEditorPopup: EditBoxPopup,
BooleanTruePresenter
BooleanTruePresenter,
EnumEditor
},
popup: {
PositionElementAlignment