mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-15 04:49:00 +00:00
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:
parent
f40750ec95
commit
467c7736e6
@ -286,7 +286,8 @@ export function createModel (builder: Builder): void {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
configOptions: {
|
configOptions: {
|
||||||
hiddenKeys: ['name']
|
hiddenKeys: ['name'],
|
||||||
|
sortable: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
contact.viewlet.TableContact
|
contact.viewlet.TableContact
|
||||||
|
@ -111,7 +111,10 @@ export function createModel (builder: Builder): void {
|
|||||||
{
|
{
|
||||||
attachTo: inventory.class.Product,
|
attachTo: inventory.class.Product,
|
||||||
descriptor: view.viewlet.Table,
|
descriptor: view.viewlet.Table,
|
||||||
config: ['', 'attachedTo', 'modifiedOn']
|
config: ['', 'attachedTo', 'modifiedOn'],
|
||||||
|
configOptions: {
|
||||||
|
sortable: true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
inventory.viewlet.TableProduct
|
inventory.viewlet.TableProduct
|
||||||
)
|
)
|
||||||
|
@ -209,7 +209,8 @@ export function createModel (builder: Builder): void {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
configOptions: {
|
configOptions: {
|
||||||
hiddenKeys: ['name']
|
hiddenKeys: ['name'],
|
||||||
|
sortable: true
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
lookup: {
|
lookup: {
|
||||||
@ -241,11 +242,6 @@ export function createModel (builder: Builder): void {
|
|||||||
'state',
|
'state',
|
||||||
'doneState',
|
'doneState',
|
||||||
'attachments',
|
'attachments',
|
||||||
{
|
|
||||||
key: '',
|
|
||||||
presenter: tracker.component.RelatedIssueSelector,
|
|
||||||
label: tracker.string.Relations
|
|
||||||
},
|
|
||||||
'comments',
|
'comments',
|
||||||
'modifiedOn',
|
'modifiedOn',
|
||||||
{
|
{
|
||||||
@ -253,6 +249,9 @@ export function createModel (builder: Builder): void {
|
|||||||
sortingKey: ['$lookup.attachedTo.$lookup.channels.lastMessage', '$lookup.attachedTo.channels']
|
sortingKey: ['$lookup.attachedTo.$lookup.channels.lastMessage', '$lookup.attachedTo.channels']
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
configOptions: {
|
||||||
|
sortable: true
|
||||||
|
},
|
||||||
options: {
|
options: {
|
||||||
lookup: {
|
lookup: {
|
||||||
_id: {
|
_id: {
|
||||||
@ -292,6 +291,7 @@ export function createModel (builder: Builder): void {
|
|||||||
descriptor: view.viewlet.List,
|
descriptor: view.viewlet.List,
|
||||||
configOptions: {
|
configOptions: {
|
||||||
hiddenKeys: ['title'],
|
hiddenKeys: ['title'],
|
||||||
|
sortable: true,
|
||||||
extraProps: {
|
extraProps: {
|
||||||
displayProps: {
|
displayProps: {
|
||||||
optional: true
|
optional: true
|
||||||
|
@ -400,7 +400,8 @@ export function createModel (builder: Builder): void {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
configOptions: {
|
configOptions: {
|
||||||
hiddenKeys: ['name']
|
hiddenKeys: ['name'],
|
||||||
|
sortable: true
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
lookup: {
|
lookup: {
|
||||||
@ -420,6 +421,9 @@ export function createModel (builder: Builder): void {
|
|||||||
attachTo: recruit.class.Applicant,
|
attachTo: recruit.class.Applicant,
|
||||||
descriptor: view.viewlet.Table,
|
descriptor: view.viewlet.Table,
|
||||||
config: ['', '$lookup.attachedTo', 'state', 'doneState', 'modifiedOn'],
|
config: ['', '$lookup.attachedTo', 'state', 'doneState', 'modifiedOn'],
|
||||||
|
configOptions: {
|
||||||
|
sortable: true
|
||||||
|
},
|
||||||
variant: 'short'
|
variant: 'short'
|
||||||
},
|
},
|
||||||
recruit.viewlet.VacancyApplicationsShort
|
recruit.viewlet.VacancyApplicationsShort
|
||||||
@ -432,6 +436,9 @@ export function createModel (builder: Builder): void {
|
|||||||
attachTo: recruit.class.Applicant,
|
attachTo: recruit.class.Applicant,
|
||||||
descriptor: view.viewlet.Table,
|
descriptor: view.viewlet.Table,
|
||||||
config: ['', '$lookup.space.name', '$lookup.space.$lookup.company', 'state', 'comments', 'doneState'],
|
config: ['', '$lookup.space.name', '$lookup.space.$lookup.company', 'state', 'comments', 'doneState'],
|
||||||
|
configOptions: {
|
||||||
|
sortable: true
|
||||||
|
},
|
||||||
variant: 'embedded'
|
variant: 'embedded'
|
||||||
},
|
},
|
||||||
recruit.viewlet.VacancyApplicationsEmbeddeed
|
recruit.viewlet.VacancyApplicationsEmbeddeed
|
||||||
@ -460,7 +467,8 @@ export function createModel (builder: Builder): void {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
configOptions: {
|
configOptions: {
|
||||||
hiddenKeys: ['name', 'space', 'modifiedOn']
|
hiddenKeys: ['name', 'space', 'modifiedOn'],
|
||||||
|
sortable: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
recruit.viewlet.TableVacancy
|
recruit.viewlet.TableVacancy
|
||||||
@ -492,7 +500,8 @@ export function createModel (builder: Builder): void {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
configOptions: {
|
configOptions: {
|
||||||
hiddenKeys: ['name', 'space', 'modifiedOn']
|
hiddenKeys: ['name', 'space', 'modifiedOn'],
|
||||||
|
sortable: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
recruit.viewlet.TableVacancyList
|
recruit.viewlet.TableVacancyList
|
||||||
@ -533,7 +542,8 @@ export function createModel (builder: Builder): void {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
configOptions: {
|
configOptions: {
|
||||||
hiddenKeys: ['name', 'attachedTo']
|
hiddenKeys: ['name', 'attachedTo'],
|
||||||
|
sortable: true
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
lookup: {
|
lookup: {
|
||||||
@ -588,7 +598,8 @@ export function createModel (builder: Builder): void {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
configOptions: {
|
configOptions: {
|
||||||
hiddenKeys: ['name', 'attachedTo']
|
hiddenKeys: ['name', 'attachedTo'],
|
||||||
|
sortable: true
|
||||||
},
|
},
|
||||||
baseQuery: {
|
baseQuery: {
|
||||||
doneState: null,
|
doneState: null,
|
||||||
@ -722,6 +733,7 @@ export function createModel (builder: Builder): void {
|
|||||||
},
|
},
|
||||||
configOptions: {
|
configOptions: {
|
||||||
hiddenKeys: ['name', 'attachedTo'],
|
hiddenKeys: ['name', 'attachedTo'],
|
||||||
|
sortable: true,
|
||||||
extraProps: {
|
extraProps: {
|
||||||
displayProps: {
|
displayProps: {
|
||||||
optional: true
|
optional: true
|
||||||
|
@ -505,6 +505,7 @@ export function createModel (builder: Builder): void {
|
|||||||
'dueDate',
|
'dueDate',
|
||||||
'attachedTo'
|
'attachedTo'
|
||||||
],
|
],
|
||||||
|
sortable: true,
|
||||||
extraProps: {
|
extraProps: {
|
||||||
displayProps: {
|
displayProps: {
|
||||||
optional: true
|
optional: true
|
||||||
@ -643,32 +644,51 @@ export function createModel (builder: Builder): void {
|
|||||||
descriptor: view.viewlet.List,
|
descriptor: view.viewlet.List,
|
||||||
viewOptions: subIssuesOptions,
|
viewOptions: subIssuesOptions,
|
||||||
variant: 'subissue',
|
variant: 'subissue',
|
||||||
|
configOptions: {
|
||||||
|
sortable: true,
|
||||||
|
hiddenKeys: ['priority', 'number', 'status', 'title', 'dueDate', 'milestone', 'estimation'],
|
||||||
|
extraProps: {
|
||||||
|
displayProps: {
|
||||||
|
optional: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
config: [
|
config: [
|
||||||
{
|
{
|
||||||
key: '',
|
key: '',
|
||||||
|
label: tracker.string.Priority,
|
||||||
presenter: tracker.component.PriorityEditor,
|
presenter: tracker.component.PriorityEditor,
|
||||||
props: { type: 'priority', kind: 'list', size: 'small' }
|
props: { type: 'priority', kind: 'list', size: 'small' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: '',
|
key: '',
|
||||||
|
label: tracker.string.Issue,
|
||||||
presenter: tracker.component.IssuePresenter,
|
presenter: tracker.component.IssuePresenter,
|
||||||
props: { type: 'issue' },
|
props: { type: 'issue' },
|
||||||
displayProps: { fixed: 'left' }
|
displayProps: { fixed: 'left' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: '',
|
key: '',
|
||||||
|
label: tracker.string.Status,
|
||||||
presenter: tracker.component.StatusEditor,
|
presenter: tracker.component.StatusEditor,
|
||||||
props: { kind: 'list', size: 'small', justify: 'center' }
|
props: { kind: 'list', size: 'small', justify: 'center' }
|
||||||
},
|
},
|
||||||
{ key: '', presenter: tracker.component.TitlePresenter, props: { shouldUseMargin: true, showParent: false } },
|
|
||||||
{ key: '', presenter: tracker.component.SubIssuesSelector, props: {} },
|
|
||||||
{
|
{
|
||||||
key: '',
|
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,
|
presenter: tracker.component.DueDatePresenter,
|
||||||
props: { kind: 'list' }
|
props: { kind: 'list' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: '',
|
key: '',
|
||||||
|
label: tracker.string.Milestone,
|
||||||
presenter: tracker.component.MilestoneEditor,
|
presenter: tracker.component.MilestoneEditor,
|
||||||
props: {
|
props: {
|
||||||
kind: 'list',
|
kind: 'list',
|
||||||
@ -683,6 +703,7 @@ export function createModel (builder: Builder): void {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: '',
|
key: '',
|
||||||
|
label: tracker.string.Estimation,
|
||||||
presenter: tracker.component.EstimationEditor,
|
presenter: tracker.component.EstimationEditor,
|
||||||
props: { kind: 'list', size: 'small' },
|
props: { kind: 'list', size: 'small' },
|
||||||
displayProps: { optional: true }
|
displayProps: { optional: true }
|
||||||
@ -720,7 +741,8 @@ export function createModel (builder: Builder): void {
|
|||||||
},
|
},
|
||||||
configOptions: {
|
configOptions: {
|
||||||
hiddenKeys: ['milestone', 'estimation', 'component', 'title', 'description'],
|
hiddenKeys: ['milestone', 'estimation', 'component', 'title', 'description'],
|
||||||
extraProps: { displayProps: { optional: true } }
|
extraProps: { displayProps: { optional: true } },
|
||||||
|
sortable: true
|
||||||
},
|
},
|
||||||
config: [
|
config: [
|
||||||
// { key: '', presenter: tracker.component.PriorityEditor, props: { kind: 'list', size: 'small' } },
|
// { key: '', presenter: tracker.component.PriorityEditor, props: { kind: 'list', size: 'small' } },
|
||||||
@ -1861,7 +1883,8 @@ export function createModel (builder: Builder): void {
|
|||||||
viewOptions: milestoneOptions,
|
viewOptions: milestoneOptions,
|
||||||
configOptions: {
|
configOptions: {
|
||||||
hiddenKeys: ['targetDate', 'label', 'description'],
|
hiddenKeys: ['targetDate', 'label', 'description'],
|
||||||
extraProps: { displayProps: { optional: true } }
|
extraProps: { displayProps: { optional: true } },
|
||||||
|
sortable: true
|
||||||
},
|
},
|
||||||
config: [
|
config: [
|
||||||
{
|
{
|
||||||
@ -1941,7 +1964,8 @@ export function createModel (builder: Builder): void {
|
|||||||
viewOptions: componentListViewOptions,
|
viewOptions: componentListViewOptions,
|
||||||
configOptions: {
|
configOptions: {
|
||||||
hiddenKeys: ['label', 'description'],
|
hiddenKeys: ['label', 'description'],
|
||||||
extraProps: { displayProps: { optional: true } }
|
extraProps: { displayProps: { optional: true } },
|
||||||
|
sortable: true
|
||||||
},
|
},
|
||||||
config: [
|
config: [
|
||||||
{
|
{
|
||||||
|
@ -67,7 +67,6 @@ import SetParentIssueActionPopup from './components/SetParentIssueActionPopup.sv
|
|||||||
import MilestoneDatePresenter from './components/milestones/MilestoneDatePresenter.svelte'
|
import MilestoneDatePresenter from './components/milestones/MilestoneDatePresenter.svelte'
|
||||||
import EditMilestone from './components/milestones/EditMilestone.svelte'
|
import EditMilestone from './components/milestones/EditMilestone.svelte'
|
||||||
import CreateIssueTemplate from './components/templates/CreateIssueTemplate.svelte'
|
import CreateIssueTemplate from './components/templates/CreateIssueTemplate.svelte'
|
||||||
import Views from './components/views/Views.svelte'
|
|
||||||
import Statuses from './components/workflow/Statuses.svelte'
|
import Statuses from './components/workflow/Statuses.svelte'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -384,7 +383,6 @@ export default async (): Promise<Resources> => ({
|
|||||||
Inbox,
|
Inbox,
|
||||||
MyIssues,
|
MyIssues,
|
||||||
Components,
|
Components,
|
||||||
Views,
|
|
||||||
IssuePresenter,
|
IssuePresenter,
|
||||||
ComponentPresenter,
|
ComponentPresenter,
|
||||||
ComponentTitlePresenter,
|
ComponentTitlePresenter,
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
import { Asset, IntlString } from '@hcengineering/platform'
|
import { Asset, IntlString } from '@hcengineering/platform'
|
||||||
import preferencePlugin from '@hcengineering/preference'
|
import preferencePlugin from '@hcengineering/preference'
|
||||||
import { createQuery, getAttributePresenterClass, getClient, hasResource } from '@hcengineering/presentation'
|
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 { BuildModelKey, Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||||
import { deepEqual } from 'fast-equals'
|
import { deepEqual } from 'fast-equals'
|
||||||
import view from '../plugin'
|
import view from '../plugin'
|
||||||
@ -36,8 +36,7 @@
|
|||||||
},
|
},
|
||||||
(res) => {
|
(res) => {
|
||||||
preference = res[0]
|
preference = res[0]
|
||||||
attributes = getConfig(viewlet, preference)
|
items = getConfig(viewlet, preference)
|
||||||
classes = groupByClasses(attributes)
|
|
||||||
loading = false
|
loading = false
|
||||||
},
|
},
|
||||||
{ limit: 1 }
|
{ limit: 1 }
|
||||||
@ -48,7 +47,7 @@
|
|||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const hierarchy = client.getHierarchy()
|
const hierarchy = client.getHierarchy()
|
||||||
let attributes: AttributeConfig[] = []
|
let items: AttributeConfig[] = []
|
||||||
let loading = true
|
let loading = true
|
||||||
|
|
||||||
interface AttributeConfig {
|
interface AttributeConfig {
|
||||||
@ -57,6 +56,7 @@
|
|||||||
value: string | BuildModelKey
|
value: string | BuildModelKey
|
||||||
_class: Ref<Class<Doc>>
|
_class: Ref<Class<Doc>>
|
||||||
icon: Asset | undefined
|
icon: Asset | undefined
|
||||||
|
order?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
function getObjectConfig (_class: Ref<Class<Doc>>, param: string): AttributeConfig {
|
function getObjectConfig (_class: Ref<Class<Doc>>, param: string): AttributeConfig {
|
||||||
@ -201,10 +201,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function save (): Promise<void> {
|
async function save (): Promise<void> {
|
||||||
const config = Array.from(classes.values())
|
const config = items.filter((p) => p.enabled).map((p) => p.value)
|
||||||
.flat()
|
|
||||||
.filter((p) => p.enabled)
|
|
||||||
.map((p) => p.value)
|
|
||||||
if (preference !== undefined) {
|
if (preference !== undefined) {
|
||||||
await client.update(preference, {
|
await client.update(preference, {
|
||||||
config
|
config
|
||||||
@ -217,29 +214,51 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// function restoreDefault (): void {
|
function restoreDefault (): void {
|
||||||
// attributes = getConfig(viewlet, undefined)
|
items = getConfig(viewlet, undefined)
|
||||||
// classes = groupByClasses(attributes)
|
save()
|
||||||
// }
|
}
|
||||||
|
|
||||||
function setStatus (result: AttributeConfig[], preference: ViewletPreference): AttributeConfig[] {
|
function setStatus (result: AttributeConfig[], preference: ViewletPreference): AttributeConfig[] {
|
||||||
for (const key of result) {
|
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
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
function groupByClasses (attributes: AttributeConfig[]): Map<Ref<Class<Doc>>, AttributeConfig[]> {
|
function dragEnd () {
|
||||||
const res = new Map()
|
selected = undefined
|
||||||
for (const attribute of attributes) {
|
save()
|
||||||
const arr = res.get(attribute._class) ?? []
|
|
||||||
arr.push(attribute)
|
|
||||||
res.set(attribute._class, arr)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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>
|
</script>
|
||||||
|
|
||||||
<div class="selectPopup p-2">
|
<div class="selectPopup p-2">
|
||||||
@ -247,23 +266,29 @@
|
|||||||
{#if loading}
|
{#if loading}
|
||||||
<Loading />
|
<Loading />
|
||||||
{:else}
|
{:else}
|
||||||
{#each Array.from(classes.keys()) as _class, i}
|
<div class="flex-row-reverse">
|
||||||
{@const items = classes.get(_class) ?? []}
|
<Button on:click={restoreDefault} label={view.string.RestoreDefaults} size={'x-small'} kind={'link'} noFocus />
|
||||||
{#if i !== 0}
|
</div>
|
||||||
<div class="menu-separator" />
|
{#each items as item, i}
|
||||||
{/if}
|
<div
|
||||||
{#each items as item}
|
class="item"
|
||||||
<div class="item">
|
bind:this={elements[i]}
|
||||||
<ToggleWithLabel
|
draggable={viewlet.configOptions?.sortable}
|
||||||
on={item.enabled}
|
on:dragstart={() => {
|
||||||
label={item.label}
|
selected = i
|
||||||
on:change={(e) => {
|
}}
|
||||||
item.enabled = e.detail
|
on:dragover|preventDefault={(e) => dragOver(e, i)}
|
||||||
save()
|
on:dragend={dragEnd}
|
||||||
}}
|
>
|
||||||
/>
|
<ToggleWithLabel
|
||||||
</div>
|
on={item.enabled}
|
||||||
{/each}
|
label={item.label}
|
||||||
|
on:change={(e) => {
|
||||||
|
item.enabled = e.detail
|
||||||
|
save()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -329,6 +329,7 @@ export interface ViewletConfigOptions {
|
|||||||
hiddenKeys?: string[]
|
hiddenKeys?: string[]
|
||||||
strict?: boolean
|
strict?: boolean
|
||||||
extraProps?: Omit<BuildModelKey, 'key'>
|
extraProps?: Omit<BuildModelKey, 'key'>
|
||||||
|
sortable?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user