[TSK-1370] Show selected filter values on top (#3174)

Signed-off-by: Sergei Ogorelkov <sergei.ogorelkov@icloud.com>
This commit is contained in:
Sergei Ogorelkov 2023-05-16 10:14:35 +04:00 committed by GitHub
parent fbd27c02c2
commit a71127828a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 129 additions and 50 deletions

View File

@ -14,16 +14,14 @@
-->
<script lang="ts">
import { ChannelProvider } from '@hcengineering/contact'
import { Class, Doc, Ref } from '@hcengineering/core'
import { Button, CheckBox, Icon, Label, resizeObserver } from '@hcengineering/ui'
import { Ref } from '@hcengineering/core'
import { CheckBox, Icon, Label, resizeObserver } from '@hcengineering/ui'
import { Filter } from '@hcengineering/view'
import { FilterQuery } from '@hcengineering/view-resources'
import view from '@hcengineering/view-resources/src/plugin'
import { FILTER_DEBOUNCE_MS, FilterQuery, sortFilterValues } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
import contact from '../plugin'
import { channelProviders } from '../utils'
import contact from '../plugin'
export let _class: Ref<Class<Doc>>
export let filter: Filter
export let onChange: (e: Filter) => void
filter.onRemove = () => {
@ -32,6 +30,8 @@
let selected: Ref<ChannelProvider>[] = filter.value
const level: number = filter.props?.level ?? 0
let filterUpdateTimeout: number | undefined
filter.modes = [contact.filter.FilterChannelIn, contact.filter.FilterChannelNin]
filter.mode = filter.mode === undefined ? filter.modes[0] : filter.mode
@ -40,12 +40,25 @@
return false
}
const checkSelected = (element: ChannelProvider): void => {
function handleFilterToggle (element: ChannelProvider) {
if (isSelected(element, selected)) {
selected = selected.filter((p) => p !== element._id)
} else {
selected = [...selected, element._id]
}
updateFilter(selected)
}
function updateFilter (newValues: Ref<ChannelProvider>[]) {
clearTimeout(filterUpdateTimeout)
filterUpdateTimeout = setTimeout(() => {
filter.value = [...newValues]
// Replace last one with value with level
filter.props = { level }
onChange(filter)
}, FILTER_DEBOUNCE_MS)
}
const dispatch = createEventDispatcher()
@ -54,11 +67,11 @@
<div class="selectPopup" use:resizeObserver={() => dispatch('changeContent')}>
<div class="scroll">
<div class="box">
{#each $channelProviders as element}
{#each sortFilterValues($channelProviders, (v) => isSelected(v, selected)) as element}
<button
class="menu-item"
on:click={() => {
checkSelected(element)
handleFilterToggle(element)
}}
>
<div class="flex-between w-full">
@ -76,15 +89,4 @@
{/each}
</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

@ -29,7 +29,7 @@
showPopup
} from '@hcengineering/ui'
import { Filter } from '@hcengineering/view'
import { FilterQuery } from '@hcengineering/view-resources'
import { FILTER_DEBOUNCE_MS, FilterQuery, sortFilterValues } from '@hcengineering/view-resources'
import { createEventDispatcher, onMount } from 'svelte'
import tags from '../plugin'
import { tagLevel } from '../utils'
@ -48,6 +48,8 @@
filter.modes = [tags.filter.FilterTagsIn, tags.filter.FilterTagsNin]
filter.mode = filter.mode === undefined ? filter.modes[0] : filter.mode
let filterUpdateTimeout: number | undefined
let categories: TagCategory[] = []
let objects: TagElement[] = []
client.findAll(tags.class.TagCategory, { targetClass: _class }).then((res) => {
@ -100,7 +102,7 @@
return false
}
const checkSelected = (element: TagElement): void => {
function handleFilterToggle (element: TagElement): void {
if (isSelected(element)) {
selected = selected.filter((p) => p !== element._id)
} else {
@ -109,10 +111,18 @@
objects = objects
categories = categories
filter.value = [...selected]
// Replace last one with value with level
filter.props = { level }
onChange(filter)
updateFilter(selected)
}
function updateFilter (newValues: Ref<TagElement>[]) {
clearTimeout(filterUpdateTimeout)
filterUpdateTimeout = setTimeout(() => {
filter.value = [...newValues]
// Replace last one with value with level
filter.props = { level }
onChange(filter)
}, FILTER_DEBOUNCE_MS)
}
$: schema = filter.key.attribute.schema ?? '0'
@ -185,11 +195,11 @@
{/if}
</button>
<div class="menu-group">
{#each values as element}
{#each sortFilterValues(values, isSelected) as element}
<button
class="menu-item"
on:click={() => {
checkSelected(element)
handleFilterToggle(element)
}}
>
<div class="flex-between w-full">

View File

@ -13,18 +13,18 @@
// limitations under the License.
-->
<script lang="ts">
import { Class, Doc, DocumentQuery, FindResult, Ref, SortingOrder } from '@hcengineering/core'
import { 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, { CheckBox, Icon, Label, Loading, deviceOptionsStore, resizeObserver } from '@hcengineering/ui'
import { FILTER_DEBOUNCE_MS, sortFilterValues } from '@hcengineering/view-resources'
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
@ -44,6 +44,8 @@
let objectsPromise: Promise<FindResult<Sprint>> | undefined = undefined
let selectedValues: Set<Ref<Sprint> | undefined | null> = new Set()
let filterUpdateTimeout: number | undefined
const client = getClient()
async function getValues (search: string): Promise<void> {
if (objectsPromise) {
@ -79,15 +81,24 @@
return values.has(value)
}
function toggle (value: Ref<Sprint> | undefined): void {
function handleFilterToggle (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)
updateFilter(selectedValues)
}
function updateFilter (newValues: Set<Ref<Sprint> | null | undefined>) {
clearTimeout(filterUpdateTimeout)
filterUpdateTimeout = setTimeout(() => {
filter.value = Array.from(newValues)
onChange(filter)
}, FILTER_DEBOUNCE_MS)
}
function getStatusItem (status: SprintStatus, docs: Sprint[]): Sprint[] {
@ -102,6 +113,7 @@
onMount(() => {
if (searchInput && !$deviceOptionsStore.isMobile) searchInput.focus()
})
getValues(search)
</script>
@ -125,7 +137,7 @@
<button
class="menu-item"
on:click={() => {
toggle(undefined)
handleFilterToggle(undefined)
}}
>
<div class="flex clear-mins">
@ -145,11 +157,11 @@
<Label label={status.label} />
</div>
</div>
{#each items as doc}
{#each sortFilterValues(items, (v) => isSelected(v._id, selectedValues)) as doc}
<button
class="menu-item"
on:click={() => {
toggle(doc._id)
handleFilterToggle(doc._id)
}}
>
<div class="flex clear-mins">

View File

@ -45,7 +45,7 @@
"@hcengineering/notification": "^0.6.12",
"@hcengineering/presentation": "^0.6.2",
"@hcengineering/setting": "^0.6.7",
"fast-equals": "^2.0.3",
"@hcengineering/text-editor": "^0.6.0"
"@hcengineering/text-editor": "^0.6.0",
"fast-equals": "^2.0.3"
}
}

View File

@ -28,6 +28,7 @@
import { Filter } from '@hcengineering/view'
import { createEventDispatcher, onMount } from 'svelte'
import view from '../../plugin'
import { FILTER_DEBOUNCE_MS, sortFilterValues } from '../../filter'
import { buildConfigLookup, getPresenter } from '../../utils'
import FilterRemovedNotification from './FilterRemovedNotification.svelte'
@ -53,6 +54,8 @@
$: isStatus = client.getHierarchy().isDerived(targetClass, core.class.Status) ?? false
let statusesCount: Record<string, number> = {}
let filterUpdateTimeout: number | undefined
const groupValues = (val: Status[]): Doc[] => {
const statuses = val
const result: Doc[] = []
@ -129,7 +132,7 @@
return values.includes(value?._id ?? value)
}
function toggle (value: Doc | undefined | null): void {
function handleFilterToggle (value: any): void {
if (isSelected(value, filter.value)) {
filter.value = filter.value.filter((p) => (value ? p !== value._id : p != null))
} else {
@ -140,7 +143,13 @@
}
}
onChange(filter)
updateFilter()
}
function updateFilter () {
clearTimeout(filterUpdateTimeout)
filterUpdateTimeout = setTimeout(() => onChange(filter), FILTER_DEBOUNCE_MS)
}
let search: string = ''
@ -183,11 +192,11 @@
{#if objectsPromise}
<Loading />
{:else}
{#each values as value, i}
{#each sortFilterValues(values, (v) => isSelected(v, filter.value)) as value}
<button
class="menu-item no-focus"
on:click={() => {
toggle(value)
handleFilterToggle(value)
}}
>
<div class="flex-between w-full">
@ -198,7 +207,9 @@
{/if}
</div>
{#if value}
<svelte:component this={attribute.presenter} {value} {...attribute.props} disabled oneLine />
{#key value._id}
<svelte:component this={attribute.presenter} {value} {...attribute.props} disabled oneLine />
{/key}
{:else}
<Label label={ui.string.NotSelected} />
{/if}

View File

@ -20,6 +20,7 @@
import { Filter } from '@hcengineering/view'
import { onMount, createEventDispatcher } from 'svelte'
import { getPresenter } from '../../utils'
import { FILTER_DEBOUNCE_MS, sortFilterValues } from '../../filter'
import view from '../../plugin'
export let _class: Ref<Class<Doc>>
@ -42,6 +43,8 @@
let objectsPromise: Promise<FindResult<Doc>> | undefined
let filterUpdateTimeout: number | undefined
async function getValues (search: string): Promise<void> {
if (objectsPromise) {
await objectsPromise
@ -100,18 +103,27 @@
return values.has(value)
}
function toggle (value: any): void {
function handleFilterToggle (value: any): void {
if (isSelected(value, selectedValues)) {
selectedValues.delete(value)
} else {
selectedValues.add(value)
}
selectedValues = selectedValues
filter.value = [...selectedValues.values()].map((v) => {
return [v, [...(realValues.get(v) ?? [])]]
})
onChange(filter)
updateFilter(selectedValues)
}
function updateFilter (newValues: Set<any>) {
clearTimeout(filterUpdateTimeout)
filterUpdateTimeout = setTimeout(() => {
filter.value = [...newValues.values()].map((v) => {
return [v, [...(realValues.get(v) ?? [])]]
})
onChange(filter)
}, FILTER_DEBOUNCE_MS)
}
let search: string = ''
@ -126,6 +138,7 @@
onMount(() => {
if (searchInput && !$deviceOptionsStore.isMobile) searchInput.focus()
})
getValues(search)
</script>
@ -149,12 +162,12 @@
{#if objectsPromise}
<Loading />
{:else}
{#each Array.from(values.keys()) as value}
{#each sortFilterValues([...values.keys()], (v) => isSelected(v, selectedValues)) as value}
{@const realValue = [...(realValues.get(value) ?? [])][0]}
<button
class="menu-item no-focus"
on:click={() => {
toggle(value)
handleFilterToggle(value)
}}
>
<div class="flex-between w-full">

View File

@ -304,3 +304,34 @@ export function getFilterKey (_class: Ref<Class<Doc>> | undefined): string {
}
return res
}
/**
* Returns a new array where the selected values have been moved to the beginning
*
* @export
* @template T
* @param {T[]} values
* @param {(value: T) => boolean} checkIsSelected
* @returns {readonly T[]}
*/
export function sortFilterValues<T> (values: T[], checkIsSelected: (value: T) => boolean): readonly T[] {
const selectedValues: T[] = []
const notSelectedValues: T[] = []
for (const value of values) {
if (checkIsSelected(value)) {
selectedValues.push(value)
} else {
notSelectedValues.push(value)
}
}
return [...selectedValues, ...notSelectedValues]
}
/**
* The number of milliseconds of delay before changing the filter value
*
* @type {200}
*/
export const FILTER_DEBOUNCE_MS: 200 = 200