mirror of
https://github.com/hcengineering/platform.git
synced 2025-06-07 00:09:34 +00:00
Add support for user-saved filtered Views (#2521)
Signed-off-by: Vyacheslav Tumanov <me@slavatumanov.me>
This commit is contained in:
parent
e9cc9e7b47
commit
c261f8f761
@ -19,7 +19,7 @@ import { Builder, Mixin, Model } from '@hcengineering/model'
|
|||||||
import core, { TClass, TDoc } from '@hcengineering/model-core'
|
import core, { TClass, TDoc } from '@hcengineering/model-core'
|
||||||
import preference, { TPreference } from '@hcengineering/model-preference'
|
import preference, { TPreference } from '@hcengineering/model-preference'
|
||||||
import type { Asset, IntlString, Resource, Status } from '@hcengineering/platform'
|
import type { Asset, IntlString, Resource, Status } from '@hcengineering/platform'
|
||||||
import type { AnyComponent } from '@hcengineering/ui'
|
import type { AnyComponent, Location } from '@hcengineering/ui'
|
||||||
import type {
|
import type {
|
||||||
Action,
|
Action,
|
||||||
ActionCategory,
|
ActionCategory,
|
||||||
@ -57,9 +57,11 @@ import type {
|
|||||||
Viewlet,
|
Viewlet,
|
||||||
ViewletDescriptor,
|
ViewletDescriptor,
|
||||||
ViewletPreference,
|
ViewletPreference,
|
||||||
ViewOptionsModel
|
ViewOptionsModel,
|
||||||
|
FilteredView
|
||||||
} from '@hcengineering/view'
|
} from '@hcengineering/view'
|
||||||
import view from './plugin'
|
import view from './plugin'
|
||||||
|
import { DOMAIN_PREFERENCE } from '@hcengineering/preference'
|
||||||
|
|
||||||
export { viewOperation } from './migration'
|
export { viewOperation } from './migration'
|
||||||
export { ViewAction, Viewlet }
|
export { ViewAction, Viewlet }
|
||||||
@ -91,6 +93,13 @@ export function classPresenter (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Model(view.class.FilteredView, core.class.Doc, DOMAIN_PREFERENCE)
|
||||||
|
export class TFilteredView extends TPreference implements FilteredView {
|
||||||
|
name!: string
|
||||||
|
location!: Location
|
||||||
|
filters!: string
|
||||||
|
}
|
||||||
|
|
||||||
@Model(view.class.FilterMode, core.class.Doc, DOMAIN_MODEL)
|
@Model(view.class.FilterMode, core.class.Doc, DOMAIN_MODEL)
|
||||||
export class TFilterMode extends TDoc implements FilterMode {
|
export class TFilterMode extends TDoc implements FilterMode {
|
||||||
label!: IntlString
|
label!: IntlString
|
||||||
@ -318,7 +327,8 @@ export function createModel (builder: Builder): void {
|
|||||||
TPreviewPresenter,
|
TPreviewPresenter,
|
||||||
TLinkPresenter,
|
TLinkPresenter,
|
||||||
TArrayEditor,
|
TArrayEditor,
|
||||||
TInlineAttributEditor
|
TInlineAttributEditor,
|
||||||
|
TFilteredView
|
||||||
)
|
)
|
||||||
|
|
||||||
classPresenter(
|
classPresenter(
|
||||||
|
@ -55,6 +55,10 @@
|
|||||||
"NoGrouping": "No grouping",
|
"NoGrouping": "No grouping",
|
||||||
"Grouping": "Grouping",
|
"Grouping": "Grouping",
|
||||||
"Ordering": "Ordering",
|
"Ordering": "Ordering",
|
||||||
"Manual": "Manual"
|
"Manual": "Manual",
|
||||||
|
|
||||||
|
"FilteredViews": "Filtered views",
|
||||||
|
"NewFilteredView": "New filtered view",
|
||||||
|
"FilteredViewName": "Filtered view name"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,9 @@
|
|||||||
"NoGrouping": "Нет группировки",
|
"NoGrouping": "Нет группировки",
|
||||||
"Grouping": "Группировка",
|
"Grouping": "Группировка",
|
||||||
"Ordering": "Сортировка",
|
"Ordering": "Сортировка",
|
||||||
"Manual": "Пользовательский"
|
"Manual": "Пользовательский",
|
||||||
|
"FilteredViews": "Фильтрованные отображения",
|
||||||
|
"NewFilteredView": "Новое фильтрованное отображение",
|
||||||
|
"FilteredViewName": "Имя фильтрованного отображения"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
import view from '../../plugin'
|
import view from '../../plugin'
|
||||||
import FilterSection from './FilterSection.svelte'
|
import FilterSection from './FilterSection.svelte'
|
||||||
import FilterTypePopup from './FilterTypePopup.svelte'
|
import FilterTypePopup from './FilterTypePopup.svelte'
|
||||||
|
import FilterSave from './FilterSave.svelte'
|
||||||
|
|
||||||
export let _class: Ref<Class<Doc>>
|
export let _class: Ref<Class<Doc>>
|
||||||
export let query: DocumentQuery<Doc>
|
export let query: DocumentQuery<Doc>
|
||||||
@ -32,7 +33,6 @@
|
|||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let maxIndex = 1
|
let maxIndex = 1
|
||||||
// const allFilters: boolean = true
|
|
||||||
|
|
||||||
function onChange (e: Filter | undefined) {
|
function onChange (e: Filter | undefined) {
|
||||||
if (e === undefined) return
|
if (e === undefined) return
|
||||||
@ -78,6 +78,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function saveFilteredView () {
|
||||||
|
showPopup(FilterSave, {})
|
||||||
|
}
|
||||||
|
|
||||||
let loading = false
|
let loading = false
|
||||||
|
|
||||||
function load (_class: Ref<Class<Doc>>) {
|
function load (_class: Ref<Class<Doc>>) {
|
||||||
@ -192,26 +196,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- SAVE BUTTON -->
|
<Button
|
||||||
<!-- <div class="buttons-group small-gap ml-4">
|
icon={view.icon.Views}
|
||||||
{#if filters.length > 1}
|
label={view.string.Save}
|
||||||
<div class="flex-baseline">
|
size={'small'}
|
||||||
<span class="overflow-label">
|
width={'fit-content'}
|
||||||
<Label label={view.string.IncludeItemsThatMatch} />
|
on:click={() => saveFilteredView()}
|
||||||
</span>
|
/>
|
||||||
<button
|
|
||||||
class="filter-button"
|
|
||||||
on:click={() => {
|
|
||||||
allFilters = !allFilters
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Label label={allFilters ? view.string.AllFilters : view.string.AnyFilter} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="buttons-divider" />
|
|
||||||
{/if}
|
|
||||||
<Button icon={view.icon.Views} label={view.string.Save} size={'small'} width={'fit-content'} />
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Card, getClient } from '@hcengineering/presentation'
|
||||||
|
import view from '../../plugin'
|
||||||
|
import { EditBox, getCurrentLocation } from '@hcengineering/ui'
|
||||||
|
import preference from '@hcengineering/preference'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import { filterStore } from '../../filter'
|
||||||
|
|
||||||
|
let filterName = ''
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
function getFilteredViewData () {
|
||||||
|
const loc = getCurrentLocation()
|
||||||
|
loc.fragment = undefined
|
||||||
|
loc.query = undefined
|
||||||
|
const filters = JSON.stringify($filterStore)
|
||||||
|
return {
|
||||||
|
name: filterName,
|
||||||
|
location: loc,
|
||||||
|
filters,
|
||||||
|
attachedTo: loc.path[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveFilter () {
|
||||||
|
await client.createDoc(view.class.FilteredView, preference.space.Preference, getFilteredViewData())
|
||||||
|
}
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
label={view.string.NewFilteredView}
|
||||||
|
okAction={saveFilter}
|
||||||
|
canSave={filterName.length > 0}
|
||||||
|
on:close={() => {
|
||||||
|
dispatch('close')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="flex-row-center">
|
||||||
|
<div class="flex-grow flex-col">
|
||||||
|
<EditBox
|
||||||
|
placeholder={view.string.FilteredViewName}
|
||||||
|
bind:value={filterName}
|
||||||
|
kind={'large-style'}
|
||||||
|
focus
|
||||||
|
focusIndex={1}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
@ -33,7 +33,13 @@ import type {
|
|||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { Asset, IntlString, Plugin, plugin, Resource, Status } from '@hcengineering/platform'
|
import { Asset, IntlString, Plugin, plugin, Resource, Status } from '@hcengineering/platform'
|
||||||
import type { Preference } from '@hcengineering/preference'
|
import type { Preference } from '@hcengineering/preference'
|
||||||
import type { AnyComponent, AnySvelteComponent, PopupAlignment, PopupPosAlignment } from '@hcengineering/ui'
|
import type {
|
||||||
|
AnyComponent,
|
||||||
|
AnySvelteComponent,
|
||||||
|
PopupAlignment,
|
||||||
|
PopupPosAlignment,
|
||||||
|
Location as PlatformLocation
|
||||||
|
} from '@hcengineering/ui'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -69,6 +75,15 @@ export interface Filter {
|
|||||||
onRemove?: () => void
|
onRemove?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface FilteredView extends Preference {
|
||||||
|
name: string
|
||||||
|
location: PlatformLocation
|
||||||
|
filters: string
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -523,7 +538,8 @@ const view = plugin(viewId, {
|
|||||||
Action: '' as Ref<Class<Action>>,
|
Action: '' as Ref<Class<Action>>,
|
||||||
ActionCategory: '' as Ref<Class<ActionCategory>>,
|
ActionCategory: '' as Ref<Class<ActionCategory>>,
|
||||||
LinkPresenter: '' as Ref<Class<LinkPresenter>>,
|
LinkPresenter: '' as Ref<Class<LinkPresenter>>,
|
||||||
FilterMode: '' as Ref<Class<FilterMode>>
|
FilterMode: '' as Ref<Class<FilterMode>>,
|
||||||
|
FilteredView: '' as Ref<Class<FilteredView>>
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
Delete: '' as Ref<Action>,
|
Delete: '' as Ref<Action>,
|
||||||
@ -560,7 +576,10 @@ const view = plugin(viewId, {
|
|||||||
string: {
|
string: {
|
||||||
CustomizeView: '' as IntlString,
|
CustomizeView: '' as IntlString,
|
||||||
LabelNA: '' as IntlString,
|
LabelNA: '' as IntlString,
|
||||||
View: '' as IntlString
|
View: '' as IntlString,
|
||||||
|
FilteredViews: '' as IntlString,
|
||||||
|
NewFilteredView: '' as IntlString,
|
||||||
|
FilteredViewName: '' as IntlString
|
||||||
},
|
},
|
||||||
icon: {
|
icon: {
|
||||||
Table: '' as Asset,
|
Table: '' as Asset,
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
import core, { Doc, Ref, SortingOrder, Space, getCurrentAccount } from '@hcengineering/core'
|
import core, { Doc, Ref, SortingOrder, Space, getCurrentAccount } from '@hcengineering/core'
|
||||||
import { getResource } from '@hcengineering/platform'
|
import { getResource } from '@hcengineering/platform'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { Button, Scroller, showPopup } from '@hcengineering/ui'
|
import { Button, IconEdit, navigate, Scroller, showPopup } from '@hcengineering/ui'
|
||||||
import type { NavigatorModel, SpecialNavModel } from '@hcengineering/workbench'
|
import type { Application, NavigatorModel, SpecialNavModel } from '@hcengineering/workbench'
|
||||||
import setting from '@hcengineering/setting'
|
import setting from '@hcengineering/setting'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import preferece, { SpacePreference } from '@hcengineering/preference'
|
import preferece, { SpacePreference } from '@hcengineering/preference'
|
||||||
@ -29,10 +29,15 @@
|
|||||||
import TreeSeparator from './navigator/TreeSeparator.svelte'
|
import TreeSeparator from './navigator/TreeSeparator.svelte'
|
||||||
import HelpAndSupport from './HelpAndSupport.svelte'
|
import HelpAndSupport from './HelpAndSupport.svelte'
|
||||||
import workbench from '../plugin'
|
import workbench from '../plugin'
|
||||||
|
import TreeNode from './navigator/TreeNode.svelte'
|
||||||
|
import TreeItem from './navigator/TreeItem.svelte'
|
||||||
|
import view, { FilteredView } from '@hcengineering/view'
|
||||||
|
import { filterStore } from '@hcengineering/view-resources'
|
||||||
|
|
||||||
export let model: NavigatorModel | undefined
|
export let model: NavigatorModel | undefined
|
||||||
export let currentSpace: Ref<Space> | undefined
|
export let currentSpace: Ref<Space> | undefined
|
||||||
export let currentSpecial: string | undefined
|
export let currentSpecial: string | undefined
|
||||||
|
export let currentApplication: Application | undefined
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const hierarchy = client.getHierarchy()
|
const hierarchy = client.getHierarchy()
|
||||||
@ -101,6 +106,18 @@
|
|||||||
|
|
||||||
$: if (model) update(model, spaces, preferences)
|
$: if (model) update(model, spaces, preferences)
|
||||||
|
|
||||||
|
function removeAction (filteredView: FilteredView) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
icon: view.icon.Archive ?? IconEdit,
|
||||||
|
label: setting.string.Delete,
|
||||||
|
action: async (ctx: any, evt: Event) => {
|
||||||
|
await client.removeDoc(view.class.FilteredView, currentSpace, filteredView._id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
async function updateSpecials (
|
async function updateSpecials (
|
||||||
specials: SpecialNavModel[],
|
specials: SpecialNavModel[],
|
||||||
spaces: Space[],
|
spaces: Space[],
|
||||||
@ -124,6 +141,11 @@
|
|||||||
return [result, requestIndex]
|
return [result, requestIndex]
|
||||||
}
|
}
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
const filteredViewsQuery = createQuery()
|
||||||
|
let filteredViews: FilteredView[] | undefined
|
||||||
|
$: filteredViewsQuery.query(view.class.FilteredView, { attachedTo: currentApplication?.alias }, (result) => {
|
||||||
|
filteredViews = result
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if model}
|
{#if model}
|
||||||
@ -144,7 +166,20 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if specials.length > 0}<TreeSeparator />{/if}
|
{#if specials.length > 0}<TreeSeparator />{/if}
|
||||||
|
{#if filteredViews && filteredViews.length > 0}
|
||||||
|
<TreeNode label={view.string.FilteredViews}>
|
||||||
|
{#each filteredViews as fV}
|
||||||
|
<TreeItem
|
||||||
|
title={fV.name}
|
||||||
|
on:click={() => {
|
||||||
|
navigate(fV.location)
|
||||||
|
$filterStore = JSON.parse(fV.filters)
|
||||||
|
}}
|
||||||
|
actions={() => removeAction(fV)}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</TreeNode>
|
||||||
|
{/if}
|
||||||
{#if starred.length}
|
{#if starred.length}
|
||||||
<StarredNav label={preference.string.Starred} spaces={starred} on:space {currentSpace} />
|
<StarredNav label={preference.string.Starred} spaces={starred} on:space {currentSpace} />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -526,6 +526,7 @@
|
|||||||
{currentSpace}
|
{currentSpace}
|
||||||
{currentSpecial}
|
{currentSpecial}
|
||||||
model={navigatorModel}
|
model={navigatorModel}
|
||||||
|
{currentApplication}
|
||||||
on:special={(evt) => selectSpecial(evt.detail)}
|
on:special={(evt) => selectSpecial(evt.detail)}
|
||||||
on:space={(evt) => selectSpace(evt.detail.space, evt.detail.spaceSpecial)}
|
on:space={(evt) => selectSpace(evt.detail.space, evt.detail.spaceSpecial)}
|
||||||
on:open={checkOnHide}
|
on:open={checkOnHide}
|
||||||
|
Loading…
Reference in New Issue
Block a user