Move Attribute setting to Settings. (#1887)

Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov 2022-05-28 10:05:36 +06:00 committed by GitHub
parent b1d751e60f
commit 9f9003ec68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 594 additions and 93 deletions

View File

@ -32,6 +32,7 @@
"@anticrm/model-view": "~0.6.0",
"@anticrm/model-presentation": "~0.6.0",
"@anticrm/model": "~0.6.0",
"@anticrm/setting": "~0.6.1",
"@anticrm/core": "~0.6.16",
"@anticrm/ui": "~0.6.0",
"@anticrm/platform": "~0.6.6",

View File

@ -36,6 +36,7 @@ import view, { actionTemplates, createAction } from '@anticrm/model-view'
import workbench from '@anticrm/model-workbench'
import type { Asset, IntlString } from '@anticrm/platform'
import contact from './plugin'
import setting from '@anticrm/setting'
export const DOMAIN_CONTACT = 'contact' as Domain
export const DOMAIN_CHANNEL = 'channel' as Domain
@ -297,6 +298,8 @@ export function createModel (builder: Builder): void {
filters: ['_class', 'city', 'modifiedOn']
})
builder.mixin(contact.class.Contact, core.class.Class, setting.mixin.Editable, {})
builder.createDoc(
presentation.class.ObjectSearchCategory,
core.space.Model,

View File

@ -35,6 +35,7 @@
"@anticrm/inventory": "~0.6.0",
"@anticrm/inventory-resources": "~0.6.0",
"@anticrm/view": "~0.6.0",
"@anticrm/setting": "~0.6.1",
"@anticrm/workbench": "~0.6.1",
"@anticrm/model-view": "~0.6.0"
}

View File

@ -22,6 +22,7 @@ import { createAction } from '@anticrm/model-view'
import workbench from '@anticrm/model-workbench'
import type {} from '@anticrm/view'
import view from '@anticrm/view'
import setting from '@anticrm/setting'
import inventory from './plugin'
export const DOMAIN_INVENTORY = 'inventory' as Domain
@ -93,6 +94,8 @@ export function createModel (builder: Builder): void {
editor: inventory.component.EditProduct
})
builder.mixin(inventory.class.Product, core.class.Class, setting.mixin.Editable, {})
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: inventory.class.Product,
descriptor: view.viewlet.Table,

View File

@ -38,6 +38,7 @@
"@anticrm/model-attachment": "~0.6.0",
"@anticrm/lead": "~0.6.0",
"@anticrm/lead-resources": "~0.6.0",
"@anticrm/setting": "~0.6.1",
"@anticrm/view": "~0.6.0",
"@anticrm/task": "~0.6.0",
"@anticrm/model-task": "~0.6.0",

View File

@ -25,6 +25,7 @@ import core from '@anticrm/model-core'
import task, { TSpaceWithStates, TTask } from '@anticrm/model-task'
import view, { createAction } from '@anticrm/model-view'
import workbench, { Application } from '@anticrm/model-workbench'
import setting from '@anticrm/setting'
import lead from './plugin'
@Model(lead.class.Funnel, task.class.SpaceWithStates)
@ -76,6 +77,8 @@ export function createModel (builder: Builder): void {
}
})
builder.mixin(lead.class.Lead, core.class.Class, setting.mixin.Editable, {})
builder.createDoc(
workbench.class.Application,
core.space.Model,

View File

@ -41,6 +41,7 @@
"@anticrm/model-chunter": "~0.6.0",
"@anticrm/view": "~0.6.0",
"@anticrm/task": "~0.6.0",
"@anticrm/setting": "~0.6.1",
"@anticrm/model-task": "~0.6.0",
"@anticrm/workbench": "~0.6.1",
"@anticrm/model-presentation": "~0.6.0",

View File

@ -42,6 +42,7 @@ import workbench, { Application, createNavigateAction } from '@anticrm/model-wor
import { IntlString } from '@anticrm/platform'
import { Applicant, Candidate, Candidates, Vacancy } from '@anticrm/recruit'
import { KeyBinding } from '@anticrm/view'
import setting from '@anticrm/setting'
import recruit from './plugin'
import { createReviewModel, reviewTableConfig, reviewTableOptions } from './review'
import { TOpinion, TReview } from './review-model'
@ -141,6 +142,10 @@ export function createModel (builder: Builder): void {
component: recruit.component.CreateCandidate
})
builder.mixin(recruit.class.Applicant, core.class.Class, setting.mixin.Editable, {})
builder.mixin(recruit.class.Vacancy, core.class.Class, setting.mixin.Editable, {})
const vacanciesId = 'vacancies'
const candidatesId = 'candidates'
const skillsId = 'skills'

View File

@ -13,14 +13,15 @@
// limitations under the License.
//
import { Builder, Model } from '@anticrm/model'
import { Builder, Mixin, Model } from '@anticrm/model'
import { Ref, Domain, DOMAIN_MODEL } from '@anticrm/core'
import core, { TDoc } from '@anticrm/model-core'
import core, { TClass, TDoc } from '@anticrm/model-core'
import setting from './plugin'
import type { Integration, IntegrationType, Handler, SettingsCategory } from '@anticrm/setting'
import type { Editable, Integration, IntegrationType, Handler, SettingsCategory } from '@anticrm/setting'
import type { Asset, IntlString } from '@anticrm/platform'
import task from '@anticrm/task'
import activity from '@anticrm/activity'
import view from '@anticrm/view'
import workbench from '@anticrm/model-workbench'
import { AnyComponent } from '@anticrm/ui'
@ -51,8 +52,11 @@ export class TIntegrationType extends TDoc implements IntegrationType {
onDisconnect!: Handler
}
@Mixin(setting.mixin.Editable, core.class.Class)
export class TEditable extends TClass implements Editable {}
export function createModel (builder: Builder): void {
builder.createModel(TIntegration, TIntegrationType, TSettingsCategory)
builder.createModel(TIntegration, TIntegrationType, TSettingsCategory, TEditable)
builder.createDoc(
setting.class.SettingsCategory,
@ -115,6 +119,18 @@ export function createModel (builder: Builder): void {
},
setting.ids.ManageStatuses
)
builder.createDoc(
setting.class.SettingsCategory,
core.space.Model,
{
name: 'classes',
label: setting.string.ClassSetting,
icon: setting.icon.Setting,
component: setting.component.ClassSetting,
order: 4500
},
setting.ids.ClassSetting
)
builder.createDoc(
setting.class.SettingsCategory,
core.space.Model,
@ -180,6 +196,26 @@ export function createModel (builder: Builder): void {
},
setting.ids.TxIntegrationDisable
)
builder.mixin(core.class.TypeString, core.class.Class, view.mixin.ObjectEditor, {
editor: setting.component.StringTypeEditor
})
builder.mixin(core.class.TypeBoolean, core.class.Class, view.mixin.ObjectEditor, {
editor: setting.component.BooleanTypeEditor
})
builder.mixin(core.class.TypeDate, core.class.Class, view.mixin.ObjectEditor, {
editor: setting.component.DateTypeEditor
})
builder.mixin(core.class.TypeNumber, core.class.Class, view.mixin.ObjectEditor, {
editor: setting.component.NumberTypeEditor
})
builder.mixin(core.class.RefTo, core.class.Class, view.mixin.ObjectEditor, {
editor: setting.component.RefEditor
})
}
export { settingOperation } from './migration'

View File

@ -27,5 +27,12 @@ export default mergeIds(settingId, setting, {
},
ids: {
TxIntegrationDisable: '' as Ref<TxViewlet>
},
component: {
StringTypeEditor: '' as AnyComponent,
BooleanTypeEditor: '' as AnyComponent,
NumberTypeEditor: '' as AnyComponent,
DateTypeEditor: '' as AnyComponent,
RefEditor: '' as AnyComponent
}
})

View File

@ -277,26 +277,6 @@ export function createModel (builder: Builder): void {
classPresenter(builder, core.class.Space, view.component.ObjectPresenter)
classPresenter(builder, core.class.Class, view.component.ClassPresenter)
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.mixin(core.class.RefTo, core.class.Class, view.mixin.ObjectEditor, {
editor: view.component.RefEditor
})
builder.createDoc(
view.class.ActionCategory,
core.space.Model,

View File

@ -78,11 +78,6 @@ export default mergeIds(viewId, view, {
RolePresenter: '' as AnyComponent,
YoutubePresenter: '' as AnyComponent,
GithubPresenter: '' as AnyComponent,
StringTypeEditor: '' as AnyComponent,
BooleanTypeEditor: '' as AnyComponent,
NumberTypeEditor: '' as AnyComponent,
DateTypeEditor: '' as AnyComponent,
RefEditor: '' as AnyComponent,
ClassPresenter: '' as AnyComponent
},
string: {

View File

@ -18,7 +18,7 @@ import type { AnyAttribute, Class, Classifier, Doc, Domain, Interface, Mixin, Ob
import { ClassifierKind } from './classes'
import core from './component'
import { _createMixinProxy, _mixinClass, _toDoc } from './proxy'
import type { Tx, TxCreateDoc, TxMixin } from './tx'
import type { Tx, TxCreateDoc, TxMixin, TxRemoveDoc, TxUpdateDoc } from './tx'
import { TxProcessor } from './tx'
/**
@ -27,6 +27,7 @@ import { TxProcessor } from './tx'
export class Hierarchy {
private readonly classifiers = new Map<Ref<Classifier>, Classifier>()
private readonly attributes = new Map<Ref<Classifier>, Map<string, AnyAttribute>>()
private readonly attributesById = new Map<Ref<AnyAttribute>, AnyAttribute>()
private readonly descendants = new Map<Ref<Classifier>, Ref<Classifier>[]>()
private readonly ancestors = new Map<Ref<Classifier>, Ref<Classifier>[]>()
private readonly proxies = new Map<Ref<Mixin<Doc>>, ProxyHandler<Doc>>()
@ -124,6 +125,12 @@ export class Hierarchy {
case core.class.TxCreateDoc:
this.txCreateDoc(tx as TxCreateDoc<Doc>)
return
case core.class.TxUpdateDoc:
this.txUpdateDoc(tx as TxUpdateDoc<Doc>)
return
case core.class.TxRemoveDoc:
this.txRemoveDoc(tx as TxRemoveDoc<Doc>)
return
case core.class.TxMixin:
this.txMixin(tx as TxMixin<Doc, Doc>)
}
@ -145,6 +152,26 @@ export class Hierarchy {
}
}
private txUpdateDoc (tx: TxUpdateDoc<Doc>): void {
if (tx.objectClass === core.class.Attribute) {
const updateTx = tx as TxUpdateDoc<AnyAttribute>
const doc = this.attributesById.get(updateTx.objectId)
if (doc === undefined) return
this.addAttribute(TxProcessor.updateDoc2Doc(doc, updateTx))
}
}
private txRemoveDoc (tx: TxRemoveDoc<Doc>): void {
if (tx.objectClass === core.class.Attribute) {
const removeTx = tx as TxRemoveDoc<AnyAttribute>
const doc = this.attributesById.get(removeTx.objectId)
if (doc === undefined) return
const map = this.attributes.get(doc.attributeOf)
map?.delete(doc.name)
this.attributesById.delete(removeTx.objectId)
}
}
private txMixin (tx: TxMixin<Doc, Doc>): void {
if (this.isDerived(tx.objectClass, core.class.Class)) {
const obj = this.getClass(tx.objectId as Ref<Class<Obj>>) as any
@ -287,6 +314,7 @@ export class Hierarchy {
this.attributes.set(_class, attributes)
}
attributes.set(attribute.name, attribute)
this.attributesById.set(attribute._id, attribute)
}
getAllAttributes (clazz: Ref<Classifier>, to?: Ref<Classifier>): Map<string, AnyAttribute> {
@ -300,10 +328,13 @@ export class Hierarchy {
break
}
}
ancestors = ancestors.filter((c) => this.isDerived(c, to as Ref<Class<Doc>>) && c !== to)
ancestors = ancestors.filter(
(c) => c !== to && (this.isInterface(this.classifiers.get(c)) || this.isDerived(c, to as Ref<Class<Doc>>))
)
}
for (const cls of ancestors) {
for (let index = ancestors.length - 1; index >= 0; index--) {
const cls = ancestors[index]
const attributes = this.attributes.get(cls)
if (attributes !== undefined) {
for (const [name, attr] of attributes) {

View File

@ -75,6 +75,9 @@ const predicates: Record<string, PredicateFactory> = {
},
$lte: (o, propertyKey) => {
return (docs) => execPredicate(docs, propertyKey, (value) => value <= o)
},
$exist: (o, propertyKey) => {
return (docs) => execPredicate(docs, propertyKey, (value) => (value !== undefined) === o)
}
}

View File

@ -28,6 +28,7 @@ export type QuerySelector<T> = {
$gte?: T extends number ? number : never
$lt?: T extends number ? number : never
$lte?: T extends number ? number : never
$exist?: boolean
$like?: string
$regex?: string
$options?: string

View File

@ -24,6 +24,7 @@
"NoMatchesInThis": "No matches in this {space}",
"NoMatchesFound": "No matches found",
"NotInThis": "Not in this {space}",
"Add": "Add"
"Add": "Add",
"Edit": "Edit"
}
}

View File

@ -24,6 +24,7 @@
"NoMatchesInThis": "В этом {space} совпадения не обнаружены",
"NoMatchesFound": "Не найдено соответсвий",
"NotInThis": "Не в этом {space}",
"Add": "Добавить"
"Add": "Добавить",
"Edit": "Редактировать"
}
}

View File

@ -53,7 +53,8 @@ export default plugin(presentationId, {
NoMatchesInThis: '' as IntlString,
NoMatchesFound: '' as IntlString,
NotInThis: '' as IntlString,
Add: '' as IntlString
Add: '' as IntlString,
Edit: '' as IntlString
},
metadata: {
RequiredVersion: '' as Metadata<string>

View File

@ -29,6 +29,17 @@
"DeleteStatusConfirm": "Do you want to delete this status?",
"Reconnect": "Reconnect",
"IntegrationDisabled": " has been disabled",
"IntegrationWith": "Integration with "
"IntegrationWith": "Integration with ",
"ClassSetting": "Class setting",
"Attributes": "Attributes",
"DeleteAttribute": "Delete attribute",
"DeleteAttributeConfirm": "Do you want to delete this attribute?",
"DeleteAttributeExistConfirm": "Do you want to delete this attribute? Data will be lost",
"Attribute": "Attribute",
"Custom": "Custom",
"Type": "Type",
"WithTime": "WithTime",
"CreatingAttribute": "Creating an attribute",
"EditAttribute": "Edit attribute"
}
}

View File

@ -29,6 +29,17 @@
"DeleteStatusConfirm": "Вы действительно хотите удалить этот статус?",
"Reconnect": "Переподключить",
"IntegrationDisabled": " была отключена",
"IntegrationWith": "Интеграция с "
"IntegrationWith": "Интеграция с ",
"ClassSetting": "Настройки класса",
"Attributes": "Атрибуты",
"DeleteAttribute": "Удалить атрибут",
"DeleteAttributeConfirm": "Вы действительно хотите удалить этот атрибут?",
"DeleteAttributeExistConfirm": "Вы действительно хотите удалить этот атрибут? Данные будут утеряны",
"Attribute": "Атрибут",
"Custom": "Пользовательский",
"Type": "Тип",
"WithTime": "Со временем",
"CreatingAttribute": "Создание атрибута",
"EditAttribute": "Редактирование атрибута"
}
}

View File

@ -44,6 +44,7 @@
"@anticrm/login-resources": "~0.6.2",
"@anticrm/task": "~0.6.0",
"@anticrm/contact-resources": "~0.6.0",
"@anticrm/login": "~0.6.1"
"@anticrm/login": "~0.6.1",
"@anticrm/model": "~0.6.0"
}
}

View File

@ -0,0 +1,145 @@
<!--
// 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, { AnyAttribute, Class, Doc, Ref } from '@anticrm/core'
import presentation, { getClient, MessageBox } from '@anticrm/presentation'
import {
Action,
CircleButton,
eventToHTMLElement,
IconAdd,
IconDelete,
IconEdit,
Label,
Menu,
IconMoreV,
showPopup
} from '@anticrm/ui'
import BooleanPresenter from '@anticrm/view-resources/src/components/BooleanPresenter.svelte'
import setting from '../plugin'
import CreateAttribute from './CreateAttribute.svelte'
import EditAttribute from './EditAttribute.svelte'
export let _class: Ref<Class<Doc>>
const client = getClient()
const hierarchy = client.getHierarchy()
$: attributes = getCustomAttributes(_class)
function getCustomAttributes (_class: Ref<Class<Doc>>): AnyAttribute[] {
const attributes = Array.from(hierarchy.getAllAttributes(_class, core.class.AttachedDoc).values())
const filtred = attributes.filter((p) => !p.hidden)
return filtred
}
function update () {
attributes = getCustomAttributes(_class)
}
function createAttribute () {
showPopup(CreateAttribute, { _class }, 'top', update)
}
async function editAttribute (attribute: AnyAttribute, exist: boolean): Promise<void> {
showPopup(EditAttribute, { attribute, exist }, 'top', update)
}
async function removeAttribute (attribute: AnyAttribute, exist: boolean): Promise<void> {
showPopup(
MessageBox,
{
label: setting.string.DeleteAttribute,
message: exist ? setting.string.DeleteAttributeExistConfirm : setting.string.DeleteAttributeConfirm
},
'top',
async (result) => {
if (result) {
await client.remove(attribute)
update()
}
}
)
}
async function showMenu (ev: MouseEvent, attribute: AnyAttribute) {
const exist = (await client.findOne(attribute.attributeOf, { [attribute.name]: { $exist: true } })) !== undefined
const actions: Action[] = [
{
label: presentation.string.Edit,
icon: IconEdit,
action: async () => {
editAttribute(attribute, exist)
}
},
{
label: presentation.string.Remove,
icon: IconDelete,
action: async () => {
removeAttribute(attribute, exist)
}
}
]
showPopup(Menu, { actions }, eventToHTMLElement(ev), () => {})
}
</script>
<div class="flex-between trans-title mb-3">
<Label label={setting.string.Attributes} />
<CircleButton icon={IconAdd} size="medium" on:click={createAttribute} />
</div>
<table class="antiTable">
<thead class="scroller-thead">
<tr class="scroller-thead__tr">
<th>
<div class="antiTable-cells">
<Label label={setting.string.Attribute} />
</div>
</th>
<th>
<div class="antiTable-cells">
<Label label={setting.string.Type} />
</div>
</th>
<th>
<div class="antiTable-cells">
<Label label={setting.string.Custom} />
</div>
</th>
</tr>
</thead>
<tbody>
{#each attributes as attr}
<tr class="antiTable-body__row" on:contextmenu|preventDefault={(ev) => showMenu(ev, attr)}>
<td>
<div class="antiTable-cells__firstCell">
<Label label={attr.label} />
{#if attr.isCustom}
<div id="context-menu" class="antiTable-cells__firstCell-menuRow" on:click={(ev) => showMenu(ev, attr)}>
<IconMoreV size={'small'} />
</div>
{/if}
</div>
</td>
<td>
<Label label={attr.type.label} />
</td>
<td>
<BooleanPresenter value={attr.isCustom ?? false} />
</td>
</tr>
{/each}
</tbody>
</table>

View File

@ -0,0 +1,62 @@
<!--
// 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, ClassifierKind, Doc, Ref } from '@anticrm/core'
import { getClient } from '@anticrm/presentation'
import { Label } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
export let classes: Ref<Class<Doc>>[] = ['contact:class:Contact' as Ref<Class<Doc>>]
export let _class: Ref<Class<Doc>> | undefined
const client = getClient()
const hierarchy = client.getHierarchy()
const dispatch = createEventDispatcher()
function getDescendants (_class: Ref<Class<Doc>>): Ref<Class<Doc>>[] {
const result: Ref<Class<Doc>>[] = []
const desc = hierarchy.getDescendants(_class)
for (const clazz of desc) {
const cls = hierarchy.getClass(clazz)
if (
cls.extends === _class &&
!cls.hidden &&
[ClassifierKind.CLASS, ClassifierKind.MIXIN].includes(cls.kind) &&
cls.label !== undefined
) {
result.push(clazz)
}
}
return result
}
</script>
{#each classes as cl}
{@const clazz = hierarchy.getClass(cl)}
{@const desc = getDescendants(cl)}
<div
class="ac-column__list-item"
class:selected={cl === _class}
on:click={() => {
dispatch('select', cl)
}}
>
<Label label={clazz.label} />
</div>
{#if desc.length}
<div class="ml-2 mt-3 mb-3">
<svelte:self classes={desc} {_class} on:select />
</div>
{/if}
{/each}

View File

@ -0,0 +1,65 @@
<!--
// 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, Doc, Ref } from '@anticrm/core'
import { getClient } from '@anticrm/presentation'
import { getCurrentLocation, Icon, Label, navigate } from '@anticrm/ui'
import setting from '../plugin'
import ClassAttributes from './ClassAttributes.svelte'
import ClassHierarchy from './ClassHierarchy.svelte'
const loc = getCurrentLocation()
const client = getClient()
const hierarchy = client.getHierarchy()
let _class: Ref<Class<Doc>> | undefined = loc.query?._class as Ref<Class<Doc>> | undefined
$: if (_class !== undefined) {
const loc = getCurrentLocation()
loc.query = undefined
navigate(loc)
}
const classes = hierarchy
.getDescendants(core.class.Doc)
.map((p) => hierarchy.getClass(p))
.filter((p) => hierarchy.hasMixin(p, setting.mixin.Editable))
.map((p) => p._id)
</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.ClassSetting} /></div>
</div>
<div class="ac-body columns hScroll">
<div class="ac-column">
<div class="overflow-y-auto">
<ClassHierarchy
{classes}
{_class}
on:select={(e) => {
_class = e.detail
}}
/>
</div>
</div>
<div class="ac-column max">
{#if _class !== undefined}
<ClassAttributes {_class} />
{/if}
</div>
</div>
</div>

View File

@ -19,7 +19,8 @@
import { AnyComponent, Component, DropdownLabelsIntl, EditBox, Label } from '@anticrm/ui'
import { DropdownIntlItem } from '@anticrm/ui/src/types'
import { createEventDispatcher } from 'svelte'
import view from '../plugin'
import setting from '../plugin'
import view from '@anticrm/view'
export let _class: Ref<Class<Doc>>
let name: string
@ -77,7 +78,7 @@
</script>
<Card
label={view.string.CreatingAttribute}
label={setting.string.CreatingAttribute}
okAction={save}
canSave={!(type === undefined || name === undefined || name.trim().length === 0)}
on:close={() => {
@ -87,10 +88,10 @@
<div class="mb-2"><EditBox bind:value={name} placeholder={core.string.Name} maxWidth="13rem" /></div>
<div class="flex-col mb-2">
<div class="flex-row-center flex-grow">
<Label label={view.string.Type} />
<Label label={setting.string.Type} />
<div class="ml-4">
<DropdownLabelsIntl
label={view.string.Type}
label={setting.string.Type}
{items}
width="8rem"
bind:selected={selectedType}
@ -109,9 +110,5 @@
/>
</div>
{/if}
<Label label={view.string.CreatingAttributeConfirm} />
</div>
</Card>
<style lang="scss">
</style>

View File

@ -0,0 +1,127 @@
<!--
// 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, { AnyAttribute, Class, DocumentUpdate, IndexKind, PropertyType, Ref, Type } from '@anticrm/core'
import { getEmbeddedLabel, translate } from '@anticrm/platform'
import presentation, { Card, getClient } from '@anticrm/presentation'
import setting from '../plugin'
import { AnyComponent, Component, DropdownIntlItem, DropdownLabelsIntl, EditBox, Label } from '@anticrm/ui'
import view from '@anticrm/view-resources/src/plugin'
import { createEventDispatcher } from 'svelte'
export let attribute: AnyAttribute
export let exist: boolean
let name: string
let type: Type<PropertyType> | undefined
let index: IndexKind | undefined
let is: AnyComponent | undefined
const client = getClient()
const hierarchy = client.getHierarchy()
const dispatch = createEventDispatcher()
translate(attribute.label, {}).then((p) => (name = p))
async function save (): Promise<void> {
const update: DocumentUpdate<AnyAttribute> = {}
const newLabel = getEmbeddedLabel(name)
if (newLabel !== attribute.label) {
update.label = newLabel
}
if (!exist) {
if (index !== attribute.index) {
update.index = index
}
if (type !== attribute.type) {
update.type = type
}
}
await client.updateDoc(attribute._class, attribute.space, attribute._id, update)
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>>> = attribute.type._class
$: 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>
<Card
label={setting.string.EditAttribute}
okLabel={presentation.string.Save}
okAction={save}
canSave={!(name === undefined || name.trim().length === 0)}
on:close={() => {
dispatch('close')
}}
>
<div class="mb-2"><EditBox bind:value={name} placeholder={core.string.Name} maxWidth="13rem" /></div>
<div class="flex-col mb-2">
<div class="flex-row-center flex-grow">
<Label label={setting.string.Type} />
</div>
<div class="flex-col mb-2">
{#if exist}
<Label label={attribute.type.label} />
{:else}
<div class="flex-row-center flex-grow">
<div class="ml-4">
<DropdownLabelsIntl
label={setting.string.Type}
{items}
width="8rem"
bind:selected={selectedType}
on:selected={(e) => selectType(e.detail)}
/>
</div>
</div>
{#if is}
<div class="flex mt-4">
<Component
{is}
on:change={(e) => {
type = e.detail?.type
index = e.detail?.index
}}
/>
</div>
{/if}
{/if}
</div>
</div>
</Card>

View File

@ -16,8 +16,8 @@
import { TypeDate } from '@anticrm/model'
import { Label } from '@anticrm/ui'
import { createEventDispatcher, onMount } from 'svelte'
import view from '../../plugin'
import BooleanEditor from '../BooleanEditor.svelte'
import setting from '../../plugin'
import BooleanEditor from '@anticrm/view-resources/src/components/BooleanEditor.svelte'
const dispatch = createEventDispatcher()
@ -29,7 +29,7 @@
</script>
<div class="flex-row-center">
<Label label={view.string.WithTime} />
<Label label={setting.string.WithTime} />
<div class="ml-2">
<BooleanEditor
withoutUndefined

View File

@ -18,7 +18,7 @@
import { getClient } from '@anticrm/presentation'
import { DOMAIN_STATE } from '@anticrm/task'
import { DropdownLabelsIntl, Label } from '@anticrm/ui'
import view from '../../plugin'
import view from '@anticrm/view-resources/src/plugin'
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()

View File

@ -23,8 +23,14 @@ import Support from './components/Support.svelte'
import Privacy from './components/Privacy.svelte'
import Terms from './components/Terms.svelte'
import Settings from './components/Settings.svelte'
import ClassSetting from './components/ClassSetting.svelte'
import TxIntegrationDisable from './components/activity/TxIntegrationDisable.svelte'
import TxIntegrationDisableReconnect from './components/activity/TxIntegrationDisableReconnect.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'
import RefEditor from './components/typeEditors/RefEditor.svelte'
export default async (): Promise<Resources> => ({
activity: {
@ -40,6 +46,12 @@ export default async (): Promise<Resources> => ({
Support,
Privacy,
Terms,
ManageStatuses
ManageStatuses,
ClassSetting,
StringTypeEditor,
BooleanTypeEditor,
NumberTypeEditor,
RefEditor,
DateTypeEditor
}
})

View File

@ -22,6 +22,16 @@ export default mergeIds(settingId, setting, {
IntegrationDisabled: '' as IntlString,
IntegrationWith: '' as IntlString,
DeleteStatus: '' as IntlString,
DeleteStatusConfirm: '' as IntlString
DeleteStatusConfirm: '' as IntlString,
DeleteAttribute: '' as IntlString,
DeleteAttributeConfirm: '' as IntlString,
DeleteAttributeExistConfirm: '' as IntlString,
Attribute: '' as IntlString,
Attributes: '' as IntlString,
Custom: '' as IntlString,
WithTime: '' as IntlString,
Type: '' as IntlString,
CreatingAttribute: '' as IntlString,
EditAttribute: '' as IntlString
}
})

View File

@ -16,7 +16,7 @@
import { Asset, IntlString, plugin, Resource } from '@anticrm/platform'
import type { Plugin } from '@anticrm/platform'
import { AnyComponent } from '@anticrm/ui'
import type { Class, Doc, Ref } from '@anticrm/core'
import type { Class, Doc, Mixin, Ref } from '@anticrm/core'
/**
* @public
@ -44,6 +44,11 @@ export interface Integration extends Doc {
value: string
}
/**
* @public
*/
export interface Editable extends Class<Doc> {}
/**
* @public
*/
@ -72,7 +77,11 @@ export default plugin(settingId, {
ManageStatuses: '' as Ref<Doc>,
Support: '' as Ref<Doc>,
Privacy: '' as Ref<Doc>,
Terms: '' as Ref<Doc>
Terms: '' as Ref<Doc>,
ClassSetting: '' as Ref<Doc>
},
mixin: {
Editable: '' as Ref<Mixin<Editable>>
},
class: {
SettingsCategory: '' as Ref<Class<SettingsCategory>>,
@ -88,7 +97,8 @@ export default plugin(settingId, {
ManageStatuses: '' as AnyComponent,
Support: '' as AnyComponent,
Privacy: '' as AnyComponent,
Terms: '' as AnyComponent
Terms: '' as AnyComponent,
ClassSetting: '' as AnyComponent
},
string: {
Settings: '' as IntlString,
@ -116,7 +126,8 @@ export default plugin(settingId, {
Signout: '' as IntlString,
InviteWorkspace: '' as IntlString,
SelectWorkspace: '' as IntlString,
Reconnect: '' as IntlString
Reconnect: '' as IntlString,
ClassSetting: '' as IntlString
},
icon: {
EditProfile: '' as Asset,

View File

@ -33,10 +33,6 @@
"Navigation": "Navigation",
"Editor": "Editor",
"MarkdownFormatting": "Formatting",
"Type": "Type",
"WithTime": "WithTime",
"CreatingAttribute": "Creating an attribute",
"CreatingAttributeConfirm": "Warning: It will not be possible for now to change or delete created attribute.",
"CustomizeView": "Customize view",
"Filter": "Filter",
"ClearFilters": "Clear filters",

View File

@ -21,10 +21,6 @@
"Navigation": "Навигация",
"Editor": "Редактор",
"MarkdownFormatting": "Форматирование",
"Type": "Тип",
"WithTime": "Со временем",
"CreatingAttribute": "Создание атрибута",
"CreatingAttributeConfirm": "Предупреждение: Изменить или удалить аттрибут в текущей версии невозможно.",
"MoveLeft": "Переместить влево",
"MoveRight": "Переместить вправо",
"MoveUp": "Переместить вверх",

View File

@ -14,10 +14,9 @@
-->
<script lang="ts">
import { Class, Doc, Ref } from '@anticrm/core'
import presentation, { AttributesBar, getClient, KeyedAttribute } from '@anticrm/presentation'
import { Button, IconAdd, Label, showPopup, Tooltip } from '@anticrm/ui'
import view from '@anticrm/view'
import { createEventDispatcher } from 'svelte'
import { AttributesBar, getClient, KeyedAttribute } from '@anticrm/presentation'
import setting from '@anticrm/setting'
import { Button, getCurrentLocation, Label, navigate, Tooltip } from '@anticrm/ui'
import { collectionsFilter, getFiltredKeys } from '../utils'
export let object: Doc
@ -39,8 +38,6 @@
$: updateKeys(ignoreKeys)
$: label = hierarchy.getClass(_class).label
const dispatch = createEventDispatcher()
</script>
{#if vertical}
@ -62,16 +59,19 @@
</div>
</div>
<div class="tool">
<Tooltip label={presentation.string.Create}>
<Tooltip label={setting.string.ClassSetting}>
<Button
icon={IconAdd}
icon={setting.icon.Setting}
kind={'transparent'}
on:click={(ev) => {
ev.stopPropagation()
showPopup(view.component.CreateAttribute, { _class }, 'top', () => {
updateKeys(ignoreKeys)
dispatch('update')
})
const loc = getCurrentLocation()
loc.path[1] = setting.ids.SettingApp
loc.path[2] = 'classes'
loc.path.length = 3
loc.query = { _class }
loc.fragment = undefined
navigate(loc)
}}
/>
</Tooltip>

View File

@ -38,12 +38,6 @@ 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'
import RefEditor from './components/typeEditors/RefEditor.svelte'
import DocAttributeBar from './components/DocAttributeBar.svelte'
import ViewletSetting from './components/ViewletSetting.svelte'
import TableBrowser from './components/TableBrowser.svelte'
@ -88,12 +82,6 @@ export default async (): Promise<Resources> => ({
TimestampFilter,
TableBrowser,
ViewletSetting,
CreateAttribute,
StringTypeEditor,
BooleanTypeEditor,
NumberTypeEditor,
RefEditor,
DateTypeEditor,
SpacePresenter,
StringEditor,
StringPresenter,

View File

@ -35,12 +35,8 @@ export default mergeIds(viewId, view, {
DeleteObjectConfirm: '' as IntlString,
Assignees: '' as IntlString,
Labels: '' as IntlString,
WithTime: '' as IntlString,
Type: '' as IntlString,
ActionPlaceholder: '' as IntlString,
CreatingAttribute: '' as IntlString,
RestoreDefaults: '' as IntlString,
CreatingAttributeConfirm: '' as IntlString,
Filter: '' as IntlString,
ClearFilters: '' as IntlString,
FilterIs: '' as IntlString,

View File

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