mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 19:11:33 +03:00
Multiselect enums (#2573)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
f2d83afb18
commit
6b05833c8b
@ -302,6 +302,10 @@ export function createModel (builder: Builder): void {
|
||||
editor: setting.component.EnumTypeEditor
|
||||
})
|
||||
|
||||
builder.mixin(core.class.ArrOf, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: setting.component.ArrayEditor
|
||||
})
|
||||
|
||||
builder.mixin(core.class.Class, core.class.Class, view.mixin.IgnoreActions, {
|
||||
actions: [view.action.Delete]
|
||||
})
|
||||
|
@ -40,7 +40,8 @@ export default mergeIds(settingId, setting, {
|
||||
RefEditor: '' as AnyComponent,
|
||||
EnumTypeEditor: '' as AnyComponent,
|
||||
Owners: '' as AnyComponent,
|
||||
CreateMixin: '' as AnyComponent
|
||||
CreateMixin: '' as AnyComponent,
|
||||
ArrayEditor: '' as AnyComponent
|
||||
},
|
||||
category: {
|
||||
Settings: '' as Ref<ActionCategory>
|
||||
|
@ -383,6 +383,9 @@ export function createModel (builder: Builder): void {
|
||||
builder.mixin(core.class.Class, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: view.component.ClassPresenter
|
||||
})
|
||||
builder.mixin(core.class.EnumOf, core.class.Class, view.mixin.ArrayEditor, {
|
||||
inlineEditor: view.component.EnumArrayEditor
|
||||
})
|
||||
|
||||
classPresenter(builder, core.class.TypeRelatedDocument, view.component.ObjectPresenter)
|
||||
|
||||
|
@ -60,6 +60,7 @@ export default mergeIds(viewId, view, {
|
||||
ClassPresenter: '' as AnyComponent,
|
||||
ClassRefPresenter: '' as AnyComponent,
|
||||
EnumEditor: '' as AnyComponent,
|
||||
EnumArrayEditor: '' as AnyComponent,
|
||||
HTMLEditor: '' as AnyComponent,
|
||||
MarkupEditor: '' as AnyComponent,
|
||||
MarkupEditorPopup: '' as AnyComponent,
|
||||
|
@ -27,7 +27,8 @@
|
||||
export let label: IntlString
|
||||
export let placeholder: IntlString | undefined = ui.string.SearchDots
|
||||
export let items: DropdownTextItem[]
|
||||
export let selected: DropdownTextItem['id'] | undefined = undefined
|
||||
export let multiselect = false
|
||||
export let selected: DropdownTextItem['id'] | DropdownTextItem['id'][] | undefined = multiselect ? [] : undefined
|
||||
|
||||
export let kind: ButtonKind = 'no-border'
|
||||
export let size: ButtonSize = 'small'
|
||||
@ -41,13 +42,10 @@
|
||||
|
||||
let container: HTMLElement
|
||||
let opened: boolean = false
|
||||
let isDisabled = false
|
||||
$: isDisabled = items.length === 0
|
||||
|
||||
let selectedItem = items.find((x) => x.id === selected)
|
||||
$: selectedItem = items.find((x) => x.id === selected)
|
||||
$: selectedItem = multiselect ? items.filter((p) => selected?.includes(p.id)) : items.find((x) => x.id === selected)
|
||||
$: if (autoSelect && selected === undefined && items[0] !== undefined) {
|
||||
selected = items[0].id
|
||||
selected = multiselect ? [items[0].id] : items[0].id
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
@ -66,19 +64,42 @@
|
||||
on:click={() => {
|
||||
if (!opened) {
|
||||
opened = true
|
||||
showPopup(DropdownLabelsPopup, { placeholder, items, selected }, container, (result) => {
|
||||
if (result) {
|
||||
selected = result
|
||||
dispatch('selected', result)
|
||||
showPopup(
|
||||
DropdownLabelsPopup,
|
||||
{ placeholder, items, multiselect, selected },
|
||||
container,
|
||||
(result) => {
|
||||
if (result) {
|
||||
selected = result
|
||||
dispatch('selected', result)
|
||||
}
|
||||
opened = false
|
||||
mgr?.setFocusPos(focusIndex)
|
||||
},
|
||||
(result) => {
|
||||
if (result) {
|
||||
selected = result
|
||||
dispatch('selected', result)
|
||||
}
|
||||
}
|
||||
opened = false
|
||||
mgr?.setFocusPos(focusIndex)
|
||||
})
|
||||
)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span slot="content" class="overflow-label disabled" class:content-color={selectedItem === undefined}>
|
||||
{#if selectedItem}{selectedItem.label}{:else}<Label label={label ?? ui.string.NotSelected} />{/if}
|
||||
{#if Array.isArray(selectedItem)}
|
||||
{#if selectedItem.length > 0}
|
||||
{#each selectedItem as seleceted, i}
|
||||
<span class:ml-1={i !== 0}>{seleceted.label}</span>
|
||||
{/each}
|
||||
{:else}
|
||||
<Label label={label ?? ui.string.NotSelected} />
|
||||
{/if}
|
||||
{:else if selectedItem}
|
||||
{selectedItem.label}
|
||||
{:else}
|
||||
<Label label={label ?? ui.string.NotSelected} />
|
||||
{/if}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -24,7 +24,8 @@
|
||||
|
||||
export let placeholder: IntlString = plugin.string.SearchDots
|
||||
export let items: DropdownTextItem[]
|
||||
export let selected: DropdownTextItem['id'] | undefined = undefined
|
||||
export let selected: DropdownTextItem['id'] | DropdownTextItem['id'][] | undefined = undefined
|
||||
export let multiselect: boolean = false
|
||||
|
||||
let search: string = ''
|
||||
let phTraslate: string = ''
|
||||
@ -45,8 +46,18 @@
|
||||
|
||||
async function handleSelection (evt: Event | undefined, selection: number): Promise<void> {
|
||||
const item = objects[selection]
|
||||
|
||||
dispatch('close', item.id)
|
||||
if (multiselect && Array.isArray(selected)) {
|
||||
const index = selected.indexOf(item.id)
|
||||
if (index !== -1) {
|
||||
selected.splice(index, 1)
|
||||
selected = selected
|
||||
} else {
|
||||
selected = selected === undefined ? [item.id] : [...selected, item.id]
|
||||
}
|
||||
dispatch('update', selected)
|
||||
} else {
|
||||
dispatch('close', item.id)
|
||||
}
|
||||
}
|
||||
|
||||
function onKeydown (key: KeyboardEvent): void {
|
||||
@ -71,6 +82,17 @@
|
||||
dispatch('close')
|
||||
}
|
||||
}
|
||||
|
||||
function isSelected (
|
||||
selected: DropdownTextItem['id'] | DropdownTextItem['id'][] | undefined,
|
||||
item: DropdownTextItem
|
||||
): boolean {
|
||||
if (Array.isArray(selected)) {
|
||||
return selected.includes(item.id)
|
||||
} else {
|
||||
return item.id === selected
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
@ -99,11 +121,22 @@
|
||||
<button
|
||||
class="menu-item flex-between w-full"
|
||||
on:click={() => {
|
||||
dispatch('close', item.id)
|
||||
if (multiselect && Array.isArray(selected)) {
|
||||
const index = selected.indexOf(item.id)
|
||||
if (index !== -1) {
|
||||
selected.splice(index, 1)
|
||||
selected = selected
|
||||
} else {
|
||||
selected = selected === undefined ? [item.id] : [...selected, item.id]
|
||||
}
|
||||
dispatch('update', selected)
|
||||
} else {
|
||||
dispatch('close', item.id)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="flex-grow caption-color lines-limit-2">{item.label}</div>
|
||||
{#if item.id === selected}
|
||||
{#if isSelected(selected, item)}
|
||||
<div class="check-right"><CheckBox checked primary /></div>
|
||||
{/if}
|
||||
</button>
|
||||
|
@ -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, { ArrOf, Class, Doc, Ref, Type } from '@hcengineering/core'
|
||||
import { ArrOf as createArrOf } from '@hcengineering/model'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { AnyComponent, Component, DropdownLabelsIntl, Label } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import setting from '../../plugin'
|
||||
|
||||
export let type: ArrOf<Doc> | undefined
|
||||
export let editable: boolean = true
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
const descendants = hierarchy.getDescendants(core.class.Type)
|
||||
|
||||
const types: Class<Type<Doc>>[] = descendants
|
||||
.map((p) => hierarchy.getClass(p))
|
||||
.filter((p) => {
|
||||
return (
|
||||
hierarchy.hasMixin(p, view.mixin.ArrayEditor) &&
|
||||
hierarchy.hasMixin(p, view.mixin.ObjectEditor) &&
|
||||
p.label !== undefined
|
||||
)
|
||||
})
|
||||
|
||||
let refClass: Ref<Doc> | undefined = type !== undefined ? hierarchy.getClass(type.of._class)._id : undefined
|
||||
|
||||
$: selected = types.find((p) => p._id === refClass)
|
||||
|
||||
const handleChange = (e: any) => {
|
||||
const type = e.detail?.type
|
||||
const res = { type: createArrOf(type) }
|
||||
dispatch('change', res)
|
||||
}
|
||||
|
||||
function getComponent (selected: Class<Type<Doc>>): AnyComponent {
|
||||
const editor = hierarchy.as(selected, view.mixin.ObjectEditor)
|
||||
return editor.editor
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex-col">
|
||||
<div class="flex-row-center flex-grow">
|
||||
<Label label={setting.string.Type} />
|
||||
<div class="ml-4">
|
||||
{#if editable}
|
||||
<DropdownLabelsIntl
|
||||
label={core.string.Class}
|
||||
items={types.map((p) => {
|
||||
return { id: p._id, label: p.label }
|
||||
})}
|
||||
width="8rem"
|
||||
bind:selected={refClass}
|
||||
/>
|
||||
{:else if selected}
|
||||
<Label label={selected.label} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if selected}
|
||||
<div class="flex mt-4">
|
||||
<Component
|
||||
is={getComponent(selected)}
|
||||
props={{
|
||||
type: type?.of,
|
||||
editable
|
||||
}}
|
||||
on:change={handleChange}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
@ -38,6 +38,7 @@ import DateTypeEditor from './components/typeEditors/DateTypeEditor.svelte'
|
||||
import EnumTypeEditor from './components/typeEditors/EnumTypeEditor.svelte'
|
||||
import HyperlinkTypeEditor from './components/typeEditors/HyperlinkTypeEditor.svelte'
|
||||
import NumberTypeEditor from './components/typeEditors/NumberTypeEditor.svelte'
|
||||
import ArrayEditor from './components/typeEditors/ArrayEditor.svelte'
|
||||
import RefEditor from './components/typeEditors/RefEditor.svelte'
|
||||
import StringTypeEditor from './components/typeEditors/StringTypeEditor.svelte'
|
||||
import WorkspaceSettings from './components/WorkspaceSettings.svelte'
|
||||
@ -90,6 +91,7 @@ export default async (): Promise<Resources> => ({
|
||||
RefEditor,
|
||||
DateTypeEditor,
|
||||
EnumTypeEditor,
|
||||
ArrayEditor,
|
||||
EditEnum,
|
||||
EnumSetting,
|
||||
Owners,
|
||||
|
59
plugins/view-resources/src/components/EnumArrayEditor.svelte
Normal file
59
plugins/view-resources/src/components/EnumArrayEditor.svelte
Normal file
@ -0,0 +1,59 @@
|
||||
<!--
|
||||
// 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, { ArrOf, EnumOf } from '@hcengineering/core'
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { DropdownLabels, DropdownTextItem } from '@hcengineering/ui'
|
||||
|
||||
export let label: IntlString
|
||||
export let value: string[] = []
|
||||
export let type: ArrOf<string>
|
||||
export let onChange: (value: string[]) => void
|
||||
|
||||
let items: DropdownTextItem[] = []
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
query.query(
|
||||
core.class.Enum,
|
||||
{
|
||||
_id: (type.of as EnumOf).of
|
||||
},
|
||||
(res) => {
|
||||
items =
|
||||
res[0]?.enumValues?.map((p) => {
|
||||
return { id: p, label: p }
|
||||
}) ?? []
|
||||
},
|
||||
{ limit: 1 }
|
||||
)
|
||||
</script>
|
||||
|
||||
<DropdownLabels
|
||||
selected={value ?? []}
|
||||
{items}
|
||||
{label}
|
||||
useFlexGrow={true}
|
||||
justify={'left'}
|
||||
size={'large'}
|
||||
kind={'link'}
|
||||
width={'100%'}
|
||||
multiselect
|
||||
autoSelect={false}
|
||||
on:selected={(e) => {
|
||||
onChange(e.detail)
|
||||
}}
|
||||
/>
|
@ -14,7 +14,15 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
export let value: string
|
||||
export let value: string | string[]
|
||||
</script>
|
||||
|
||||
<span class="lines-limit-2 select-text">{value}</span>
|
||||
<span class="lines-limit-2 select-text">
|
||||
{#if Array.isArray(value)}
|
||||
{#each value as str, i}
|
||||
<span class:ml-1={i !== 0}>{str}</span>
|
||||
{/each}
|
||||
{:else}
|
||||
{value}
|
||||
{/if}
|
||||
</span>
|
||||
|
@ -63,6 +63,7 @@ import UpDownNavigator from './components/UpDownNavigator.svelte'
|
||||
import ValueSelector from './components/ValueSelector.svelte'
|
||||
import ViewletSettingButton from './components/ViewletSettingButton.svelte'
|
||||
import SpaceRefPresenter from './components/SpaceRefPresenter.svelte'
|
||||
import EnumArrayEditor from './components/EnumArrayEditor.svelte'
|
||||
|
||||
import {
|
||||
afterResult,
|
||||
@ -174,7 +175,8 @@ export default async (): Promise<Resources> => ({
|
||||
ListView,
|
||||
GrowPresenter,
|
||||
IndexedDocumentPreview,
|
||||
SpaceRefPresenter
|
||||
SpaceRefPresenter,
|
||||
EnumArrayEditor
|
||||
},
|
||||
popup: {
|
||||
PositionElementAlignment
|
||||
|
Loading…
Reference in New Issue
Block a user