UBER-504: fix presenters on ListItem. Add DeviceSizes. (#3463)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov 2023-06-28 07:26:37 +03:00 committed by GitHub
parent dbfbcc12ee
commit c7ade707c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 345 additions and 98 deletions

View File

@ -474,14 +474,19 @@ export function createModel (builder: Builder): void {
props: {}, props: {},
displayProps: { key: 'title' } displayProps: { key: 'title' }
}, },
{ key: 'comments', displayProps: { key: 'comments' } }, {
{ key: 'attachments', displayProps: { key: 'attachments' } }, key: '',
{ key: '', label: tracker.string.SubIssues, presenter: tracker.component.SubIssuesSelector, props: {} }, label: tracker.string.SubIssues,
presenter: tracker.component.SubIssuesSelector,
props: {}
},
{ key: 'comments', displayProps: { key: 'comments', suffix: true } },
{ key: 'attachments', displayProps: { key: 'attachments', suffix: true } },
{ key: '', displayProps: { grow: true } }, { key: '', displayProps: { grow: true } },
{ {
key: 'labels', key: 'labels',
presenter: tags.component.LabelsPresenter, presenter: tags.component.LabelsPresenter,
displayProps: { optional: true, compression: true }, displayProps: { optional: true },
props: { kind: 'list', full: false } props: { kind: 'list', full: false }
}, },
{ {
@ -497,7 +502,6 @@ export function createModel (builder: Builder): void {
displayProps: { displayProps: {
key: 'milestone', key: 'milestone',
excludeByKey: 'milestone', excludeByKey: 'milestone',
compression: true,
optional: true optional: true
} }
}, },
@ -514,7 +518,6 @@ export function createModel (builder: Builder): void {
displayProps: { displayProps: {
key: 'component', key: 'component',
excludeByKey: 'component', excludeByKey: 'component',
compression: true,
optional: true optional: true
} }
}, },
@ -522,7 +525,7 @@ export function createModel (builder: Builder): void {
key: '', key: '',
label: tracker.string.DueDate, label: tracker.string.DueDate,
presenter: tracker.component.DueDatePresenter, presenter: tracker.component.DueDatePresenter,
displayProps: { key: 'dueDate', optional: true, compression: true }, displayProps: { key: 'dueDate', optional: true },
props: { kind: 'list' } props: { kind: 'list' }
}, },
{ {
@ -530,7 +533,7 @@ export function createModel (builder: Builder): void {
label: tracker.string.Estimation, label: tracker.string.Estimation,
presenter: tracker.component.EstimationEditor, presenter: tracker.component.EstimationEditor,
props: { kind: 'list', size: 'small' }, props: { kind: 'list', size: 'small' },
displayProps: { key: 'estimation', fixed: 'left', compression: true, dividerBefore: true } displayProps: { key: 'estimation', fixed: 'left', dividerBefore: true }
}, },
{ {
key: 'modifiedOn', key: 'modifiedOn',
@ -697,7 +700,7 @@ export function createModel (builder: Builder): void {
size: 'small', size: 'small',
shouldShowPlaceholder: false shouldShowPlaceholder: false
}, },
displayProps: { key: 'component', optional: true, compression: true } displayProps: { key: 'component', optional: true }
}, },
{ {
key: '', key: '',
@ -708,7 +711,7 @@ export function createModel (builder: Builder): void {
size: 'small', size: 'small',
shouldShowPlaceholder: false shouldShowPlaceholder: false
}, },
displayProps: { key: 'milestone', optional: true, compression: true } displayProps: { key: 'milestone', optional: true }
}, },
{ {
key: '', key: '',
@ -718,7 +721,7 @@ export function createModel (builder: Builder): void {
kind: 'list', kind: 'list',
size: 'small' size: 'small'
}, },
displayProps: { key: 'estimation', optional: true, compression: true } displayProps: { key: 'estimation', optional: true }
}, },
{ key: '', displayProps: { grow: true } }, { key: '', displayProps: { grow: true } },
{ {

View File

@ -453,6 +453,7 @@ input.search {
width: 1.5rem; width: 1.5rem;
height: 1.5rem; height: 1.5rem;
} }
&.halfcontent { color: var(--theme-halfcontent-color); }
} }
&:hover .icon { color: var(--theme-caption-color); } &:hover .icon { color: var(--theme-caption-color); }
} }
@ -904,6 +905,7 @@ a.no-line {
.content-trans-color { color: var(--theme-trans-color); } .content-trans-color { color: var(--theme-trans-color); }
.content-darker-color { color: var(--theme-darker-color); } .content-darker-color { color: var(--theme-darker-color); }
.content-halfcontent-color { color: var(--theme-halfcontent-color); }
.content-dark-color { color: var(--theme-dark-color); } .content-dark-color { color: var(--theme-dark-color); }
.content-color { color: var(--theme-content-color); } .content-color { color: var(--theme-content-color); }
.caption-color { color: var(--theme-caption-color); } .caption-color { color: var(--theme-caption-color); }

View File

@ -890,27 +890,28 @@
.optional-bar { .optional-bar {
overflow: hidden; overflow: hidden;
display: flex; display: flex;
justify-content: flex-end;
align-items: center; align-items: center;
flex-shrink: 1; flex-grow: 1;
min-width: 0; border-radius: 1.625rem;
border-radius: 0 1.49rem 1.49rem 0; transition: flex-shrink 0.25s cubic-bezier(0.38, 0.01, 0.33, 1) 0s;
&:hover {
flex-shrink: .5;
min-width: initial;
}
.label-wrapper { .label-wrapper {
display: flex; display: flex;
align-items: center; align-items: center;
flex-shrink: 10;
width: auto; width: auto;
min-width: 0; min-width: 0;
} }
& > *:not(:last-child) {
flex-shrink: 10;
width: min-content;
}
& > *:last-child { & > *:last-child {
flex-shrink: 0; flex-shrink: 0;
width: max-content; width: max-content;
} }
& > * { margin-left: .375rem; } & > *:not(:first-child) { margin-left: .25rem; }
& > * > * { min-width: fit-content; }
} }
} }
// Labels on the ListView // Labels on the ListView
@ -920,10 +921,13 @@
.list-container .antiButton.list:hover, .list-container .antiButton.list:hover,
.list-container .datetime-button, .list-container .datetime-button,
.list-container .datetime-button:hover { .list-container .datetime-button:hover {
padding-left: .5rem !important;
padding-right: .5rem !important;
font-size: 0.8125rem !important;
background-color: var(--theme-list-button-color) !important; background-color: var(--theme-list-button-color) !important;
&:not(.only-icon) .btn-icon, &:not(.only-icon) .btn-icon,
&:not(.only-icon) .icon { margin-right: .5rem !important; } &:not(.only-icon) .icon { margin-right: .375rem !important; }
.icon, .btn-icon { color: var(--theme-halfcontent-color) !important; } .icon, .btn-icon { color: var(--theme-halfcontent-color) !important; }
.label { .label {
font-size: 0.8125rem !important; font-size: 0.8125rem !important;

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import platform, { addEventListener, getMetadata, OK, PlatformEvent, Status } from '@hcengineering/platform' import platform, { addEventListener, getMetadata, OK, PlatformEvent, Status } from '@hcengineering/platform'
import { onDestroy } from 'svelte' import { onDestroy } from 'svelte'
import type { AnyComponent } from '../../types' import type { AnyComponent, WidthType } from '../../types'
// import { applicationShortcutKey } from '../../utils' // import { applicationShortcutKey } from '../../utils'
import { getCurrentLocation, location, navigate, locationStorageKeyId } from '../../location' import { getCurrentLocation, location, navigate, locationStorageKeyId } from '../../location'
@ -100,6 +100,41 @@
document.addEventListener('dblclick', (event) => { document.addEventListener('dblclick', (event) => {
event.preventDefault() event.preventDefault()
}) })
let remove: any = null
const sizes: Record<WidthType, boolean> = { xs: false, sm: false, md: false, lg: false, xl: false, xxl: false }
const css: Record<WidthType, string> = { xs: '', sm: '', md: '', lg: '', xl: '', xxl: '' }
const deviceSizes: WidthType[] = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl']
const deviceWidths = [480, 680, 760, 1024, 1208, -1]
deviceSizes.forEach((ds, i) => {
if (i === 0) css[ds] = `(max-width: ${deviceWidths[i]}px)`
else if (i === deviceSizes.length - 1) css[ds] = `(min-width: ${deviceWidths[i - 1]}.01px)`
else css[ds] = `(min-width: ${deviceWidths[i - 1]}.01px) and (max-width: ${deviceWidths[i]}px)`
})
const getSize = (width: number): WidthType => {
return deviceSizes[
deviceWidths.findIndex((it) => (it === -1 ? deviceWidths[deviceWidths.length - 2] < width : it > width))
]
}
const updateDeviceSize = () => {
if (remove !== null) remove()
const size = getSize(docWidth)
const mqString = css[size]
const media = matchMedia(mqString)
deviceWidths.forEach((_, i) => (sizes[deviceSizes[i]] = false))
deviceWidths.forEach((dw, i) => {
sizes[deviceSizes[i]] =
dw === -1 ? deviceWidths[deviceWidths.length - 2] < docWidth : docWidth > dw || size === deviceSizes[i]
})
$deviceInfo.size = size
$deviceInfo.sizes = sizes
media.addEventListener('change', updateDeviceSize)
remove = () => {
media.removeEventListener('change', updateDeviceSize)
}
}
updateDeviceSize()
</script> </script>
<svelte:window bind:innerWidth={docWidth} bind:innerHeight={docHeight} /> <svelte:window bind:innerWidth={docWidth} bind:innerHeight={docHeight} />

View File

@ -220,6 +220,8 @@ export const deviceOptionsStore = writable<DeviceOptions>({
isPortrait: false, isPortrait: false,
isMobile: false, isMobile: false,
fontSize: 0, fontSize: 0,
size: null,
sizes: { xs: false, sm: false, md: false, lg: false, xl: false, xxl: false },
minWidth: false, minWidth: false,
twoRows: false twoRows: false
}) })

View File

@ -295,12 +295,16 @@ export const tableHRscheduleY: FadeOptions = { multipler: { top: 5, bottom: 0 }
export const issueSP: FadeOptions = { multipler: { top: 2.75, bottom: 0 } } export const issueSP: FadeOptions = { multipler: { top: 2.75, bottom: 0 } }
export const emojiSP: FadeOptions = { multipler: { top: 1.5, bottom: 0 } } export const emojiSP: FadeOptions = { multipler: { top: 1.5, bottom: 0 } }
export type WidthType = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl'
export interface DeviceOptions { export interface DeviceOptions {
docWidth: number docWidth: number
docHeight: number docHeight: number
isPortrait: boolean isPortrait: boolean
isMobile: boolean isMobile: boolean
fontSize: number fontSize: number
size: WidthType | null
sizes: Record<WidthType, boolean>
minWidth: boolean minWidth: boolean
twoRows: boolean twoRows: boolean
theme?: any theme?: any

View File

@ -23,29 +23,40 @@
export let object: Doc export let object: Doc
export let size: ButtonSize = 'small' export let size: ButtonSize = 'small'
export let kind: ButtonKind = 'link' export let kind: ButtonKind = 'link'
export let showCounter = true export let showCounter: boolean = true
export let compactMode: boolean = false
</script> </script>
{#if value && value > 0} {#if value && value > 0}
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
<DocNavLink {object} inline noUnderline={true}> <DocNavLink {object} inline noUnderline={true} shrink={0}>
{#if kind === 'list'} {#if kind === 'list'}
<div {#if compactMode}
use:tooltip={{ <div
component: AttachmentPopup, use:tooltip={{
props: { objectId: object._id, attachments: value, object } component: AttachmentPopup,
}} props: { objectId: object._id, attachments: value, object }
class="sm-tool-icon" }}
> class="sm-tool-icon"
<Button {kind} {size}>
<div slot="content" class="flex-row-center">
<span class="icon"><IconAttachment {size} /></span>
{#if showCounter}
{value}
{/if}
</div></Button
> >
</div> <div class="icon halfcontent"><IconAttachment {size} /></div>
{#if showCounter}{value ?? 0}{/if}
</div>
{:else}
<Button
{kind}
{size}
showTooltip={{
component: AttachmentPopup,
props: { objectId: object._id, attachments: value, object }
}}
>
<div slot="icon"><IconAttachment {size} /></div>
<div slot="content" style:margin-left={showCounter ? '.375rem' : '0'}>
{#if showCounter}{value ?? 0}{/if}
</div>
</Button>
{/if}
{:else} {:else}
<div <div
use:tooltip={{ use:tooltip={{

View File

@ -23,30 +23,41 @@
export let object: Doc export let object: Doc
export let size: ButtonSize = 'small' export let size: ButtonSize = 'small'
export let kind: ButtonKind = 'link' export let kind: ButtonKind = 'link'
export let showCounter = true export let showCounter: boolean = true
export let compactMode: boolean = false
export let withInput: boolean = true export let withInput: boolean = true
</script> </script>
{#if value && value > 0} {#if value && value > 0}
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
<DocNavLink {object} inline noUnderline={true}> <DocNavLink {object} inline noUnderline={true} shrink={0}>
{#if kind === 'list'} {#if kind === 'list'}
<div {#if compactMode}
use:tooltip={{ <div
component: CommentPopup, use:tooltip={{
props: { objectId: object._id, object, withInput } component: CommentPopup,
}} props: { objectId: object._id, object, withInput }
class="sm-tool-icon" }}
> class="sm-tool-icon"
<Button {kind} {size}> >
<div slot="content" class="flex-row-center"> <div class="icon halfcontent"><IconThread {size} /></div>
<span class="icon"><IconThread size={'small'} /></span> {#if showCounter}{value ?? 0}{/if}
{#if showCounter} </div>
{value ?? 0} {:else}
{/if} <Button
{kind}
{size}
showTooltip={{
component: CommentPopup,
props: { objectId: object._id, object, withInput }
}}
>
<div slot="icon"><IconThread {size} /></div>
<div slot="content" style:margin-left={showCounter ? '.375rem' : '0'}>
{#if showCounter}{value ?? 0}{/if}
</div> </div>
</Button> </Button>
</div> {/if}
{:else} {:else}
<div <div
use:tooltip={{ use:tooltip={{

View File

@ -37,6 +37,7 @@
"Weight": "Weight", "Weight": "Weight",
"Expert": "Expert", "Expert": "Expert",
"Meaningfull": "Meaningfull", "Meaningfull": "Meaningfull",
"Initial": "Initial" "Initial": "Initial",
"NumberLabels": "{count, plural, =0 {no labels} =1 {1 label} other {# labels}}"
} }
} }

View File

@ -37,6 +37,7 @@
"Weight": "Вес", "Weight": "Вес",
"Expert": "Эксперт", "Expert": "Эксперт",
"Meaningfull": "Значимый", "Meaningfull": "Значимый",
"Initial": "Начальный" "Initial": "Начальный",
"NumberLabels": "{count, plural, =0 {нет меток} one {# метка} few {# метки} other {# меток}}"
} }
} }

View File

@ -3,10 +3,12 @@
import { createQuery } from '@hcengineering/presentation' import { createQuery } from '@hcengineering/presentation'
import type { TagReference } from '@hcengineering/tags' import type { TagReference } from '@hcengineering/tags'
import tags from '@hcengineering/tags' import tags from '@hcengineering/tags'
import { getEventPopupPositionElement, resizeObserver, showPopup } from '@hcengineering/ui' import { getEventPopupPositionElement, resizeObserver, showPopup, tooltip } from '@hcengineering/ui'
import { afterUpdate, createEventDispatcher } from 'svelte' import { afterUpdate, createEventDispatcher } from 'svelte'
import TagReferencePresenter from './TagReferencePresenter.svelte' import TagReferencePresenter from './TagReferencePresenter.svelte'
import TagsReferencePresenter from './TagsReferencePresenter.svelte'
import TagsEditorPopup from './TagsEditorPopup.svelte' import TagsEditorPopup from './TagsEditorPopup.svelte'
import TagsItemPresenter from './TagsItemPresenter.svelte'
export let value: number export let value: number
export let object: WithLookup<Doc> export let object: WithLookup<Doc>
@ -51,11 +53,23 @@
</script> </script>
{#if kind === 'list' || kind === 'link'} {#if kind === 'list' || kind === 'link'}
{#each items as value} {#if items.length > 4}
<div class="label-box no-shrink" title={value.title}> <div
<TagReferencePresenter attr={undefined} {value} {kind} /> class="label-box no-shrink"
use:tooltip={{
component: TagsItemPresenter,
props: { value: items, kind: 'list' }
}}
>
<TagsReferencePresenter {items} {kind} />
</div> </div>
{/each} {:else}
{#each items as value}
<div class="label-box no-shrink" title={value.title}>
<TagReferencePresenter attr={undefined} {value} {kind} />
</div>
{/each}
{/if}
{:else} {:else}
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
<div <div
@ -93,15 +107,15 @@
.label-box { .label-box {
display: flex; display: flex;
align-items: center; align-items: center;
flex-shrink: 10; // flex-shrink: 10;
width: auto; width: auto;
min-width: 0; min-width: 0;
border-radius: 0.25rem; border-radius: 0.25rem;
transition: box-shadow 0.15s ease-in-out; transition: box-shadow 0.15s ease-in-out;
&:not(.no-shrink):last-child { // &:not(.no-shrink):last-child {
flex-shrink: 0; // flex-shrink: 0;
} // }
} }
.wrap-short:not(:last-child) { .wrap-short:not(:last-child) {
margin-right: 0.375rem; margin-right: 0.375rem;

View File

@ -14,13 +14,25 @@
--> -->
<script lang="ts"> <script lang="ts">
import { TagReference } from '@hcengineering/tags' import { TagReference } from '@hcengineering/tags'
import TagReferencePresenter from './TagReferencePresenter.svelte'
import TagItem from './TagItem.svelte' import TagItem from './TagItem.svelte'
export let value: TagReference[] | TagReference export let value: TagReference[] | TagReference
export let kind: 'tag' | 'list' = 'tag'
$: values = Array.isArray(value) ? value : [value] $: values = Array.isArray(value) ? value : [value]
</script> </script>
{#each values as v} {#if kind === 'list'}
<TagItem tag={v} /> <div class="flex-center flex-wrap">
{/each} {#each values as v}
<div class="m-0-5">
<TagReferencePresenter attr={undefined} value={v} {kind} />
</div>
{/each}
</div>
{:else}
{#each values as v}
<TagItem tag={v} />
{/each}
{/if}

View File

@ -0,0 +1,119 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { TagReference } from '@hcengineering/tags'
import plugin from '../plugin'
import { getPlatformColorDef, themeStore, Label } from '@hcengineering/ui'
export let items: TagReference[]
export let kind: 'list' | 'link' = 'list'
$: colors = items.slice(0, 3).map((it) => {
return getPlatformColorDef(it.color ?? 0, $themeStore.dark)
})
</script>
{#if items}
{#if kind === 'link'}
<button class="link-container">
{#each colors as color}
<div class="color" style:background-color={color.color} />
{/each}
<span class="label overflow-label ml-1 text-sm caption-color max-w-40">
<Label label={plugin.string.NumberLabels} params={{ count: items.length }} />
</span>
</button>
{:else if kind === 'list'}
<div class="listitems-container">
{#each colors as color}
<div class="color" style:background-color={color.color} />
{/each}
<span class="label overflow-label ml-1-5 max-w-40">
<Label label={plugin.string.NumberLabels} params={{ count: items.length }} />
</span>
</div>
{/if}
{/if}
<style lang="scss">
.listitems-container {
overflow: hidden;
display: flex;
align-items: center;
flex-shrink: 0;
padding-left: 0.5rem;
height: 1.75rem;
min-width: 0;
min-height: 0;
background-color: var(--theme-button-enabled);
border: 1px solid var(--theme-button-border);
border-radius: 1.5rem;
user-select: none;
&:hover {
background-color: var(--theme-button-hovered);
}
.label {
color: var(--theme-caption-color);
}
}
.link-container {
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
padding: 0 0.375rem;
height: 1.5rem;
min-width: 1.5rem;
font-size: 0.75rem;
line-height: 0.75rem;
white-space: nowrap;
color: var(--theme-content-color);
background-color: var(--theme-link-button-color);
border: 1px solid var(--theme-button-border);
border-radius: 0.25rem;
transition-property: border, background-color, color, box-shadow;
transition-duration: 0.15s;
&:hover {
color: var(--theme-caption-color);
background-color: var(--theme-link-button-hover);
border-color: var(--theme-list-divider-color);
transition-duration: 0;
}
&:focus {
border-color: var(--primary-edit-border-color) !important;
}
&:disabled {
color: rgb(var(--theme-caption-color) / 40%);
cursor: not-allowed;
}
}
.color {
flex-shrink: 0;
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
&:not(:nth-child(3)) {
mask: radial-gradient(circle at 100% 50%, rgba(0, 0, 0, 0) 48.5%, rgb(0, 0, 0) 50%);
}
&:not(:first-child) {
margin-left: calc(1px - 0.25rem);
}
}
</style>

View File

@ -49,7 +49,8 @@ export default mergeIds(tagsId, tags, {
Weight: '' as IntlString, Weight: '' as IntlString,
Expert: '' as IntlString, Expert: '' as IntlString,
Meaningfull: '' as IntlString, Meaningfull: '' as IntlString,
Initial: '' as IntlString Initial: '' as IntlString,
NumberLabels: '' as IntlString
}, },
function: { function: {
FilterTagsInResult: '' as FilterFunction, FilterTagsInResult: '' as FilterFunction,

View File

@ -56,9 +56,8 @@
{#if (value.component && value.component !== $activeComponent && groupBy !== 'component') || shouldShowPlaceholder} {#if (value.component && value.component !== $activeComponent && groupBy !== 'component') || shouldShowPlaceholder}
<div <div
class="clear-mins" class="label-wrapper"
class:minus-margin={kind === 'list-header'} class:minus-margin={kind === 'list-header'}
class:label-wrapper={compression}
use:tooltip={{ label: value.component ? tracker.string.MoveToComponent : tracker.string.AddToComponent }} use:tooltip={{ label: value.component ? tracker.string.MoveToComponent : tracker.string.AddToComponent }}
> >
<ComponentSelector <ComponentSelector
@ -74,7 +73,7 @@
{onlyIcon} {onlyIcon}
{enlargedText} {enlargedText}
value={value.component} value={value.component}
short={compression} short
onChange={handleComponentIdChanged} onChange={handleComponentIdChanged}
/> />
</div> </div>

View File

@ -39,6 +39,7 @@
export let size: ButtonSize = 'small' export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'left' export let justify: 'left' | 'center' = 'left'
export let width: string | undefined = 'min-contet' export let width: string | undefined = 'min-contet'
export let compactMode: boolean = false
let btn: HTMLElement let btn: HTMLElement
@ -150,9 +151,11 @@
<svelte:fragment slot="content"> <svelte:fragment slot="content">
{#if subIssues} {#if subIssues}
<div class="flex-row-center content-color text-sm pointer-events-none"> <div class="flex-row-center content-color text-sm pointer-events-none">
<div class="mr-1-5"> {#if !compactMode}
<ProgressCircle bind:value={countComplete} bind:max={subIssues.length} size={'small'} primary /> <div class="mr-1-5">
</div> <ProgressCircle bind:value={countComplete} bind:max={subIssues.length} size={'small'} primary />
</div>
{/if}
{countComplete}/{subIssues.length} {countComplete}/{subIssues.length}
</div> </div>
{/if} {/if}

View File

@ -78,7 +78,7 @@
{#if kind === 'list'} {#if kind === 'list'}
{#if value.milestone} {#if value.milestone}
<div class="clear-mins" class:label-wrapper={compression}> <div class="label-wrapper">
<MilestoneSelector <MilestoneSelector
{kind} {kind}
{size} {size}
@ -90,7 +90,7 @@
{popupPlaceholder} {popupPlaceholder}
{onlyIcon} {onlyIcon}
{enlargedText} {enlargedText}
short={compression} short
showTooltip={{ label: value.milestone ? tracker.string.MoveToMilestone : tracker.string.AddToMilestone }} showTooltip={{ label: value.milestone ? tracker.string.MoveToMilestone : tracker.string.AddToMilestone }}
value={value.milestone} value={value.milestone}
onChange={handleMilestoneIdChanged} onChange={handleMilestoneIdChanged}

View File

@ -38,6 +38,7 @@
export let disableHeader = false export let disableHeader = false
export let props: Record<string, any> = {} export let props: Record<string, any> = {}
export let selection: number | undefined = undefined export let selection: number | undefined = undefined
export let compactMode: boolean = false
export let documents: Doc[] | undefined = undefined export let documents: Doc[] | undefined = undefined
@ -138,6 +139,7 @@
{disableHeader} {disableHeader}
{props} {props}
{listDiv} {listDiv}
{compactMode}
bind:dragItem bind:dragItem
on:select={(evt) => { on:select={(evt) => {
select(0, evt.detail) select(0, evt.detail)

View File

@ -58,6 +58,7 @@
export let listDiv: HTMLDivElement export let listDiv: HTMLDivElement
export let selection: number | undefined = undefined export let selection: number | undefined = undefined
export let groupPersistKey: string export let groupPersistKey: string
export let compactMode: boolean = false
$: groupByKey = viewOptions.groupBy[level] ?? noCategory $: groupByKey = viewOptions.groupBy[level] ?? noCategory
let categories: CategoryType[] = [] let categories: CategoryType[] = []
@ -310,6 +311,7 @@
{createItemDialogProps} {createItemDialogProps}
{createItemLabel} {createItemLabel}
{viewOptionsConfig} {viewOptionsConfig}
{compactMode}
on:check on:check
on:uncheckAll on:uncheckAll
on:row-focus on:row-focus

View File

@ -66,6 +66,7 @@
export let listDiv: HTMLDivElement export let listDiv: HTMLDivElement
export let index: number export let index: number
export let groupPersistKey: string export let groupPersistKey: string
export let compactMode: boolean = false
$: lastLevel = level + 1 >= viewOptions.groupBy.length $: lastLevel = level + 1 >= viewOptions.groupBy.length
@ -448,6 +449,7 @@
on:mouseover={mouseAttractor(() => handleRowFocused(docObject))} on:mouseover={mouseAttractor(() => handleRowFocused(docObject))}
on:mouseenter={mouseAttractor(() => handleRowFocused(docObject))} on:mouseenter={mouseAttractor(() => handleRowFocused(docObject))}
{props} {props}
{compactMode}
on:on-mount={() => { on:on-mount={() => {
wasLoaded = true wasLoaded = true
}} }}

View File

@ -16,7 +16,7 @@
import { AnyAttribute, Doc, getObjectValue } from '@hcengineering/core' import { AnyAttribute, Doc, getObjectValue } from '@hcengineering/core'
import notification from '@hcengineering/notification' import notification from '@hcengineering/notification'
import { getClient, updateAttribute } from '@hcengineering/presentation' import { getClient, updateAttribute } from '@hcengineering/presentation'
import { CheckBox, Component, deviceOptionsStore as deviceInfo, IconCircles, tooltip } from '@hcengineering/ui' import { CheckBox, Component, IconCircles, tooltip } from '@hcengineering/ui'
import { AttributeModel } from '@hcengineering/view' import { AttributeModel } from '@hcengineering/view'
import { createEventDispatcher, onMount } from 'svelte' import { createEventDispatcher, onMount } from 'svelte'
import view from '../../plugin' import view from '../../plugin'
@ -31,6 +31,7 @@
export let last: boolean = false export let last: boolean = false
export let lastCat: boolean = false export let lastCat: boolean = false
export let props: Record<string, any> = {} export let props: Record<string, any> = {}
export let compactMode: boolean = false
export function scroll () { export function scroll () {
elem?.scrollIntoView({ behavior: 'auto', block: 'nearest' }) elem?.scrollIntoView({ behavior: 'auto', block: 'nearest' })
@ -48,8 +49,6 @@
return elem return elem
} }
$: compactMode = $deviceInfo.twoRows
const client = getClient() const client = getClient()
function onChange (value: any, doc: Doc, key: string, attribute: AnyAttribute) { function onChange (value: any, doc: Doc, key: string, attribute: AnyAttribute) {
@ -64,13 +63,6 @@
return (value: any) => onChange(value, docObject, attribute.key, attr) return (value: any) => onChange(value, docObject, attribute.key, attr)
} }
let noCompressed: number
$: if (model) {
noCompressed = -1
model.forEach((m, i) => {
if (m.displayProps?.compression) noCompressed = i
})
}
onMount(() => { onMount(() => {
dispatch('on-mount') dispatch('on-mount')
}) })
@ -120,32 +112,55 @@
/> />
</div> </div>
</div> </div>
{#each model.filter((p) => !(p.displayProps?.optional === true)) as attributeModel, i} {#each model.filter((p) => !(p.displayProps?.optional === true || p.displayProps?.suffix === true)) as attributeModel, i}
{@const displayProps = attributeModel.displayProps} {@const displayProps = attributeModel.displayProps}
{#if !groupByKey || displayProps?.excludeByKey !== groupByKey} {#if !groupByKey || displayProps?.excludeByKey !== groupByKey}
{#if displayProps?.grow} {#if displayProps?.grow}
<GrowPresenter />
{#if !compactMode} {#if !compactMode}
{#each model.filter((p) => p.displayProps?.optional === true) as attrModel, j} {#each model.filter((p) => p.displayProps?.suffix === true) as attrModel}
<ListPresenter <ListPresenter
{docObject} {docObject}
attributeModel={attrModel} attributeModel={attrModel}
{props} {props}
compression={j !== noCompressed}
value={getObjectValue(attrModel.key, docObject)} value={getObjectValue(attrModel.key, docObject)}
onChange={getOnChange(docObject, attrModel)} onChange={getOnChange(docObject, attrModel)}
/> />
{/each} {/each}
{/if} {/if}
<GrowPresenter />
{#if !compactMode}
<div class="optional-bar">
{#each model.filter((p) => p.displayProps?.optional === true) as attrModel}
<ListPresenter
{docObject}
attributeModel={attrModel}
{props}
value={getObjectValue(attrModel.key, docObject)}
onChange={getOnChange(docObject, attrModel)}
/>
{/each}
</div>
{:else}
{#each model.filter((p) => p.displayProps?.suffix === true) as attrModel}
<ListPresenter
{docObject}
attributeModel={attrModel}
{props}
value={getObjectValue(attrModel.key, docObject)}
onChange={getOnChange(docObject, attrModel)}
compactMode
/>
{/each}
{/if}
{:else} {:else}
<ListPresenter <ListPresenter
{docObject} {docObject}
{attributeModel} {attributeModel}
{props} {props}
compression={i !== noCompressed}
value={getObjectValue(attributeModel.key, docObject)} value={getObjectValue(attributeModel.key, docObject)}
onChange={getOnChange(docObject, attributeModel)} onChange={getOnChange(docObject, attributeModel)}
hideDivider={i === 0} hideDivider={i === 0}
{compactMode}
/> />
{/if} {/if}
{/if} {/if}
@ -164,7 +179,7 @@
<IconCircles /> <IconCircles />
</div> </div>
<div class="scroll-box gap-2"> <div class="scroll-box gap-2">
{#each model.filter((m) => m.displayProps?.optional || m.displayProps?.compression) as attributeModel, j} {#each model.filter((m) => m.displayProps?.optional) as attributeModel, j}
{@const displayProps = attributeModel.displayProps} {@const displayProps = attributeModel.displayProps}
{@const value = getObjectValue(attributeModel.key, docObject)} {@const value = getObjectValue(attributeModel.key, docObject)}
{#if displayProps?.excludeByKey !== groupByKey && value !== undefined} {#if displayProps?.excludeByKey !== groupByKey && value !== undefined}

View File

@ -25,6 +25,7 @@
export let props: Record<string, any> export let props: Record<string, any>
export let compression: boolean = false export let compression: boolean = false
export let hideDivider: boolean = false export let hideDivider: boolean = false
export let compactMode: boolean = false
$: dp = attributeModel?.displayProps $: dp = attributeModel?.displayProps
@ -47,6 +48,7 @@
{value} {value}
{onChange} {onChange}
kind={'list'} kind={'list'}
{compactMode}
{...joinProps(attributeModel, docObject, props)} {...joinProps(attributeModel, docObject, props)}
/> />
</FixedColumn> </FixedColumn>
@ -56,7 +58,7 @@
{value} {value}
{onChange} {onChange}
kind={'list'} kind={'list'}
compression={dp?.compression && compression} {compactMode}
{...joinProps(attributeModel, docObject, props)} {...joinProps(attributeModel, docObject, props)}
/> />
{/if} {/if}

View File

@ -24,6 +24,7 @@
let list: List let list: List
let scroll: Scroller let scroll: Scroller
let divScroll: HTMLDivElement let divScroll: HTMLDivElement
let listWidth: number
const listProvider = new ListSelectionProvider((offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => { const listProvider = new ListSelectionProvider((offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => {
if (dir === 'vertical') { if (dir === 'vertical') {
@ -43,7 +44,7 @@
}} }}
/> />
<div class="w-full h-full py-4 clear-mins"> <div bind:clientWidth={listWidth} class="w-full h-full py-4 clear-mins">
<Scroller <Scroller
bind:this={scroll} bind:this={scroll}
bind:divScroll bind:divScroll
@ -64,6 +65,7 @@
{createItemLabel} {createItemLabel}
{viewOptions} {viewOptions}
{props} {props}
compactMode={listWidth <= 800}
viewOptionsConfig={viewlet.viewOptions?.other} viewOptionsConfig={viewlet.viewOptions?.other}
selectedObjectIds={$selectionStore ?? []} selectedObjectIds={$selectionStore ?? []}
selection={listProvider.current($focusStore)} selection={listProvider.current($focusStore)}

View File

@ -535,8 +535,8 @@ export interface DisplayProps {
key?: string key?: string
excludeByKey?: string excludeByKey?: string
fixed?: 'left' | 'right' // using for align items in row fixed?: 'left' | 'right' // using for align items in row
suffix?: boolean
optional?: boolean optional?: boolean
compression?: boolean
grow?: boolean grow?: boolean
dividerBefore?: boolean // should show divider before dividerBefore?: boolean // should show divider before
} }