mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-26 10:20:01 +00:00
Add Submenu component. Update tooltip, Menu. (#2088)
Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
parent
d2e969b715
commit
b8e10b4240
packages
theme/styles
ui/src
plugins
tracker-resources/src/components
workbench-resources/src/components
tests/sanity/tests
@ -29,6 +29,14 @@ a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
&:visited { color: var(--theme-caption-color); }
|
||||
|
||||
&.stealth {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
&:hover, &:active { text-decoration: none; }
|
||||
}
|
||||
}
|
||||
button {
|
||||
display: flex;
|
||||
@ -196,6 +204,7 @@ input.search {
|
||||
.justify-start { justify-content: flex-start; }
|
||||
.justify-end { justify-content: flex-end; }
|
||||
.justify-center { justify-content: center; }
|
||||
.justify-stretch { justify-content: stretch; }
|
||||
.items-baseline { align-items: baseline; }
|
||||
.items-center { align-items: center; }
|
||||
|
||||
|
@ -238,6 +238,12 @@
|
||||
}
|
||||
}
|
||||
&.withCheck { justify-content: space-between; }
|
||||
&.withIcon {
|
||||
margin: 0;
|
||||
|
||||
.icon { color: var(--content-color); }
|
||||
&:focus .icon { color: var(--accent-color); }
|
||||
}
|
||||
|
||||
// &:hover { background-color: var(--popup-bg-hover); }
|
||||
&:focus {
|
||||
@ -331,6 +337,41 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Submenu
|
||||
.antiPopup-submenu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
justify-content: start;
|
||||
padding: .25rem .75rem;
|
||||
min-width: 0;
|
||||
min-height: 2rem;
|
||||
text-align: left;
|
||||
color: var(--caption-color);
|
||||
cursor: pointer;
|
||||
|
||||
.icon { color: var(--content-color); }
|
||||
&:focus .icon,
|
||||
&.withHover:hover .icon { color: var(--accent-color); }
|
||||
&.withHover:hover { background-color: var(--popup-bg-hover); }
|
||||
}
|
||||
|
||||
.antiPopup .ap-menuItem.arrow,
|
||||
.selectPopup .menu-item.arrow,
|
||||
.antiPopup-submenu {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '▶';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0.5rem;
|
||||
font-size: 0.375rem;
|
||||
color: var(--dark-color);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.notifyPopup {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
|
@ -59,41 +59,43 @@
|
||||
</div>
|
||||
{/if}
|
||||
{#each actions as action, i}
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
<button
|
||||
bind:this={btns[i]}
|
||||
class="ap-menuItem flex-row-center withIcon"
|
||||
on:keydown={(evt) => keyDown(evt, i)}
|
||||
on:mouseover={(evt) => {
|
||||
evt.currentTarget.focus()
|
||||
}}
|
||||
on:click={(evt) => {
|
||||
if (!action.inline) {
|
||||
dispatch('close')
|
||||
}
|
||||
action.action(ctx, evt)
|
||||
}}
|
||||
>
|
||||
{#if action.icon}
|
||||
<div class="icon"><Icon icon={action.icon} size={'small'} /></div>
|
||||
{/if}
|
||||
<div class="ml-3 pr-1"><Label label={action.label} /></div>
|
||||
</button>
|
||||
{#if action.link}
|
||||
<a class="stealth" href={action.link}>
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
<button
|
||||
bind:this={btns[i]}
|
||||
class="ap-menuItem flex-row-center withIcon w-full"
|
||||
on:keydown={(evt) => keyDown(evt, i)}
|
||||
on:mouseover={(evt) => evt.currentTarget.focus()}
|
||||
on:click|preventDefault|stopPropagation={(evt) => {
|
||||
if (!action.inline) dispatch('close')
|
||||
action.action(ctx, evt)
|
||||
}}
|
||||
>
|
||||
{#if action.icon}<div class="icon mr-3"><Icon icon={action.icon} size={'small'} /></div>{/if}
|
||||
<span class="overflow-label pr-1"><Label label={action.label} /></span>
|
||||
</button>
|
||||
</a>
|
||||
{:else}
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
<button
|
||||
bind:this={btns[i]}
|
||||
class="ap-menuItem flex-row-center withIcon"
|
||||
on:keydown={(evt) => keyDown(evt, i)}
|
||||
on:mouseover={(evt) => evt.currentTarget.focus()}
|
||||
on:click={(evt) => {
|
||||
if (!action.inline) dispatch('close')
|
||||
action.action(ctx, evt)
|
||||
}}
|
||||
>
|
||||
{#if action.icon}
|
||||
<div class="icon mr-3"><Icon icon={action.icon} size={'small'} /></div>
|
||||
{/if}
|
||||
<span class="overflow-label pr-1"><Label label={action.label} /></span>
|
||||
</button>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ap-space" />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.withIcon {
|
||||
margin: 0;
|
||||
|
||||
.icon {
|
||||
color: var(--content-color);
|
||||
}
|
||||
&:focus .icon {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
53
packages/ui/src/components/Submenu.svelte
Normal file
53
packages/ui/src/components/Submenu.svelte
Normal file
@ -0,0 +1,53 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
// 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 { Asset, IntlString } from '@anticrm/platform'
|
||||
import type { LabelAndProps, AnySvelteComponent, AnyComponent } from '../types'
|
||||
import { Icon, Label, Component, Menu } from '..'
|
||||
import { tooltip } from '../tooltips'
|
||||
|
||||
export let component: AnySvelteComponent | AnyComponent | undefined = undefined
|
||||
export let props: any = {}
|
||||
export let options: LabelAndProps = { kind: 'submenu' }
|
||||
export let focusIndex = -1
|
||||
|
||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||
export let text: string | undefined = undefined
|
||||
export let label: IntlString | undefined = undefined
|
||||
export let labelProps: Record<string, any> = {}
|
||||
export let withHover: boolean = false
|
||||
|
||||
let element: HTMLElement
|
||||
let optionsMod: LabelAndProps
|
||||
$: optionsMod = { component: options.component ?? Menu, props, element, kind: 'submenu' }
|
||||
</script>
|
||||
|
||||
<div bind:this={element} use:tooltip={optionsMod} class="antiPopup-submenu" class:withHover tabindex={focusIndex}>
|
||||
{#if component}
|
||||
{#if typeof component === 'string'}
|
||||
<Component is={component} {props} />
|
||||
{:else}
|
||||
<svelte:component this={component} {...props} />
|
||||
{/if}
|
||||
{:else}
|
||||
{#if icon}
|
||||
<div class="icon mr-3"><Icon {icon} size={'small'} /></div>
|
||||
{/if}
|
||||
<span class="overflow-label pr-1">
|
||||
{#if label}<Label {label} params={labelProps} />
|
||||
{:else if text}{text}{/if}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
@ -26,31 +26,37 @@
|
||||
let tooltipSW: boolean // tooltipSW = true - Label; false - Component
|
||||
let nubDirection: 'top' | 'bottom' | 'left' | 'right' | undefined = undefined
|
||||
let clWidth: number
|
||||
let docWidth: number
|
||||
let docHeight: number
|
||||
|
||||
$: tooltipSW = !$tooltip.component
|
||||
$: tooltipSW = !$tooltip.component && $tooltip.kind !== 'submenu'
|
||||
$: onUpdate = $tooltip.onUpdate
|
||||
$: kind = $tooltip.kind
|
||||
|
||||
const clearStyles = (): void => {
|
||||
tooltipHTML.style.top =
|
||||
tooltipHTML.style.bottom =
|
||||
tooltipHTML.style.left =
|
||||
tooltipHTML.style.right =
|
||||
tooltipHTML.style.height =
|
||||
''
|
||||
}
|
||||
|
||||
const fitTooltip = (): void => {
|
||||
if (($tooltip.label || $tooltip.component) && tooltipHTML) {
|
||||
if ($tooltip.element) {
|
||||
const doc = document.body.getBoundingClientRect()
|
||||
rect = $tooltip.element.getBoundingClientRect()
|
||||
rectAnchor = $tooltip.anchor
|
||||
? $tooltip.anchor.getBoundingClientRect()
|
||||
: $tooltip.element.getBoundingClientRect()
|
||||
|
||||
if ($tooltip.component) {
|
||||
tooltipHTML.style.top =
|
||||
tooltipHTML.style.bottom =
|
||||
tooltipHTML.style.left =
|
||||
tooltipHTML.style.right =
|
||||
tooltipHTML.style.height =
|
||||
''
|
||||
if (rect.bottom + tooltipHTML.clientHeight + 28 < doc.height) {
|
||||
clearStyles()
|
||||
if (rect.bottom + tooltipHTML.clientHeight + 28 < docHeight) {
|
||||
tooltipHTML.style.top = `calc(${rect.bottom}px + 5px + .25rem)`
|
||||
dir = 'bottom'
|
||||
} else if (rect.top > doc.height - rect.bottom) {
|
||||
tooltipHTML.style.bottom = `calc(${doc.height - rect.y}px + 5px + .25rem)`
|
||||
} else if (rect.top > docHeight - rect.bottom) {
|
||||
tooltipHTML.style.bottom = `calc(${docHeight - rect.y}px + 5px + .25rem)`
|
||||
if (tooltipHTML.clientHeight > rect.top - 28) {
|
||||
tooltipHTML.style.top = '1rem'
|
||||
tooltipHTML.style.height = `calc(${rect.top}px - 5px - 1.25rem)`
|
||||
@ -58,15 +64,15 @@
|
||||
dir = 'top'
|
||||
} else {
|
||||
tooltipHTML.style.top = `calc(${rect.bottom}px + 5px + .25rem)`
|
||||
if (tooltipHTML.clientHeight > doc.height - rect.bottom - 28) {
|
||||
if (tooltipHTML.clientHeight > docHeight - rect.bottom - 28) {
|
||||
tooltipHTML.style.bottom = '1rem'
|
||||
tooltipHTML.style.height = `calc(${doc.height - rect.bottom}px - 5px - 1.25rem)`
|
||||
tooltipHTML.style.height = `calc(${docHeight - rect.bottom}px - 5px - 1.25rem)`
|
||||
}
|
||||
dir = 'bottom'
|
||||
}
|
||||
|
||||
const tempLeft = rect.width / 2 + rect.left - clWidth / 2
|
||||
if (tempLeft + clWidth > doc.width - 8) tooltipHTML.style.right = '.5rem'
|
||||
if (tempLeft + clWidth > docWidth - 8) tooltipHTML.style.right = '.5rem'
|
||||
else if (tempLeft < 8) tooltipHTML.style.left = '.5rem'
|
||||
else tooltipHTML.style.left = `${tempLeft}px`
|
||||
|
||||
@ -79,8 +85,8 @@
|
||||
}
|
||||
} else {
|
||||
if (!$tooltip.direction) {
|
||||
if (rectAnchor.right < doc.width / 5) dir = 'right'
|
||||
else if (rectAnchor.left > doc.width - doc.width / 5) dir = 'left'
|
||||
if (rectAnchor.right < docWidth / 5) dir = 'right'
|
||||
else if (rectAnchor.left > docWidth - docWidth / 5) dir = 'left'
|
||||
else if (rectAnchor.top < tooltipHTML.clientHeight) dir = 'bottom'
|
||||
else dir = 'top'
|
||||
} else dir = $tooltip.direction
|
||||
@ -91,14 +97,14 @@
|
||||
tooltipHTML.style.transform = 'translateY(-50%)'
|
||||
} else if (dir === 'left') {
|
||||
tooltipHTML.style.top = rectAnchor.y + rectAnchor.height / 2 + 'px'
|
||||
tooltipHTML.style.right = `calc(${doc.width - rectAnchor.x}px + .75rem)`
|
||||
tooltipHTML.style.right = `calc(${docWidth - rectAnchor.x}px + .75rem)`
|
||||
tooltipHTML.style.transform = 'translateY(-50%)'
|
||||
} else if (dir === 'bottom') {
|
||||
tooltipHTML.style.top = `calc(${rectAnchor.bottom}px + .5rem)`
|
||||
tooltipHTML.style.left = rectAnchor.x + rectAnchor.width / 2 + 'px'
|
||||
tooltipHTML.style.transform = 'translateX(-50%)'
|
||||
} else if (dir === 'top') {
|
||||
tooltipHTML.style.bottom = `calc(${doc.height - rectAnchor.y}px + .75rem)`
|
||||
tooltipHTML.style.bottom = `calc(${docHeight - rectAnchor.y}px + .75rem)`
|
||||
tooltipHTML.style.left = rectAnchor.x + rectAnchor.width / 2 + 'px'
|
||||
tooltipHTML.style.transform = 'translateX(-50%)'
|
||||
}
|
||||
@ -116,6 +122,29 @@
|
||||
} else if (tooltipHTML) tooltipHTML.style.visibility = 'hidden'
|
||||
}
|
||||
|
||||
const fitSubmenu = (): void => {
|
||||
if (($tooltip.label || $tooltip.component) && tooltipHTML) {
|
||||
clearStyles()
|
||||
if ($tooltip.element) {
|
||||
rect = $tooltip.element.getBoundingClientRect()
|
||||
const rectP = tooltipHTML.getBoundingClientRect()
|
||||
const dirH =
|
||||
docWidth - rect.right - rectP.width - 16 > 0 ? 'right' : rect.left > docWidth - rect.right ? 'left' : 'right'
|
||||
const dirV =
|
||||
docHeight - rect.top - rectP.height - 16 > 0
|
||||
? 'bottom'
|
||||
: rect.bottom > docHeight - rect.top
|
||||
? 'top'
|
||||
: 'bottom'
|
||||
if (dirH === 'right') tooltipHTML.style.left = rect.right - 4 + 'px'
|
||||
else tooltipHTML.style.right = docWidth - rect.left - 4 + 'px'
|
||||
if (dirV === 'bottom') tooltipHTML.style.top = rect.top - 4 + 'px'
|
||||
else tooltipHTML.style.bottom = docHeight - rect.bottom - 4 + 'px'
|
||||
tooltipHTML.style.visibility = 'visible'
|
||||
}
|
||||
} else if (tooltipHTML) tooltipHTML.style.visibility = 'hidden'
|
||||
}
|
||||
|
||||
const hideTooltip = (): void => {
|
||||
if (tooltipHTML) tooltipHTML.style.visibility = 'hidden'
|
||||
closeTooltip()
|
||||
@ -124,29 +153,23 @@
|
||||
const whileShow = (ev: MouseEvent): void => {
|
||||
if ($tooltip.element && tooltipHTML) {
|
||||
const rectP = tooltipHTML.getBoundingClientRect()
|
||||
const dT: number = dir === 'bottom' ? 12 : 0
|
||||
const dB: number = dir === 'top' ? 12 : 0
|
||||
const dT: number = dir === 'bottom' && $tooltip.kind !== 'submenu' ? 12 : 0
|
||||
const dB: number = dir === 'top' && $tooltip.kind !== 'submenu' ? 12 : 0
|
||||
const inTrigger: boolean = ev.x >= rect.left && ev.x <= rect.right && ev.y >= rect.top && ev.y <= rect.bottom
|
||||
const inPopup: boolean =
|
||||
ev.x >= rectP.left && ev.x <= rectP.right && ev.y >= rectP.top - dT && ev.y <= rectP.bottom + dB
|
||||
|
||||
if (tooltipSW) {
|
||||
if (!inTrigger) {
|
||||
hideTooltip()
|
||||
}
|
||||
} else {
|
||||
if (!(inTrigger || inPopup)) {
|
||||
hideTooltip()
|
||||
}
|
||||
}
|
||||
if ((tooltipSW && !inTrigger) || !(inTrigger || inPopup)) hideTooltip()
|
||||
}
|
||||
}
|
||||
|
||||
afterUpdate(() => fitTooltip())
|
||||
afterUpdate(() => (kind === 'submenu' ? fitSubmenu() : fitTooltip()))
|
||||
onDestroy(() => hideTooltip())
|
||||
</script>
|
||||
|
||||
<svelte:window
|
||||
bind:innerWidth={docWidth}
|
||||
bind:innerHeight={docHeight}
|
||||
on:resize={hideTooltip}
|
||||
on:mousemove={(ev) => {
|
||||
whileShow(ev)
|
||||
@ -159,7 +182,7 @@
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{#if $tooltip.component}
|
||||
{#if $tooltip.component && $tooltip.kind !== 'submenu'}
|
||||
<div class="popup-tooltip" class:doublePadding={$tooltip.label} bind:clientWidth={clWidth} bind:this={tooltipHTML}>
|
||||
{#if $tooltip.label}<div class="fs-title mb-4">
|
||||
<Label label={$tooltip.label} params={$tooltip.props ?? {}} />
|
||||
@ -179,13 +202,36 @@
|
||||
{/if}
|
||||
</div>
|
||||
<div bind:this={nubHTML} class="nub {nubDirection ?? ''}" />
|
||||
{:else if $tooltip.label}
|
||||
{:else if $tooltip.label && $tooltip.kind !== 'submenu'}
|
||||
<div class="tooltip {dir ?? ''}" bind:this={tooltipHTML}>
|
||||
<Label label={$tooltip.label} params={$tooltip.props ?? {}} />
|
||||
</div>
|
||||
{:else if $tooltip.kind === 'submenu'}
|
||||
<div class="submenu-container {dir ?? ''}" bind:clientWidth={clWidth} bind:this={tooltipHTML}>
|
||||
{#if typeof $tooltip.component === 'string'}
|
||||
<Component
|
||||
is={$tooltip.component}
|
||||
props={$tooltip.props}
|
||||
on:update={onUpdate !== undefined ? onUpdate : async () => {}}
|
||||
/>
|
||||
{:else}
|
||||
<svelte:component
|
||||
this={$tooltip.component}
|
||||
{...$tooltip.props}
|
||||
on:update={onUpdate !== undefined ? onUpdate : async () => {}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.submenu-container {
|
||||
position: fixed;
|
||||
width: auto;
|
||||
height: auto;
|
||||
border-radius: 0.5rem;
|
||||
z-index: 10000;
|
||||
}
|
||||
.popup-tooltip {
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
|
@ -93,6 +93,7 @@ export { default as DropdownLabelsIntl } from './components/DropdownLabelsIntl.s
|
||||
export { default as DropdownRecord } from './components/DropdownRecord.svelte'
|
||||
export { default as ShowMore } from './components/ShowMore.svelte'
|
||||
export { default as Menu } from './components/Menu.svelte'
|
||||
export { default as Submenu } from './components/Submenu.svelte'
|
||||
export { default as TimeShiftPicker } from './components/TimeShiftPicker.svelte'
|
||||
export { default as ErrorPresenter } from './components/ErrorPresenter.svelte'
|
||||
export { default as Scroller } from './components/Scroller.svelte'
|
||||
|
@ -9,7 +9,8 @@ const emptyTooltip: LabelAndProps = {
|
||||
component: undefined,
|
||||
props: undefined,
|
||||
anchor: undefined,
|
||||
onUpdate: undefined
|
||||
onUpdate: undefined,
|
||||
kind: 'tooltip'
|
||||
}
|
||||
let storedValue: LabelAndProps = emptyTooltip
|
||||
export const tooltipstore = writable<LabelAndProps>(emptyTooltip)
|
||||
@ -23,10 +24,14 @@ export function tooltip (node: HTMLElement, options?: LabelAndProps): any {
|
||||
const show = (): void => {
|
||||
const shown = !!(storedValue.label !== undefined || storedValue.component !== undefined)
|
||||
if (!shown) {
|
||||
clearTimeout(toHandler)
|
||||
toHandler = setTimeout(() => {
|
||||
showTooltip(opt.label, node, opt.direction, opt.component, opt.props, opt.anchor, opt.onUpdate)
|
||||
}, 250)
|
||||
if (opt.kind !== 'submenu') {
|
||||
clearTimeout(toHandler)
|
||||
toHandler = setTimeout(() => {
|
||||
showTooltip(opt.label, node, opt.direction, opt.component, opt.props, opt.anchor, opt.onUpdate, opt.kind)
|
||||
}, 250)
|
||||
} else {
|
||||
showTooltip(opt.label, node, opt.direction, opt.component, opt.props, opt.anchor, opt.onUpdate, opt.kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
const hide = (): void => {
|
||||
@ -53,7 +58,8 @@ export function showTooltip (
|
||||
component?: AnySvelteComponent | AnyComponent,
|
||||
props?: any,
|
||||
anchor?: HTMLElement,
|
||||
onUpdate?: (result: any) => void
|
||||
onUpdate?: (result: any) => void,
|
||||
kind?: 'tooltip' | 'submenu'
|
||||
): void {
|
||||
storedValue = {
|
||||
label: label,
|
||||
@ -62,7 +68,8 @@ export function showTooltip (
|
||||
component: component,
|
||||
props: props,
|
||||
anchor: anchor,
|
||||
onUpdate: onUpdate
|
||||
onUpdate: onUpdate,
|
||||
kind: kind ?? 'tooltip'
|
||||
}
|
||||
tooltipstore.set(storedValue)
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ export interface Action {
|
||||
icon: Asset | AnySvelteComponent
|
||||
action: (props: any, ev: Event) => Promise<void>
|
||||
inline?: boolean
|
||||
link?: string
|
||||
}
|
||||
|
||||
export interface IPopupItem {
|
||||
@ -120,6 +121,7 @@ export interface LabelAndProps {
|
||||
props?: any
|
||||
anchor?: HTMLElement
|
||||
onUpdate?: (result: any) => void
|
||||
kind?: 'tooltip' | 'submenu'
|
||||
}
|
||||
|
||||
export interface ListItem {
|
||||
|
@ -15,9 +15,8 @@
|
||||
<script lang="ts">
|
||||
import { AttachedData, FindOptions, SortingOrder } from '@anticrm/core'
|
||||
import { Issue, IssueStatusCategory, Team, calcRank } from '@anticrm/tracker'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { createQuery, getClient, ObjectPopup } from '@anticrm/presentation'
|
||||
import { Icon } from '@anticrm/ui'
|
||||
import ObjectPopup from '@anticrm/presentation/src/components/ObjectPopup.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import tracker from '../plugin'
|
||||
import { getIssueId } from '../utils'
|
||||
|
@ -19,7 +19,6 @@
|
||||
import presentation, { createQuery, getClient, MessageViewer } from '@anticrm/presentation'
|
||||
import type { Issue, IssueStatus, Team } from '@anticrm/tracker'
|
||||
import {
|
||||
ActionIcon,
|
||||
Button,
|
||||
EditBox,
|
||||
IconDownOutline,
|
||||
@ -203,11 +202,11 @@
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="tools">
|
||||
{#if isEditing}
|
||||
<Button kind="transparent" label={presentation.string.Cancel} on:click={cancelEditing} />
|
||||
<Button kind={'transparent'} label={presentation.string.Cancel} on:click={cancelEditing} />
|
||||
<Button disabled={!canSave} label={presentation.string.Save} on:click={save} />
|
||||
{:else}
|
||||
<Button icon={IconEdit} kind="transparent" size="medium" on:click={edit} />
|
||||
<ActionIcon icon={IconMoreH} size={'medium'} action={showMenu} />
|
||||
<Button icon={IconEdit} kind={'transparent'} size={'medium'} on:click={edit} />
|
||||
<Button icon={IconMoreH} kind={'transparent'} size={'medium'} on:click={showMenu} />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
|
@ -26,8 +26,12 @@
|
||||
Label,
|
||||
navigate,
|
||||
setMetadataLocalStorage,
|
||||
showPopup
|
||||
showPopup,
|
||||
Submenu,
|
||||
locationToUrl
|
||||
} from '@anticrm/ui'
|
||||
import type { Action } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
|
||||
const client = getClient()
|
||||
async function getItems (): Promise<SettingsCategory[]> {
|
||||
@ -84,6 +88,27 @@
|
||||
if (profile === undefined) return
|
||||
selectCategory(profile)
|
||||
}
|
||||
|
||||
function getURLCategory (sp: SettingsCategory): string {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[1] = setting.ids.SettingApp
|
||||
loc.path[2] = sp.name
|
||||
loc.path.length = 3
|
||||
return locationToUrl(loc)
|
||||
}
|
||||
|
||||
const getSubmenu = (items: SettingsCategory[]): Action[] => {
|
||||
const actions: Action[] = filterItems(items).map((i) => {
|
||||
return {
|
||||
icon: i.icon,
|
||||
label: i.label,
|
||||
action: async () => selectCategory(i),
|
||||
link: getURLCategory(i),
|
||||
inline: true
|
||||
}
|
||||
})
|
||||
return actions
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="selectPopup autoHeight">
|
||||
@ -107,29 +132,27 @@
|
||||
</div>
|
||||
</div>
|
||||
{#if items}
|
||||
{#each filterItems(items) as item}
|
||||
<button class="menu-item" on:click={() => selectCategory(item)}>
|
||||
<div class="mr-2">
|
||||
<Icon icon={item.icon} size={'small'} />
|
||||
</div>
|
||||
<Label label={item.label} />
|
||||
</button>
|
||||
{/each}
|
||||
<Submenu
|
||||
icon={view.icon.Setting}
|
||||
label={setting.string.Settings}
|
||||
props={{ actions: getSubmenu(items) }}
|
||||
withHover
|
||||
/>
|
||||
{/if}
|
||||
<button class="menu-item" on:click={selectWorkspace}>
|
||||
<div class="mr-2">
|
||||
<div class="icon mr-3">
|
||||
<Icon icon={setting.icon.SelectWorkspace} size={'small'} />
|
||||
</div>
|
||||
<Label label={setting.string.SelectWorkspace} />
|
||||
</button>
|
||||
<button class="menu-item" on:click={inviteWorkspace}>
|
||||
<div class="mr-2">
|
||||
<div class="icon mr-3">
|
||||
<Icon icon={login.icon.InviteWorkspace} size={'small'} />
|
||||
</div>
|
||||
<Label label={setting.string.InviteWorkspace} />
|
||||
</button>
|
||||
<button class="menu-item" on:click={signOut}>
|
||||
<div class="mr-2">
|
||||
<div class="icon mr-3">
|
||||
<Icon icon={setting.icon.Signout} size={'small'} />
|
||||
</div>
|
||||
<Label label={setting.string.Signout} />
|
||||
|
@ -14,8 +14,9 @@ test.describe('contact tests', () => {
|
||||
await page.goto(`${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp`)
|
||||
// Click #profile-button
|
||||
await page.click('#profile-button')
|
||||
// Click text=Setting
|
||||
await page.click('text=Setting')
|
||||
await page.click('.antiPopup-submenu >> text=Settings')
|
||||
// Click button:has-text("Setting")
|
||||
await page.click('button:has-text("Setting")')
|
||||
await expect(page).toHaveURL(
|
||||
`${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp/setting%3Aids%3ASettingApp/setting`
|
||||
)
|
||||
@ -46,8 +47,9 @@ test.describe('contact tests', () => {
|
||||
await page.goto(`${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp`)
|
||||
// Click #profile-button
|
||||
await page.click('#profile-button')
|
||||
// Click text=Templates
|
||||
await page.click('text=Templates')
|
||||
await page.click('.antiPopup-submenu >> text=Settings')
|
||||
// Click button:has-text("Templates")
|
||||
await page.click('button:has-text("Templates")')
|
||||
await expect(page).toHaveURL(
|
||||
`${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp/setting%3Aids%3ASettingApp/message-templates`
|
||||
)
|
||||
@ -80,8 +82,9 @@ test.describe('contact tests', () => {
|
||||
await page.goto(`${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp`)
|
||||
// Click #profile-button
|
||||
await page.click('#profile-button')
|
||||
// Click text=Manage Statuses
|
||||
await page.click('text=Manage Statuses')
|
||||
await page.click('.antiPopup-submenu >> text=Settings')
|
||||
// Click button:has-text("Manage Statuses")
|
||||
await page.click('button:has-text("Manage Statuses")')
|
||||
await expect(page).toHaveURL(
|
||||
`${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp/setting%3Aids%3ASettingApp/statuses`
|
||||
)
|
||||
|
@ -53,4 +53,16 @@ test.describe('workbench tests', () => {
|
||||
// Click text=John Appleseed
|
||||
await expect(page.locator('text=John Appleseed')).toBeVisible()
|
||||
})
|
||||
test('submenu', async ({ page }) => {
|
||||
// await page.goto('http://localhost:8080/workbench%3Acomponent%3AWorkbenchApp');
|
||||
// Click #profile-button
|
||||
await page.click('#profile-button')
|
||||
// Click text=Settings
|
||||
await page.click('.antiPopup-submenu >> text=Settings')
|
||||
// Click button:has-text("Terms")
|
||||
await page.click('button:has-text("Terms")')
|
||||
await expect(page).toHaveURL(`${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp/setting%3Aids%3ASettingApp/terms`)
|
||||
// Click .ac-header
|
||||
await expect(page.locator('.ac-header >> text=Terms')).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user