mirror of
https://github.com/hcengineering/platform.git
synced 2025-01-03 08:57:14 +03:00
[TSK-1370] Show selected filter values on top (#3174)
Signed-off-by: Sergei Ogorelkov <sergei.ogorelkov@icloud.com>
This commit is contained in:
parent
fbd27c02c2
commit
a71127828a
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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">
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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}
|
||||
|
@ -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">
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user