UBERF-5603 (#4754)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov
2024-02-23 15:39:49 +07:00
committed by GitHub
parent cc8a1d549a
commit ffc8d1dfac
184 changed files with 2755 additions and 561 deletions
@@ -39,6 +39,7 @@
export let shouldShowName = true
export let shrink: number = 0
export let isAction: boolean = false
export let readonly: boolean = false
$: _object =
(typeof object !== 'string' ? object : undefined) ?? (typeof value !== 'string' ? value : undefined) ?? []
@@ -186,6 +187,7 @@
{width}
{short}
{shrink}
{readonly}
{shouldShowName}
showNavigate={false}
justify={'left'}
@@ -21,6 +21,7 @@
export let value: WithLookup<Issue>
export let width: string | undefined = undefined
export let editable: boolean = true
const client = getClient()
$: shouldIgnoreOverdue =
@@ -49,7 +50,7 @@
kind={'link'}
value={value.dueDate}
{width}
editable
{editable}
onChange={(e) => handleDueDateChanged(e)}
{shouldIgnoreOverdue}
/>
@@ -52,7 +52,15 @@
{/if}
{:else if value}
<div class="flex-row-center">
<DocNavLink object={value} {onClick} {disabled} {noUnderline} component={tracker.component.EditIssue} shrink={0}>
<DocNavLink
object={value}
{onClick}
{disabled}
{noUnderline}
{inline}
component={tracker.component.EditIssue}
shrink={0}
>
<span class="issuePresenterRoot" class:list={kind === 'list'} class:cursor-pointer={!disabled}>
{#if shouldShowAvatar}
<div class="icon" use:tooltip={{ label: tracker.string.Issue }}>
@@ -62,6 +62,7 @@
openDoc,
SelectDirection,
setGroupByValues,
showMenu,
statusStore
} from '@hcengineering/view-resources'
import { ChatMessagesPresenter } from '@hcengineering/chunter-resources'
@@ -138,10 +139,6 @@
;(document.activeElement as HTMLElement)?.blur()
})
const showMenu = async (ev: MouseEvent, items: Doc[]): Promise<void> => {
ev.preventDefault()
showPopup(Menu, { object: items, baseMenuClass }, getEventPositionElement(ev))
}
// Category information only
let tasks: DocWithRank[] = []
@@ -337,7 +334,9 @@
on:check={(evt) => {
listProvider.updateSelection(evt.detail.docs, evt.detail.value)
}}
on:contextmenu={(evt) => showMenu(evt.detail.evt, evt.detail.objects)}
on:contextmenu={(evt) => {
showMenu(evt.detail.evt, { object: evt.detail.objects, baseMenuClass })
}}
>
<svelte:fragment slot="header" let:state let:count let:index>
{@const color = accentColors.get(`${index}${$themeStore.dark}${groupByKey}`)}
@@ -6,6 +6,8 @@
export let value: Issue
export let type: 'isBlocking' | 'blockedBy' | 'relations'
export let blockedBy: Doc[] | undefined = undefined
export let disabled: boolean = false
export let readonly: boolean = false
$: valueGroup = (type === 'isBlocking' ? blockedBy ?? [] : value[type] ?? []).reduce<
Map<Ref<Class<Doc>>, Ref<Doc>[]>
@@ -25,7 +27,7 @@
{#each classes as classCategory}
{@const vals = valueGroup.get(classCategory)}
{#if vals}
<RelationEditorPart {value} _class={classCategory} documentIds={vals} {type} />
<RelationEditorPart {value} _class={classCategory} documentIds={vals} {type} {disabled} {readonly} />
{/if}
{/each}
</div>
@@ -13,6 +13,8 @@
export let _class: Ref<Class<Doc>>
export let documentIds: Ref<Doc>[]
export let type: 'isBlocking' | 'blockedBy' | 'relations'
export let disabled: boolean = false
export let readonly: boolean = false
const client = getClient()
const issuesQuery = createQuery()
@@ -29,6 +31,7 @@
})
async function handleClick (issue: RelatedDocument) {
if (readonly || disabled) return
const prop = type === 'isBlocking' ? 'blockedBy' : type
const isBlockingValue = await client.findAll(value._class, { 'blockedBy._id': value._id })
@@ -67,6 +70,7 @@
}
async function handleRedirect (issue: Issue) {
if (disabled) return
const loc = await issueLinkFragmentProvider(issue)
navigate(loc)
}
@@ -80,13 +84,15 @@
<div class="tag-container">
<Icon {icon} size={'small'} />
<div class="flex-grow">
<button on:click|stopPropagation={() => handleRedirect(issue)}>
<button {disabled} on:click|stopPropagation={() => handleRedirect(issue)}>
<span class="overflow-label ml-1-5 content-color">{issue.identifier}</span>
</button>
</div>
<button class="btn-close" on:click|stopPropagation={() => handleClick(issue)}>
<Icon icon={IconClose} size={'x-small'} />
</button>
{#if !readonly}
<button class="btn-close" on:click|stopPropagation={() => handleClick(issue)}>
<Icon icon={IconClose} size={'x-small'} />
</button>
{/if}
</div>
{:else}
<div class="tag-container between">
@@ -94,9 +100,11 @@
is={view.component.ObjectPresenter}
props={{ objectId: doc._id, _class: doc._class, value: doc, noUnderline: true, props: { avatarSize: 'card' } }}
/>
<button class="btn-close" on:click|stopPropagation={() => handleClick(doc)}>
<Icon icon={IconClose} size={'x-small'} />
</button>
{#if !readonly}
<button class="btn-close" on:click|stopPropagation={() => handleClick(doc)}>
<Icon icon={IconClose} size={'x-small'} />
</button>
{/if}
</div>
{/if}
{/each}
@@ -53,16 +53,6 @@
align-items: center;
flex-shrink: 1;
min-width: 1rem;
&.list {
color: var(--theme-caption-color);
}
&:hover {
text-decoration: underline;
}
&:active {
color: var(--theme-content-color);
}
}
.with-margin {
@@ -21,7 +21,7 @@
import tags from '@hcengineering/tags'
import type { Issue } from '@hcengineering/tracker'
import { Component, Label } from '@hcengineering/ui'
import { getFiltredKeys, isCollectionAttr, ObjectBox } from '@hcengineering/view-resources'
import { getFiltredKeys, isCollectionAttr, ObjectBox, restrictionStore } from '@hcengineering/view-resources'
import tracker from '../../../plugin'
import ComponentEditor from '../../components/ComponentEditor.svelte'
import MilestoneEditor from '../../milestones/MilestoneEditor.svelte'
@@ -33,6 +33,7 @@
export let issue: Issue
export let showAllMixins: boolean = false
export let readonly = false
const query = createQuery()
let showIsBlocking = false
@@ -125,31 +126,37 @@
<Label label={tracker.string.Status} />
</span>
<StatusEditor value={issue} size={'medium'} iconSize={'small'} shouldShowLabel />
<StatusEditor value={issue} size={'medium'} iconSize={'small'} shouldShowLabel isEditable={!readonly} />
{#if issue.blockedBy?.length}
<span class="labelTop">
<Label label={tracker.string.BlockedBy} />
</span>
<RelationEditor value={issue} type="blockedBy" />
<RelationEditor value={issue} type="blockedBy" {readonly} disabled={$restrictionStore.disableNavigation} />
{/if}
{#if showIsBlocking}
<span class="labelTop">
<Label label={tracker.string.Blocks} />
</span>
<RelationEditor value={issue} type="isBlocking" {blockedBy} />
<RelationEditor
value={issue}
type="isBlocking"
{blockedBy}
{readonly}
disabled={$restrictionStore.disableNavigation}
/>
{/if}
{#if issue.relations?.length}
<span class="labelTop">
<Label label={tracker.string.Related} />
</span>
<RelationEditor value={issue} type="relations" />
<RelationEditor value={issue} type="relations" {readonly} disabled={$restrictionStore.disableNavigation} />
{/if}
<span class="labelOnPanel">
<Label label={tracker.string.Priority} />
</span>
<PriorityEditor value={issue} size={'medium'} shouldShowLabel />
<PriorityEditor value={issue} size={'medium'} shouldShowLabel isEditable={!readonly} />
<span class="labelOnPanel">
<Label label={core.string.CreatedBy} />
@@ -168,24 +175,27 @@
<span class="labelOnPanel">
<Label label={tracker.string.Assignee} />
</span>
<AssigneeEditor object={issue} size={'medium'} avatarSize={'card'} width="100%" />
<AssigneeEditor object={issue} size={'medium'} avatarSize={'card'} width="100%" {readonly} />
<span class="labelTop">
<Label label={tracker.string.Labels} />
</span>
<Component is={tags.component.TagsAttributeEditor} props={{ object: issue, label: tracker.string.AddLabel }} />
<Component
is={tags.component.TagsAttributeEditor}
props={{ object: issue, label: tracker.string.AddLabel, readonly }}
/>
<div class="divider" />
<span class="labelOnPanel">
<Label label={tracker.string.Component} />
</span>
<ComponentEditor value={issue} space={issue.space} size={'medium'} />
<ComponentEditor value={issue} space={issue.space} size={'medium'} isEditable={!readonly} />
<span class="labelOnPanel">
<Label label={tracker.string.Milestone} />
</span>
<MilestoneEditor value={issue} space={issue.space} size={'medium'} />
<MilestoneEditor value={issue} space={issue.space} size={'medium'} isEditable={!readonly} />
{#if issue.dueDate !== null}
<div class="divider" />
@@ -193,13 +203,13 @@
<span class="labelOnPanel">
<Label label={tracker.string.DueDate} />
</span>
<DueDateEditor value={issue} width={'100%'} />
<DueDateEditor value={issue} width={'100%'} editable={!readonly} />
{/if}
{#if keys.length > 0}
<div class="divider" />
{#each keys as key (typeof key === 'string' ? key : key.key)}
<AttributeBarEditor {key} _class={issue._class} object={issue} showHeader={true} size={'medium'} />
<AttributeBarEditor {readonly} {key} _class={issue._class} object={issue} showHeader={true} size={'medium'} />
{/each}
{/if}
@@ -211,6 +221,7 @@
<AttributeBarEditor
{key}
_class={mixin._id}
{readonly}
object={hierarchy.as(issue, mixin._id)}
showHeader={true}
size={'medium'}
@@ -40,11 +40,10 @@
Spinner,
createFocusManager,
getCurrentResolvedLocation,
navigate,
showPopup
navigate
} from '@hcengineering/ui'
import view from '@hcengineering/view'
import { ContextMenu, DocNavLink, ParentsNavigator } from '@hcengineering/view-resources'
import { DocNavLink, ParentsNavigator, showMenu } from '@hcengineering/view-resources'
import { createEventDispatcher, onDestroy } from 'svelte'
import { generateIssueShortLink } from '../../../issues'
import tracker from '../../../plugin'
@@ -58,6 +57,7 @@
export let _class: Ref<Class<Issue>>
export let embedded: boolean = false
export let kind: 'default' | 'modern' = 'default'
export let readonly: boolean = false
let lastId: Ref<Doc> = _id
const queryClient = createQuery()
@@ -121,13 +121,9 @@
}
}
function showMenu (ev?: Event): void {
function showContextMenu (ev: MouseEvent): void {
if (issue !== undefined) {
showPopup(
ContextMenu,
{ object: issue, excludedActions: [view.action.Open] },
(ev as MouseEvent).target as HTMLElement
)
showMenu(ev, { object: issue, excludedActions: [view.action.Open] })
}
}
@@ -181,6 +177,8 @@
<Panel
object={issue}
isHeader={false}
withoutInput={readonly}
allowClose={!embedded}
isAside={true}
isSub={false}
{embedded}
@@ -223,25 +221,27 @@
</svelte:fragment>
<svelte:fragment slot="utils">
<Button icon={IconMoreH} iconProps={{ size: 'medium' }} kind={'icon'} on:click={showMenu} />
<CopyToClipboard issueUrl={generateIssueShortLink(issue.identifier)} />
<Button
icon={setting.icon.Setting}
kind={'icon'}
iconProps={{ size: 'medium' }}
showTooltip={{ label: setting.string.ClassSetting }}
on:click={(ev) => {
ev.stopPropagation()
const loc = getCurrentResolvedLocation()
loc.path[2] = settingId
loc.path[3] = 'setting'
loc.path[4] = 'classes'
loc.path.length = 5
loc.query = { _class }
loc.fragment = undefined
navigate(loc)
}}
/>
{#if !readonly}
<Button icon={IconMoreH} iconProps={{ size: 'medium' }} kind={'icon'} on:click={showContextMenu} />
<CopyToClipboard issueUrl={generateIssueShortLink(issue.identifier)} />
<Button
icon={setting.icon.Setting}
kind={'icon'}
iconProps={{ size: 'medium' }}
showTooltip={{ label: setting.string.ClassSetting }}
on:click={(ev) => {
ev.stopPropagation()
const loc = getCurrentResolvedLocation()
loc.path[2] = settingId
loc.path[3] = 'setting'
loc.path[4] = 'classes'
loc.path.length = 5
loc.query = { _class }
loc.fragment = undefined
navigate(loc)
}}
/>
{/if}
<Button
icon={IconMixin}
iconProps={{ size: 'medium' }}
@@ -265,6 +265,7 @@
<EditBox
focusIndex={1}
bind:value={title}
disabled={readonly}
placeholder={tracker.string.IssueTitlePlaceholder}
kind="large-style"
on:blur={save}
@@ -273,6 +274,7 @@
<AttachmentStyleBoxCollabEditor
focusIndex={30}
object={issue}
{readonly}
key={{ key: 'description', attr: descriptionKey }}
bind:this={descriptionBox}
placeholder={tracker.string.IssueDescriptionPlaceholder}
@@ -292,7 +294,7 @@
{#if editorFooter}
<div class="step-tb-6">
<Component is={editorFooter.footer} props={{ object: issue, _class, ...editorFooter.props }} />
<Component is={editorFooter.footer} props={{ object: issue, _class, ...editorFooter.props, readonly }} />
</div>
{/if}
@@ -303,7 +305,7 @@
<svelte:fragment slot="custom-attributes">
{#if issue !== undefined && currentProject}
<div class="space-divider" />
<ControlPanel {issue} {showAllMixins} />
<ControlPanel {issue} {showAllMixins} {readonly} />
{/if}
<div class="popupPanel-body__aside-grid">
@@ -18,7 +18,7 @@
import { Issue } from '@hcengineering/tracker'
import { Button, Chevron, ExpandCollapse, IconAdd, closeTooltip, resizeObserver, showPopup } from '@hcengineering/ui'
import view, { ViewOptions, Viewlet, ViewletPreference } from '@hcengineering/view'
import { ViewletsSettingButton } from '@hcengineering/view-resources'
import { ViewletsSettingButton, restrictionStore } from '@hcengineering/view-resources'
import { afterUpdate } from 'svelte'
import tracker from '../../../plugin'
import CreateIssue from '../../CreateIssue.svelte'
@@ -139,19 +139,21 @@
{#if hasSubIssues}
<slot name="buttons" />
{/if}
<Button
id="add-sub-issue"
icon={IconAdd}
label={hasSubIssues ? undefined : createLabel}
labelParams={{ subIssues: 0 }}
kind={'ghost'}
showTooltip={{ label: createLabel, direction: 'bottom' }}
on:click={() => {
isCollapsed = false
closeTooltip()
openNewIssueDialog()
}}
/>
{#if !$restrictionStore.readonly}
<Button
id="add-sub-issue"
icon={IconAdd}
label={hasSubIssues ? undefined : createLabel}
labelParams={{ subIssues: 0 }}
kind={'ghost'}
showTooltip={{ label: createLabel, direction: 'bottom' }}
on:click={() => {
isCollapsed = false
closeTooltip()
openNewIssueDialog()
}}
/>
{/if}
</div>
</div>
{#if hasSubIssues && viewOptions && viewlet}
@@ -165,7 +167,7 @@
}}
>
<SubIssueList
createItemDialog={CreateIssue}
createItemDialog={!$restrictionStore.readonly ? CreateIssue : undefined}
createItemLabel={tracker.string.AddIssueTooltip}
createItemDialogProps={{ space: object.space, ...createParams, shouldSaveDraft }}
focusIndex={focusIndex === -1 ? -1 : focusIndex + 1}
@@ -30,6 +30,9 @@
export let compactMode: boolean = false
export let configurations: Record<Ref<Class<Doc>>, Viewlet['config']> = {}
export let preference: ViewletPreference[] = []
export let createItemDialog: AnySvelteComponent | AnyComponent | undefined = undefined
export let createItemLabel: IntlString | undefined = undefined
export let createItemDialogProps: Record<string, any> | undefined = undefined
let list: List
@@ -62,10 +65,6 @@
isFocus: () => focused
})
export let createItemDialog: AnySvelteComponent | AnyComponent | undefined = undefined
export let createItemLabel: IntlString | undefined = undefined
export let createItemDialogProps: Record<string, any> | undefined = undefined
const dispatch = createEventDispatcher()
</script>
@@ -15,7 +15,7 @@
<script lang="ts">
import { Issue, trackerId } from '@hcengineering/tracker'
import { Button, IconScaleFull, Label, closeTooltip, getCurrentResolvedLocation, navigate } from '@hcengineering/ui'
import { createFilter, setFilters } from '@hcengineering/view-resources'
import { createFilter, restrictionStore, setFilters } from '@hcengineering/view-resources'
import tracker from '../../../plugin'
import QueryIssuesList from './QueryIssuesList.svelte'
@@ -44,24 +44,26 @@
<Label label={tracker.string.SubIssuesList} params={{ subIssues: size }} />
</svelte:fragment>
<svelte:fragment slot="buttons">
<Button
icon={IconScaleFull}
kind={'ghost'}
showTooltip={{ label: tracker.string.OpenSubIssues, direction: 'bottom' }}
on:click={() => {
const filter = createFilter(tracker.class.Issue, 'attachedTo', [issue._id])
if (filter !== undefined) {
closeTooltip()
const loc = getCurrentResolvedLocation()
loc.fragment = undefined
loc.query = undefined
loc.path[2] = trackerId
loc.path[3] = issue.space
loc.path[4] = 'issues'
navigate(loc)
setFilters([filter])
}
}}
/>
{#if !$restrictionStore.disableNavigation}
<Button
icon={IconScaleFull}
kind={'ghost'}
showTooltip={{ label: tracker.string.OpenSubIssues, direction: 'bottom' }}
on:click={() => {
const filter = createFilter(tracker.class.Issue, 'attachedTo', [issue._id])
if (filter !== undefined) {
closeTooltip()
const loc = getCurrentResolvedLocation()
loc.fragment = undefined
loc.query = undefined
loc.path[2] = trackerId
loc.path[3] = issue.space
loc.path[4] = 'issues'
navigate(loc)
setFilters([filter])
}
}}
/>
{/if}
</svelte:fragment>
</QueryIssuesList>
@@ -16,18 +16,13 @@
import contact from '@hcengineering/contact'
import { AssigneeBox } from '@hcengineering/contact-resources'
import { Issue } from '@hcengineering/tracker'
import { ListView, deviceOptionsStore as deviceInfo, getEventPositionElement, showPopup } from '@hcengineering/ui'
import { ContextMenu, FixedColumn, ListSelectionProvider } from '@hcengineering/view-resources'
import { ListView, deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
import { FixedColumn, ListSelectionProvider, showMenu } from '@hcengineering/view-resources'
import tracker from '../../../plugin'
import { activeProjects } from '../../../utils'
import EstimationEditor from './EstimationEditor.svelte'
export let issues: Issue[]
function showContextMenu (ev: MouseEvent, object: Issue) {
showPopup(ContextMenu, { object }, $deviceInfo.isMobile ? 'top' : getEventPositionElement(ev))
}
const listProvider = new ListSelectionProvider(() => {})
$: twoRows = $deviceInfo.twoRows
</script>
@@ -36,11 +31,10 @@
<ListView count={issues.length} addClass={'step-tb-2-accent'}>
<svelte:fragment slot="item" let:item>
{@const issue = issues[item]}
{@const currentProject = $activeProjects.get(issue.space)}
<div
class="{twoRows ? 'flex-col' : 'flex-between'} p-text-2 clear-mins"
on:contextmenu|preventDefault={(ev) => {
showContextMenu(ev, issue)
on:contextmenu={(ev) => {
showMenu(ev, { object: issue })
}}
on:mouseover={() => {
listProvider.updateFocus(issue)
@@ -48,6 +48,7 @@
{size}
{justify}
{width}
disabled={readonly}
on:click={(ev) => {
if (!shown && !readonly) {
showPopup(EditBoxPopup, { value, format: 'number' }, eventToHTMLElement(ev), (res) => {
@@ -29,6 +29,7 @@
export let kind: 'no-border' | 'link' = 'no-border'
export let size: 'small' | 'medium' | 'large' = 'large'
export let currentProject: Project | undefined
export let readonly: boolean = false
$: if (currentProject === undefined) {
currentProject = $activeProjects.get(object.space)
@@ -37,6 +38,7 @@
$: defaultTimeReportDay = currentProject?.defaultTimeReportDay
function addTimeReport (event: MouseEvent): void {
if (readonly) return
showPopup(
TimeSpendReportPopup,
{
@@ -52,6 +54,7 @@
)
}
function showReports (event: MouseEvent): void {
if (readonly) return
showPopup(ReportsPopup, { issue: object }, eventToHTMLElement(event))
}
$: childTime = floorFractionDigits(
@@ -66,6 +69,7 @@
<div
id="ReportedTimeEditor"
class="link-container antiButton link {size} flex-grow flex-between"
class:readonly
on:click={showReports}
>
{#if value !== undefined}
@@ -78,9 +82,11 @@
{:else}
<span class="content-dark-color"><Label label={placeholder} /></span>
{/if}
<div class="add-action">
<ActionIcon icon={IconAdd} size={'small'} action={addTimeReport} />
</div>
{#if !readonly}
<div class="add-action">
<ActionIcon icon={IconAdd} size={'small'} action={addTimeReport} />
</div>
{/if}
</div>
{:else if value !== undefined}
<span class="flex-row-center">
@@ -101,9 +107,6 @@
}
&:hover {
background-color: var(--theme-bg-color);
border-color: var(--theme-divider-color);
.add-action {
visibility: visible;
}
@@ -22,10 +22,9 @@
ListView,
deviceOptionsStore as deviceInfo,
eventToHTMLElement,
getEventPositionElement,
showPopup
} from '@hcengineering/ui'
import { ContextMenu, FixedColumn, ListSelectionProvider } from '@hcengineering/view-resources'
import { FixedColumn, ListSelectionProvider, showMenu } from '@hcengineering/view-resources'
import tracker from '../../../plugin'
import { activeProjects } from '../../../utils'
import TimePresenter from './TimePresenter.svelte'
@@ -33,10 +32,6 @@
export let reports: WithLookup<TimeSpendReport>[]
function showContextMenu (ev: MouseEvent, object: TimeSpendReport) {
showPopup(ContextMenu, { object }, getEventPositionElement(ev))
}
const listProvider = new ListSelectionProvider(() => {})
const toProjectId = (ref: Ref<Space>) => ref as Ref<Project>
@@ -70,8 +65,8 @@
{@const currentProject = $activeProjects.get(toProjectId(report.space))}
<div
class="{twoRows ? 'flex-col' : 'flex-between'} p-text-2 clear-mins"
on:contextmenu|preventDefault={(ev) => {
showContextMenu(ev, report)
on:contextmenu={(ev) => {
showMenu(ev, { object: report })
}}
on:mouseenter={() => {
listProvider.updateFocus(report)
@@ -22,9 +22,9 @@
import setting, { settingId } from '@hcengineering/setting'
import tags from '@hcengineering/tags'
import { IssueTemplate, IssueTemplateChild, Project } from '@hcengineering/tracker'
import { Button, EditBox, IconMoreH, Label, getCurrentResolvedLocation, navigate, showPopup } from '@hcengineering/ui'
import { ContextMenu } from '@hcengineering/view-resources'
import { Button, EditBox, IconMoreH, Label, getCurrentResolvedLocation, navigate } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { showMenu } from '@hcengineering/view-resources'
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
import tracker from '../../plugin'
@@ -91,13 +91,9 @@
}
}
function showMenu (ev?: Event): void {
function showContextMenu (ev: MouseEvent): void {
if (template !== undefined) {
showPopup(
ContextMenu,
{ object: template, excludedActions: [view.action.Open] },
(ev as MouseEvent).target as HTMLElement
)
showMenu(ev, { object: template, excludedActions: [view.action.Open] })
}
}
@@ -183,7 +179,7 @@
{/if}
</svelte:fragment>
<svelte:fragment slot="utils">
<Button icon={IconMoreH} iconProps={{ size: 'medium' }} kind={'icon'} on:click={showMenu} />
<Button icon={IconMoreH} iconProps={{ size: 'medium' }} kind={'icon'} on:click={showContextMenu} />
<Button
icon={setting.icon.Setting}
iconProps={{ size: 'medium' }}