mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 11:01:54 +03:00
TSK-1404 Improve sprint filter (#3164)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
2e6181acf1
commit
bbf81ada7c
@ -1374,6 +1374,22 @@ export function createModel (builder: Builder): void {
|
||||
builder.mixin(tracker.class.Sprint, core.class.Class, view.mixin.ClassFilters, {
|
||||
filters: []
|
||||
})
|
||||
builder.mixin(tracker.class.Sprint, core.class.Class, view.mixin.ClassFilters, {
|
||||
filters: ['status'],
|
||||
strict: true
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.Sprint, core.class.Class, view.mixin.AttributeFilter, {
|
||||
component: tracker.component.SprintFilter
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.TypeSprintStatus, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: tracker.component.SprintStatusPresenter
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.TypeSprintStatus, core.class.Class, view.mixin.AttributeFilter, {
|
||||
component: view.component.ValueFilter
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.Component, core.class.Class, view.mixin.ClassFilters, {
|
||||
filters: []
|
||||
@ -1805,8 +1821,7 @@ export function createModel (builder: Builder): void {
|
||||
viewOptions: sprintOptions,
|
||||
config: [
|
||||
{
|
||||
key: '',
|
||||
presenter: tracker.component.SprintStatusPresenter,
|
||||
key: 'status',
|
||||
props: { width: '1rem', kind: 'list', size: 'small', justify: 'center' }
|
||||
},
|
||||
{ key: '', presenter: tracker.component.SprintPresenter, props: { shouldUseMargin: true } },
|
||||
|
@ -52,7 +52,8 @@ export default mergeIds(trackerId, tracker, {
|
||||
SprintSelector: '' as AnyComponent,
|
||||
IssueStatistics: '' as AnyComponent,
|
||||
TimeSpendReportPopup: '' as AnyComponent,
|
||||
NotificationIssuePresenter: '' as AnyComponent
|
||||
NotificationIssuePresenter: '' as AnyComponent,
|
||||
SprintFilter: '' as AnyComponent
|
||||
},
|
||||
app: {
|
||||
Tracker: '' as Ref<Application>
|
||||
|
@ -42,7 +42,7 @@ import type {
|
||||
IgnoreActions,
|
||||
InlineAttributEditor,
|
||||
KeyBinding,
|
||||
KeyFilter,
|
||||
KeyFilterPreset,
|
||||
LinkPresenter,
|
||||
LinkProvider,
|
||||
ListHeaderExtra,
|
||||
@ -126,7 +126,7 @@ export class TFilterMode extends TDoc implements FilterMode {
|
||||
|
||||
@Mixin(view.mixin.ClassFilters, core.class.Class)
|
||||
export class TClassFilters extends TClass implements ClassFilters {
|
||||
filters!: (string | KeyFilter)[]
|
||||
filters!: (string | KeyFilterPreset)[]
|
||||
ignoreKeys?: string[] | undefined
|
||||
strict?: boolean | undefined
|
||||
}
|
||||
|
@ -0,0 +1,168 @@
|
||||
<!--
|
||||
// Copyright © 2023 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, DocumentQuery, FindResult, Ref, SortingOrder } from '@hcengineering/core'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import presentation, { getClient } from '@hcengineering/presentation'
|
||||
import { Project, Sprint, SprintStatus } from '@hcengineering/tracker'
|
||||
import ui, { deviceOptionsStore, Icon, Label, CheckBox, Loading, resizeObserver } from '@hcengineering/ui'
|
||||
import view, { Filter } from '@hcengineering/view'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import tracker from '../../plugin'
|
||||
import { sprintStatusAssets } from '../../types'
|
||||
import SprintTitlePresenter from './SprintTitlePresenter.svelte'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let space: Ref<Project> | undefined = undefined
|
||||
export let filter: Filter
|
||||
export let onChange: (e: Filter) => void
|
||||
|
||||
filter.modes = filter.modes ?? [view.filter.FilterObjectIn, view.filter.FilterObjectNin]
|
||||
filter.mode = filter.mode ?? filter.modes[0]
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let search: string = ''
|
||||
let phTraslate: string = ''
|
||||
let searchInput: HTMLInputElement
|
||||
$: translate(presentation.string.Search, {}).then((res) => {
|
||||
phTraslate = res
|
||||
})
|
||||
|
||||
let values: Sprint[] = []
|
||||
let objectsPromise: Promise<FindResult<Sprint>> | undefined = undefined
|
||||
let selectedValues: Set<Ref<Sprint> | undefined | null> = new Set()
|
||||
|
||||
const client = getClient()
|
||||
async function getValues (search: string): Promise<void> {
|
||||
if (objectsPromise) {
|
||||
await objectsPromise
|
||||
}
|
||||
values = []
|
||||
selectedValues.clear()
|
||||
const spaceQuery: DocumentQuery<Sprint> = space ? { space } : {}
|
||||
const resultQuery: DocumentQuery<Sprint> =
|
||||
search !== ''
|
||||
? {
|
||||
label: { $like: '%' + search + '%' }
|
||||
}
|
||||
: {}
|
||||
objectsPromise = client.findAll(
|
||||
tracker.class.Sprint,
|
||||
{ ...resultQuery, ...spaceQuery },
|
||||
{
|
||||
sort: { label: SortingOrder.Ascending }
|
||||
}
|
||||
)
|
||||
const res = await objectsPromise
|
||||
|
||||
values = res
|
||||
selectedValues = new Set(res.map((p) => p._id).filter((p) => filter.value.includes(p)))
|
||||
if (filter.value.includes(undefined) || filter.value.includes(null)) {
|
||||
selectedValues.add(undefined)
|
||||
}
|
||||
objectsPromise = undefined
|
||||
}
|
||||
|
||||
function isSelected (value: Ref<Sprint> | undefined, values: Set<Ref<Sprint> | undefined | null>): boolean {
|
||||
return values.has(value)
|
||||
}
|
||||
|
||||
function toggle (value: Ref<Sprint> | undefined): void {
|
||||
if (isSelected(value, selectedValues)) {
|
||||
selectedValues.delete(value)
|
||||
} else {
|
||||
selectedValues.add(value)
|
||||
}
|
||||
selectedValues = selectedValues
|
||||
filter.value = Array.from(selectedValues)
|
||||
onChange(filter)
|
||||
}
|
||||
|
||||
function getStatusItem (status: SprintStatus, docs: Sprint[]): Sprint[] {
|
||||
return docs.filter((p) => p.status === status)
|
||||
}
|
||||
|
||||
function getStatuses (): SprintStatus[] {
|
||||
const res = Array.from(Object.values(SprintStatus).filter((v) => !isNaN(Number(v)))) as SprintStatus[]
|
||||
return res
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (searchInput && !$deviceOptionsStore.isMobile) searchInput.focus()
|
||||
})
|
||||
getValues(search)
|
||||
</script>
|
||||
|
||||
<div class="selectPopup" use:resizeObserver={() => dispatch('changeContent')}>
|
||||
<div class="header">
|
||||
<input
|
||||
bind:this={searchInput}
|
||||
type="text"
|
||||
bind:value={search}
|
||||
on:change={() => {
|
||||
getValues(search)
|
||||
}}
|
||||
placeholder={phTraslate}
|
||||
/>
|
||||
</div>
|
||||
<div class="scroll">
|
||||
<div class="box">
|
||||
{#if objectsPromise}
|
||||
<Loading />
|
||||
{:else}
|
||||
<button
|
||||
class="menu-item"
|
||||
on:click={() => {
|
||||
toggle(undefined)
|
||||
}}
|
||||
>
|
||||
<div class="flex clear-mins">
|
||||
<div class="check pointer-events-none">
|
||||
<CheckBox checked={isSelected(undefined, selectedValues)} primary />
|
||||
</div>
|
||||
<Label label={ui.string.NotSelected} />
|
||||
</div>
|
||||
</button>
|
||||
{#each getStatuses() as group}
|
||||
{@const status = sprintStatusAssets[group]}
|
||||
{@const items = getStatusItem(group, values)}
|
||||
{#if items.length > 0}
|
||||
<div class="flex-row-center p-1">
|
||||
<Icon icon={status.icon} size={'small'} />
|
||||
<div class="ml-2">
|
||||
<Label label={status.label} />
|
||||
</div>
|
||||
</div>
|
||||
{#each items as doc}
|
||||
<button
|
||||
class="menu-item"
|
||||
on:click={() => {
|
||||
toggle(doc._id)
|
||||
}}
|
||||
>
|
||||
<div class="flex clear-mins">
|
||||
<div class="check pointer-events-none">
|
||||
<CheckBox checked={isSelected(doc._id, selectedValues)} primary />
|
||||
</div>
|
||||
<SprintTitlePresenter value={doc} />
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -20,7 +20,7 @@
|
||||
|
||||
export let value: WithLookup<Sprint>
|
||||
export let shouldShowAvatar: boolean = true
|
||||
export let onClick: () => void | undefined
|
||||
export let onClick: (() => void) | undefined = undefined
|
||||
export let disabled = false
|
||||
export let inline: boolean = false
|
||||
|
||||
|
@ -13,42 +13,29 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Sprint, SprintStatus } from '@hcengineering/tracker'
|
||||
import { SprintStatus } from '@hcengineering/tracker'
|
||||
import type { ButtonKind, ButtonSize } from '@hcengineering/ui'
|
||||
import tracker from '../../plugin'
|
||||
|
||||
import SprintStatusSelector from './SprintStatusSelector.svelte'
|
||||
|
||||
export let value: Sprint
|
||||
export let isEditable: boolean = true
|
||||
export let shouldShowLabel: boolean = false
|
||||
export let value: SprintStatus
|
||||
export let onChange: ((value: SprintStatus | undefined) => void) | undefined = undefined
|
||||
export let kind: ButtonKind = 'link'
|
||||
export let size: ButtonSize = 'large'
|
||||
export let justify: 'left' | 'center' = 'left'
|
||||
export let width: string | undefined = '100%'
|
||||
|
||||
const client = getClient()
|
||||
|
||||
const handleComponentStatusChanged = async (newStatus: SprintStatus | undefined) => {
|
||||
if (!isEditable || newStatus === undefined || value.status === newStatus) {
|
||||
return
|
||||
}
|
||||
|
||||
await client.update(value, { status: newStatus })
|
||||
}
|
||||
$: isEditable = onChange !== undefined
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
<SprintStatusSelector
|
||||
{kind}
|
||||
{size}
|
||||
{width}
|
||||
{justify}
|
||||
{isEditable}
|
||||
{shouldShowLabel}
|
||||
showTooltip={isEditable ? { label: tracker.string.SetStatus } : undefined}
|
||||
selectedSprintStatus={value.status}
|
||||
onSprintStatusChange={handleComponentStatusChanged}
|
||||
/>
|
||||
{/if}
|
||||
<SprintStatusSelector
|
||||
{kind}
|
||||
{size}
|
||||
{width}
|
||||
{justify}
|
||||
{isEditable}
|
||||
showTooltip={isEditable ? { label: tracker.string.SetStatus } : undefined}
|
||||
selectedSprintStatus={value}
|
||||
onSprintStatusChange={onChange}
|
||||
/>
|
||||
|
@ -136,6 +136,7 @@ import ProjectPresenter from './components/projects/ProjectPresenter.svelte'
|
||||
import ProjectSpacePresenter from './components/projects/ProjectSpacePresenter.svelte'
|
||||
import IssueStatistics from './components/sprints/IssueStatistics.svelte'
|
||||
import SprintRefPresenter from './components/sprints/SprintRefPresenter.svelte'
|
||||
import SprintFilter from './components/sprints/SprintFilter.svelte'
|
||||
|
||||
export { default as SubIssueList } from './components/issues/edit/SubIssueList.svelte'
|
||||
|
||||
@ -445,7 +446,8 @@ export default async (): Promise<Resources> => ({
|
||||
TimeSpendReportPopup,
|
||||
SprintDatePresenter,
|
||||
SprintLeadPresenter,
|
||||
NotificationIssuePresenter
|
||||
NotificationIssuePresenter,
|
||||
SprintFilter
|
||||
},
|
||||
completion: {
|
||||
IssueQuery: async (client: Client, query: string, filter?: { in?: RelatedDocument[], nin?: RelatedDocument[] }) =>
|
||||
|
@ -10,6 +10,7 @@
|
||||
"lint": "svelte-check && eslint",
|
||||
"lint:fix": "eslint --fix src",
|
||||
"format": "prettier --write --plugin-search-dir=. src && eslint --fix src",
|
||||
"svelte-check": "svelte-check --output human",
|
||||
"build:watch": "tsc --incremental --noEmit --outDir ./dist_cache"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -25,7 +25,7 @@
|
||||
showPopup,
|
||||
Submenu
|
||||
} from '@hcengineering/ui'
|
||||
import { ClassFilters, Filter, KeyFilter } from '@hcengineering/view'
|
||||
import { ClassFilters, Filter, KeyFilter, KeyFilterPreset } from '@hcengineering/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { buildFilterKey, FilterQuery } from '../../filter'
|
||||
import view from '../../plugin'
|
||||
@ -36,6 +36,7 @@
|
||||
export let filter: Filter | undefined
|
||||
export let index: number
|
||||
export let onChange: (e: Filter) => void
|
||||
export let nestedFrom: KeyFilter | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
@ -43,7 +44,7 @@
|
||||
function getFilters (_class: Ref<Class<Doc>>, mixin: ClassFilters): KeyFilter[] {
|
||||
if (mixin.filters === undefined) return []
|
||||
const filters = mixin.filters.map((p) => {
|
||||
return typeof p === 'string' ? buildFilterFromKey(_class, p) : p
|
||||
return typeof p === 'string' ? buildFilterFromKey(_class, p) : buildFilterFromPreset(p)
|
||||
})
|
||||
const result: KeyFilter[] = []
|
||||
for (const filter of filters) {
|
||||
@ -52,6 +53,19 @@
|
||||
return result
|
||||
}
|
||||
|
||||
function buildFilterFromPreset (p: KeyFilterPreset): KeyFilter | undefined {
|
||||
if (p.key !== '') {
|
||||
const attribute = hierarchy.getAttribute(p._class, p.key)
|
||||
const clazz = hierarchy.getClass(p._class)
|
||||
return {
|
||||
...p,
|
||||
attribute,
|
||||
label: p.label ?? attribute.label,
|
||||
icon: p.icon ?? attribute.icon ?? clazz.icon ?? view.icon.Setting
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildFilterFromKey (_class: Ref<Class<Doc>>, key: string): KeyFilter | undefined {
|
||||
const attribute = hierarchy.getAttribute(_class, key)
|
||||
return buildFilterKey(hierarchy, _class, key, attribute)
|
||||
@ -69,7 +83,7 @@
|
||||
return
|
||||
}
|
||||
const value = getValue(attribute.name, attribute.type)
|
||||
if (result.findIndex((p) => p.attribute.name === value) !== -1) {
|
||||
if (result.findIndex((p) => p.attribute?.name === value) !== -1) {
|
||||
return
|
||||
}
|
||||
const filter = buildFilterKey(hierarchy, _class, value, attribute)
|
||||
@ -93,7 +107,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
function getTypes (_class: Ref<Class<Doc>>): KeyFilter[] {
|
||||
function getTypes (_class: Ref<Class<Doc>>, nestedFrom: KeyFilter | undefined): KeyFilter[] {
|
||||
if (nestedFrom !== undefined) {
|
||||
return getNestedTypes(nestedFrom)
|
||||
} else {
|
||||
return getOwnTypes(_class)
|
||||
}
|
||||
}
|
||||
|
||||
function getNestedTypes (type: KeyFilter): KeyFilter[] {
|
||||
const targetClass = (hierarchy.getAttribute(type._class, type.key).type as RefTo<Doc>).to
|
||||
return getOwnTypes(targetClass)
|
||||
}
|
||||
|
||||
function getOwnTypes (_class: Ref<Class<Doc>>): KeyFilter[] {
|
||||
const clazz = hierarchy.getClass(_class)
|
||||
const mixin = hierarchy.as(clazz, view.mixin.ClassFilters)
|
||||
const result = getFilters(_class, mixin)
|
||||
@ -159,24 +186,48 @@
|
||||
closePopup()
|
||||
closeTooltip()
|
||||
|
||||
showPopup(
|
||||
type.component,
|
||||
{
|
||||
_class,
|
||||
space,
|
||||
filter: filter || {
|
||||
key: type,
|
||||
value: [],
|
||||
index
|
||||
if (nestedFrom !== undefined && type !== nestedFrom) {
|
||||
const change = (e: Filter | undefined) => {
|
||||
if (nestedFrom) {
|
||||
setNestedFilter(nestedFrom, e)
|
||||
}
|
||||
}
|
||||
const targetClass = (hierarchy.getAttribute(nestedFrom._class, nestedFrom.key).type as RefTo<Doc>).to
|
||||
showPopup(
|
||||
type.component,
|
||||
{
|
||||
_class: targetClass,
|
||||
space,
|
||||
filter: filter || {
|
||||
key: type,
|
||||
value: [],
|
||||
index
|
||||
},
|
||||
onChange: change
|
||||
},
|
||||
onChange
|
||||
},
|
||||
target
|
||||
)
|
||||
target
|
||||
)
|
||||
} else {
|
||||
showPopup(
|
||||
type.component,
|
||||
{
|
||||
_class,
|
||||
space,
|
||||
filter: filter || {
|
||||
key: type,
|
||||
value: [],
|
||||
index
|
||||
},
|
||||
onChange
|
||||
},
|
||||
target
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function hasNested (type: KeyFilter): boolean {
|
||||
const targetClass = (hierarchy.getAttribute(type._class, type.key).type as RefTo<Doc>).to
|
||||
if (targetClass === undefined) return false
|
||||
const clazz = hierarchy.getClass(targetClass)
|
||||
return hierarchy.hasMixin(clazz, view.mixin.ClassFilters)
|
||||
}
|
||||
@ -199,38 +250,51 @@
|
||||
dispatch('close')
|
||||
}
|
||||
|
||||
function getNestedProps (type: KeyFilter): any {
|
||||
const targetClass = (hierarchy.getAttribute(type._class, type.key).type as RefTo<Doc>).to
|
||||
return {
|
||||
_class: targetClass,
|
||||
space,
|
||||
index,
|
||||
target,
|
||||
onChange: (e: Filter | undefined) => {
|
||||
setNestedFilter(type, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const elements: HTMLElement[] = []
|
||||
</script>
|
||||
|
||||
<div class="selectPopup" use:resizeObserver={() => dispatch('changeContent')}>
|
||||
<Scroller>
|
||||
{#each getTypes(_class) as type, i}
|
||||
{#if filter === undefined && type.component === view.component.ObjectFilter && hasNested(type)}
|
||||
{#if nestedFrom}
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
<button
|
||||
class="menu-item withIcon"
|
||||
on:keydown={(event) => keyDown(event, -1)}
|
||||
on:mouseover={(event) => {
|
||||
event.currentTarget.focus()
|
||||
}}
|
||||
on:click={() => {
|
||||
if (nestedFrom) {
|
||||
click(nestedFrom)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="icon mr-3">
|
||||
{#if nestedFrom.icon}
|
||||
<Icon icon={nestedFrom.icon} size={'small'} />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="overflow-label pr-1"><Label label={nestedFrom.label} /></div>
|
||||
</button>
|
||||
{/if}
|
||||
{#each getTypes(_class, nestedFrom) as type, i}
|
||||
{#if filter === undefined && hasNested(type)}
|
||||
<Submenu
|
||||
bind:element={elements[i]}
|
||||
on:keydown={(event) => keyDown(event, i)}
|
||||
on:mouseover={() => {
|
||||
elements[i]?.focus()
|
||||
}}
|
||||
on:click={() => {
|
||||
click(type)
|
||||
}}
|
||||
icon={type.icon}
|
||||
label={type.label}
|
||||
props={getNestedProps(type)}
|
||||
props={{
|
||||
_class,
|
||||
space,
|
||||
index,
|
||||
target,
|
||||
onChange,
|
||||
nestedFrom: type
|
||||
}}
|
||||
options={{ component: view.component.FilterTypePopup }}
|
||||
withHover
|
||||
/>
|
||||
|
@ -7,7 +7,8 @@ import core, {
|
||||
FindResult,
|
||||
Hierarchy,
|
||||
ObjQueryType,
|
||||
Ref
|
||||
Ref,
|
||||
RefTo
|
||||
} from '@hcengineering/core'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { LiveQuery, createQuery, getClient } from '@hcengineering/presentation'
|
||||
@ -198,12 +199,28 @@ export function buildFilterKey (
|
||||
key: string,
|
||||
attribute: AnyAttribute
|
||||
): KeyFilter | undefined {
|
||||
const attrOf = hierarchy.getClass(attribute.attributeOf)
|
||||
const isRef = hierarchy.isDerived(attribute.type._class, core.class.RefTo)
|
||||
if (isRef) {
|
||||
const targetClass = (attribute.type as RefTo<Doc>).to
|
||||
const filter = hierarchy.classHierarchyMixin(targetClass, view.mixin.AttributeFilter)
|
||||
if (filter?.component !== undefined) {
|
||||
return {
|
||||
_class,
|
||||
key,
|
||||
attribute,
|
||||
label: attribute.label,
|
||||
icon: attribute.icon ?? filter.icon ?? attrOf.icon ?? view.icon.Setting,
|
||||
component: filter.component
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isCollection = hierarchy.isDerived(attribute.type._class, core.class.Collection)
|
||||
const targetClass = isCollection ? (attribute.type as Collection<AttachedDoc>).of : attribute.type._class
|
||||
const clazz = hierarchy.getClass(targetClass)
|
||||
const filter = hierarchy.as(clazz, view.mixin.AttributeFilter)
|
||||
|
||||
const attrOf = hierarchy.getClass(attribute.attributeOf)
|
||||
if (filter.component === undefined) return undefined
|
||||
return {
|
||||
_class,
|
||||
|
@ -49,9 +49,19 @@ import type {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface KeyFilter {
|
||||
export interface KeyFilterPreset {
|
||||
_class: Ref<Class<Doc>>
|
||||
key: string
|
||||
attribute?: AnyAttribute
|
||||
component: AnyComponent
|
||||
label?: IntlString
|
||||
icon?: Asset | AnySvelteComponent | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface KeyFilter extends KeyFilterPreset {
|
||||
attribute: AnyAttribute
|
||||
component: AnyComponent
|
||||
label: IntlString
|
||||
@ -102,7 +112,7 @@ export interface FilteredView extends Preference {
|
||||
* @public
|
||||
*/
|
||||
export interface ClassFilters extends Class<Doc> {
|
||||
filters: (KeyFilter | string)[]
|
||||
filters: (KeyFilterPreset | string)[]
|
||||
ignoreKeys?: string[]
|
||||
|
||||
// Ignore attributes not specified in the "filters" array
|
||||
|
Loading…
Reference in New Issue
Block a user