UBER-219: updated CreateIssue layout (#3244)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov
2023-05-24 17:27:57 +07:00
committed by GitHub
parent d8e69eb6bc
commit edfc99ccd8
73 changed files with 454 additions and 303 deletions
@@ -41,7 +41,6 @@
Project
} from '@hcengineering/tracker'
import {
ActionIcon,
addNotification,
Button,
Component,
@@ -50,11 +49,11 @@
EditBox,
FocusHandler,
IconAttachment,
IconMoreH,
Label,
Menu,
showPopup
} from '@hcengineering/ui'
import { Attachment } from '@hcengineering/attachment'
import { AttachmentPresenter } from '@hcengineering/attachment-resources'
import view from '@hcengineering/view'
import { ObjectBox } from '@hcengineering/view-resources'
import { createEventDispatcher, onDestroy } from 'svelte'
@@ -68,7 +67,6 @@
import StatusEditor from './issues/StatusEditor.svelte'
import EstimationEditor from './issues/timereport/EstimationEditor.svelte'
import MilestoneSelector from './milestones/MilestoneSelector.svelte'
import SetDueDateActionPopup from './SetDueDateActionPopup.svelte'
import SetParentIssueActionPopup from './SetParentIssueActionPopup.svelte'
import SubIssues from './SubIssues.svelte'
import { createBacklinks } from '@hcengineering/chunter-resources'
@@ -409,51 +407,17 @@
descriptionBox?.removeDraft(false)
}
async function showMoreActions (ev: Event) {
ev.preventDefault()
const selectDueDate = {
label: object.dueDate === null ? tracker.string.SetDueDate : tracker.string.ChangeDueDate,
icon: tracker.icon.DueDate,
action: async () =>
showPopup(
SetDueDateActionPopup,
{ value: object },
'top',
undefined,
(newDueDate) => newDueDate !== undefined && (object.dueDate = newDueDate)
)
}
const setParentIssue = {
label: parentIssue ? tracker.string.ChangeParent : tracker.string.SetParent,
icon: tracker.icon.Parent,
action: async () =>
showPopup(
SetParentIssueActionPopup,
{ value: { ...object, space: _space, attachedTo: parentIssue?._id } },
'top',
(selectedIssue) => {
if (selectedIssue !== undefined) {
parentIssue = selectedIssue
object.parentIssue = parentIssue?._id
}
}
)
}
const removeParentIssue = parentIssue && {
label: tracker.string.RemoveParent,
icon: tracker.icon.Parent,
action: clearParentIssue
}
async function setParentIssue () {
showPopup(
Menu,
{
actions: [selectDueDate, setParentIssue, ...(removeParentIssue ? [removeParentIssue] : [])]
},
ev.target as HTMLElement
SetParentIssueActionPopup,
{ value: { ...object, space: _space, attachedTo: parentIssue?._id } },
'top',
(selectedIssue) => {
if (selectedIssue !== undefined) {
parentIssue = selectedIssue
object.parentIssue = parentIssue?._id
}
}
)
}
@@ -530,6 +494,8 @@
$: objectId = object._id
const manager = createFocusManager()
let attachments: Map<Ref<Attachment>, Attachment> = new Map<Ref<Attachment>, Attachment>()
</script>
<FocusHandler {manager} />
@@ -540,8 +506,9 @@
{canSave}
okLabel={tracker.string.SaveIssue}
on:close={() => dispatch('close')}
createMore={false}
onCancel={showConfirmationDialog}
hideAttachments={attachments.size === 0}
hideSubheader={!parentIssue}
on:changeContent
>
<svelte:fragment slot="header">
@@ -550,7 +517,7 @@
label={tracker.string.Project}
bind:space={_space}
kind={'secondary'}
size={'large'}
size={'small'}
/>
<ObjectBox
_class={tracker.class.IssueTemplate}
@@ -560,7 +527,7 @@
}}
on:change={handleTemplateChange}
kind={'secondary'}
size={'large'}
size={'small'}
label={tracker.string.NoIssueTemplate}
icon={tracker.icon.IssueTemplates}
searchField={'title'}
@@ -576,20 +543,29 @@
<Label {label} />
</div>
{#if relatedTo}
<div class="mr-2">
<div class="lower mr-2">
<Label label={tracker.string.RelatedTo} />
</div>
<Component
is={view.component.ObjectPresenter}
props={{ value: relatedTo, _class: relatedTo._class, objectId: relatedTo._id, inline: true }}
props={{
value: relatedTo,
_class: relatedTo._class,
objectId: relatedTo._id,
inline: true,
shouldShowAvatar: false,
noUnderline: true
}}
/>
{/if}
</div>
</svelte:fragment>
{#if parentIssue}
<ParentIssue issue={parentIssue} on:close={clearParentIssue} />
{/if}
<div id="issue-name">
<svelte:fragment slot="subheader">
{#if parentIssue}
<ParentIssue issue={parentIssue} on:close={clearParentIssue} />
{/if}
</svelte:fragment>
<div id="issue-name" class="m-3 clear-mins">
<EditBox
focusIndex={1}
bind:value={object.title}
@@ -610,7 +586,8 @@
space={_space}
alwaysEdit
showButtons={false}
emphasized
kind={'indented'}
fakeAttach={'hidden'}
enableBackReferences={true}
bind:content={object.description}
placeholder={tracker.string.IssueDescriptionPlaceholder}
@@ -620,6 +597,13 @@
object.attachments = ev.detail.value
}
}}
on:attachments={(ev) => {
if (ev.detail.size > 0) attachments = ev.detail.values
else if (ev.detail.size === 0 && ev.detail.values) {
attachments.clear()
attachments = attachments
}
}}
/>
{/key}
</div>
@@ -640,6 +624,7 @@
size={'large'}
defaultIssueStatus={currentProject?.defaultIssueStatus}
shouldShowLabel={true}
short
on:refocus={() => {
manager.setFocusPos(3)
}}
@@ -672,6 +657,7 @@
kind={'secondary'}
size={'large'}
width={'min-content'}
short
on:change={({ detail }) => {
object.assignee = detail
manager.setFocusPos(5)
@@ -696,9 +682,6 @@
object.labels = object.labels.filter((it) => it._id !== evt.detail)
}}
/>
<div id="estimation-editor">
<EstimationEditor focusIndex={7} kind={'secondary'} size={'large'} value={object} />
</div>
<ComponentSelector
focusIndex={8}
value={object.component}
@@ -706,25 +689,63 @@
isEditable={true}
kind={'secondary'}
size={'large'}
short
/>
<MilestoneSelector
focusIndex={9}
value={object.milestone}
onChange={handleMilestoneIdChanged}
useComponent={(!originalIssue && object.component) || undefined}
kind={'secondary'}
size={'large'}
/>
{#if object.dueDate !== null}
<DatePresenter bind:value={object.dueDate} kind={'secondary'} size={'large'} editable />
<div id="estimation-editor" class="new-line">
<EstimationEditor focusIndex={7} kind={'secondary'} size={'large'} value={object} />
</div>
<div id="milestone-editor" class="new-line">
<MilestoneSelector
focusIndex={9}
value={object.milestone}
onChange={handleMilestoneIdChanged}
useComponent={(!originalIssue && object.component) || undefined}
kind={'secondary'}
size={'large'}
short
/>
</div>
<div id="duedate-editor" class="new-line">
<DatePresenter
bind:value={object.dueDate}
labelNull={tracker.string.NoDueDate}
icon={tracker.icon.DueDate}
kind={'secondary'}
size={'large'}
editable
/>
</div>
<div id="parentissue-editor" class="new-line">
<Button
icon={tracker.icon.Parent}
label={object.parentIssue ? tracker.string.RemoveParent : tracker.string.SetParent}
kind={'secondary'}
size={'large'}
notSelected={object.parentIssue === undefined}
on:click={object.parentIssue ? clearParentIssue : setParentIssue}
/>
</div>
</svelte:fragment>
<svelte:fragment slot="attachments">
{#if attachments.size > 0}
{#each Array.from(attachments.values()) as attachment}
<AttachmentPresenter
value={attachment}
removable
on:remove={(result) => {
if (result.detail !== undefined) descriptionBox.removeAttachmentById(result.detail._id)
}}
/>
{/each}
{/if}
<div id="more-actions"><ActionIcon icon={IconMoreH} size={'medium'} action={showMoreActions} /></div>
</svelte:fragment>
<svelte:fragment slot="footer">
<Button
focusIndex={10}
icon={IconAttachment}
iconProps={{ fill: 'var(--theme-dark-color)' }}
size={'large'}
kind={'transparent'}
on:click={() => {
descriptionBox.attach()
}}
@@ -45,6 +45,7 @@
okAction={onSave}
canSave={object.label !== ''}
okLabel={tracker.string.CreateComponent}
gap={'gapV-4'}
on:close={() => dispatch('close')}
on:changeContent
>
@@ -61,7 +62,7 @@
<StyledTextArea
bind:content={object.description}
placeholder={tracker.string.ComponentDescriptionPlaceholder}
emphasized
kind={'emphasized'}
showButtons={false}
/>
<svelte:fragment slot="pool">
@@ -33,6 +33,7 @@
{fill}
id={category._id}
style:transform={category._id === tracker.issueStatusCategory.Started ? 'rotate(-90deg)' : ''}
style:flex-shrink={0}
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
>
@@ -30,6 +30,7 @@
export let tooltipAlignment: TooltipAlignment | undefined = undefined
export let width: string = '100%'
export let focusIndex: number | undefined = undefined
export let short: boolean = false
const client = getClient()
const dispatch = createEventDispatcher()
@@ -99,6 +100,7 @@
{kind}
{avatarSize}
{width}
{short}
showNavigate={false}
justify={'left'}
showTooltip={{ label: tracker.string.AssignTo, direction: tooltipAlignment }}
@@ -24,7 +24,7 @@
</script>
<div
class="ac-header full divide"
class="ac-header full divide caption-height"
class:header-with-mode-selector={modeSelectorProps !== undefined}
class:header-without-label={!label}
>
@@ -15,10 +15,11 @@
<script lang="ts">
import { createQuery } from '@hcengineering/presentation'
import { Project, Issue } from '@hcengineering/tracker'
import { Spinner, IconClose, tooltip } from '@hcengineering/ui'
import { Spinner, IconClose, Button } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import tracker from '../../plugin'
import { getIssueId } from '../../issues'
import PriorityRefPresenter from './PriorityRefPresenter.svelte'
export let issue: Issue
@@ -31,51 +32,65 @@
$: issueId = project && getIssueId(project, issue)
</script>
<div class="flex-center root">
<div class="parentIssue-container">
<div class="flex-no-shrink mr-1-5">
<PriorityRefPresenter value={issue.priority} shouldShowLabel={false} />
</div>
{#if issueId}
<span class="overflow-label flex-no-shrink">{issueId}</span>
<span class="overflow-label flex-no-shrink content-dark-color">{issueId}</span>
{:else}
<Spinner size="small" />
{/if}
<span class="overflow-label issue-title">{issue.title}</span>
<Button
icon={IconClose}
showTooltip={{ label: tracker.string.RemoveParent, direction: 'bottom' }}
kind={'transparent'}
size={'small'}
on:click={() => dispatch('close')}
/>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
<!-- <div
class="button-close"
use:tooltip={{ label: tracker.string.RemoveParent, direction: 'bottom' }}
on:click={() => dispatch('close')}
>
<IconClose size="x-small" />
</div>
</div> -->
</div>
<style lang="scss">
.root {
padding: 0.375rem 0.75rem;
line-height: 150%;
.parentIssue-container {
display: flex;
align-items: center;
padding: 0.25rem 0.25rem 0.25rem 0.75rem;
max-width: fit-content;
border: 1px solid var(--button-border-color);
min-width: 0;
// line-height: 150%;
height: 2.25rem;
border: 1px solid var(--theme-button-border);
border-radius: 0.25rem;
box-shadow: var(--button-shadow);
}
.issue-title {
margin: 0 0.75rem 0 0.5rem;
margin: 0 0.25rem 0 0.375rem;
padding-right: 0.75rem;
color: var(--accent-color);
border-right: 1px solid var(--button-border-color);
min-width: 0;
color: var(--theme-caption-color);
border-right: 1px solid var(--theme-button-border);
}
.button-close {
cursor: pointer;
position: relative;
color: var(--content-color);
color: var(--theme-dark-color);
transition: color 0.15s;
&:hover {
color: var(--caption-color);
color: var(--theme-content-color);
}
&:active {
color: var(--accent-color);
color: var(--theme-dark-color);
}
&::before {
position: absolute;
@@ -22,6 +22,7 @@
export let colorInherit: boolean = false
export let accent: boolean = false
export let inline: boolean = false
export let shouldShowLabel: boolean = true
$: icon = issuePriorities[value]?.icon
$: label = issuePriorities[value]?.label
@@ -31,12 +32,14 @@
{#if !inline && icon}
<Icon {icon} {size} fill={'var(--theme-caption-color)'} />
{/if}
<span
class="overflow-label"
class:ml-2={!inline && icon}
style:color={colorInherit ? 'inherit' : 'var(--theme-content-color)'}
class:fs-bold={accent}
>
<Label {label} />
</span>
{#if shouldShowLabel}
<span
class="overflow-label"
class:ml-2={!inline && icon}
style:color={colorInherit ? 'inherit' : 'var(--theme-content-color)'}
class:fs-bold={accent}
>
<Label {label} />
</span>
{/if}
</div>
@@ -40,6 +40,7 @@
export let width: string | undefined = undefined
export let defaultIssueStatus: Ref<IssueStatus> | undefined = undefined
export let focusIndex: number | undefined = undefined
export let short: boolean = false
const client = getClient()
const dispatch = createEventDispatcher()
@@ -120,7 +121,10 @@
{#if selectedStatus}<IssueStatusIcon value={selectedStatus} size={kind === 'list' ? 'small' : 'medium'} />{/if}
</div>
{#if selectedStatusLabel}
<span class="{kind === 'list' ? 'ml-1 text-md' : 'ml-2 text-base'} overflow-label disabled content-color">
<span
class="{kind === 'list' ? 'ml-1 text-md' : 'ml-2 text-base'} overflow-label disabled content-color"
class:max-w-20={short}
>
{selectedStatusLabel}
</span>
{/if}
@@ -134,22 +138,25 @@
{kind}
{width}
{focusIndex}
{short}
on:click={handleStatusEditorOpened}
>
<span slot="content" class="flex-row-center pointer-events-none">
<svelte:fragment slot="icon">
{#if selectedStatus}
<IssueStatusIcon value={selectedStatus} size={iconSize} />
{/if}
</svelte:fragment>
<svelte:fragment slot="content">
{#if selectedStatusLabel}
<span
class="overflow-label disabled"
class:ml-1={selectedStatus && smallgap}
class:ml-1-5={selectedStatus && smallgap}
class:ml-2={selectedStatus && !smallgap}
>
{selectedStatusLabel}
</span>
{/if}
</span>
</svelte:fragment>
</Button>
{/if}
{/if}
@@ -84,7 +84,7 @@
label={tracker.string.TimeSpendValue}
notSelected={value.estimation === 0}
labelParams={{ value: value.estimation }}
icon={tracker.icon.Estimation}
icon={tracker.icon.DueDate}
{justify}
{width}
{size}
@@ -68,6 +68,7 @@
dispatch('close')
}}
okLabel={presentation.string.Ok}
gap={'gapV-4'}
on:close={() => {
dispatch('close', null)
}}
@@ -89,6 +89,7 @@
label={value === undefined ? tracker.string.TimeSpendReportAdd : tracker.string.TimeSpendReportValue}
{canSave}
okAction={create}
gap={'gapV-4'}
on:close
okLabel={value === undefined ? presentation.string.Create : presentation.string.Save}
on:changeContent
@@ -43,7 +43,7 @@
value={dateMs}
editable={true}
shouldShowLabel={true}
icon={'normal'}
iconModifier={'normal'}
{kind}
{size}
on:change={handleDateChanged}
@@ -57,6 +57,7 @@
okAction={onSave}
canSave={object.label !== ''}
okLabel={tracker.string.CreateMilestone}
gap={'gapV-4'}
on:close={() => dispatch('close')}
on:changeContent
>
@@ -73,7 +74,7 @@
<StyledTextArea
bind:content={object.description}
placeholder={tracker.string.ComponentDescriptionPlaceholder}
emphasized
kind={'emphasized'}
showButtons={false}
/>
<svelte:fragment slot="pool">
@@ -183,6 +183,7 @@
okLabel={isNew ? presentation.string.Create : presentation.string.Save}
okAction={handleSave}
canSave={name.length > 0 && !(members.length === 0 && isPrivate)}
gap={'gapV-4'}
on:close={() => {
dispatch('close')
}}
@@ -64,6 +64,7 @@
okLabel={tracker.string.CreateScrum}
{canSave}
okAction={onSave}
gap={'gapV-4'}
on:close={() => dispatch('close')}
on:changeContent
>
@@ -74,7 +75,7 @@
<StyledTextArea
bind:content={object.description}
placeholder={tracker.string.ScrumDescriptionPlaceholder}
emphasized
kind={'emphasized'}
/>
<svelte:fragment slot="pool">
<UserBoxList bind:items={object.members} label={tracker.string.ScrumMembersSearchPlaceholder} />
@@ -125,10 +125,10 @@
okAction={createIssueTemplate}
{canSave}
okLabel={tracker.string.SaveProcess}
gap={'gapV-4'}
on:close={() => {
dispatch('close')
}}
createMore={false}
on:changeContent
>
<svelte:fragment slot="header">
@@ -148,7 +148,7 @@
<StyledTextBox
alwaysEdit
showButtons={false}
emphasized
kind={'emphasized'}
bind:content={object.description}
placeholder={tracker.string.IssueDescriptionPlaceholder}
/>