UBER-351 Use rank for table view configuration to allow reordering of columns (#3321)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2023-06-01 17:33:42 +06:00 committed by GitHub
parent f40750ec95
commit 467c7736e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 123 additions and 59 deletions

View File

@ -286,7 +286,8 @@ export function createModel (builder: Builder): void {
}
],
configOptions: {
hiddenKeys: ['name']
hiddenKeys: ['name'],
sortable: true
}
},
contact.viewlet.TableContact

View File

@ -111,7 +111,10 @@ export function createModel (builder: Builder): void {
{
attachTo: inventory.class.Product,
descriptor: view.viewlet.Table,
config: ['', 'attachedTo', 'modifiedOn']
config: ['', 'attachedTo', 'modifiedOn'],
configOptions: {
sortable: true
}
},
inventory.viewlet.TableProduct
)

View File

@ -209,7 +209,8 @@ export function createModel (builder: Builder): void {
}
],
configOptions: {
hiddenKeys: ['name']
hiddenKeys: ['name'],
sortable: true
},
options: {
lookup: {
@ -241,11 +242,6 @@ export function createModel (builder: Builder): void {
'state',
'doneState',
'attachments',
{
key: '',
presenter: tracker.component.RelatedIssueSelector,
label: tracker.string.Relations
},
'comments',
'modifiedOn',
{
@ -253,6 +249,9 @@ export function createModel (builder: Builder): void {
sortingKey: ['$lookup.attachedTo.$lookup.channels.lastMessage', '$lookup.attachedTo.channels']
}
],
configOptions: {
sortable: true
},
options: {
lookup: {
_id: {
@ -292,6 +291,7 @@ export function createModel (builder: Builder): void {
descriptor: view.viewlet.List,
configOptions: {
hiddenKeys: ['title'],
sortable: true,
extraProps: {
displayProps: {
optional: true

View File

@ -400,7 +400,8 @@ export function createModel (builder: Builder): void {
}
],
configOptions: {
hiddenKeys: ['name']
hiddenKeys: ['name'],
sortable: true
},
options: {
lookup: {
@ -420,6 +421,9 @@ export function createModel (builder: Builder): void {
attachTo: recruit.class.Applicant,
descriptor: view.viewlet.Table,
config: ['', '$lookup.attachedTo', 'state', 'doneState', 'modifiedOn'],
configOptions: {
sortable: true
},
variant: 'short'
},
recruit.viewlet.VacancyApplicationsShort
@ -432,6 +436,9 @@ export function createModel (builder: Builder): void {
attachTo: recruit.class.Applicant,
descriptor: view.viewlet.Table,
config: ['', '$lookup.space.name', '$lookup.space.$lookup.company', 'state', 'comments', 'doneState'],
configOptions: {
sortable: true
},
variant: 'embedded'
},
recruit.viewlet.VacancyApplicationsEmbeddeed
@ -460,7 +467,8 @@ export function createModel (builder: Builder): void {
}
],
configOptions: {
hiddenKeys: ['name', 'space', 'modifiedOn']
hiddenKeys: ['name', 'space', 'modifiedOn'],
sortable: true
}
},
recruit.viewlet.TableVacancy
@ -492,7 +500,8 @@ export function createModel (builder: Builder): void {
}
],
configOptions: {
hiddenKeys: ['name', 'space', 'modifiedOn']
hiddenKeys: ['name', 'space', 'modifiedOn'],
sortable: true
}
},
recruit.viewlet.TableVacancyList
@ -533,7 +542,8 @@ export function createModel (builder: Builder): void {
}
],
configOptions: {
hiddenKeys: ['name', 'attachedTo']
hiddenKeys: ['name', 'attachedTo'],
sortable: true
},
options: {
lookup: {
@ -588,7 +598,8 @@ export function createModel (builder: Builder): void {
}
},
configOptions: {
hiddenKeys: ['name', 'attachedTo']
hiddenKeys: ['name', 'attachedTo'],
sortable: true
},
baseQuery: {
doneState: null,
@ -722,6 +733,7 @@ export function createModel (builder: Builder): void {
},
configOptions: {
hiddenKeys: ['name', 'attachedTo'],
sortable: true,
extraProps: {
displayProps: {
optional: true

View File

@ -505,6 +505,7 @@ export function createModel (builder: Builder): void {
'dueDate',
'attachedTo'
],
sortable: true,
extraProps: {
displayProps: {
optional: true
@ -643,32 +644,51 @@ export function createModel (builder: Builder): void {
descriptor: view.viewlet.List,
viewOptions: subIssuesOptions,
variant: 'subissue',
configOptions: {
sortable: true,
hiddenKeys: ['priority', 'number', 'status', 'title', 'dueDate', 'milestone', 'estimation'],
extraProps: {
displayProps: {
optional: true
}
}
},
config: [
{
key: '',
label: tracker.string.Priority,
presenter: tracker.component.PriorityEditor,
props: { type: 'priority', kind: 'list', size: 'small' }
},
{
key: '',
label: tracker.string.Issue,
presenter: tracker.component.IssuePresenter,
props: { type: 'issue' },
displayProps: { fixed: 'left' }
},
{
key: '',
label: tracker.string.Status,
presenter: tracker.component.StatusEditor,
props: { kind: 'list', size: 'small', justify: 'center' }
},
{ key: '', presenter: tracker.component.TitlePresenter, props: { shouldUseMargin: true, showParent: false } },
{ key: '', presenter: tracker.component.SubIssuesSelector, props: {} },
{
key: '',
label: tracker.string.Title,
presenter: tracker.component.TitlePresenter,
props: { shouldUseMargin: true, showParent: false }
},
{ key: '', label: tracker.string.SubIssues, presenter: tracker.component.SubIssuesSelector, props: {} },
{
key: '',
label: tracker.string.DueDate,
presenter: tracker.component.DueDatePresenter,
props: { kind: 'list' }
},
{
key: '',
label: tracker.string.Milestone,
presenter: tracker.component.MilestoneEditor,
props: {
kind: 'list',
@ -683,6 +703,7 @@ export function createModel (builder: Builder): void {
},
{
key: '',
label: tracker.string.Estimation,
presenter: tracker.component.EstimationEditor,
props: { kind: 'list', size: 'small' },
displayProps: { optional: true }
@ -720,7 +741,8 @@ export function createModel (builder: Builder): void {
},
configOptions: {
hiddenKeys: ['milestone', 'estimation', 'component', 'title', 'description'],
extraProps: { displayProps: { optional: true } }
extraProps: { displayProps: { optional: true } },
sortable: true
},
config: [
// { key: '', presenter: tracker.component.PriorityEditor, props: { kind: 'list', size: 'small' } },
@ -1861,7 +1883,8 @@ export function createModel (builder: Builder): void {
viewOptions: milestoneOptions,
configOptions: {
hiddenKeys: ['targetDate', 'label', 'description'],
extraProps: { displayProps: { optional: true } }
extraProps: { displayProps: { optional: true } },
sortable: true
},
config: [
{
@ -1941,7 +1964,8 @@ export function createModel (builder: Builder): void {
viewOptions: componentListViewOptions,
configOptions: {
hiddenKeys: ['label', 'description'],
extraProps: { displayProps: { optional: true } }
extraProps: { displayProps: { optional: true } },
sortable: true
},
config: [
{

View File

@ -67,7 +67,6 @@ import SetParentIssueActionPopup from './components/SetParentIssueActionPopup.sv
import MilestoneDatePresenter from './components/milestones/MilestoneDatePresenter.svelte'
import EditMilestone from './components/milestones/EditMilestone.svelte'
import CreateIssueTemplate from './components/templates/CreateIssueTemplate.svelte'
import Views from './components/views/Views.svelte'
import Statuses from './components/workflow/Statuses.svelte'
import {
@ -384,7 +383,6 @@ export default async (): Promise<Resources> => ({
Inbox,
MyIssues,
Components,
Views,
IssuePresenter,
ComponentPresenter,
ComponentTitlePresenter,

View File

@ -17,7 +17,7 @@
import { Asset, IntlString } from '@hcengineering/platform'
import preferencePlugin from '@hcengineering/preference'
import { createQuery, getAttributePresenterClass, getClient, hasResource } from '@hcengineering/presentation'
import { Loading, ToggleWithLabel } from '@hcengineering/ui'
import { Button, Loading, ToggleWithLabel } from '@hcengineering/ui'
import { BuildModelKey, Viewlet, ViewletPreference } from '@hcengineering/view'
import { deepEqual } from 'fast-equals'
import view from '../plugin'
@ -36,8 +36,7 @@
},
(res) => {
preference = res[0]
attributes = getConfig(viewlet, preference)
classes = groupByClasses(attributes)
items = getConfig(viewlet, preference)
loading = false
},
{ limit: 1 }
@ -48,7 +47,7 @@
const client = getClient()
const hierarchy = client.getHierarchy()
let attributes: AttributeConfig[] = []
let items: AttributeConfig[] = []
let loading = true
interface AttributeConfig {
@ -57,6 +56,7 @@
value: string | BuildModelKey
_class: Ref<Class<Doc>>
icon: Asset | undefined
order?: number
}
function getObjectConfig (_class: Ref<Class<Doc>>, param: string): AttributeConfig {
@ -201,10 +201,7 @@
}
async function save (): Promise<void> {
const config = Array.from(classes.values())
.flat()
.filter((p) => p.enabled)
.map((p) => p.value)
const config = items.filter((p) => p.enabled).map((p) => p.value)
if (preference !== undefined) {
await client.update(preference, {
config
@ -217,29 +214,51 @@
}
}
// function restoreDefault (): void {
// attributes = getConfig(viewlet, undefined)
// classes = groupByClasses(attributes)
// }
function restoreDefault (): void {
items = getConfig(viewlet, undefined)
save()
}
function setStatus (result: AttributeConfig[], preference: ViewletPreference): AttributeConfig[] {
for (const key of result) {
key.enabled = preference.config.findIndex((p) => deepEqual(p, key.value)) !== -1
const index = preference.config.findIndex((p) => deepEqual(p, key.value))
key.enabled = index !== -1
key.order = index !== -1 ? index : undefined
}
result.sort((a, b) => {
if (a.order === undefined && b.order === undefined) return 0
if (a.order === undefined) return 1
if (b.order === undefined) return -1
return a.order - b.order
})
return result
}
function groupByClasses (attributes: AttributeConfig[]): Map<Ref<Class<Doc>>, AttributeConfig[]> {
const res = new Map()
for (const attribute of attributes) {
const arr = res.get(attribute._class) ?? []
arr.push(attribute)
res.set(attribute._class, arr)
}
return res
function dragEnd () {
selected = undefined
save()
}
let classes: Map<Ref<Class<Doc>>, AttributeConfig[]> = new Map()
function dragOver (e: DragEvent, i: number) {
const s = selected as number
if (dragswap(e, i, s)) {
;[items[i], items[s]] = [items[s], items[i]]
selected = i
}
}
const elements: HTMLElement[] = []
function dragswap (ev: MouseEvent, i: number, s: number): boolean {
if (i < s) {
return ev.offsetY < elements[i].offsetHeight / 2
} else if (i > s) {
return ev.offsetY > elements[i].offsetHeight / 2
}
return false
}
let selected: number | undefined
</script>
<div class="selectPopup p-2">
@ -247,23 +266,29 @@
{#if loading}
<Loading />
{:else}
{#each Array.from(classes.keys()) as _class, i}
{@const items = classes.get(_class) ?? []}
{#if i !== 0}
<div class="menu-separator" />
{/if}
{#each items as item}
<div class="item">
<ToggleWithLabel
on={item.enabled}
label={item.label}
on:change={(e) => {
item.enabled = e.detail
save()
}}
/>
</div>
{/each}
<div class="flex-row-reverse">
<Button on:click={restoreDefault} label={view.string.RestoreDefaults} size={'x-small'} kind={'link'} noFocus />
</div>
{#each items as item, i}
<div
class="item"
bind:this={elements[i]}
draggable={viewlet.configOptions?.sortable}
on:dragstart={() => {
selected = i
}}
on:dragover|preventDefault={(e) => dragOver(e, i)}
on:dragend={dragEnd}
>
<ToggleWithLabel
on={item.enabled}
label={item.label}
on:change={(e) => {
item.enabled = e.detail
save()
}}
/>
</div>
{/each}
{/if}
</div>

View File

@ -329,6 +329,7 @@ export interface ViewletConfigOptions {
hiddenKeys?: string[]
strict?: boolean
extraProps?: Omit<BuildModelKey, 'key'>
sortable?: boolean
}
/**