mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-29 19:56:18 +00:00
Saved filter improve (#3180)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
d137e5548c
commit
696541af2d
@ -13,23 +13,22 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Class, Client, Data, Doc, DocumentQuery, Ref, Space } from '@hcengineering/core'
|
||||
import type { Account, Class, Client, Data, Doc, DocumentQuery, Domain, Ref, Space } from '@hcengineering/core'
|
||||
import { DOMAIN_MODEL } from '@hcengineering/core'
|
||||
import { Builder, Mixin, Model } from '@hcengineering/model'
|
||||
import core, { TClass, TDoc } from '@hcengineering/model-core'
|
||||
import preference, { TPreference } from '@hcengineering/model-preference'
|
||||
import type { Asset, IntlString, Resource, Status } from '@hcengineering/platform'
|
||||
import { DOMAIN_PREFERENCE } from '@hcengineering/preference'
|
||||
import type { AnyComponent, Location } from '@hcengineering/ui'
|
||||
import type {
|
||||
Action,
|
||||
ActionCategory,
|
||||
ActivityAttributePresenter,
|
||||
AllValuesFunc,
|
||||
ArrayEditor,
|
||||
AttributeEditor,
|
||||
AttributeFilter,
|
||||
AttributePresenter,
|
||||
ActivityAttributePresenter,
|
||||
BuildModelKey,
|
||||
ClassFilters,
|
||||
ClassSortFuncs,
|
||||
@ -57,9 +56,9 @@ import type {
|
||||
ObjectValidator,
|
||||
PreviewPresenter,
|
||||
SortFunc,
|
||||
SpacePresenter,
|
||||
SpaceHeader,
|
||||
SpaceName,
|
||||
SpacePresenter,
|
||||
ViewAction,
|
||||
ViewActionInput,
|
||||
ViewContext,
|
||||
@ -75,6 +74,8 @@ export { viewId } from '@hcengineering/view'
|
||||
export { viewOperation } from './migration'
|
||||
export { ViewAction, Viewlet }
|
||||
|
||||
export const DOMAIN_VIEW = 'view' as Domain
|
||||
|
||||
export function createAction<T extends Doc = Doc, P = Record<string, any>> (
|
||||
builder: Builder,
|
||||
data: Data<Action<T, P>>,
|
||||
@ -108,14 +109,18 @@ export function classPresenter (
|
||||
}
|
||||
}
|
||||
|
||||
@Model(view.class.FilteredView, core.class.Doc, DOMAIN_PREFERENCE)
|
||||
export class TFilteredView extends TPreference implements FilteredView {
|
||||
@Model(view.class.FilteredView, core.class.Doc, DOMAIN_VIEW)
|
||||
export class TFilteredView extends TDoc implements FilteredView {
|
||||
name!: string
|
||||
location!: Location
|
||||
filters!: string
|
||||
viewOptions?: ViewOptions
|
||||
filterClass?: Ref<Class<Doc>>
|
||||
viewletId?: Ref<Viewlet> | null
|
||||
users!: Ref<Account>[]
|
||||
createdBy!: Ref<Account>
|
||||
attachedTo!: string
|
||||
sharable?: boolean
|
||||
}
|
||||
|
||||
@Model(view.class.FilterMode, core.class.Doc, DOMAIN_MODEL)
|
||||
|
@ -13,20 +13,11 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, {
|
||||
AnyAttribute,
|
||||
Class,
|
||||
Doc,
|
||||
DOMAIN_TX,
|
||||
Ref,
|
||||
TxCreateDoc,
|
||||
TxCUD,
|
||||
TxProcessor,
|
||||
TxRemoveDoc
|
||||
} from '@hcengineering/core'
|
||||
import core, { AnyAttribute, DOMAIN_TX, Ref, TxCreateDoc, TxCUD, TxProcessor, TxRemoveDoc } from '@hcengineering/core'
|
||||
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model'
|
||||
import { DOMAIN_PREFERENCE } from '@hcengineering/preference'
|
||||
import { BuildModelKey, FilteredView, Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||
import { DOMAIN_VIEW } from '.'
|
||||
import view from './plugin'
|
||||
|
||||
async function migrateViewletPreference (client: MigrationClient): Promise<void> {
|
||||
@ -68,41 +59,25 @@ async function migrateViewletPreference (client: MigrationClient): Promise<void>
|
||||
}
|
||||
|
||||
async function migrateSavedFilters (client: MigrationClient): Promise<void> {
|
||||
const preferences = await client.find<FilteredView>(DOMAIN_PREFERENCE, {
|
||||
await client.move(
|
||||
DOMAIN_PREFERENCE,
|
||||
{
|
||||
_class: view.class.FilteredView
|
||||
},
|
||||
DOMAIN_VIEW
|
||||
)
|
||||
const preferences = await client.find<FilteredView>(DOMAIN_VIEW, {
|
||||
_class: view.class.FilteredView,
|
||||
viewOptions: { $exists: true }
|
||||
users: { $exists: false }
|
||||
})
|
||||
for (const pref of preferences) {
|
||||
if (pref.viewOptions === undefined) continue
|
||||
if (Array.isArray(pref.viewOptions.groupBy)) continue
|
||||
pref.viewOptions.groupBy = [pref.viewOptions.groupBy]
|
||||
await client.update<FilteredView>(
|
||||
DOMAIN_PREFERENCE,
|
||||
DOMAIN_VIEW,
|
||||
{
|
||||
_id: pref._id
|
||||
},
|
||||
{
|
||||
viewOptions: pref.viewOptions
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateSavedFiltersViewlets (client: MigrationClient): Promise<void> {
|
||||
const preferences = await client.find<FilteredView>(DOMAIN_PREFERENCE, {
|
||||
_class: view.class.FilteredView,
|
||||
viewletId: /^\S{24}$/ as any,
|
||||
attachedTo: 'tracker' as any
|
||||
})
|
||||
for (const pref of preferences) {
|
||||
await client.update<FilteredView>(
|
||||
DOMAIN_PREFERENCE,
|
||||
{
|
||||
_id: pref._id
|
||||
},
|
||||
{
|
||||
viewletId: 'tracker:viewlet:IssueList' as Ref<Viewlet>,
|
||||
filterClass: 'tracker:class:Issue' as Ref<Class<Doc>>
|
||||
users: [pref.createdBy]
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -178,7 +153,6 @@ export const viewOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
await migrateViewletPreference(client)
|
||||
await migrateSavedFilters(client)
|
||||
await migrateSavedFiltersViewlets(client)
|
||||
await fixViewletPreferenceRemovedAttributes(client)
|
||||
await fixPreferenceObjectKey(client)
|
||||
},
|
||||
|
@ -78,6 +78,10 @@
|
||||
"ThisMonth": "This month",
|
||||
"NextMonth": "Next month",
|
||||
"NotSpecified": "Not specified",
|
||||
"CustomDate": "Custom date"
|
||||
"CustomDate": "Custom date",
|
||||
"AddSavedView": "Add saved view",
|
||||
"Public": "Public",
|
||||
"Hide": "Hide",
|
||||
"SaveAs": "Save as"
|
||||
}
|
||||
}
|
||||
|
@ -75,6 +75,10 @@
|
||||
"ThisMonth": "Этот месяц",
|
||||
"NextMonth": "Следующий месяц",
|
||||
"NotSpecified": "Не указана",
|
||||
"CustomDate": "Выбранная дата"
|
||||
"AddSavedView": "Добавить сохраненное отображение",
|
||||
"CustomDate": "Выбранная дата",
|
||||
"Public": "Публичныйы",
|
||||
"Hide": "Спрятать",
|
||||
"SaveAs": "Сохранить как"
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,75 @@
|
||||
<script lang="ts">
|
||||
import { getCurrentAccount } from '@hcengineering/core'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { deviceOptionsStore, resizeObserver } from '@hcengineering/ui'
|
||||
import { FilteredView } from '@hcengineering/view'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import view from '../../plugin'
|
||||
|
||||
export let attachedTo: string | undefined
|
||||
|
||||
const me = getCurrentAccount()._id
|
||||
const q = createQuery()
|
||||
let views: FilteredView[] = []
|
||||
|
||||
const baseQuery = {
|
||||
attachedTo,
|
||||
sharable: true,
|
||||
createdBy: { $ne: me }
|
||||
}
|
||||
|
||||
$: query =
|
||||
search === ''
|
||||
? baseQuery
|
||||
: {
|
||||
...baseQuery,
|
||||
name: { $like: `%${search}%` }
|
||||
}
|
||||
|
||||
$: q.query(view.class.FilteredView, query, (res) => {
|
||||
views = res.filter((p) => !p.users.includes(me))
|
||||
})
|
||||
|
||||
const client = getClient()
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function add (sv: FilteredView): Promise<void> {
|
||||
await client.update(sv, { $push: { users: me } })
|
||||
dispatch('close')
|
||||
}
|
||||
|
||||
let search: string = ''
|
||||
let phTraslate: string = ''
|
||||
let searchInput: HTMLInputElement
|
||||
$: translate(presentation.string.Search, {}).then((res) => {
|
||||
phTraslate = res
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
if (searchInput && !$deviceOptionsStore.isMobile) searchInput.focus()
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="selectPopup" use:resizeObserver={() => dispatch('changeContent')}>
|
||||
<div class="header">
|
||||
<input bind:this={searchInput} type="text" bind:value={search} placeholder={phTraslate} />
|
||||
</div>
|
||||
<div class="scroll">
|
||||
<div class="box">
|
||||
{#each views as value}
|
||||
<button
|
||||
class="menu-item no-focus"
|
||||
on:click={() => {
|
||||
add(value)
|
||||
}}
|
||||
>
|
||||
<div class="flex-row-center w-full">
|
||||
{value.name}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -13,17 +13,19 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Class, Doc, DocumentQuery, Ref } from '@hcengineering/core'
|
||||
import { Class, Doc, DocumentQuery, Ref, getCurrentAccount } from '@hcengineering/core'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Button, IconAdd, eventToHTMLElement, showPopup } from '@hcengineering/ui'
|
||||
import { Filter, ViewOptions } from '@hcengineering/view'
|
||||
import { Button, IconAdd, eventToHTMLElement, getCurrentLocation, showPopup } from '@hcengineering/ui'
|
||||
import { Filter, FilteredView, ViewOptions, Viewlet } from '@hcengineering/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { filterStore, removeFilter, updateFilter } from '../../filter'
|
||||
import { filterStore, removeFilter, updateFilter, selectedFilterStore } from '../../filter'
|
||||
import view from '../../plugin'
|
||||
import FilterSave from './FilterSave.svelte'
|
||||
import FilterSection from './FilterSection.svelte'
|
||||
import FilterTypePopup from './FilterTypePopup.svelte'
|
||||
import { activeViewlet, getActiveViewletId, makeViewletKey } from '../../utils'
|
||||
import { getViewOptions, viewOptionStore } from '../../viewOptions'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let query: DocumentQuery<Doc>
|
||||
@ -58,6 +60,23 @@
|
||||
showPopup(FilterSave, { viewOptions, _class })
|
||||
}
|
||||
|
||||
async function saveCurrentFilteredView (filter: FilteredView | undefined) {
|
||||
if (filter !== undefined) {
|
||||
const filters = JSON.stringify($filterStore)
|
||||
await client.update(filter, {
|
||||
filters,
|
||||
viewOptions,
|
||||
viewletId: getActiveViewletId()
|
||||
})
|
||||
selectedFilterStore.set({
|
||||
...filter,
|
||||
filters,
|
||||
viewOptions,
|
||||
viewletId: getActiveViewletId()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function makeQuery (query: DocumentQuery<Doc>, filters: Filter[]): Promise<void> {
|
||||
const newQuery = hierarchy.clone(query)
|
||||
for (let i = 0; i < filters.length; i++) {
|
||||
@ -125,6 +144,27 @@
|
||||
|
||||
$: clazz = hierarchy.getClass(_class)
|
||||
$: visible = hierarchy.hasMixin(clazz, view.mixin.ClassFilters)
|
||||
|
||||
const me = getCurrentAccount()._id
|
||||
|
||||
function selectedFilterChanged (
|
||||
selectedFilter: FilteredView | undefined,
|
||||
filters: Filter[],
|
||||
activeViewlet: Record<string, Ref<Viewlet> | null>,
|
||||
viewOptionStore: Map<string, ViewOptions>
|
||||
): boolean {
|
||||
if (selectedFilter === undefined) return false
|
||||
if (selectedFilter.createdBy !== me) return false
|
||||
const loc = getCurrentLocation()
|
||||
const key = makeViewletKey(loc)
|
||||
if (selectedFilter.viewletId !== activeViewlet[key]) return true
|
||||
if (selectedFilter.filters !== JSON.stringify(filters)) return true
|
||||
if (selectedFilter.viewletId !== null) {
|
||||
const viewOptions = getViewOptions({ _id: selectedFilter.viewletId } as Viewlet, viewOptionStore)
|
||||
if (JSON.stringify(selectedFilter.viewOptions) !== JSON.stringify(viewOptions)) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if visible && $filterStore && $filterStore.length > 0}
|
||||
@ -135,6 +175,7 @@
|
||||
{filter}
|
||||
on:change={() => {
|
||||
makeQuery(query, $filterStore)
|
||||
updateFilter(filter)
|
||||
}}
|
||||
on:remove={() => {
|
||||
removeFilter(i)
|
||||
@ -146,7 +187,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button icon={view.icon.Views} label={view.string.Save} width={'fit-content'} on:click={() => saveFilteredView()} />
|
||||
<div class="flex gap-1-5">
|
||||
<Button
|
||||
icon={view.icon.Views}
|
||||
label={view.string.SaveAs}
|
||||
width={'fit-content'}
|
||||
on:click={() => saveFilteredView()}
|
||||
/>
|
||||
{#if selectedFilterChanged($selectedFilterStore, $filterStore, $activeViewlet, $viewOptionStore)}
|
||||
<Button
|
||||
icon={view.icon.Views}
|
||||
label={view.string.Save}
|
||||
width={'fit-content'}
|
||||
on:click={() => saveCurrentFilteredView($selectedFilterStore)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
@ -15,9 +15,9 @@
|
||||
<script lang="ts">
|
||||
import { Class, Doc, Ref, Space } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Button, IconClose, eventToHTMLElement, showPopup } from '@hcengineering/ui'
|
||||
import { Button, IconClose, eventToHTMLElement, resolvedLocationStore, showPopup } from '@hcengineering/ui'
|
||||
import { Filter } from '@hcengineering/view'
|
||||
import { filterStore, setFilters } from '../../filter'
|
||||
import { filterStore, getFilterKey, setFilters } from '../../filter'
|
||||
import view from '../../plugin'
|
||||
import FilterTypePopup from './FilterTypePopup.svelte'
|
||||
import IconFilter from '../icons/Filter.svelte'
|
||||
@ -29,6 +29,25 @@
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
resolvedLocationStore.subscribe(() => {
|
||||
load(_class)
|
||||
})
|
||||
|
||||
function load (_class: Ref<Class<Doc>> | undefined) {
|
||||
const key = getFilterKey(_class)
|
||||
const items = localStorage.getItem(key)
|
||||
if (items !== null) {
|
||||
filterStore.set(JSON.parse(items))
|
||||
}
|
||||
}
|
||||
|
||||
function save (_class: Ref<Class<Doc>> | undefined, p: Filter[]) {
|
||||
const key = getFilterKey(_class)
|
||||
localStorage.setItem(key, JSON.stringify(p))
|
||||
}
|
||||
|
||||
filterStore.subscribe((p) => save(_class, p))
|
||||
|
||||
function onChange (e: Filter | undefined) {
|
||||
if (e !== undefined) setFilters([e])
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { Class, Doc, Ref, getCurrentAccount } from '@hcengineering/core'
|
||||
import preference from '@hcengineering/preference'
|
||||
import { Card, getClient } from '@hcengineering/presentation'
|
||||
import { Button, EditBox, getCurrentResolvedLocation } from '@hcengineering/ui'
|
||||
import { Button, EditBox, ToggleWithLabel, getCurrentResolvedLocation } from '@hcengineering/ui'
|
||||
import { ViewOptions } from '@hcengineering/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { filterStore } from '../../filter'
|
||||
@ -12,6 +12,9 @@
|
||||
export let viewOptions: ViewOptions | undefined = undefined
|
||||
export let _class: Ref<Class<Doc>>
|
||||
|
||||
const me = getCurrentAccount()._id
|
||||
let sharable = true
|
||||
|
||||
let filterName = ''
|
||||
const client = getClient()
|
||||
|
||||
@ -27,7 +30,9 @@
|
||||
filters,
|
||||
attachedTo: loc.path[2] as Ref<Doc>,
|
||||
viewOptions,
|
||||
viewletId: getActiveViewletId()
|
||||
viewletId: getActiveViewletId(),
|
||||
sharable,
|
||||
users: [me]
|
||||
})
|
||||
}
|
||||
|
||||
@ -57,4 +62,5 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ToggleWithLabel bind:on={sharable} label={view.string.Public} />
|
||||
</Card>
|
||||
|
@ -13,7 +13,7 @@ import core, {
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { LiveQuery, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { AnyComponent, locationToUrl, getCurrentResolvedLocation } from '@hcengineering/ui'
|
||||
import { Filter, FilterMode, KeyFilter } from '@hcengineering/view'
|
||||
import { Filter, FilterMode, FilteredView, KeyFilter } from '@hcengineering/view'
|
||||
import { get, writable } from 'svelte/store'
|
||||
import view from './plugin'
|
||||
|
||||
@ -22,6 +22,11 @@ import view from './plugin'
|
||||
*/
|
||||
export const filterStore = writable<Filter[]>([])
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const selectedFilterStore = writable<FilteredView | undefined>()
|
||||
|
||||
export function setFilters (filters: Filter[]): void {
|
||||
const old = get(filterStore)
|
||||
old.forEach((p) => p.onRemove?.())
|
||||
@ -288,9 +293,14 @@ export function createFilter (_class: Ref<Class<Doc>>, key: string, value: any[]
|
||||
}
|
||||
}
|
||||
|
||||
export function getFilterKey (_class: Ref<Class<Doc>>): string {
|
||||
export function getFilterKey (_class: Ref<Class<Doc>> | undefined): string {
|
||||
const loc = getCurrentResolvedLocation()
|
||||
loc.path.length = 3
|
||||
loc.fragment = undefined
|
||||
loc.query = undefined
|
||||
return 'filter' + locationToUrl(loc) + _class
|
||||
let res = 'filter' + locationToUrl(loc)
|
||||
if (_class !== undefined) {
|
||||
res = res + _class
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
@ -102,6 +102,7 @@ export { getActions, invokeAction } from './actions'
|
||||
export { default as ActionContext } from './components/ActionContext.svelte'
|
||||
export { default as ActionHandler } from './components/ActionHandler.svelte'
|
||||
export { default as FilterButton } from './components/filter/FilterButton.svelte'
|
||||
export { default as AddSavedView } from './components/filter/AddSavedView.svelte'
|
||||
export { default as FixedColumn } from './components/FixedColumn.svelte'
|
||||
export { default as SourcePresenter } from './components/inference/SourcePresenter.svelte'
|
||||
export { default as LinkPresenter } from './components/LinkPresenter.svelte'
|
||||
|
@ -76,7 +76,8 @@ export default mergeIds(viewId, view, {
|
||||
ThisMonth: '' as IntlString,
|
||||
NextMonth: '' as IntlString,
|
||||
NotSpecified: '' as IntlString,
|
||||
CustomDate: '' as IntlString
|
||||
CustomDate: '' as IntlString,
|
||||
SaveAs: '' as IntlString
|
||||
},
|
||||
function: {
|
||||
StatusSort: '' as SortFunc
|
||||
|
@ -15,6 +15,7 @@
|
||||
//
|
||||
|
||||
import type {
|
||||
Account,
|
||||
AnyAttribute,
|
||||
CategoryType,
|
||||
Class,
|
||||
@ -35,7 +36,7 @@ import type {
|
||||
Type,
|
||||
UXObject
|
||||
} from '@hcengineering/core'
|
||||
import { Asset, IntlString, Plugin, plugin, Resource, Status } from '@hcengineering/platform'
|
||||
import { Asset, IntlString, Plugin, Resource, Status, plugin } from '@hcengineering/platform'
|
||||
import type { Preference } from '@hcengineering/preference'
|
||||
import type {
|
||||
AnyComponent,
|
||||
@ -99,13 +100,17 @@ export interface Filter {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface FilteredView extends Preference {
|
||||
export interface FilteredView extends Doc {
|
||||
name: string
|
||||
location: PlatformLocation
|
||||
filters: string
|
||||
viewOptions?: ViewOptions
|
||||
filterClass?: Ref<Class<Doc>>
|
||||
viewletId?: Ref<Viewlet> | null
|
||||
sharable?: boolean
|
||||
users: Ref<Account>[]
|
||||
createdBy: Ref<Account>
|
||||
attachedTo: string
|
||||
}
|
||||
|
||||
/**
|
||||
@ -707,7 +712,10 @@ const view = plugin(viewId, {
|
||||
SelectToMove: '' as IntlString,
|
||||
Cancel: '' as IntlString,
|
||||
List: '' as IntlString,
|
||||
Timeline: '' as IntlString
|
||||
AddSavedView: '' as IntlString,
|
||||
Timeline: '' as IntlString,
|
||||
Public: '' as IntlString,
|
||||
Hide: '' as IntlString
|
||||
},
|
||||
icon: {
|
||||
Table: '' as Asset,
|
||||
|
@ -1,16 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { Doc, Ref } from '@hcengineering/core'
|
||||
import { Ref, getCurrentAccount } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import setting from '@hcengineering/setting'
|
||||
import { Action, Location, location, navigate } from '@hcengineering/ui'
|
||||
import { Action, IconAdd, Location, eventToHTMLElement, location, navigate, showPopup } from '@hcengineering/ui'
|
||||
import view, { Filter, FilteredView, ViewOptions, Viewlet } from '@hcengineering/view'
|
||||
import {
|
||||
AddSavedView,
|
||||
TreeItem,
|
||||
TreeNode,
|
||||
activeViewlet,
|
||||
filterStore,
|
||||
getViewOptions,
|
||||
makeViewletKey,
|
||||
selectedFilterStore,
|
||||
setActiveViewletId,
|
||||
setFilters,
|
||||
setViewOptions,
|
||||
@ -23,16 +25,15 @@
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
const me = getCurrentAccount()._id
|
||||
|
||||
const filteredViewsQuery = createQuery()
|
||||
let filteredViews: FilteredView[] | undefined
|
||||
$: filteredViewsQuery.query(
|
||||
view.class.FilteredView,
|
||||
{ attachedTo: currentApplication?.alias as Ref<Doc> },
|
||||
(result) => {
|
||||
filteredViews = result
|
||||
}
|
||||
)
|
||||
let availableFilteredViews: FilteredView[] = []
|
||||
let myFilteredViews: FilteredView[] = []
|
||||
$: filteredViewsQuery.query(view.class.FilteredView, { attachedTo: currentApplication?.alias }, (result) => {
|
||||
myFilteredViews = result.filter((p) => p.users.includes(me))
|
||||
availableFilteredViews = result.filter((p) => p.sharable && !p.users.includes(me))
|
||||
})
|
||||
|
||||
async function removeAction (filteredView: FilteredView): Promise<Action[]> {
|
||||
return [
|
||||
@ -46,9 +47,27 @@
|
||||
]
|
||||
}
|
||||
|
||||
async function viewAction (filteredView: FilteredView): Promise<Action[]> {
|
||||
if (filteredView.createdBy === me) return await removeAction(filteredView)
|
||||
return await hideAction(filteredView)
|
||||
}
|
||||
|
||||
async function hideAction (object: FilteredView): Promise<Action[]> {
|
||||
return [
|
||||
{
|
||||
icon: view.icon.Archive,
|
||||
label: view.string.Hide,
|
||||
action: async (ctx: any, evt: Event) => {
|
||||
await client.update(object, { $pull: { users: me } })
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
let selectedId: Ref<FilteredView> | undefined = undefined
|
||||
|
||||
async function load (fv: FilteredView): Promise<void> {
|
||||
selectedFilterStore.set(fv)
|
||||
navigate({
|
||||
path: fv.location.path,
|
||||
query: fv.location.query ?? undefined,
|
||||
@ -65,10 +84,28 @@
|
||||
}
|
||||
|
||||
const clearSelection = () => {
|
||||
selectedFilterStore.set(undefined)
|
||||
selectedId = undefined
|
||||
dispatch('select', false)
|
||||
}
|
||||
|
||||
function checkFilter (
|
||||
fv: FilteredView,
|
||||
loc: Location,
|
||||
filters: string,
|
||||
viewOptionStore: Map<string, ViewOptions>
|
||||
): boolean {
|
||||
if (fv.location.path.join() !== loc.path.join()) return false
|
||||
if (fv.filters !== filters) return false
|
||||
const key = makeViewletKey(loc)
|
||||
if (fv.viewletId !== $activeViewlet[key]) return false
|
||||
if (fv.viewletId !== null) {
|
||||
const viewOptions = getViewOptions({ _id: fv.viewletId } as Viewlet, viewOptionStore)
|
||||
if (JSON.stringify(fv.viewOptions) !== JSON.stringify(viewOptions)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function checkSelected (
|
||||
fs: Filter[],
|
||||
loc: Location,
|
||||
@ -77,18 +114,19 @@
|
||||
) {
|
||||
const filters = JSON.stringify(fs)
|
||||
if (loc && Array.isArray(fs) && fs.length > 0 && Array.isArray(filteredViews)) {
|
||||
for (const fv of filteredViews) {
|
||||
if (fv.location.path.join() !== loc.path.join()) continue
|
||||
if (fv.filters !== filters) continue
|
||||
const key = makeViewletKey(loc)
|
||||
if (fv.viewletId !== $activeViewlet[key]) continue
|
||||
if (fv.viewletId !== null) {
|
||||
const viewOptions = getViewOptions({ _id: fv.viewletId } as Viewlet, viewOptionStore)
|
||||
if (JSON.stringify(fv.viewOptions) !== JSON.stringify(viewOptions)) continue
|
||||
if ($selectedFilterStore !== undefined) {
|
||||
if ($selectedFilterStore.location.path.join() === loc.path.join()) {
|
||||
selectedId = $selectedFilterStore._id
|
||||
dispatch('select', true)
|
||||
return
|
||||
}
|
||||
}
|
||||
for (const fv of filteredViews) {
|
||||
if (checkFilter(fv, loc, filters, viewOptionStore)) {
|
||||
selectedId = fv._id
|
||||
dispatch('select', true)
|
||||
return
|
||||
}
|
||||
selectedId = fv._id
|
||||
dispatch('select', true)
|
||||
return
|
||||
}
|
||||
clearSelection()
|
||||
} else {
|
||||
@ -96,19 +134,36 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: checkSelected($filterStore, $location, filteredViews, $viewOptionStore)
|
||||
$: dispatch('shown', filteredViews !== undefined && filteredViews.length > 0)
|
||||
$: checkSelected($filterStore, $location, myFilteredViews, $viewOptionStore)
|
||||
|
||||
$: shown = myFilteredViews.length > 0 || availableFilteredViews.length > 0
|
||||
$: dispatch('shown', shown)
|
||||
|
||||
async function getActions (availableFilteredViews: FilteredView[]): Promise<Action[]> {
|
||||
if (availableFilteredViews.length > 0) {
|
||||
const add: Action = {
|
||||
label: view.string.AddSavedView,
|
||||
icon: IconAdd,
|
||||
action: async (_, e): Promise<void> => {
|
||||
showPopup(AddSavedView, { attachedTo: currentApplication?.alias }, eventToHTMLElement(e as MouseEvent))
|
||||
}
|
||||
}
|
||||
return [add]
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if filteredViews && filteredViews.length > 0}
|
||||
<TreeNode label={view.string.FilteredViews} parent>
|
||||
{#each filteredViews as fv}
|
||||
{#if shown}
|
||||
<TreeNode label={view.string.FilteredViews} parent actions={async () => getActions(availableFilteredViews)}>
|
||||
{#each myFilteredViews as fv}
|
||||
<TreeItem
|
||||
_id={fv._id}
|
||||
title={fv.name}
|
||||
selected={selectedId === fv._id}
|
||||
on:click={() => load(fv)}
|
||||
actions={() => removeAction(fv)}
|
||||
actions={() => viewAction(fv)}
|
||||
/>
|
||||
{/each}
|
||||
</TreeNode>
|
||||
|
@ -170,7 +170,7 @@ export class FullTextIndex implements WithFind {
|
||||
let { docs, pass } = await this.indexer.search(classes, findQuery, fullTextLimit)
|
||||
|
||||
if (docs.length === 0 && pass) {
|
||||
docs = [...docs, ...(await this.adapter.search(classes, query, fullTextLimit))]
|
||||
docs = await this.adapter.search(classes, query, fullTextLimit)
|
||||
}
|
||||
const indexedDocMap = new Map<Ref<Doc>, IndexedDoc>()
|
||||
|
||||
|
@ -115,10 +115,6 @@ export class FullTextPushStage implements FullTextPipelineStage {
|
||||
if (pipeline.cancelling) {
|
||||
return
|
||||
}
|
||||
if (pipeline.cancelling) {
|
||||
return
|
||||
}
|
||||
|
||||
const elasticDoc = createElasticDoc(doc)
|
||||
try {
|
||||
updateDoc2Elastic(doc.attributes, elasticDoc)
|
||||
@ -169,12 +165,12 @@ export class FullTextPushStage implements FullTextPipelineStage {
|
||||
// Perform bulk update to elastic
|
||||
try {
|
||||
await this.fulltextAdapter.updateMany(bulk)
|
||||
for (const doc of toIndex) {
|
||||
await pipeline.update(doc._id, true, {})
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error(err)
|
||||
}
|
||||
for (const doc of toIndex) {
|
||||
await pipeline.update(doc._id, true, {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -418,6 +418,9 @@ export class FullTextIndexPipeline implements FullTextPipeline {
|
||||
{ collector: st.stageId },
|
||||
async (ctx) => await st.collect(toIndex, this, ctx)
|
||||
)
|
||||
if (this.cancelling) {
|
||||
break
|
||||
}
|
||||
|
||||
toIndex.forEach((it) => _classUpdate.add(it.objectClass))
|
||||
|
||||
@ -432,6 +435,9 @@ export class FullTextIndexPipeline implements FullTextPipeline {
|
||||
async (ctx) => await nst.collect(toIndex2, this, ctx)
|
||||
)
|
||||
}
|
||||
if (this.cancelling) {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break
|
||||
|
Loading…
Reference in New Issue
Block a user