Channel filter (#2663)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2023-02-20 16:03:35 +06:00 committed by GitHub
parent f71554a016
commit 126d57884f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 190 additions and 8 deletions

View File

@ -487,6 +487,10 @@ export function createModel (builder: Builder): void {
filters: ['_class', 'city', 'modifiedOn']
})
builder.mixin(contact.class.Channel, core.class.Class, view.mixin.AttributeFilter, {
component: contact.component.ChannelFilter
})
builder.mixin(contact.class.Contact, core.class.Class, setting.mixin.Editable, {
value: true
})
@ -552,6 +556,26 @@ export function createModel (builder: Builder): void {
builder.mixin(contact.class.Contact, core.class.Class, core.mixin.FullTextSearchContext, {
fullTextSummary: true
})
builder.createDoc(
view.class.FilterMode,
core.space.Model,
{
label: view.string.FilterIsEither,
result: contact.function.FilterChannelInResult
},
contact.filter.FilterChannelIn
)
builder.createDoc(
view.class.FilterMode,
core.space.Model,
{
label: view.string.FilterIsNot,
result: contact.function.FilterChannelNinResult
},
contact.filter.FilterChannelNin
)
}
export { contactOperation } from './migration'

View File

@ -46,7 +46,8 @@ export default mergeIds(contactId, contact, {
EmployeeArrayEditor: '' as AnyComponent,
EmployeeEditor: '' as AnyComponent,
CreateEmployee: '' as AnyComponent,
AccountArrayEditor: '' as AnyComponent
AccountArrayEditor: '' as AnyComponent,
ChannelFilter: '' as AnyComponent
},
string: {
Persons: '' as IntlString,

View File

@ -0,0 +1,110 @@
<!--
// 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 { ChannelProvider } from '@hcengineering/contact'
import { Class, Doc, FindResult, Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Button, CheckBox, Icon, Label, Loading, resizeObserver } from '@hcengineering/ui'
import { Filter } from '@hcengineering/view'
import { FilterQuery } from '@hcengineering/view-resources'
import view from '@hcengineering/view-resources/src/plugin'
import { createEventDispatcher } from 'svelte'
import contact from '../plugin'
export let _class: Ref<Class<Doc>>
export let filter: Filter
export let onChange: (e: Filter) => void
filter.onRemove = () => {
FilterQuery.remove(filter.index)
}
const client = getClient()
let selected: Ref<ChannelProvider>[] = filter.value
const level: number = filter.props?.level ?? 0
filter.modes = [contact.filter.FilterChannelIn, contact.filter.FilterChannelNin]
filter.mode = filter.mode === undefined ? filter.modes[0] : filter.mode
let objects: ChannelProvider[] = []
let objectsPromise: Promise<FindResult<ChannelProvider>> | undefined
async function getValues (): Promise<void> {
if (objectsPromise) {
await objectsPromise
}
objectsPromise = client.findAll(contact.class.ChannelProvider, {})
objects = await objectsPromise
objectsPromise = undefined
}
const isSelected = (element: ChannelProvider): boolean => {
if (selected.filter((p) => p === element._id).length > 0) return true
return false
}
const checkSelected = (element: ChannelProvider): void => {
if (isSelected(element)) {
selected = selected.filter((p) => p !== element._id)
} else {
selected = [...selected, element._id]
}
objects = objects
}
const dispatch = createEventDispatcher()
getValues()
</script>
<div class="selectPopup" use:resizeObserver={() => dispatch('changeContent')}>
<div class="scroll">
<div class="box">
{#if objectsPromise}
<Loading />
{:else}
{#each objects as element}
<button
class="menu-item"
on:click={() => {
checkSelected(element)
}}
>
<div class="flex-between w-full">
<div class="flex">
<div class="check pointer-events-none">
<CheckBox checked={isSelected(element)} primary />
</div>
{#if element.icon}
<span class="mr-2"><Icon icon={element.icon} size="inline" /></span>
{/if}
<Label label={element.label} />
</div>
</div>
</button>
{/each}
{/if}
</div>
</div>
<Button
shape={'round'}
label={view.string.Apply}
on:click={async () => {
filter.value = [...selected]
// Replace last one with value with level
filter.props = { level }
onChange(filter)
dispatch('close')
}}
/>
</div>

View File

@ -54,8 +54,9 @@ import SocialEditor from './components/SocialEditor.svelte'
import ContactRefPresenter from './components/ContactRefPresenter.svelte'
import PersonRefPresenter from './components/PersonRefPresenter.svelte'
import EmployeeRefPresenter from './components/EmployeeRefPresenter.svelte'
import ChannelFilter from './components/ChannelFilter.svelte'
import contact from './plugin'
import { employeeSort } from './utils'
import { employeeSort, filterChannelInResult, filterChannelNinResult } from './utils'
export {
Channels,
@ -168,7 +169,8 @@ export default async (): Promise<Resources> => ({
EmployeeArrayEditor,
EmployeeEditor,
CreateEmployee,
AccountArrayEditor
AccountArrayEditor,
ChannelFilter
},
completion: {
EmployeeQuery: async (
@ -188,6 +190,8 @@ export default async (): Promise<Resources> => ({
GetFileUrl: getFileUrl,
GetGravatarUrl: getGravatarUrl,
GetColorUrl: (uri: string) => uri,
EmployeeSort: employeeSort
EmployeeSort: employeeSort,
FilterChannelInResult: filterChannelInResult,
FilterChannelNinResult: filterChannelNinResult
}
})

View File

@ -16,7 +16,7 @@
import contact, { contactId } from '@hcengineering/contact'
import { IntlString, mergeIds } from '@hcengineering/platform'
import { SortFunc } from '@hcengineering/view'
import { FilterFunction, SortFunc } from '@hcengineering/view'
export default mergeIds(contactId, contact, {
string: {
@ -66,6 +66,8 @@ export default mergeIds(contactId, contact, {
NotSpecified: '' as IntlString
},
function: {
EmployeeSort: '' as SortFunc
EmployeeSort: '' as SortFunc,
FilterChannelInResult: '' as FilterFunction,
FilterChannelNinResult: '' as FilterFunction
}
})

View File

@ -15,8 +15,10 @@
//
import contact, { ChannelProvider, Employee, formatName } from '@hcengineering/contact'
import { Ref, Timestamp, toIdMap } from '@hcengineering/core'
import { Doc, ObjQueryType, Ref, Timestamp, toIdMap } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import view, { Filter } from '@hcengineering/view'
import { FilterQuery } from '@hcengineering/view-resources'
const client = getClient()
const channelProviders = client.findAll(contact.class.ChannelProvider, {})
@ -70,3 +72,38 @@ export async function employeeSort (value: Array<Ref<Employee>>): Promise<Array<
})
})
}
export async function filterChannelInResult (filter: Filter, onUpdate: () => void): Promise<ObjQueryType<any>> {
const result = await getRefs(filter, onUpdate)
return { $in: result }
}
export async function filterChannelNinResult (filter: Filter, onUpdate: () => void): Promise<ObjQueryType<any>> {
const result = await getRefs(filter, onUpdate)
return { $nin: result }
}
export async function getRefs (filter: Filter, onUpdate: () => void): Promise<Array<Ref<Doc>>> {
const lq = FilterQuery.getLiveQuery(filter.index)
const client = getClient()
const mode = await client.findOne(view.class.FilterMode, { _id: filter.mode })
if (mode === undefined) return []
const promise = new Promise<Array<Ref<Doc>>>((resolve, reject) => {
const refresh = lq.query(
contact.class.Channel,
{
provider: { $in: filter.value }
},
(refs) => {
const result = Array.from(new Set(refs.map((p) => p.attachedTo)))
FilterQuery.results.set(filter.index, result)
resolve(result)
onUpdate()
}
)
if (!refresh) {
resolve(FilterQuery.results.get(filter.index) ?? [])
}
})
return await promise
}

View File

@ -32,7 +32,7 @@ import {
import type { Asset, Plugin, Resource } from '@hcengineering/platform'
import { IntlString, plugin } from '@hcengineering/platform'
import type { AnyComponent, IconSize } from '@hcengineering/ui'
import { ViewAction, Viewlet } from '@hcengineering/view'
import { FilterMode, ViewAction, Viewlet } from '@hcengineering/view'
/**
* @public
@ -283,6 +283,10 @@ const contactPlugin = plugin(contactId, {
viewlet: {
TableMember: '' as Ref<Viewlet>,
TableContact: '' as Ref<Viewlet>
},
filter: {
FilterChannelIn: '' as Ref<FilterMode>,
FilterChannelNin: '' as Ref<FilterMode>
}
})