UBER-538: update ListView layout. Subissues, related issues. (#3467)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov 2023-06-29 07:29:12 +03:00 committed by GitHub
parent f450e03bb7
commit ee0754422d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 301 additions and 205 deletions

View File

@ -683,18 +683,30 @@ export function createModel (builder: Builder): void {
descriptor: view.viewlet.List, descriptor: view.viewlet.List,
config: [ config: [
{ key: '', displayProps: { fixed: 'left', key: 'app' } }, { key: '', displayProps: { fixed: 'left', key: 'app' } },
{
key: 'state',
props: { kind: 'list', size: 'small', shouldShowName: false },
displayProps: { excludeByKey: 'state' }
},
{ {
key: '$lookup.attachedTo', key: '$lookup.attachedTo',
presenter: contact.component.PersonPresenter, presenter: contact.component.PersonPresenter,
label: recruit.string.Talent, label: recruit.string.Talent,
sortingKey: '$lookup.attachedTo.name', sortingKey: '$lookup.attachedTo.name',
displayProps: { fixed: 'left', key: 'talent' },
props: { props: {
_class: recruit.mixin.Candidate, _class: recruit.mixin.Candidate,
inline: true inline: true
} }
}, },
{ key: 'state', displayProps: { fixed: 'left', key: 'state' }, props: { inline: true, showLabel: false } }, { key: 'attachments', displayProps: { key: 'attachments', suffix: true } },
{ key: 'comments', displayProps: { key: 'comments', suffix: true } },
{
key: '',
presenter: tracker.component.RelatedIssueSelector,
label: tracker.string.Issues,
props: { size: 'small' }
},
{ key: '', displayProps: { grow: true } },
{ {
key: '$lookup.space.company', key: '$lookup.space.company',
displayProps: { fixed: 'left', key: 'company' }, displayProps: { fixed: 'left', key: 'company' },
@ -703,15 +715,6 @@ export function createModel (builder: Builder): void {
maxWidth: '10rem' maxWidth: '10rem'
} }
}, },
{
key: '',
presenter: tracker.component.RelatedIssueSelector,
label: tracker.string.Issues,
displayProps: { fixed: 'left', key: 'issues' }
},
{ key: '', displayProps: { grow: true } },
{ key: 'attachments', displayProps: { fixed: 'left', key: 'attachments' } },
{ key: 'comments', displayProps: { fixed: 'left', key: 'comments' } },
{ {
key: '$lookup.attachedTo.$lookup.channels', key: '$lookup.attachedTo.$lookup.channels',
label: contact.string.ContactInfo, label: contact.string.ContactInfo,
@ -719,15 +722,14 @@ export function createModel (builder: Builder): void {
props: { props: {
length: 'full', length: 'full',
size: 'inline' size: 'inline'
},
displayProps: {
fixed: 'left',
key: 'channels',
dividerBefore: true
} }
}, },
{ key: 'modifiedOn', displayProps: { key: 'modified', fixed: 'left', dividerBefore: true } }, { key: 'modifiedOn', displayProps: { key: 'modified', fixed: 'right', dividerBefore: true } },
{ key: 'assignee', displayProps: { key: 'assignee', fixed: 'right' }, props: { shouldShowLabel: false } } {
key: 'assignee',
props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' },
displayProps: { key: 'assignee', fixed: 'right', excludeByKey: 'assignee' }
}
], ],
options: { options: {
lookup: { lookup: {

View File

@ -597,20 +597,22 @@ export function createModel (builder: Builder): void {
key: '', key: '',
label: tracker.string.Priority, 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' },
displayProps: { key: 'subpriority' }
}, },
{ {
key: '', key: '',
label: tracker.string.Issue, label: tracker.string.Issue,
presenter: tracker.component.IssuePresenter, presenter: tracker.component.IssuePresenter,
props: { type: 'issue' }, props: { type: 'issue' },
displayProps: { fixed: 'left' } displayProps: { key: 'subissue', fixed: 'left' }
}, },
{ {
key: '', key: '',
label: tracker.string.Status, 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' },
displayProps: { key: 'substatus' }
}, },
{ {
key: '', key: '',
@ -619,12 +621,7 @@ export function createModel (builder: Builder): void {
props: { shouldUseMargin: true, showParent: false } props: { shouldUseMargin: true, showParent: false }
}, },
{ key: '', label: tracker.string.SubIssues, presenter: tracker.component.SubIssuesSelector, props: {} }, { key: '', label: tracker.string.SubIssues, presenter: tracker.component.SubIssuesSelector, props: {} },
{ { key: '', displayProps: { grow: true } },
key: '',
label: tracker.string.DueDate,
presenter: tracker.component.DueDatePresenter,
props: { kind: 'list', size: 'small' }
},
{ {
key: '', key: '',
label: tracker.string.Milestone, label: tracker.string.Milestone,
@ -640,22 +637,28 @@ export function createModel (builder: Builder): void {
optional: true optional: true
} }
}, },
{
key: '',
label: tracker.string.DueDate,
presenter: tracker.component.DueDatePresenter,
displayProps: { key: 'dueDate', optional: true },
props: { kind: 'list', size: 'small' }
},
{ {
key: '', key: '',
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: { optional: true }
}, },
{ key: '', displayProps: { grow: true } },
{ {
key: 'modifiedOn', key: 'modifiedOn',
presenter: tracker.component.ModificationDatePresenter, presenter: tracker.component.ModificationDatePresenter,
displayProps: { fixed: 'right', optional: true } displayProps: { key: 'submodified', fixed: 'right' }
}, },
{ {
key: 'assignee', key: 'assignee',
presenter: tracker.component.AssigneeEditor, presenter: tracker.component.AssigneeEditor,
displayProps: { key: 'assigee', fixed: 'right' },
props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' } props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' }
} }
] ]

View File

@ -365,6 +365,7 @@
&.dark { background-color: var(--body-accent); } &.dark { background-color: var(--body-accent); }
&.noMargin { margin: 0; } &.noMargin { margin: 0; }
& + & { display: none; }
} }
.antiHSpacer { .antiHSpacer {
@ -390,7 +391,7 @@
align-items: center; align-items: center;
height: 2.5rem; height: 2.5rem;
min-height: 2.5rem; min-height: 2.5rem;
border-bottom: 1px solid var(--divider-color); border-bottom: 1px solid var(--theme-divider-color);
&.high { &.high {
padding-right: 1rem; padding-right: 1rem;
@ -403,7 +404,7 @@
align-items: center; align-items: center;
margin-right: .5rem; margin-right: .5rem;
height: 2rem; height: 2rem;
color: var(--caption-color); color: var(--theme-caption-color);
} }
&__title { &__title {
min-width: 0; min-width: 0;
@ -422,7 +423,7 @@
min-width: 0; min-width: 0;
font-weight: 500; font-weight: 500;
font-size: 1rem; font-size: 1rem;
color: var(--caption-color); color: var(--theme-caption-color);
background: var(--header-bg-color); background: var(--header-bg-color);
border-radius: .5rem .5rem 0 0; border-radius: .5rem .5rem 0 0;
} }

View File

@ -895,7 +895,8 @@
flex-grow: 1; flex-grow: 1;
border-radius: 1.625rem; border-radius: 1.625rem;
transition: flex-shrink 0.25s cubic-bezier(0.38, 0.01, 0.33, 1) 0s; transition: flex-shrink 0.25s cubic-bezier(0.38, 0.01, 0.33, 1) 0s;
&:not(:empty) { min-width: 7.5rem; }
&:hover { &:hover {
flex-shrink: .5; flex-shrink: .5;
min-width: initial; min-width: initial;
@ -903,7 +904,6 @@
.label-wrapper { .label-wrapper {
display: flex; display: flex;
align-items: center; align-items: center;
width: auto;
min-width: 0; min-width: 0;
} }
& > *:last-child { & > *:last-child {

View File

@ -50,6 +50,7 @@
export let iconSize: IconSize = size === 'inline' ? 'inline' : 'small' export let iconSize: IconSize = size === 'inline' ? 'inline' : 'small'
export let iconRightSize: IconSize = 'x-small' export let iconRightSize: IconSize = 'x-small'
export let short: boolean = false export let short: boolean = false
export let shrink: number = 0
export let accent: boolean = false export let accent: boolean = false
export let noFocus: boolean = false export let noFocus: boolean = false
@ -112,6 +113,7 @@
class:short class:short
style:width style:width
style:height style:height
style:flex-shrink={shrink}
{title} {title}
type={kind === 'primary' ? 'submit' : 'button'} type={kind === 'primary' ? 'submit' : 'button'}
on:click|stopPropagation|preventDefault on:click|stopPropagation|preventDefault

View File

@ -30,6 +30,7 @@
import { themeStore } from '@hcengineering/theme' import { themeStore } from '@hcengineering/theme'
export let value: number | undefined export let value: number | undefined
export let kind: 'no-border' | 'list' = 'no-border'
let time: string = '' let time: string = ''
@ -73,6 +74,11 @@
: undefined : undefined
</script> </script>
<span use:tooltip={{ label: ui.string.TimeTooltip, props: { value: tooltipValue } }} class="overflow-label"> <span
use:tooltip={{ label: ui.string.TimeTooltip, props: { value: tooltipValue } }}
class="overflow-label"
class:text-sm={kind === 'list'}
class:content-dark-color={kind === 'list'}
>
{time} {time}
</span> </span>

View File

@ -28,7 +28,7 @@
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
</script> </script>
<div class="flex-between"> <div class="flex-between w-full">
<div class="caption"> <div class="caption">
<Label {label} /> <Label {label} />
{#if description} {#if description}

View File

@ -63,7 +63,7 @@
export let focusIndex = -1 export let focusIndex = -1
export let showTooltip: LabelAndProps | PersonLabelTooltip | undefined = undefined export let showTooltip: LabelAndProps | PersonLabelTooltip | undefined = undefined
export let showNavigate = true export let showNavigate = true
export let shouldShowName = true export let shouldShowName: boolean = true
export let id: string | undefined = undefined export let id: string | undefined = undefined
export let short: boolean = false export let short: boolean = false

View File

@ -253,58 +253,95 @@
} }
</script> </script>
<div {#if kind === 'list'}
class="{displayItems.length === 0 ? 'clear-mins' : 'buttons-group'} {kind === 'no-border'
? 'xsmall-gap'
: 'xxsmall-gap'}"
class:short={displayItems.length > 4 && length === 'short'}
class:tiny={displayItems.length > 2 && length === 'tiny'}
>
{#each displayItems as item, i} {#each displayItems as item, i}
<Button <div class="label-wrapper">
focusIndex={focusIndex === -1 ? focusIndex : focusIndex + 1 + i} <Button
id={item.label} focusIndex={focusIndex === -1 ? focusIndex : focusIndex + 1 + i}
bind:input={btns[i]} id={item.label}
icon={item.icon} bind:input={btns[i]}
kind={highlighted.includes(item.provider) ? 'dangerous' : kind} icon={item.icon}
{size} kind={highlighted.includes(item.provider) ? 'dangerous' : kind}
{shape} {size}
highlight={item.integration || item.notification} {shape}
on:click={(ev) => { highlight={item.integration || item.notification}
if (editable && !restricted.includes(item.provider)) { on:click={(ev) => {
closeTooltip() if (editable && !restricted.includes(item.provider)) {
editChannel(eventToHTMLElement(ev), i, item) closeTooltip()
} else { editChannel(eventToHTMLElement(ev), i, item)
dispatch('open', item) } else {
} dispatch('open', item)
}} }
showTooltip={{ }}
component: opened !== i ? ChannelEditor : undefined, showTooltip={{
props: { component: opened !== i ? ChannelEditor : undefined,
value: item.value, props: {
placeholder: item.placeholder, value: item.value,
editable: editable !== undefined ? false : undefined, placeholder: item.placeholder,
openable: item.presenter ?? item.action ?? false editable: editable !== undefined ? false : undefined,
}, openable: item.presenter ?? item.action ?? false
onUpdate: (result) => { },
updateTooltip(result, item, i) onUpdate: (result) => {
} updateTooltip(result, item, i)
}} }
/> }}
/>
</div>
{/each} {/each}
{#if actions.length > 0 && editable} {:else}
<Button <div
focusIndex={focusIndex === -1 ? focusIndex : focusIndex + 2 + displayItems.length} class="{displayItems.length === 0 ? 'clear-mins' : 'buttons-group'} {kind === 'no-border'
id={presentation.string.AddSocialLinks} ? 'xsmall-gap'
bind:input={addBtn} : 'xxsmall-gap'}"
icon={contact.icon.SocialEdit} class:short={displayItems.length > 4 && length === 'short'}
label={displayItems.length === 0 ? presentation.string.AddSocialLinks : undefined} class:tiny={displayItems.length > 2 && length === 'tiny'}
notSelected={displayItems.length === 0} >
{kind} {#each displayItems as item, i}
{size} <Button
{shape} focusIndex={focusIndex === -1 ? focusIndex : focusIndex + 1 + i}
showTooltip={{ label: presentation.string.AddSocialLinks }} id={item.label}
on:click={showMenu} bind:input={btns[i]}
/> icon={item.icon}
{/if} kind={highlighted.includes(item.provider) ? 'dangerous' : kind}
</div> {size}
{shape}
highlight={item.integration || item.notification}
on:click={(ev) => {
if (editable && !restricted.includes(item.provider)) {
closeTooltip()
editChannel(eventToHTMLElement(ev), i, item)
} else {
dispatch('open', item)
}
}}
showTooltip={{
component: opened !== i ? ChannelEditor : undefined,
props: {
value: item.value,
placeholder: item.placeholder,
editable: editable !== undefined ? false : undefined,
openable: item.presenter ?? item.action ?? false
},
onUpdate: (result) => {
updateTooltip(result, item, i)
}
}}
/>
{/each}
{#if actions.length > 0 && editable}
<Button
focusIndex={focusIndex === -1 ? focusIndex : focusIndex + 2 + displayItems.length}
id={presentation.string.AddSocialLinks}
bind:input={addBtn}
icon={contact.icon.SocialEdit}
label={displayItems.length === 0 ? presentation.string.AddSocialLinks : undefined}
notSelected={displayItems.length === 0}
{kind}
{size}
{shape}
showTooltip={{ label: presentation.string.AddSocialLinks }}
on:click={showMenu}
/>
{/if}
</div>
{/if}

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { Employee } from '@hcengineering/contact' import { Employee } from '@hcengineering/contact'
import { Ref } from '@hcengineering/core' import { Ref } from '@hcengineering/core'
import { ButtonKind } from '@hcengineering/ui' import { ButtonKind, IconSize } from '@hcengineering/ui'
import { PersonLabelTooltip } from '..' import { PersonLabelTooltip } from '..'
import contact from '../plugin' import contact from '../plugin'
import { employeeByIdStore } from '../utils' import { employeeByIdStore } from '../utils'
@ -14,7 +14,9 @@
export let onChange: ((value: Ref<Employee>) => void) | undefined = undefined export let onChange: ((value: Ref<Employee>) => void) | undefined = undefined
export let colorInherit: boolean = false export let colorInherit: boolean = false
export let accent: boolean = false export let accent: boolean = false
export let inline = false export let inline: boolean = false
export let shouldShowName: boolean = true
export let avatarSize: IconSize = kind === 'list-header' ? 'smaller' : 'x-small'
$: employee = value ? $employeeByIdStore.get(value) : undefined $: employee = value ? $employeeByIdStore.get(value) : undefined
@ -37,6 +39,8 @@
kind={'link'} kind={'link'}
showNavigate={false} showNavigate={false}
justify={'left'} justify={'left'}
{shouldShowName}
{avatarSize}
on:change={({ detail }) => onChange?.(detail)} on:change={({ detail }) => onChange?.(detail)}
on:accent-color on:accent-color
/> />
@ -49,8 +53,8 @@
shouldShowAvatar shouldShowAvatar
shouldShowPlaceholder shouldShowPlaceholder
defaultName={contact.string.NotSpecified} defaultName={contact.string.NotSpecified}
shouldShowName={kind !== 'list'} shouldShowName={shouldShowName ? kind !== 'list' : false}
avatarSize={kind === 'list-header' ? 'smaller' : 'x-small'} {avatarSize}
{colorInherit} {colorInherit}
{accent} {accent}
on:accent-color on:accent-color

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { Employee } from '@hcengineering/contact' import { Employee } from '@hcengineering/contact'
import { Ref } from '@hcengineering/core' import { Ref } from '@hcengineering/core'
import { ButtonKind } from '@hcengineering/ui' import { ButtonKind, IconSize } from '@hcengineering/ui'
import { PersonLabelTooltip } from '..' import { PersonLabelTooltip } from '..'
import EmployeeAttributePresenter from './EmployeeAttributePresenter.svelte' import EmployeeAttributePresenter from './EmployeeAttributePresenter.svelte'
@ -11,7 +11,9 @@
export let onChange: ((value: Ref<Employee>) => void) | undefined = undefined export let onChange: ((value: Ref<Employee>) => void) | undefined = undefined
export let colorInherit: boolean = false export let colorInherit: boolean = false
export let accent: boolean = false export let accent: boolean = false
export let inline = false export let inline: boolean = false
export let shouldShowName: boolean = true
export let avatarSize: IconSize = kind === 'secondary' ? 'small' : 'card'
</script> </script>
{#if Array.isArray(value)} {#if Array.isArray(value)}
@ -25,6 +27,8 @@
{inline} {inline}
{colorInherit} {colorInherit}
{accent} {accent}
{shouldShowName}
{avatarSize}
on:accent-color on:accent-color
/> />
{/each} {/each}
@ -38,6 +42,8 @@
{inline} {inline}
{colorInherit} {colorInherit}
{accent} {accent}
{shouldShowName}
{avatarSize}
on:accent-color on:accent-color
/> />
{/if} {/if}

View File

@ -29,6 +29,7 @@
export let size: ButtonSize = 'small' export let size: ButtonSize = 'small'
export let width: string = 'min-content' export let width: string = 'min-content'
export let justify: 'left' | 'center' = 'center' export let justify: 'left' | 'center' = 'center'
export let shouldShowName: boolean = true
let state: State let state: State
let opened: boolean = false let opened: boolean = false
@ -56,12 +57,16 @@
} }
</script> </script>
<Button {kind} {size} {width} {justify} on:click={handleClick}> {#if kind === 'list' || kind === 'list-header'}
<svelte:fragment slot="content"> <StatePresenter value={state} {shouldShowName} shouldShowTooltip on:click={handleClick} />
{#if state} {:else}
<div class="pointer-events-none clear-mins"> <Button {kind} {size} {width} {justify} on:click={handleClick}>
<StatePresenter value={state} /> <svelte:fragment slot="content">
</div> {#if state}
{/if} <div class="pointer-events-none clear-mins">
</svelte:fragment> <StatePresenter value={state} {shouldShowName} />
</Button> </div>
{/if}
</svelte:fragment>
</Button>
{/if}

View File

@ -29,6 +29,8 @@
export let inline: boolean = false export let inline: boolean = false
export let disabled: boolean = false export let disabled: boolean = false
export let oneLine: boolean = false export let oneLine: boolean = false
export let shouldShowName: boolean = true
export let shouldShowTooltip: boolean = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -43,22 +45,26 @@
</script> </script>
{#if value} {#if value}
<div class="flex-presenter" class:inline-presenter={inline}> <!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="flex-presenter" class:inline-presenter={inline} on:click>
{#if shouldShowAvatar} {#if shouldShowAvatar}
<div <div
class="state-container" class="state-container"
class:inline class:inline
class:mr-2={shouldShowName}
style="background-color: {color?.color ?? defaultBackground($themeStore.dark)}" style="background-color: {color?.color ?? defaultBackground($themeStore.dark)}"
title={shouldShowTooltip ? value.name : undefined}
/> />
{/if} {/if}
<span class="overflow-label label" class:nowrap={oneLine} class:no-underline={disabled}>{value.name}</span> {#if shouldShowName}
<span class="overflow-label label" class:nowrap={oneLine} class:no-underline={disabled}>{value.name}</span>
{/if}
</div> </div>
{/if} {/if}
<style lang="scss"> <style lang="scss">
.state-container { .state-container {
flex-shrink: 0; flex-shrink: 0;
margin-right: 0.5rem;
width: 0.875rem; width: 0.875rem;
height: 0.875rem; height: 0.875rem;
border: 1px solid rgba(0, 0, 0, 0.1); border: 1px solid rgba(0, 0, 0, 0.1);

View File

@ -15,7 +15,7 @@
--> -->
<script lang="ts"> <script lang="ts">
import { Ref, Status, StatusValue } from '@hcengineering/core' import { Ref, Status, StatusValue } from '@hcengineering/core'
import type { ButtonSize } from '@hcengineering/ui' import type { ButtonKind, ButtonSize } from '@hcengineering/ui'
import { State } from '@hcengineering/task' import { State } from '@hcengineering/task'
import { statusStore } from '@hcengineering/view-resources' import { statusStore } from '@hcengineering/view-resources'
import StateEditor from './StateEditor.svelte' import StateEditor from './StateEditor.svelte'
@ -23,14 +23,17 @@
export let value: Ref<State> | StatusValue export let value: Ref<State> | StatusValue
export let onChange: ((value: Ref<State>) => void) | undefined = undefined export let onChange: ((value: Ref<State>) => void) | undefined = undefined
export let kind: ButtonKind = 'link'
export let size: ButtonSize = 'medium' export let size: ButtonSize = 'medium'
export let shouldShowName: boolean = true
$: state = $statusStore.get(typeof value === 'string' ? value : (value?.values?.[0]?._id as Ref<Status>)) $: state = $statusStore.get(typeof value === 'string' ? value : (value?.values?.[0]?._id as Ref<Status>))
</script> </script>
{#if value} {#if value}
{#if onChange !== undefined && state !== undefined} {#if onChange !== undefined && state !== undefined}
<StateEditor value={state._id} space={state.space} {onChange} kind="link" {size} /> <StateEditor value={state._id} space={state.space} {onChange} {kind} {size} {shouldShowName} />
{:else} {:else}
<StatePresenter value={state} on:accent-color /> <StatePresenter value={state} {shouldShowName} on:accent-color />
{/if} {/if}
{/if} {/if}

View File

@ -39,6 +39,7 @@
</script> </script>
<div class="selectPopup" use:resizeObserver={() => dispatch('changeContent')}> <div class="selectPopup" use:resizeObserver={() => dispatch('changeContent')}>
<div class="menu-space" />
<div class="scroll"> <div class="scroll">
<div class="box"> <div class="box">
{#each states as state} {#each states as state}
@ -55,6 +56,7 @@
{/each} {/each}
</div> </div>
</div> </div>
<div class="menu-space" />
</div> </div>
<style lang="scss"> <style lang="scss">

View File

@ -45,6 +45,6 @@
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
font-size: 0.75rem; font-size: 0.75rem;
color: var(--content-color); color: var(--theme-dark-color);
} }
</style> </style>

View File

@ -53,16 +53,16 @@
.names { .names {
display: inline-flex; display: inline-flex;
min-width: 0; min-width: 0;
color: var(--content-color); color: var(--theme-dark-color);
} }
.name { .name {
&:hover { &:hover {
color: var(--caption-color); color: var(--theme-caption-color);
text-decoration: underline; text-decoration: underline;
} }
&:active { &:active {
color: var(--accent-color); color: var(--theme-content-color);
} }
&::before { &::before {
content: ''; content: '';

View File

@ -32,7 +32,6 @@
class="name overflow-label select-text" class="name overflow-label select-text"
class:with-margin={shouldUseMargin} class:with-margin={shouldUseMargin}
class:list={kind === 'list'} class:list={kind === 'list'}
style:max-width={showParent ? `${value.parents.length !== 0 ? 95 : 100}%` : '100%'}
title={value.title} title={value.title}
> >
<DocNavLink <DocNavLink

View File

@ -166,7 +166,7 @@
<ParentsNavigator element={issue} /> <ParentsNavigator element={issue} />
{/if} {/if}
<span class="ml-4 fs-title select-text-i"> <span class="ml-4 fs-title select-text-i overflow-label">
{#if embedded} {#if embedded}
<DocNavLink object={issue}> <DocNavLink object={issue}>
{#if issueId}{issueId}{/if} {#if issueId}{issueId}{/if}

View File

@ -26,7 +26,8 @@
export let issues: Issue[] | undefined = undefined export let issues: Issue[] | undefined = undefined
export let viewlet: Viewlet export let viewlet: Viewlet
export let viewOptions: ViewOptions export let viewOptions: ViewOptions
export let disableHeader = false export let disableHeader: boolean = false
export let compactMode: boolean = false
// Extra properties // Extra properties
export let projects: Map<Ref<Project>, Project> | undefined export let projects: Map<Ref<Project>, Project> | undefined
@ -86,6 +87,7 @@
{createItemDialogProps} {createItemDialogProps}
{createItemLabel} {createItemLabel}
selectedObjectIds={$selectionStore ?? []} selectedObjectIds={$selectionStore ?? []}
{compactMode}
on:row-focus={(event) => { on:row-focus={(event) => {
listProvider.updateFocus(event.detail ?? undefined) listProvider.updateFocus(event.detail ?? undefined)
}} }}

View File

@ -46,6 +46,7 @@
export let shouldSaveDraft: boolean = false export let shouldSaveDraft: boolean = false
let isCollapsed = false let isCollapsed = false
let listWidth: number
$: hasSubIssues = issue.subIssues > 0 $: hasSubIssues = issue.subIssues > 0
@ -84,11 +85,10 @@
}) })
</script> </script>
<div class="flex-between"> <div class="flex-between mb-1">
{#if hasSubIssues} {#if hasSubIssues}
<Button <Button
width="min-content" width="min-content"
size="small"
kind="transparent" kind="transparent"
on:click={() => { on:click={() => {
isCollapsed = !isCollapsed isCollapsed = !isCollapsed
@ -100,16 +100,14 @@
</svelte:fragment> </svelte:fragment>
</Button> </Button>
{/if} {/if}
<div class="flex-row-center"> <div class="flex-row-center gap-2">
{#if viewlet && hasSubIssues && viewOptions} {#if viewlet && hasSubIssues && viewOptions}
<ViewletSettingButton bind:viewOptions {viewlet} kind={'transparent'} /> <ViewletSettingButton bind:viewOptions {viewlet} kind={'transparent'} />
{/if} {/if}
{#if hasSubIssues} {#if hasSubIssues}
<Button <Button
width="min-content"
icon={IconScaleFull} icon={IconScaleFull}
kind={'transparent'} kind={'transparent'}
size={'small'}
showTooltip={{ label: tracker.string.OpenSubIssues, direction: 'bottom' }} showTooltip={{ label: tracker.string.OpenSubIssues, direction: 'bottom' }}
on:click={() => { on:click={() => {
const filter = createFilter(tracker.class.Issue, 'attachedTo', [issue._id]) const filter = createFilter(tracker.class.Issue, 'attachedTo', [issue._id])
@ -129,12 +127,10 @@
{/if} {/if}
<Button <Button
id="add-sub-issue" id="add-sub-issue"
width="min-content"
icon={hasSubIssues ? IconAdd : undefined} icon={hasSubIssues ? IconAdd : undefined}
label={hasSubIssues ? undefined : tracker.string.AddSubIssues} label={hasSubIssues ? undefined : tracker.string.AddSubIssues}
labelParams={{ subIssues: 0 }} labelParams={{ subIssues: 0 }}
kind={'transparent'} kind={'transparent'}
size={'small'}
showTooltip={{ label: tracker.string.AddSubIssues, props: { subIssues: 1 }, direction: 'bottom' }} showTooltip={{ label: tracker.string.AddSubIssues, props: { subIssues: 1 }, direction: 'bottom' }}
on:click={() => { on:click={() => {
isCollapsed = false isCollapsed = false
@ -144,29 +140,29 @@
/> />
</div> </div>
</div> </div>
<div class="mt-1"> {#if hasSubIssues && viewOptions && viewlet}
{#if hasSubIssues && viewOptions && viewlet} {#if !isCollapsed}
{#if !isCollapsed} <ExpandCollapse isExpanded={!isCollapsed}>
<ExpandCollapse isExpanded={!isCollapsed}> <div class="list" class:collapsed={isCollapsed} bind:clientWidth={listWidth}>
<div class="list" class:collapsed={isCollapsed}> <SubIssueList
<SubIssueList createItemDialog={CreateIssue}
createItemDialog={CreateIssue} createItemLabel={tracker.string.AddIssueTooltip}
createItemLabel={tracker.string.AddIssueTooltip} createItemDialogProps={{ space: issue.space, parentIssue: issue, shouldSaveDraft }}
createItemDialogProps={{ space: issue.space, parentIssue: issue, shouldSaveDraft }} focusIndex={focusIndex === -1 ? -1 : focusIndex + 1}
focusIndex={focusIndex === -1 ? -1 : focusIndex + 1} projects={_projects}
projects={_projects} {viewlet}
{viewlet} {viewOptions}
{viewOptions} query={{ attachedTo: issue._id }}
query={{ attachedTo: issue._id }} compactMode={listWidth <= 600}
/> />
</div> </div>
</ExpandCollapse> </ExpandCollapse>
{/if}
{/if} {/if}
</div> {/if}
<style lang="scss"> <style lang="scss">
.list { .list {
padding-top: 0.75rem;
border-top: 1px solid var(--divider-color); border-top: 1px solid var(--divider-color);
&.collapsed { &.collapsed {

View File

@ -39,6 +39,7 @@
export let size: ButtonSize = 'inline' export let size: ButtonSize = 'inline'
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
@ -130,9 +131,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"> {#if !compactMode}
<ProgressCircle bind:value={countComplete} bind:max={subIssues.length} size={'inline'} 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

@ -27,7 +27,8 @@
export let object: Doc export let object: Doc
export let viewlet: Viewlet export let viewlet: Viewlet
export let viewOptions: ViewOptions export let viewOptions: ViewOptions
export let disableHeader = false export let disableHeader: boolean = false
export let compactMode: boolean = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -59,12 +60,13 @@
issues={subIssues} issues={subIssues}
{projects} {projects}
{disableHeader} {disableHeader}
{compactMode}
createItemDialog={CreateIssue} createItemDialog={CreateIssue}
createItemLabel={tracker.string.AddIssueTooltip} createItemLabel={tracker.string.AddIssueTooltip}
createItemDialogProps={{ relatedTo: object }} createItemDialogProps={{ relatedTo: object }}
/> />
{:else} {:else}
<div class="antiSection-empty solid flex-col mt-3"> <div class="antiSection-empty solid flex-col">
<div class="flex-center content-color"> <div class="flex-center content-color">
<AddIssueDuo size={'large'} /> <AddIssueDuo size={'large'} />
</div> </div>
@ -78,7 +80,7 @@
</div> </div>
{/if} {/if}
{:else} {:else}
<div class="flex-center pt-3"> <div class="flex-center">
<Spinner /> <Spinner />
</div> </div>
{/if} {/if}

View File

@ -21,6 +21,7 @@
const client = getClient() const client = getClient()
let viewlet: Viewlet | undefined let viewlet: Viewlet | undefined
let listWidth: number
const vquery = createQuery() const vquery = createQuery()
$: vquery.query(view.class.Viewlet, { _id: tracker.viewlet.SubIssues }, (res) => { $: vquery.query(view.class.Viewlet, { _id: tracker.viewlet.SubIssues }, (res) => {
@ -41,12 +42,12 @@
</script> </script>
{#if $configurationStore.has(trackerId)} {#if $configurationStore.has(trackerId)}
<div class="antiSection"> <div class="antiSection" bind:clientWidth={listWidth}>
<div class="antiSection-header"> <div class="antiSection-header mb-3">
<div class="antiSection-header__icon"> <div class="antiSection-header__icon">
<Icon icon={tracker.icon.Issue} size={'small'} /> <Icon icon={tracker.icon.Issue} size={'small'} />
</div> </div>
<span class="antiSection-header__title short"> <span class="antiSection-header__title short overflow-label">
<Label {label} /> <Label {label} />
</span> </span>
{#if headerRemoval} {#if headerRemoval}
@ -64,7 +65,7 @@
{:else} {:else}
<span class="flex-grow" /> <span class="flex-grow" />
{/if} {/if}
<div class="buttons-group small-gap"> <div class="flex-row-center gap-2">
{#if viewlet && viewOptions} {#if viewlet && viewOptions}
<ViewletSettingButton bind:viewOptions {viewlet} kind={'transparent'} /> <ViewletSettingButton bind:viewOptions {viewlet} kind={'transparent'} />
{/if} {/if}
@ -74,13 +75,19 @@
label={undefined} label={undefined}
labelParams={{ subIssues: 0 }} labelParams={{ subIssues: 0 }}
kind={'transparent'} kind={'transparent'}
shape={'circle'}
on:click={createIssue} on:click={createIssue}
/> />
</div> </div>
</div> </div>
{#if viewlet} {#if viewlet}
<RelatedIssues {object} {viewOptions} {viewlet} on:add-issue={createIssue} disableHeader={headerRemoval} /> <RelatedIssues
{object}
{viewOptions}
{viewlet}
on:add-issue={createIssue}
disableHeader={headerRemoval}
compactMode={listWidth <= 600}
/>
{/if} {/if}
</div> </div>
{/if} {/if}

View File

@ -83,7 +83,6 @@
{kind} {kind}
{size} {size}
{shape} {shape}
{width}
{justify} {justify}
{isEditable} {isEditable}
{shouldShowLabel} {shouldShowLabel}

View File

@ -17,7 +17,7 @@
import { Button, ButtonSize, TimeSince } from '@hcengineering/ui' import { Button, ButtonSize, TimeSince } from '@hcengineering/ui'
export let value: number export let value: number
export let kind: 'no-border' | 'link' = 'no-border' export let kind: 'no-border' | 'link' | 'list' = 'no-border'
export let readonly = false export let readonly = false
export let size: ButtonSize = 'small' export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'center' export let justify: 'left' | 'center' = 'center'
@ -31,5 +31,5 @@
</svelte:fragment> </svelte:fragment>
</Button> </Button>
{:else} {:else}
<TimeSince {value} /> <TimeSince {value} {kind} />
{/if} {/if}

View File

@ -312,46 +312,56 @@
let selected: number | undefined let selected: number | undefined
</script> </script>
<div class="selectPopup p-2"> <div class="selectPopup">
<div class="menu-space" />
<div class="scroll"> <div class="scroll">
{#if loading} <div class="box">
<Loading /> {#if loading}
{:else} <Loading />
<div class="flex-row-reverse"> {:else}
<Button on:click={restoreDefault} label={view.string.RestoreDefaults} size={'x-small'} kind={'link'} noFocus /> <div class="flex-row-reverse mb-2 mr-2">
</div> <Button
{#each items as item, i} on:click={restoreDefault}
{#if isAttribute(item)} label={view.string.RestoreDefaults}
<div size={'x-small'}
class="item" kind={'link'}
bind:this={elements[i]} noFocus
draggable={viewlet.configOptions?.sortable && item.enabled} />
on:dragstart={(ev) => { </div>
if (ev.dataTransfer) { {#each items as item, i}
ev.dataTransfer.effectAllowed = 'move' {#if isAttribute(item)}
ev.dataTransfer.dropEffect = 'move' <div
} class="menu-item flex-row-center"
// ev.preventDefault() bind:this={elements[i]}
ev.stopPropagation() draggable={viewlet.configOptions?.sortable && item.enabled}
selected = i on:dragstart={(ev) => {
}} if (ev.dataTransfer) {
on:dragover|preventDefault={(e) => dragOver(e, i)} ev.dataTransfer.effectAllowed = 'move'
on:dragend={dragEnd} ev.dataTransfer.dropEffect = 'move'
> }
<ToggleWithLabel // ev.preventDefault()
on={item.enabled} ev.stopPropagation()
label={item.label} selected = i
on:change={(e) => {
change(item, e.detail)
}} }}
/> on:dragover|preventDefault={(e) => dragOver(e, i)}
</div> on:dragend={dragEnd}
{:else} >
<div class="antiDivider" /> <ToggleWithLabel
{/if} on={item.enabled}
{/each} label={item.label}
{/if} on:change={(e) => {
change(item, e.detail)
}}
/>
</div>
{:else}
<div class="antiDivider" />
{/if}
{/each}
{/if}
</div>
</div> </div>
<div class="menu-space" />
</div> </div>
<style lang="scss"> <style lang="scss">

View File

@ -31,12 +31,13 @@
</script> </script>
{#if viewlet} {#if viewlet}
<div class="flex-row-center"> <div class="flex-row-center gap-2 reverse">
<div class="mr-3"><ViewOptionsButton {viewlet} {kind} {viewOptions} /></div> <ViewOptionsButton {viewlet} {kind} {viewOptions} />
<Button <Button
icon={view.icon.Configure} icon={view.icon.Configure}
label={view.string.Show} label={view.string.Show}
{kind} {kind}
shrink={1}
showTooltip={{ label: view.string.CustomizeView, direction: 'bottom' }} showTooltip={{ label: view.string.CustomizeView, direction: 'bottom' }}
bind:input={btn} bind:input={btn}
on:click={clickHandler} on:click={clickHandler}

View File

@ -1,12 +1,11 @@
<span class="root" /> <div class="grow-container" />
<style lang="scss"> <style lang="scss">
.root { .grow-container {
display: flex; display: flex;
flex-grow: 1; flex-grow: 1;
min-width: 0; min-width: 0;
white-space: nowrap; flex-shrink: initial;
overflow: hidden; flex-basis: initial;
flex-shrink: 10;
} }
</style> </style>

View File

@ -141,6 +141,7 @@
{/each} {/each}
</div> </div>
{:else} {:else}
<GrowPresenter />
{#each model.filter((p) => p.displayProps?.suffix === true) as attrModel} {#each model.filter((p) => p.displayProps?.suffix === true) as attrModel}
<ListPresenter <ListPresenter
{docObject} {docObject}