Save view options (#2528)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2023-01-21 20:16:14 +06:00 committed by GitHub
parent a7da534e06
commit a7cdd8a680
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 159 additions and 91 deletions

View File

@ -30,7 +30,7 @@
TabList
} from '@hcengineering/ui'
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
import { FilterButton, getViewOptions, ViewletSettingButton } from '@hcengineering/view-resources'
import { FilterButton, getViewOptions, setActiveViewletId, ViewletSettingButton } from '@hcengineering/view-resources'
import calendar from '../plugin'
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
@ -56,6 +56,7 @@
viewlets = res
if (selectedViewlet === undefined || res.findIndex((p) => p._id === selectedViewlet?._id) === -1) {
selectedViewlet = res[0]
setActiveViewletId(selectedViewlet._id)
}
},
{ lookup: { descriptor: view.class.ViewletDescriptor } }

View File

@ -22,6 +22,7 @@
ActionContext,
FilterButton,
getViewOptions,
setActiveViewletId,
TableBrowser,
ViewletSettingButton
} from '@hcengineering/view-resources'
@ -51,6 +52,7 @@
.then((res) => {
viewlet = res
if (res !== undefined) {
setActiveViewletId(res._id)
preferenceQuery.query(
view.class.ViewletPreference,
{

View File

@ -18,7 +18,7 @@
import { createQuery, getClient, UsersPopup, IconMembersOutline } from '@hcengineering/presentation'
import { Button, IconAdd, Label, showPopup, Icon } from '@hcengineering/ui'
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
import { getViewOptions, Table, ViewletSettingButton } from '@hcengineering/view-resources'
import { getViewOptions, setActiveViewletId, Table, ViewletSettingButton } from '@hcengineering/view-resources'
import contact from '../plugin'
export let objectId: Ref<Doc>
@ -76,6 +76,7 @@
.then((res) => {
descr = res
if (res !== undefined) {
setActiveViewletId(res._id)
preferenceQuery.query(
view.class.ViewletPreference,
{

View File

@ -24,6 +24,7 @@
ActionContext,
FilterButton,
getViewOptions,
setActiveViewletId,
TableBrowser,
ViewletSettingButton
} from '@hcengineering/view-resources'
@ -53,6 +54,7 @@
.then((res) => {
viewlet = res
if (res !== undefined) {
setActiveViewletId(res._id)
preferenceQuery.query(
view.class.ViewletPreference,
{

View File

@ -19,7 +19,7 @@
import { createQuery, getClient, UsersPopup } from '@hcengineering/presentation'
import { Button, eventToHTMLElement, IconAdd, Label, Scroller, showPopup } from '@hcengineering/ui'
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
import { getViewOptions, Table, ViewletSettingButton } from '@hcengineering/view-resources'
import { getViewOptions, setActiveViewletId, Table, ViewletSettingButton } from '@hcengineering/view-resources'
import hr from '../plugin'
import { addMember } from '../utils'
@ -77,6 +77,7 @@
.then((res) => {
descr = res
if (res !== undefined) {
setActiveViewletId(res._id)
preferenceQuery.query(
view.class.ViewletPreference,
{

View File

@ -20,7 +20,7 @@
import { createQuery, getClient } from '@hcengineering/presentation'
import { Button, Label, Loading, Scroller, tableSP } from '@hcengineering/ui'
import view, { BuildModelKey, Viewlet, ViewletPreference } from '@hcengineering/view'
import { getViewOptions, Table, ViewletSettingButton } from '@hcengineering/view-resources'
import { getViewOptions, setActiveViewletId, Table, ViewletSettingButton } from '@hcengineering/view-resources'
import hr from '../../plugin'
import {
EmployeeReports,
@ -182,6 +182,7 @@
.then((res) => {
descr = res
if (res !== undefined) {
setActiveViewletId(res._id)
preferenceQuery.query(
view.class.ViewletPreference,
{

View File

@ -18,7 +18,13 @@
import { Vacancy } from '@hcengineering/recruit'
import { Button, Icon, IconAdd, Label, Loading, SearchEdit, showPopup } from '@hcengineering/ui'
import view, { BuildModelKey, Viewlet, ViewletPreference } from '@hcengineering/view'
import { FilterButton, getViewOptions, TableBrowser, ViewletSettingButton } from '@hcengineering/view-resources'
import {
FilterButton,
getViewOptions,
setActiveViewletId,
TableBrowser,
ViewletSettingButton
} from '@hcengineering/view-resources'
import recruit from '../plugin'
import CreateVacancy from './CreateVacancy.svelte'
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
@ -104,6 +110,7 @@
.then((res) => {
descr = res
if (res !== undefined) {
setActiveViewletId(res._id)
preferenceQuery.query(
view.class.ViewletPreference,
{

View File

@ -50,6 +50,7 @@
if (result.detail !== undefined) {
if (viewlet?._id === result.detail.id) return
viewlet = viewlets.find((vl) => vl._id === result.detail.id)
console.log('set viewlet by issue headed')
if (viewlet) setActiveViewletId(viewlet._id)
}
}}

View File

@ -5,7 +5,7 @@
import { Issue, IssueStatus, Team } from '@hcengineering/tracker'
import { Button, IconDetails, IconDetailsFilled } from '@hcengineering/ui'
import view, { Viewlet } from '@hcengineering/view'
import { FilterBar, getActiveViewletId, getViewOptions } from '@hcengineering/view-resources'
import { FilterBar, getActiveViewletId, getViewOptions, setActiveViewletId } from '@hcengineering/view-resources'
import ViewletSettingButton from '@hcengineering/view-resources/src/components/ViewletSettingButton.svelte'
import tracker from '../../plugin'
import IssuesContent from './IssuesContent.svelte'
@ -45,6 +45,7 @@
)
const _id = getActiveViewletId()
viewlet = viewlets.find((viewlet) => viewlet._id === _id) || viewlets[0]
setActiveViewletId(viewlet._id)
}
$: if (!label && title) {
translate(title, {}).then((res) => {
@ -118,7 +119,7 @@
</svelte:fragment>
</IssuesHeader>
<slot name="afterHeader" />
<FilterBar _class={tracker.class.Issue} query={searchQuery} on:change={(e) => (resultQuery = e.detail)} />
<FilterBar _class={tracker.class.Issue} query={searchQuery} {viewOptions} on:change={(e) => (resultQuery = e.detail)} />
<div class="flex w-full h-full clear-mins">
{#if viewlet && _teams && issueStatuses}
<IssuesContent {viewlet} query={resultQuery} {space} teams={_teams} {issueStatuses} {viewOptions} />

View File

@ -5,7 +5,7 @@
import { IssueTemplate } from '@hcengineering/tracker'
import { Button, IconAdd, IconDetails, IconDetailsFilled, showPopup } from '@hcengineering/ui'
import view, { Viewlet } from '@hcengineering/view'
import { FilterBar, getActiveViewletId, getViewOptions } from '@hcengineering/view-resources'
import { FilterBar, getActiveViewletId, getViewOptions, setActiveViewletId } from '@hcengineering/view-resources'
import ViewletSettingButton from '@hcengineering/view-resources/src/components/ViewletSettingButton.svelte'
import tracker from '../../plugin'
import IssuesHeader from '../issues/IssuesHeader.svelte'
@ -47,6 +47,7 @@
)
const _id = getActiveViewletId()
viewlet = viewlets.find((viewlet) => viewlet._id === _id) || viewlets[0]
setActiveViewletId(viewlet._id)
}
$: if (!label && title) {
translate(title, {}).then((res) => {
@ -105,7 +106,12 @@
</svelte:fragment>
</IssuesHeader>
<slot name="afterHeader" />
<FilterBar _class={tracker.class.IssueTemplate} query={searchQuery} on:change={(e) => (resultQuery = e.detail)} />
<FilterBar
_class={tracker.class.IssueTemplate}
{viewOptions}
query={searchQuery}
on:change={(e) => (resultQuery = e.detail)}
/>
<div class="flex w-full h-full clear-mins">
{#if viewlet && viewOptions}
<IssueTemplatesContent {viewOptions} {viewlet} query={resultQuery} />

View File

@ -17,7 +17,7 @@
import { getResource } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import { Button, eventToHTMLElement, getCurrentLocation, IconAdd, locationToUrl, showPopup } from '@hcengineering/ui'
import { Filter } from '@hcengineering/view'
import { Filter, ViewOptions } from '@hcengineering/view'
import { createEventDispatcher } from 'svelte'
import { filterStore } from '../../filter'
import view from '../../plugin'
@ -27,6 +27,7 @@
export let _class: Ref<Class<Doc>>
export let query: DocumentQuery<Doc>
export let viewOptions: ViewOptions | undefined = undefined
const client = getClient()
const hierarchy = client.getHierarchy()
@ -79,7 +80,7 @@
}
async function saveFilteredView () {
showPopup(FilterSave, {})
showPopup(FilterSave, { viewOptions })
}
let loading = false

View File

@ -5,25 +5,28 @@
import preference from '@hcengineering/preference'
import { createEventDispatcher } from 'svelte'
import { filterStore } from '../../filter'
import { ViewOptions } from '@hcengineering/view'
import { Doc, Ref } from '@hcengineering/core'
import { getActiveViewletId } from '../../utils'
export let viewOptions: ViewOptions | undefined = undefined
let filterName = ''
const client = getClient()
function getFilteredViewData () {
async function saveFilter () {
const loc = getCurrentLocation()
loc.fragment = undefined
loc.query = undefined
const filters = JSON.stringify($filterStore)
return {
await client.createDoc(view.class.FilteredView, preference.space.Preference, {
name: filterName,
location: loc,
filters,
attachedTo: loc.path[2]
}
}
async function saveFilter () {
await client.createDoc(view.class.FilteredView, preference.space.Preference, getFilteredViewData())
attachedTo: loc.path[2] as Ref<Doc>,
viewOptions,
viewletId: getActiveViewletId()
})
}
const dispatch = createEventDispatcher()

View File

@ -34,6 +34,7 @@ import {
AnyComponent,
ErrorPresenter,
getCurrentLocation,
Location,
getPlatformColorForText,
locationToUrl
} from '@hcengineering/ui'
@ -474,16 +475,16 @@ export function isCollectionAttr (hierarchy: Hierarchy, key: KeyedAttribute): bo
return hierarchy.isDerived(key.attr.type._class, core.class.Collection)
}
function makeViewletKey (): string {
const loc = getCurrentLocation()
function makeViewletKey (loc?: Location): string {
loc = loc ?? getCurrentLocation()
loc.fragment = undefined
loc.query = undefined
return 'viewlet' + locationToUrl(loc)
}
export function setActiveViewletId (viewletId: Ref<Viewlet> | null): void {
const key = makeViewletKey()
if (viewletId !== null) {
export function setActiveViewletId (viewletId: Ref<Viewlet> | null, loc?: Location): void {
const key = makeViewletKey(loc)
if (viewletId !== null && viewletId !== undefined) {
localStorage.setItem(key, viewletId)
} else {
localStorage.removeItem(key)

View File

@ -17,25 +17,25 @@ export function isDropdownType (viewOption: ViewOptionModel): viewOption is Drop
return viewOption.type === 'dropdown'
}
function makeViewOptionsKey (prefix: string): string {
function makeViewOptionsKey (viewlet: Viewlet): string {
const prefix = viewlet?._id + (viewlet?.variant !== undefined ? `-${viewlet.variant}` : '')
const loc = getCurrentLocation()
loc.fragment = undefined
loc.query = undefined
return `viewOptions:${prefix}:${locationToUrl(loc)}`
}
function _setViewOptions (prefix: string, options: ViewOptions): void {
const key = makeViewOptionsKey(prefix)
function _setViewOptions (viewlet: Viewlet, options: ViewOptions): void {
const key = makeViewOptionsKey(viewlet)
localStorage.setItem(key, JSON.stringify(options))
}
export function setViewOptions (viewlet: Viewlet, options: ViewOptions): void {
const viewletKey = viewlet?._id + (viewlet?.variant !== undefined ? `-${viewlet.variant}` : '')
_setViewOptions(viewletKey, options)
_setViewOptions(viewlet, options)
}
function _getViewOptions (prefix: string): ViewOptions | null {
const key = makeViewOptionsKey(prefix)
function _getViewOptions (viewlet: Viewlet): ViewOptions | null {
const key = makeViewOptionsKey(viewlet)
const options = localStorage.getItem(key)
if (options === null) return null
return JSON.parse(options)
@ -45,6 +45,5 @@ export function getViewOptions (viewlet: Viewlet | undefined, defaults = defaulO
if (viewlet === undefined) {
return { ...defaults }
}
const viewletKey = viewlet?._id + (viewlet?.variant !== undefined ? `-${viewlet.variant}` : '')
return _getViewOptions(viewletKey) ?? defaults
return _getViewOptions(viewlet) ?? defaults
}

View File

@ -82,6 +82,8 @@ export interface FilteredView extends Preference {
name: string
location: PlatformLocation
filters: string
viewOptions?: ViewOptions
viewletId?: Ref<Viewlet> | null
}
/**

View File

@ -13,26 +13,22 @@
// limitations under the License.
-->
<script lang="ts">
import core, { Doc, Ref, SortingOrder, Space, getCurrentAccount } from '@hcengineering/core'
import core, { Doc, getCurrentAccount, Ref, SortingOrder, Space } from '@hcengineering/core'
import { getResource } from '@hcengineering/platform'
import preference, { SpacePreference } from '@hcengineering/preference'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Button, IconEdit, navigate, Scroller, showPopup } from '@hcengineering/ui'
import type { Application, NavigatorModel, SpecialNavModel } from '@hcengineering/workbench'
import setting from '@hcengineering/setting'
import { Button, Scroller, showPopup } from '@hcengineering/ui'
import type { Application, NavigatorModel, SpecialNavModel } from '@hcengineering/workbench'
import { createEventDispatcher } from 'svelte'
import preferece, { SpacePreference } from '@hcengineering/preference'
import workbench from '../plugin'
import { getSpecialSpaceClass } from '../utils'
import preference from '@hcengineering/preference'
import HelpAndSupport from './HelpAndSupport.svelte'
import SpacesNav from './navigator/SpacesNav.svelte'
import SpecialElement from './navigator/SpecialElement.svelte'
import StarredNav from './navigator/StarredNav.svelte'
import TreeSeparator from './navigator/TreeSeparator.svelte'
import HelpAndSupport from './HelpAndSupport.svelte'
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'
import SavedView from './SavedView.svelte'
export let model: NavigatorModel | undefined
export let currentSpace: Ref<Space> | undefined
@ -69,7 +65,7 @@
const preferenceQuery = createQuery()
preferenceQuery.query(preferece.class.SpacePreference, {}, (res) => {
preferenceQuery.query(preference.class.SpacePreference, {}, (res) => {
preferences = new Map(
res.map((r) => {
return [r.attachedTo, r]
@ -106,18 +102,6 @@
$: 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 (
specials: SpecialNavModel[],
spaces: Space[],
@ -141,11 +125,6 @@
return [result, requestIndex]
}
const dispatch = createEventDispatcher()
const filteredViewsQuery = createQuery()
let filteredViews: FilteredView[] | undefined
$: filteredViewsQuery.query(view.class.FilteredView, { attachedTo: currentApplication?.alias }, (result) => {
filteredViews = result
})
</script>
{#if model}
@ -166,20 +145,7 @@
{/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}
<SavedView {currentApplication} />
{#if starred.length}
<StarredNav label={preference.string.Starred} spaces={starred} on:space {currentSpace} />
{/if}

View File

@ -0,0 +1,57 @@
<script lang="ts">
import { Doc, Ref } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import setting from '@hcengineering/setting'
import { Action, navigate } from '@hcengineering/ui'
import view, { FilteredView } from '@hcengineering/view'
import { filterStore, setActiveViewletId, setViewOptions } from '@hcengineering/view-resources'
import { Application } from '@hcengineering/workbench'
import TreeItem from './navigator/TreeItem.svelte'
import TreeNode from './navigator/TreeNode.svelte'
export let currentApplication: Application | undefined
const client = getClient()
const filteredViewsQuery = createQuery()
let filteredViews: FilteredView[] | undefined
$: filteredViewsQuery.query(
view.class.FilteredView,
{ attachedTo: currentApplication?.alias as Ref<Doc> },
(result) => {
filteredViews = result
}
)
async function removeAction (filteredView: FilteredView): Promise<Action[]> {
return [
{
icon: view.icon.Delete,
label: setting.string.Delete,
action: async (ctx: any, evt: Event) => {
await client.remove(filteredView)
}
}
]
}
async function load (fv: FilteredView): Promise<void> {
if (fv.viewletId !== undefined && fv.viewletId !== null) {
const viewlet = await client.findOne(view.class.Viewlet, { _id: fv.viewletId })
setActiveViewletId(fv.viewletId, fv.location)
if (viewlet !== undefined && fv.viewOptions !== undefined) {
setViewOptions(viewlet, fv.viewOptions)
}
}
navigate(fv.location)
$filterStore = JSON.parse(fv.filters)
}
</script>
{#if filteredViews && filteredViews.length > 0}
<TreeNode label={view.string.FilteredViews}>
{#each filteredViews as fv}
<TreeItem _id={fv._id} title={fv.name} on:click={() => load(fv)} actions={() => removeAction(fv)} />
{/each}
</TreeNode>
{/if}

View File

@ -18,7 +18,7 @@
import { IntlString } from '@hcengineering/platform'
import { createQuery } from '@hcengineering/presentation'
import { AnyComponent, Component, Loading } from '@hcengineering/ui'
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
import view, { Viewlet, ViewletPreference, ViewOptions } from '@hcengineering/view'
import { FilterBar } from '@hcengineering/view-resources'
export let _class: Ref<Class<Doc>>
@ -27,6 +27,7 @@
export let viewlet: WithLookup<Viewlet> | undefined
export let createItemDialog: AnyComponent | undefined
export let createItemLabel: IntlString | undefined
export let viewOptions: ViewOptions
const preferenceQuery = createQuery()
let preference: ViewletPreference | undefined
@ -56,7 +57,7 @@
{#if loading}
<Loading />
{:else}
<FilterBar {_class} query={searchQuery} on:change={(e) => (resultQuery = e.detail)} />
<FilterBar {_class} query={searchQuery} {viewOptions} on:change={(e) => (resultQuery = e.detail)} />
<Component
is={viewlet.$lookup?.descriptor?.component}
props={{

View File

@ -17,19 +17,22 @@
import core, { WithLookup } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
import { AnyComponent, Button, IconAdd, SearchEdit, showPanel, showPopup, TabList } from '@hcengineering/ui'
import view, { Viewlet } from '@hcengineering/view'
import {
getActiveViewletId,
getViewOptions,
setActiveViewletId,
ViewletSettingButton
} from '@hcengineering/view-resources'
AnyComponent,
Button,
deviceOptionsStore as deviceInfo,
IconAdd,
SearchEdit,
showPanel,
showPopup,
TabList
} from '@hcengineering/ui'
import view, { Viewlet, ViewOptions } from '@hcengineering/view'
import { getActiveViewletId, setActiveViewletId, ViewletSettingButton } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
import plugin from '../plugin'
import { classIcon } from '../utils'
import Header from './Header.svelte'
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
export let spaceId: Ref<Space> | undefined
export let createItemDialog: AnyComponent | undefined
@ -38,6 +41,7 @@
export let viewlet: WithLookup<Viewlet> | undefined
export let viewlets: WithLookup<Viewlet>[] = []
export let _class: Ref<Class<Doc>> | undefined = undefined
export let viewOptions: ViewOptions
const client = getClient()
const hierarchy = client.getHierarchy()
@ -79,6 +83,7 @@
const _id = getActiveViewletId()
const index = viewlets.findIndex((p) => p._id === (viewlet?._id ?? _id))
viewlet = index === -1 ? viewlets[0] : viewlets[index]
setActiveViewletId(viewlet._id)
}
$: viewslist = viewlets.map((views) => {
return {
@ -89,8 +94,6 @@
})
$: twoRows = $deviceInfo.twoRows
$: viewOptions = getViewOptions(viewlet)
</script>
<div class="ac-header withSettings" class:full={!twoRows} class:mini={twoRows}>
@ -130,6 +133,7 @@
on:select={(result) => {
if (result.detail !== undefined) {
viewlet = viewlets.find((vl) => vl._id === result.detail.id)
console.log('set viewlet by space headed')
if (viewlet) setActiveViewletId(viewlet._id)
}
}}

View File

@ -18,7 +18,7 @@
import { getClient } from '@hcengineering/presentation'
import { AnyComponent, Component } from '@hcengineering/ui'
import view, { Viewlet } from '@hcengineering/view'
import { getActiveViewletId } from '@hcengineering/view-resources'
import { getActiveViewletId, getViewOptions } from '@hcengineering/view-resources'
import type { ViewConfiguration } from '@hcengineering/workbench'
import SpaceContent from './SpaceContent.svelte'
import SpaceHeader from './SpaceHeader.svelte'
@ -79,6 +79,8 @@
function setViewlet (e: CustomEvent<WithLookup<Viewlet>>) {
viewlet = e.detail
}
$: viewOptions = getViewOptions(viewlet)
</script>
{#if _class && space}
@ -95,9 +97,10 @@
{viewlets}
{createItemDialog}
{createItemLabel}
{viewOptions}
bind:search
bind:viewlet
/>
{/if}
<SpaceContent space={space._id} {_class} {createItemDialog} {createItemLabel} bind:search {viewlet} />
<SpaceContent space={space._id} {_class} {createItemDialog} {viewOptions} {createItemLabel} bind:search {viewlet} />
{/if}

View File

@ -29,7 +29,13 @@
showPopup
} from '@hcengineering/ui'
import view, { Viewlet, ViewletDescriptor, ViewletPreference } from '@hcengineering/view'
import { FilterBar, FilterButton, getViewOptions, ViewletSettingButton } from '@hcengineering/view-resources'
import {
FilterBar,
FilterButton,
getViewOptions,
setActiveViewletId,
ViewletSettingButton
} from '@hcengineering/view-resources'
export let _class: Ref<Class<Doc>>
export let space: Ref<Space> | undefined = undefined
@ -71,6 +77,7 @@
.then((res) => {
viewlet = res
if (res !== undefined) {
setActiveViewletId(res._id)
preferenceQuery.query(
view.class.ViewletPreference,
{
@ -120,6 +127,7 @@
<FilterBar
{_class}
query={searchQuery}
{viewOptions}
on:change={(e) => {
resultQuery = e.detail
}}