mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 03:14:40 +03:00
Updated navigator layout (#5619)
Signed-off-by: Alexander Platov <alexander.platov@hardcoreeng.com>
This commit is contained in:
parent
07d35f0827
commit
a437926f35
@ -360,6 +360,7 @@ input.search {
|
||||
color: var(--theme-content-color);
|
||||
}
|
||||
}
|
||||
&.colorInherit .label { color: inherit; }
|
||||
}
|
||||
.flex-presenter {
|
||||
display: flex;
|
||||
|
@ -24,6 +24,7 @@
|
||||
--spacing-1_5: 0.75rem;
|
||||
--spacing-1_75: 0.875rem;
|
||||
--spacing-2: 1rem;
|
||||
--spacing-2_25: 1.125rem;
|
||||
--spacing-2_5: 1.25rem;
|
||||
--spacing-2_75: 1.375rem;
|
||||
--spacing-3: 1.5rem;
|
||||
|
@ -315,7 +315,7 @@
|
||||
}
|
||||
.antiNav-divider {
|
||||
flex-shrink: 0;
|
||||
margin: .5rem 0;
|
||||
margin: .75rem 0;
|
||||
height: 1px;
|
||||
|
||||
&.line { background-color: var(--theme-navpanel-divider); }
|
||||
|
@ -747,7 +747,7 @@
|
||||
&:is(.small, .large) .hulyAccordionItem-header__chevron > * {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.hulyAccordionItem-content {
|
||||
overflow: hidden;
|
||||
|
@ -39,12 +39,10 @@
|
||||
button.type-link { width: 100%; }
|
||||
}
|
||||
.hulyNavPanel-container .hulyNavItem-container,
|
||||
.hulyNavPanel-container .hulyTaskNavLink-container {
|
||||
.hulyNavPanel-container .hulyTaskNavLink-container,
|
||||
.parentSelector .hulyNavItem-container {
|
||||
margin: 0 0.75rem;
|
||||
}
|
||||
.hulyNavPanel-container .hulyNavItem-container + .hulyAccordionItem-container {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
.hulyNavPanel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -65,6 +63,156 @@
|
||||
}
|
||||
&.small { padding-bottom: .75rem; }
|
||||
}
|
||||
|
||||
.hulyNavGroup-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
|
||||
.hulyNavGroup-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
margin: 0;
|
||||
padding: var(--spacing-1) var(--spacing-2_25) var(--spacing-1) var(--spacing-2);
|
||||
min-height: var(--global-medium-Size);
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
&.disabled { cursor: default; }
|
||||
&__chevron,
|
||||
&__arrow,
|
||||
&__icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
&__chevron {
|
||||
margin: 0 0.125rem;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
color: var(--global-disabled-TextColor);
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--extra-small-BorderRadius);
|
||||
|
||||
& > * {
|
||||
transform-origin: center;
|
||||
transform: rotate(0deg);
|
||||
transition: transform 0.1s ease-in-out;
|
||||
}
|
||||
&.collapsed > * { transform: rotate(-90deg); }
|
||||
}
|
||||
&__icon {
|
||||
margin: 0 0.125rem;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
color: var(--global-primary-TextColor);
|
||||
background-color: var(--theme-navpanel-selected);
|
||||
border: 1px solid var(--global-subtle-ui-BorderColor);
|
||||
border-radius: var(--extra-small-BorderRadius);
|
||||
|
||||
&.folder {
|
||||
background-color: var(--theme-statusbar-color);
|
||||
border-color: var(--global-surface-01-BorderColor);
|
||||
}
|
||||
}
|
||||
&__label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-0_5);
|
||||
padding: var(--spacing-0_25) var(--spacing-0_5);
|
||||
min-width: 0;
|
||||
text-transform: uppercase;
|
||||
color: var(--global-tertiary-TextColor);
|
||||
border-radius: var(--extra-small-BorderRadius);
|
||||
}
|
||||
&__tools {
|
||||
display: none;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
gap: var(--spacing-0_5);
|
||||
margin-left: var(--spacing-1);
|
||||
min-width: 0;
|
||||
max-width: 50%;
|
||||
}
|
||||
&__arrow {
|
||||
margin-left: var(--spacing-0_5);
|
||||
width: var(--global-min-Size);
|
||||
height: var(--global-min-Size);
|
||||
color: var(--global-accent-IconColor);
|
||||
}
|
||||
|
||||
&.showMenu,
|
||||
&.highlighted,
|
||||
&.selected,
|
||||
&:hover {
|
||||
.hulyNavGroup-header__tools { display: flex; }
|
||||
}
|
||||
&.showMenu,
|
||||
&.highlighted,
|
||||
&.selected {
|
||||
.hulyNavGroup-header__label { background-color: var(--global-ui-BackgroundColor); }
|
||||
.hulyNavGroup-header__chevron { color: var(--global-tertiary-TextColor); }
|
||||
.hulyNavGroup-header__label { color: var(--global-secondary-TextColor); }
|
||||
}
|
||||
&.selected { background-color: var(--global-ui-highlight-BackgroundColor); }
|
||||
&.isOpen + .hulyNavGroup-content {
|
||||
max-height: 100%;
|
||||
|
||||
&:not(:has(.nested)) { margin-bottom: var(--spacing-1_5); }
|
||||
}
|
||||
}
|
||||
&.nested .hulyNavGroup-header {
|
||||
.hulyNavGroup-header__icon { margin: 0 var(--spacing-0_25) 0 0; }
|
||||
.hulyNavGroup-header__label { padding: 0 var(--spacing-0_75) 0 0; }
|
||||
&.isOpen .hulyNavGroup-header__label { background-color: var(--global-ui-BackgroundColor); }
|
||||
}
|
||||
.hulyNavGroup-header:hover,
|
||||
&.nested .hulyNavGroup-header:hover {
|
||||
.hulyNavGroup-header__chevron { background-color: var(--global-ui-BackgroundColor); }
|
||||
.hulyNavGroup-header__label { background-color: var(--global-ui-hover-BackgroundColor); }
|
||||
.hulyNavGroup-header__chevron { color: var(--button-subtle-IconColor); }
|
||||
.hulyNavGroup-header__label { color: var(--global-primary-TextColor); }
|
||||
}
|
||||
&.nested.selectable .hulyNavGroup-header {
|
||||
margin: 0 var(--spacing-1_5) var(--spacing-1_5);
|
||||
padding: var(--spacing-0_5) var(--spacing-0_75) var(--spacing-0_5) var(--spacing-0_5);
|
||||
min-height: var(--global-small-Size);
|
||||
border-radius: var(--small-BorderRadius);
|
||||
|
||||
&.selected { padding-right: var(--spacing-0_75); }
|
||||
&.isOpen { margin-bottom: 0; }
|
||||
}
|
||||
|
||||
&:not(.nested, .noDivider),
|
||||
&.noDivider + &.noDivider { border-top: 1px solid var(--theme-navpanel-divider); }
|
||||
|
||||
.hulyNavGroup-content {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
min-width: 0;
|
||||
max-height: 0;
|
||||
}
|
||||
}
|
||||
.hulyNavItem-container + .hulyNavGroup-container,
|
||||
.hulyNavPanel-container a.noUnderline + .hulyNavGroup-container { margin-top: .75rem; }
|
||||
|
||||
.hulyNavItem-container .hulyNavItem-chevron:enabled {
|
||||
& > * {
|
||||
transform-origin: center;
|
||||
transform: rotate(-90deg);
|
||||
transition: transform 0.1s ease-in-out;
|
||||
}
|
||||
&.isOpen > * { transform: rotate(0deg); }
|
||||
}
|
||||
|
||||
.hulySidePanel-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -49,6 +49,7 @@
|
||||
} else if (type === 'type-button' && !hasMenu) {
|
||||
actualIconSize = 'medium'
|
||||
}
|
||||
$: iconOnly = title === undefined && label === undefined && $$slots.default === undefined && icon !== undefined
|
||||
|
||||
export function focus () {
|
||||
element?.focus()
|
||||
@ -94,6 +95,7 @@
|
||||
class:inheritColor
|
||||
class:inheritFont
|
||||
class:menu={hasMenu}
|
||||
class:iconOnly
|
||||
disabled={loading || disabled}
|
||||
use:tp={tooltip}
|
||||
on:click|stopPropagation
|
||||
@ -153,9 +155,10 @@
|
||||
height: var(--global-large-Size);
|
||||
border-radius: var(--medium-BorderRadius);
|
||||
|
||||
&.type-button {
|
||||
&.type-button:not(.iconOnly) {
|
||||
padding: 0 var(--spacing-2);
|
||||
}
|
||||
&.iconOnly,
|
||||
&.type-button-icon {
|
||||
width: var(--global-large-Size);
|
||||
}
|
||||
@ -164,9 +167,10 @@
|
||||
height: var(--global-medium-Size);
|
||||
border-radius: var(--medium-BorderRadius);
|
||||
|
||||
&.type-button {
|
||||
&.type-button:not(.iconOnly) {
|
||||
padding: 0 var(--spacing-2);
|
||||
}
|
||||
&.iconOnly,
|
||||
&.type-button-icon {
|
||||
width: var(--global-medium-Size);
|
||||
}
|
||||
@ -176,9 +180,10 @@
|
||||
gap: var(--spacing-0_5);
|
||||
border-radius: var(--small-BorderRadius);
|
||||
|
||||
&.type-button {
|
||||
&.type-button:not(.iconOnly) {
|
||||
padding: 0 var(--spacing-1);
|
||||
}
|
||||
&.iconOnly,
|
||||
&.type-button-icon {
|
||||
width: var(--global-small-Size);
|
||||
}
|
||||
@ -187,9 +192,10 @@
|
||||
height: var(--global-extra-small-Size);
|
||||
border-radius: var(--extra-small-BorderRadius);
|
||||
|
||||
&.type-button {
|
||||
&.type-button:not(.iconOnly) {
|
||||
padding: 0 var(--spacing-1);
|
||||
}
|
||||
&.iconOnly,
|
||||
&.type-button-icon {
|
||||
width: var(--global-extra-small-Size);
|
||||
}
|
||||
|
@ -30,6 +30,7 @@
|
||||
export let inheritColor: boolean = false
|
||||
export let tooltip: LabelAndProps | undefined = undefined
|
||||
export let focusIndex = -1
|
||||
export let id: string | undefined = undefined
|
||||
|
||||
let element: ButtonBase | undefined
|
||||
|
||||
@ -53,6 +54,7 @@
|
||||
{hasMenu}
|
||||
{tooltip}
|
||||
{focusIndex}
|
||||
{id}
|
||||
on:click
|
||||
on:keydown
|
||||
/>
|
||||
|
@ -16,7 +16,7 @@
|
||||
import type { Asset, IntlString } from '@hcengineering/platform'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { ComponentType, createEventDispatcher } from 'svelte'
|
||||
import { closePopup, showPopup } from '..'
|
||||
import { closePopup, showPopup, type LabelAndProps, closeTooltip } from '..'
|
||||
import { AnySvelteComponent, DropdownIntlItem } from '../types'
|
||||
import ButtonBase from './ButtonBase.svelte'
|
||||
import ModernPopup from './ModernPopup.svelte'
|
||||
@ -32,6 +32,8 @@
|
||||
export let loading: boolean = false
|
||||
export let inheritColor: boolean = false
|
||||
export let noSelection: boolean = false
|
||||
export let autoSelectionIfOne: boolean = false
|
||||
export let tooltip: LabelAndProps | undefined = undefined
|
||||
|
||||
export let items: DropdownIntlItem[]
|
||||
export let params: Record<string, any> = {}
|
||||
@ -46,7 +48,13 @@
|
||||
|
||||
function openPopup () {
|
||||
if (!opened) {
|
||||
if (autoSelectionIfOne && items.length === 1) {
|
||||
selected = items[0].id
|
||||
dispatch('selected', selected)
|
||||
return
|
||||
}
|
||||
opened = true
|
||||
closeTooltip()
|
||||
showPopup(ModernPopup, { items, selected: noSelection ? undefined : selected, params }, element, (result) => {
|
||||
if (result) {
|
||||
selected = result
|
||||
@ -86,5 +94,6 @@
|
||||
pressed={opened}
|
||||
{focusIndex}
|
||||
{id}
|
||||
{tooltip}
|
||||
on:click={openPopup}
|
||||
/>
|
||||
|
@ -15,10 +15,10 @@
|
||||
<script lang="ts">
|
||||
export let isOpen: boolean
|
||||
export let empty: boolean = false
|
||||
export let level: number = 1
|
||||
export let level: number = 0
|
||||
</script>
|
||||
|
||||
<div class="hulyFold-container" class:opened={isOpen && !empty} style:margin-left={`${(level - 1) * 1.5}rem`}>
|
||||
<div class="hulyFold-container" class:opened={isOpen && !empty} style:margin-left={`${level * 1.5}rem`}>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
{#if empty}
|
||||
<path
|
||||
|
@ -23,6 +23,7 @@
|
||||
export let inheritFont: boolean = false
|
||||
export let focusIndex = -1
|
||||
export let tooltip: LabelAndProps | undefined = undefined
|
||||
export let id: string | undefined = undefined
|
||||
</script>
|
||||
|
||||
<ButtonBase
|
||||
@ -41,6 +42,7 @@
|
||||
{focusIndex}
|
||||
{tooltip}
|
||||
{autoFocus}
|
||||
{id}
|
||||
on:click
|
||||
>
|
||||
<slot />
|
||||
|
@ -13,58 +13,119 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Doc, Ref } from '@hcengineering/core'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import type { AnyComponent } from '..'
|
||||
import { showPopup, Menu, Action, Label, Component, IconOpenedArrow } from '..'
|
||||
import type { Asset, IntlString } from '@hcengineering/platform'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import type { AnyComponent, IconSize, AnySvelteComponent } from '..'
|
||||
import {
|
||||
showPopup,
|
||||
Menu,
|
||||
Action,
|
||||
Label,
|
||||
Component,
|
||||
IconDown,
|
||||
IconDownOutline,
|
||||
IconOpenedArrow,
|
||||
IconFolderCollapsed,
|
||||
IconFolderExpanded,
|
||||
Icon,
|
||||
getTreeCollapsed,
|
||||
setTreeCollapsed,
|
||||
tooltip
|
||||
} from '..'
|
||||
|
||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||
export let folderIcon: boolean = false
|
||||
export let iconProps: any | undefined = undefined
|
||||
export let iconSize: IconSize = 'small'
|
||||
export let label: IntlString | undefined = undefined
|
||||
export let title: string | undefined = undefined
|
||||
export let categoryName: string
|
||||
export let tools: AnyComponent | undefined = undefined
|
||||
export let isOpen: boolean = true
|
||||
export let isFold: boolean = false
|
||||
export let empty: boolean = false
|
||||
export let collapsedPrefix: string = ''
|
||||
export let visible: boolean = false
|
||||
export let highlighted: boolean = false
|
||||
export let selected: boolean = false
|
||||
export let second: boolean = false
|
||||
export let type: 'default' | 'nested' | 'nested-selectable' = 'default'
|
||||
export let noDivider: boolean = false
|
||||
export let showMenu: boolean = false
|
||||
export let shouldTooltip: boolean = false
|
||||
export let forciblyСollapsed: boolean = false
|
||||
export let actions: Action[] = []
|
||||
export let _id: Ref<Doc> | string | undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: id = `navGroup-${categoryName}`
|
||||
let pressed: boolean = false
|
||||
|
||||
const toggle = (): void => {
|
||||
isOpen = !isOpen
|
||||
dispatch('toggle', isOpen)
|
||||
if ((!selected || empty) && type === 'nested-selectable') dispatch('click')
|
||||
else if (!empty) {
|
||||
isOpen = !isOpen
|
||||
dispatch('toggle', isOpen)
|
||||
}
|
||||
}
|
||||
|
||||
function handleMenuClicked (ev: MouseEvent): void {
|
||||
if (actions.length === 0) return
|
||||
showPopup(Menu, { actions }, ev.target as HTMLElement)
|
||||
ev.stopPropagation()
|
||||
pressed = true
|
||||
showPopup(Menu, { actions }, ev.target as HTMLElement, () => {
|
||||
pressed = false
|
||||
})
|
||||
}
|
||||
$: isOpen = !getTreeCollapsed(_id, collapsedPrefix)
|
||||
$: setTreeCollapsed(_id, !isOpen, collapsedPrefix)
|
||||
$: visibleIcon = folderIcon ? (isOpen && !empty ? IconFolderExpanded : IconFolderCollapsed) : icon
|
||||
</script>
|
||||
|
||||
<div class="hulyAccordionItem-container" class:second>
|
||||
<button class="hulyAccordionItem-header nav small" class:isOpen class:selected on:click={toggle}>
|
||||
{#if isFold}
|
||||
<button class="hulyAccordionItem-header__chevron" class:collapsed={!isOpen}>
|
||||
<IconOpenedArrow size={'small'} />
|
||||
<div
|
||||
class="hulyNavGroup-container"
|
||||
class:nested={type === 'nested' || type === 'nested-selectable'}
|
||||
class:selectable={type === 'nested-selectable'}
|
||||
class:noDivider
|
||||
>
|
||||
<button
|
||||
class="hulyNavGroup-header"
|
||||
class:isOpen={(isOpen || visible) && !empty}
|
||||
class:highlighted
|
||||
class:selected
|
||||
class:showMenu={showMenu || pressed}
|
||||
on:click={toggle}
|
||||
>
|
||||
{#if isFold && !empty}
|
||||
<button class="hulyNavGroup-header__chevron" class:collapsed={!isOpen}>
|
||||
<IconDown size={'small'} />
|
||||
</button>
|
||||
{/if}
|
||||
<div class="hulyAccordionItem-header__label-wrapper font-medium-12">
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="hulyNavGroup-header__label font-medium-12" on:click={handleMenuClicked}>
|
||||
{#if visibleIcon}
|
||||
<div class="hulyNavGroup-header__icon" class:folder={folderIcon}>
|
||||
<Icon icon={visibleIcon} size={iconSize} {iconProps} />
|
||||
</div>
|
||||
{/if}
|
||||
<span
|
||||
class="hulyAccordionItem-header__label"
|
||||
class:cursor-default={actions.length === 0}
|
||||
on:click|stopPropagation={handleMenuClicked}
|
||||
use:tooltip={shouldTooltip ? { label: label ?? getEmbeddedLabel(title ?? ''), direction: 'top' } : undefined}
|
||||
class="overflow-label"
|
||||
>
|
||||
{#if label}<Label {label} />{/if}
|
||||
{#if title}{title}{/if}
|
||||
</span>
|
||||
{#if actions.length > 0}
|
||||
<IconDownOutline size={'tiny'} />
|
||||
{/if}
|
||||
</div>
|
||||
{#if isFold}<div class="flex-grow" />{/if}
|
||||
<div class="flex-grow" />
|
||||
{#if $$slots.extra}<slot name="extra" />{/if}
|
||||
{#if tools || $$slots.tools}
|
||||
<div class="hulyAccordionItem-header__tools">
|
||||
<div class="hulyNavGroup-header__tools">
|
||||
{#if tools}
|
||||
<Component
|
||||
is={tools}
|
||||
@ -77,8 +138,17 @@
|
||||
<slot name="tools" />
|
||||
</div>
|
||||
{/if}
|
||||
{#if selected && type === 'nested-selectable'}
|
||||
<div class="hulyNavGroup-header__arrow"><IconOpenedArrow size={'small'} /></div>
|
||||
{/if}
|
||||
</button>
|
||||
<div {id} class="hulyAccordionItem-content">
|
||||
<slot />
|
||||
</div>
|
||||
{#if !empty}
|
||||
<div {id} class="hulyNavGroup-content">
|
||||
{#if (!isOpen && visible) || forciblyСollapsed}
|
||||
<slot name="visible" {isOpen} />
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -14,9 +14,23 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Asset, IntlString } from '@hcengineering/platform'
|
||||
import { Icon, Label, IconOpenedArrow, Fold, AnySvelteComponent, IconSize } from '..'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import {
|
||||
Icon,
|
||||
Label,
|
||||
IconOpenedArrow,
|
||||
IconDown,
|
||||
AnySvelteComponent,
|
||||
IconSize,
|
||||
getTreeCollapsed,
|
||||
setTreeCollapsed,
|
||||
tooltip,
|
||||
IconFolderExpanded,
|
||||
IconFolderCollapsed
|
||||
} from '..'
|
||||
|
||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||
export let folderIcon: boolean = false
|
||||
export let iconProps: any | undefined = undefined
|
||||
export let iconSize: IconSize = 'small'
|
||||
export let label: IntlString | undefined = undefined
|
||||
@ -26,50 +40,101 @@
|
||||
export let color: string | null = null
|
||||
export let count: number | null = null
|
||||
export let selected: boolean = false
|
||||
export let indent: boolean = false
|
||||
export let bold: boolean = false
|
||||
export let disabled: boolean = false
|
||||
export let isFold: boolean = false
|
||||
export let isOpen: boolean = false
|
||||
export let isSecondary: boolean = false
|
||||
export let withBackground: boolean = false
|
||||
export let showMenu: boolean = false
|
||||
export let shouldTooltip: boolean = false
|
||||
export let empty: boolean = false
|
||||
export let level: number = 1
|
||||
export let collapsedPrefix: string = ''
|
||||
export let visible: boolean = false
|
||||
export let forciblyСollapsed: boolean = false
|
||||
export let level: number = 0
|
||||
export let _id: any = undefined
|
||||
|
||||
let labelWidth: number
|
||||
let levelReset: boolean = false
|
||||
let hovered: boolean = false
|
||||
|
||||
$: showArrow = selected && (type === 'type-link' || type === 'type-object')
|
||||
$: if (!showMenu && levelReset && !hovered) levelReset = false
|
||||
$: isOpen = !getTreeCollapsed(_id, collapsedPrefix)
|
||||
$: setTreeCollapsed(_id, !isOpen, collapsedPrefix)
|
||||
$: visibleIcon = folderIcon ? (isOpen && !empty ? IconFolderExpanded : IconFolderCollapsed) : icon
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
<button
|
||||
class="hulyNavItem-container {type} {type === 'type-anchor-link' || isSecondary
|
||||
? 'font-regular-12'
|
||||
: 'font-regular-14'}"
|
||||
class:fold={isFold}
|
||||
class:selected
|
||||
class:bold
|
||||
class:indent
|
||||
class:disabled
|
||||
class:showMenu
|
||||
class:noActions={$$slots.actions === undefined}
|
||||
on:mouseover={() => {
|
||||
if (!levelReset && labelWidth < 16 && level > 0) levelReset = true
|
||||
if (!hovered) hovered = true
|
||||
}}
|
||||
on:mouseleave={() => {
|
||||
if (levelReset && !showMenu) levelReset = false
|
||||
hovered = false
|
||||
}}
|
||||
on:click={() => {
|
||||
if (selected && isFold) isOpen = !isOpen
|
||||
}}
|
||||
on:click
|
||||
on:contextmenu
|
||||
>
|
||||
{#if isFold}
|
||||
<Fold {isOpen} {empty} {level} />
|
||||
<button
|
||||
class="hulyNavItem-chevron"
|
||||
class:isOpen
|
||||
style:margin-left={`${(levelReset ? 0 : level) * 1.25}rem`}
|
||||
disabled={empty}
|
||||
on:click|stopPropagation={() => {
|
||||
if (!empty) isOpen = !isOpen
|
||||
}}
|
||||
>
|
||||
{#if !empty}<IconDown size={'x-small'} />{/if}
|
||||
</button>
|
||||
{/if}
|
||||
{#if icon || (type === 'type-tag' && color)}
|
||||
{#if visibleIcon || (type === 'type-tag' && color)}
|
||||
<div class="hulyNavItem-icon" class:withBackground class:w-auto={iconSize === 'x-small'}>
|
||||
{#if type !== 'type-tag' && icon}
|
||||
<Icon {icon} size={iconSize} {iconProps} />
|
||||
{#if type !== 'type-tag' && visibleIcon}
|
||||
<Icon icon={visibleIcon} size={iconSize} {iconProps} />
|
||||
{:else if type === 'type-tag'}
|
||||
<div style:background-color={color} class="hulyNavItem-icon__tag" />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<span
|
||||
class="hulyNavItem-label"
|
||||
class:font-medium-12={description}
|
||||
class:flex-grow={!(type === 'type-anchor-link' || description)}
|
||||
bind:clientWidth={labelWidth}
|
||||
use:tooltip={shouldTooltip ? { label: label ?? getEmbeddedLabel(title ?? ''), direction: 'top' } : undefined}
|
||||
class="{description ? 'hulyNavItem-wideLabel' : 'hulyNavItem-label'} overflow-label"
|
||||
class:flex-grow={!(type === 'type-anchor-link')}
|
||||
style:color={type === 'type-tag' && selected ? color : null}
|
||||
>
|
||||
{#if label}<Label {label} />{/if}
|
||||
{#if title}{title}{/if}
|
||||
<slot />
|
||||
{#if description}
|
||||
<span class="hulyNavItem-label font-medium-12 mr-0-5">
|
||||
{#if label}<Label {label} />{/if}
|
||||
{#if title}{title}{/if}
|
||||
<slot />
|
||||
</span>
|
||||
{description}
|
||||
{:else}
|
||||
{#if label}<Label {label} />{/if}
|
||||
{#if title}{title}{/if}
|
||||
<slot />
|
||||
{/if}
|
||||
</span>
|
||||
{#if description}
|
||||
<span class="hulyNavItem-label description flex-grow">{description}</span>
|
||||
{/if}
|
||||
{#if $$slots.extra}<slot name="extra" />{/if}
|
||||
{#if showMenu || $$slots.actions}
|
||||
<div class="hulyNavItem-actions">
|
||||
<slot name="actions" />
|
||||
@ -81,13 +146,23 @@
|
||||
</span>
|
||||
{/if}
|
||||
<slot name="notify" />
|
||||
{#if selected && (type === 'type-link' || type === 'type-object')}
|
||||
{#if showArrow}
|
||||
<div class="hulyNavItem-icon right"><IconOpenedArrow size={'small'} /></div>
|
||||
{/if}
|
||||
</button>
|
||||
{#if (isFold && (isOpen || (!isOpen && visible)) && !empty) || forciblyСollapsed}
|
||||
<div class="hulyNavItem-dropbox">
|
||||
{#if (!isOpen && visible) || forciblyСollapsed}
|
||||
<slot name="visible" {isOpen} />
|
||||
{:else}
|
||||
<slot name="dropbox" />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.hulyNavItem-container {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: stretch;
|
||||
align-items: center;
|
||||
@ -98,11 +173,29 @@
|
||||
border-radius: var(--small-BorderRadius);
|
||||
outline: none;
|
||||
|
||||
.hulyNavItem-chevron,
|
||||
.hulyNavItem-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.hulyNavItem-chevron {
|
||||
margin: 0;
|
||||
margin-right: var(--spacing-0_75);
|
||||
padding: 0;
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
color: var(--global-tertiary-TextColor);
|
||||
border: none;
|
||||
border-radius: var(--min-BorderRadius);
|
||||
outline: none;
|
||||
|
||||
&:disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
.hulyNavItem-icon {
|
||||
width: var(--global-min-Size);
|
||||
height: var(--global-min-Size);
|
||||
color: var(--global-primary-TextColor);
|
||||
@ -129,22 +222,13 @@
|
||||
border-radius: var(--extra-small-BorderRadius);
|
||||
}
|
||||
}
|
||||
.hulyNavItem-label {
|
||||
white-space: nowrap;
|
||||
word-break: break-all;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
.hulyNavItem-label,
|
||||
.hulyNavItem-wideLabel {
|
||||
text-align: left;
|
||||
min-width: 0;
|
||||
color: var(--global-primary-TextColor);
|
||||
|
||||
&.description {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
.hulyNavItem-label + .hulyNavItem-label {
|
||||
margin-left: var(--spacing-0_5);
|
||||
.hulyNavItem-wideLabel {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.hulyNavItem-actions {
|
||||
display: none;
|
||||
@ -169,23 +253,25 @@
|
||||
cursor: auto;
|
||||
background-color: var(--global-ui-highlight-BackgroundColor);
|
||||
|
||||
&:not(.type-anchor-link) .hulyNavItem-label:not(.description) {
|
||||
font-weight: 700;
|
||||
}
|
||||
// &:not(.type-anchor-link) .hulyNavItem-label:not(.description) {
|
||||
// font-weight: 700;
|
||||
// }
|
||||
.hulyNavItem-count {
|
||||
color: var(--global-secondary-TextColor);
|
||||
}
|
||||
}
|
||||
// &.bold:not(.type-anchor-link) .hulyNavItem-label:not(.description) {
|
||||
// font-weight: 700;
|
||||
// }
|
||||
|
||||
&.type-link {
|
||||
padding: 0 var(--spacing-1_25);
|
||||
padding: 0 var(--spacing-0_5) 0 var(--spacing-1_25);
|
||||
|
||||
&.selected {
|
||||
&:not(.fold) {
|
||||
padding: 0 var(--spacing-0_75) 0 var(--spacing-1_25);
|
||||
}
|
||||
&.fold {
|
||||
padding: 0 var(--spacing-0_75) 0 var(--spacing-0_5);
|
||||
padding: 0 var(--spacing-0_75) 0 var(--spacing-1_25);
|
||||
|
||||
&.indent {
|
||||
padding-left: var(--spacing-4);
|
||||
}
|
||||
.hulyNavItem-icon {
|
||||
color: var(--global-accent-TextColor);
|
||||
@ -200,6 +286,11 @@
|
||||
}
|
||||
&.type-tag {
|
||||
padding: 0 var(--spacing-1_25);
|
||||
|
||||
.hulyNavItem-icon {
|
||||
width: 0.75rem;
|
||||
margin-right: 0.625rem;
|
||||
}
|
||||
}
|
||||
&.type-object {
|
||||
padding: 0 var(--spacing-0_5) 0 var(--spacing-0_5);
|
||||
@ -244,17 +335,38 @@
|
||||
color: var(--global-primary-TextColor);
|
||||
}
|
||||
}
|
||||
&.fold {
|
||||
padding-left: var(--spacing-0_5);
|
||||
&.indent {
|
||||
padding-left: var(--spacing-4);
|
||||
}
|
||||
&:hover .hulyNavItem-chevron:enabled {
|
||||
color: var(--global-secondary-TextColor);
|
||||
background-color: var(--button-tertiary-hover-BackgroundColor);
|
||||
}
|
||||
|
||||
:global(.hulyFold-container) {
|
||||
margin-right: var(--spacing-0_75);
|
||||
&:not(.noActions):hover,
|
||||
&:not(.noActions).showMenu {
|
||||
.hulyNavItem-actions {
|
||||
display: flex;
|
||||
}
|
||||
.hulyNavItem-icon.right {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover .hulyNavItem-actions,
|
||||
&.showMenu .hulyNavItem-actions {
|
||||
display: flex;
|
||||
.hulyNavItem-icon {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.hulyNavItem-label {
|
||||
color: rgb(var(--theme-caption-color) / 40%);
|
||||
}
|
||||
}
|
||||
}
|
||||
.hulyNavItem-dropbox {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
export let size: 'x-small' | 'small' | 'medium' | 'large'
|
||||
const fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
export let size: 'x-small' | 'small' | 'medium' | 'large' | 'full'
|
||||
export let size: 'tiny' | 'x-small' | 'small' | 'medium' | 'large' | 'full'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
|
34
packages/ui/src/components/icons/FolderCollapsed.svelte
Normal file
34
packages/ui/src/components/icons/FolderCollapsed.svelte
Normal file
@ -0,0 +1,34 @@
|
||||
<!--
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
export let fill: string = 'currentColor'
|
||||
export let size: 'small' | 'medium' | 'large' = 'small'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" {fill}>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M1 4.5C1 3.11929 2.11929 2 3.5 2H5.33579C5.73361 2 6.11514 2.15804 6.39645 2.43934L7.81066 3.85355C7.90443 3.94732 8.03161 4 8.16421 4H12.5C13.8807 4 15 5.11929 15 6.5V11.5C15 12.8807 13.8807 14 12.5 14H3.5C2.11929 14 1 12.8807 1 11.5V4.5ZM3.5 3C2.67157 3 2 3.67157 2 4.5V11.5C2 12.3284 2.67157 13 3.5 13H12.5C13.3284 13 14 12.3284 14 11.5V6.5C14 5.67157 13.3284 5 12.5 5H8.16421C7.76639 5 7.38486 4.84197 7.10355 4.56066L5.68934 3.14645C5.59557 3.05268 5.46839 3 5.33579 3H3.5Z"
|
||||
fill-opacity="0.6"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M3.5 3C2.67157 3 2 3.67157 2 4.5V11.5C2 12.3284 2.67157 13 3.5 13H12.5C13.3284 13 14 12.3284 14 11.5V6.5C14 5.67157 13.3284 5 12.5 5H8.16421C7.76639 5 7.38486 4.84197 7.10355 4.56066L5.68934 3.14645C5.59557 3.05268 5.46839 3 5.33579 3H3.5Z"
|
||||
fill-opacity="0.1"
|
||||
/>
|
||||
</svg>
|
34
packages/ui/src/components/icons/FolderExpanded.svelte
Normal file
34
packages/ui/src/components/icons/FolderExpanded.svelte
Normal file
@ -0,0 +1,34 @@
|
||||
<!--
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
export let fill: string = 'currentColor'
|
||||
export let size: 'small' | 'medium' | 'large' = 'small'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" xmlns="http://www.w3.org/2000/svg" width="17" height="16" viewBox="0 0 17 16" {fill}>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M14 7V6.5C14 5.67157 13.3284 5 12.5 5H8.16421C7.76639 5 7.38486 4.84197 7.10355 4.56066L5.68934 3.14645C5.59557 3.05268 5.46839 3 5.33579 3H3.5C2.67157 3 2 3.67157 2 4.5V10.5L2.58555 8.45056C2.83087 7.59196 3.61564 7 4.5086 7H14Z"
|
||||
fill-opacity="0.1"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M3.5 2C2.11929 2 1 3.11929 1 4.5V11.5C1 12.8807 2.11929 14 3.5 14H13.4914C14.3844 14 15.1691 13.408 15.4144 12.5494L16.2716 9.54944C16.5735 8.49282 15.9696 7.44018 15 7.10762V6.5C15 5.11929 13.8807 4 12.5 4H8.16421C8.0316 4 7.90443 3.94732 7.81066 3.85355L6.39645 2.43934C6.11514 2.15804 5.73361 2 5.33579 2H3.5ZM14 7V6.5C14 5.67157 13.3284 5 12.5 5H8.16421C7.76639 5 7.38486 4.84197 7.10355 4.56066L5.68934 3.14645C5.59557 3.05268 5.46839 3 5.33579 3H3.5C2.67157 3 2 3.67157 2 4.5V10.5L2.58555 8.45056C2.83087 7.59196 3.61564 7 4.5086 7H14Z"
|
||||
fill-opacity="0.6"
|
||||
/>
|
||||
</svg>
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
export let size: 'x-small' | 'small' | 'medium' | 'large'
|
||||
const fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
|
@ -226,6 +226,8 @@ export { default as IconDropdownRight } from './components/icons/DropdownRight.s
|
||||
export { default as IconKeyCommand } from './components/icons/KeyCommand.svelte'
|
||||
export { default as IconKeyOption } from './components/icons/KeyOption.svelte'
|
||||
export { default as IconKeyShift } from './components/icons/KeyShift.svelte'
|
||||
export { default as IconFolderCollapsed } from './components/icons/FolderCollapsed.svelte'
|
||||
export { default as IconFolderExpanded } from './components/icons/FolderExpanded.svelte'
|
||||
export { default as IconCheckmark } from './components/icons/Checkmark.svelte'
|
||||
|
||||
export { default as PanelInstance } from './components/PanelInstance.svelte'
|
||||
|
@ -197,16 +197,17 @@ export function navigate (location: PlatformLocation, replace = false): boolean
|
||||
}
|
||||
|
||||
const COLLAPSED = 'COLLAPSED'
|
||||
export const getCollapsedKey = (_id: string): string => `${getCurrentLocation().path[1]}_${_id}_collapsed`
|
||||
export const getCollapsedKey = (_id: string, prefix?: string): string =>
|
||||
`${getCurrentLocation().path[1]}_${prefix !== undefined && prefix !== '' ? prefix + '_###_' + _id : _id}_collapsed`
|
||||
|
||||
export const getTreeCollapsed = (_id: any): boolean => {
|
||||
export const getTreeCollapsed = (_id: any, prefix?: string): boolean => {
|
||||
if (_id === undefined || _id === 'undefined') return false
|
||||
return localStorage.getItem(getCollapsedKey(_id as string)) === COLLAPSED
|
||||
return localStorage.getItem(getCollapsedKey(_id as string, prefix)) === COLLAPSED
|
||||
}
|
||||
|
||||
export const setTreeCollapsed = (_id: any, collapsed: boolean): void => {
|
||||
export const setTreeCollapsed = (_id: any, collapsed: boolean, prefix?: string): void => {
|
||||
if (_id === undefined || _id === 'undefined') return
|
||||
const key = getCollapsedKey(_id)
|
||||
const key = getCollapsedKey(_id, prefix)
|
||||
collapsed ? localStorage.setItem(key, COLLAPSED) : localStorage.removeItem(key)
|
||||
}
|
||||
|
||||
|
@ -107,6 +107,11 @@
|
||||
}
|
||||
|
||||
function handleChannelSelected (event: CustomEvent): void {
|
||||
if (event.detail === null) {
|
||||
selectedData = undefined
|
||||
return
|
||||
}
|
||||
|
||||
const detail = (event.detail ?? {}) as SelectChannelEvent
|
||||
|
||||
selectedData = { _id: detail.object._id, _class: detail.object._class }
|
||||
|
@ -115,7 +115,7 @@
|
||||
</script>
|
||||
|
||||
<NavItem
|
||||
id={item.id}
|
||||
_id={item.id}
|
||||
icon={item.icon}
|
||||
withIconBackground={item.withIconBackground}
|
||||
isSecondary={item.isSecondary}
|
||||
|
@ -27,7 +27,6 @@
|
||||
import chunter from '../../../plugin'
|
||||
import { ChatNavItemModel, SortFnOptions } from '../types'
|
||||
import { getObjectIcon, getChannelName } from '../../../utils'
|
||||
import { navigatorStateStore, toggleSections } from '../utils'
|
||||
|
||||
export let id: string
|
||||
export let header: IntlString
|
||||
@ -45,12 +44,9 @@
|
||||
let items: ChatNavItemModel[] = []
|
||||
let visibleItems: ChatNavItemModel[] = []
|
||||
|
||||
let isCollapsed = false
|
||||
let canShowMore = false
|
||||
let isShownMore = false
|
||||
|
||||
$: isCollapsed = $navigatorStateStore.collapsedSections.includes(id)
|
||||
|
||||
$: void getChatNavItems(objects).then((res) => {
|
||||
items = res
|
||||
})
|
||||
@ -137,49 +133,48 @@
|
||||
|
||||
return result
|
||||
}
|
||||
$: visibleItem = visibleItems.find(({ id }) => id === objectId)
|
||||
</script>
|
||||
|
||||
{#if visibleItems.length > 0 && contexts.length > 0}
|
||||
<NavGroup
|
||||
_id={id}
|
||||
label={header}
|
||||
categoryName={id}
|
||||
{actions}
|
||||
isOpen={!isCollapsed}
|
||||
highlighted={items.some((it) => it.id === objectId)}
|
||||
isFold
|
||||
second
|
||||
on:toggle={() => {
|
||||
toggleSections(id)
|
||||
}}
|
||||
empty={visibleItems.length === 0}
|
||||
visible={visibleItem !== undefined}
|
||||
noDivider
|
||||
>
|
||||
{#if !isCollapsed}
|
||||
{#each visibleItems as item (item.id)}
|
||||
{@const context = contexts.find(({ attachedTo }) => attachedTo === item.id)}
|
||||
<ChatNavItem {context} isSelected={objectId === item.id} {item} type={'type-object'} on:select />
|
||||
{/each}
|
||||
{#if canShowMore}
|
||||
<div class="showMore">
|
||||
<ModernButton
|
||||
label={isShownMore ? ui.string.ShowLess : ui.string.ShowMore}
|
||||
kind="tertiary"
|
||||
inheritFont
|
||||
size="extra-small"
|
||||
on:click={onShowMore}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if objectId}
|
||||
{@const item = visibleItems.find(({ id }) => id === objectId)}
|
||||
{#if item}
|
||||
{@const context = contexts.find(({ attachedTo }) => attachedTo === item.id)}
|
||||
<ChatNavItem {context} isSelected {item} on:select />
|
||||
{/if}
|
||||
{#each visibleItems as item (item.id)}
|
||||
{@const context = contexts.find(({ attachedTo }) => attachedTo === item.id)}
|
||||
<ChatNavItem {context} isSelected={objectId === item.id} {item} type={'type-object'} on:select />
|
||||
{/each}
|
||||
{#if canShowMore}
|
||||
<div class="showMore">
|
||||
<ModernButton
|
||||
label={isShownMore ? ui.string.ShowLess : ui.string.ShowMore}
|
||||
kind="tertiary"
|
||||
inheritFont
|
||||
size="extra-small"
|
||||
on:click={onShowMore}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<svelte:fragment slot="visible" let:isOpen>
|
||||
{#if visibleItem !== undefined && !isOpen}
|
||||
{@const context = contexts.find(({ attachedTo }) => attachedTo === visibleItem?.id)}
|
||||
<ChatNavItem {context} isSelected item={visibleItem} type={'type-object'} on:select />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</NavGroup>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.showMore {
|
||||
margin-top: var(--spacing-1);
|
||||
margin: var(--spacing-1);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
</style>
|
||||
|
@ -18,7 +18,7 @@
|
||||
import { DocNotifyContext } from '@hcengineering/notification'
|
||||
import { SpecialNavModel } from '@hcengineering/workbench'
|
||||
import { NavLink } from '@hcengineering/view-resources'
|
||||
import { TreeSeparator } from '@hcengineering/workbench-resources'
|
||||
import { TreeSeparator, NavFooter } from '@hcengineering/workbench-resources'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
|
||||
|
||||
@ -35,6 +35,7 @@
|
||||
|
||||
const notificationClient = InboxNotificationsClientImpl.getClient()
|
||||
const contextsStore = notificationClient.contexts
|
||||
let pressed: boolean = false
|
||||
|
||||
const globalActions = [
|
||||
{
|
||||
@ -66,16 +67,19 @@
|
||||
}
|
||||
|
||||
function addButtonClicked (ev: MouseEvent): void {
|
||||
showPopup(Menu, { actions: globalActions }, ev.target as HTMLElement)
|
||||
pressed = true
|
||||
showPopup(Menu, { actions: globalActions }, ev.target as HTMLElement, () => {
|
||||
pressed = false
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="hulyNavPanel-header">
|
||||
<div class="hulyNavPanel-header withButton">
|
||||
<span class="overflow-label">
|
||||
<Label label={chunter.string.Chat} />
|
||||
</span>
|
||||
{#if hasAccountRole(getCurrentAccount(), AccountRole.User)}
|
||||
<ButtonIcon icon={IconAdd} kind={'primary'} size={'small'} on:click={addButtonClicked} />
|
||||
<ButtonIcon icon={IconAdd} hasMenu {pressed} kind={'primary'} size={'small'} on:click={addButtonClicked} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@ -86,7 +90,7 @@
|
||||
{#await isSpecialVisible(special, $contextsStore) then isVisible}
|
||||
{#if isVisible}
|
||||
<NavLink space={special.id}>
|
||||
<ChatSpecialElement {special} {currentSpecial} />
|
||||
<ChatSpecialElement {special} {currentSpecial} on:select />
|
||||
</NavLink>
|
||||
{/if}
|
||||
{/await}
|
||||
@ -106,16 +110,16 @@
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Scroller>
|
||||
<Scroller shrink>
|
||||
{#each chatNavGroupModels as model}
|
||||
<ChatNavGroup {object} {objectId} {model} on:select />
|
||||
{/each}
|
||||
<div class="antiNav-space" />
|
||||
</Scroller>
|
||||
<NavFooter />
|
||||
|
||||
<style lang="scss">
|
||||
.search {
|
||||
padding: var(--spacing-3) var(--spacing-1_5) var(--spacing-1_5);
|
||||
padding: var(--spacing-1_5);
|
||||
border-bottom: 1px solid var(--theme-navpanel-divider);
|
||||
}
|
||||
</style>
|
||||
|
@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { SpecialNavModel } from '@hcengineering/workbench'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
|
||||
@ -29,6 +30,8 @@
|
||||
export let currentSpecial: SpecialNavModel | undefined = undefined
|
||||
export let type: 'type-link' | 'type-tag' | 'type-anchor-link' | 'type-object' = 'type-link'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const notificationsClient = InboxNotificationsClientImpl.getClient()
|
||||
const notificationsByContextStore = notificationsClient.inboxNotificationsByContext
|
||||
|
||||
@ -67,7 +70,7 @@
|
||||
</script>
|
||||
|
||||
<NavItem
|
||||
id={special.id}
|
||||
_id={special.id}
|
||||
icon={special.icon}
|
||||
intlTitle={special.label}
|
||||
withIconBackground={false}
|
||||
@ -75,4 +78,5 @@
|
||||
{elementsCount}
|
||||
isSelected={special.id === currentSpecial?.id}
|
||||
{type}
|
||||
on:click={() => dispatch('select', null)}
|
||||
/>
|
||||
|
@ -27,7 +27,7 @@
|
||||
import { NotifyMarker } from '@hcengineering/notification-resources'
|
||||
import { Asset, IntlString } from '@hcengineering/platform'
|
||||
|
||||
export let id: string
|
||||
export let _id: string
|
||||
export let icon: Asset | AnySvelteComponent | undefined
|
||||
export let iconProps: any | undefined = undefined
|
||||
export let iconSize: IconSize = 'small'
|
||||
@ -50,7 +50,7 @@
|
||||
$: menuActions = actions.filter(({ inline }) => inline !== true)
|
||||
|
||||
function handleMenuClicked (ev: MouseEvent): void {
|
||||
showPopup(Menu, { actions: menuActions, ctx: id }, ev.target as HTMLElement, () => {
|
||||
showPopup(Menu, { actions: menuActions, ctx: _id }, ev.target as HTMLElement, () => {
|
||||
menuOpened = false
|
||||
})
|
||||
menuOpened = true
|
||||
@ -62,6 +62,7 @@
|
||||
</script>
|
||||
|
||||
<NavItem
|
||||
{_id}
|
||||
{icon}
|
||||
{iconProps}
|
||||
{iconSize}
|
||||
|
@ -26,14 +26,13 @@
|
||||
getDocumentName,
|
||||
DocumentState
|
||||
} from '@hcengineering/controlled-documents'
|
||||
|
||||
import DocHierarchyTreeElement from './DocHierarchyTreeElement.svelte'
|
||||
import { TreeItem } from '@hcengineering/view-resources'
|
||||
import { compareDocs } from '../../utils'
|
||||
|
||||
export let projectMeta: ProjectMeta[] = []
|
||||
export let childrenByParent: Record<Ref<DocumentMeta>, Array<ProjectMeta>>
|
||||
export let selected: Ref<Doc> | undefined
|
||||
export let level = 1
|
||||
export let level: number = 0
|
||||
export let getMoreActions: ((obj: Doc, originalEvent?: MouseEvent) => Promise<Action[]>) | undefined = undefined
|
||||
export let collapsedPrefix: string = ''
|
||||
|
||||
@ -111,33 +110,36 @@
|
||||
|
||||
{#if doc}
|
||||
{@const children = childrenByParent[doc.attachedTo] ?? []}
|
||||
<DocHierarchyTreeElement
|
||||
docId={doc._id}
|
||||
<TreeItem
|
||||
_id={doc._id}
|
||||
icon={documents.icon.Document}
|
||||
iconProps={{
|
||||
fill: 'currentColor'
|
||||
}}
|
||||
title={getDocumentName(doc)}
|
||||
selected={selected === prjdoc._id}
|
||||
parent={children.length > 0}
|
||||
getMoreActions={getMoreActions !== undefined ? () => getDocMoreActions(prjdoc) : undefined}
|
||||
selected={selected === doc._id || selected === prjdoc._id}
|
||||
isFold
|
||||
empty={children.length === 0 || children === undefined}
|
||||
actions={getMoreActions !== undefined ? () => getDocMoreActions(prjdoc) : undefined}
|
||||
{level}
|
||||
{collapsedPrefix}
|
||||
on:click={() => {
|
||||
dispatch('selected', prjdoc)
|
||||
}}
|
||||
>
|
||||
{#if children.length}
|
||||
<svelte:self
|
||||
projectMeta={children}
|
||||
{childrenByParent}
|
||||
{selected}
|
||||
{collapsedPrefix}
|
||||
{getMoreActions}
|
||||
level={level + 1}
|
||||
on:selected
|
||||
/>
|
||||
{/if}
|
||||
</DocHierarchyTreeElement>
|
||||
<svelte:fragment slot="dropbox">
|
||||
{#if children.length}
|
||||
<svelte:self
|
||||
projectMeta={children}
|
||||
{childrenByParent}
|
||||
{selected}
|
||||
{collapsedPrefix}
|
||||
{getMoreActions}
|
||||
level={level + 1}
|
||||
on:selected
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</TreeItem>
|
||||
{/if}
|
||||
{/each}
|
||||
|
@ -1,102 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { type Ref } from '@hcengineering/core'
|
||||
import { type Asset, getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { type Action, type AnySvelteComponent, tooltip } from '@hcengineering/ui'
|
||||
import {
|
||||
Icon,
|
||||
IconChevronDown,
|
||||
IconMoreH,
|
||||
Menu,
|
||||
getTreeCollapsed,
|
||||
setTreeCollapsed,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import { DocumentSpace } from '@hcengineering/controlled-documents'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let _id: Ref<DocumentSpace>
|
||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||
export let iconProps: Record<string, any> | undefined = undefined
|
||||
export let title: string
|
||||
export let selected: boolean = false
|
||||
export let getMoreActions: ((originalEvent?: MouseEvent) => Promise<Action[]>) | undefined = undefined
|
||||
export let collapsed: boolean = getTreeCollapsed(_id)
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let hovered = false
|
||||
async function onMenuClick (ev: MouseEvent): Promise<void> {
|
||||
if (getMoreActions === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
hovered = true
|
||||
const actions = await getMoreActions(ev)
|
||||
showPopup(Menu, { actions, ctx: _id }, ev.target as HTMLElement, () => {
|
||||
hovered = false
|
||||
})
|
||||
}
|
||||
|
||||
$: if (_id !== undefined) collapsed = getTreeCollapsed(_id)
|
||||
$: setTreeCollapsed(_id, collapsed)
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="antiNav-element tree parent"
|
||||
class:selected
|
||||
class:hovered
|
||||
class:collapsed
|
||||
on:click={() => {
|
||||
collapsed = !collapsed
|
||||
dispatch('click')
|
||||
}}
|
||||
>
|
||||
{#if icon}
|
||||
<div class="an-element__icon folder">
|
||||
<Icon {icon} {iconProps} size={'small'} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<span class="an-element__label" use:tooltip={{ label: getEmbeddedLabel(title) }}>
|
||||
{title}
|
||||
</span>
|
||||
|
||||
<div
|
||||
class="an-element__tool arrow hidden"
|
||||
on:click|preventDefault|stopPropagation={() => {
|
||||
collapsed = !collapsed
|
||||
}}
|
||||
>
|
||||
<IconChevronDown size={'small'} />
|
||||
</div>
|
||||
|
||||
<div class="an-element__grow" />
|
||||
|
||||
<slot name="extra" />
|
||||
|
||||
<div class="w-6">
|
||||
<div class="an-element__tool" class:pressed={hovered} on:click|preventDefault|stopPropagation={onMenuClick}>
|
||||
<IconMoreH size={'small'} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if !collapsed}
|
||||
<div class="antiNav-element__dropbox"><slot /></div>
|
||||
{/if}
|
@ -1,103 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { type Doc, type Ref } from '@hcengineering/core'
|
||||
import { type Asset, getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import type { Action, AnySvelteComponent } from '@hcengineering/ui'
|
||||
import { Icon, IconChevronDown, IconMoreH, Menu, showPopup, tooltip } from '@hcengineering/ui'
|
||||
|
||||
import { getPrefixedTreeCollapsed, setPrefixedTreeCollapsed } from '../../utils'
|
||||
|
||||
export let docId: Ref<Doc>
|
||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||
export let iconProps: Record<string, any> | undefined = undefined
|
||||
export let title: string
|
||||
export let parent: boolean = false
|
||||
export let selected: boolean = false
|
||||
export let folder: boolean = false
|
||||
export let level: number = 0
|
||||
export let getMoreActions: ((originalEvent?: MouseEvent) => Promise<Action[]>) | undefined = undefined
|
||||
export let collapsedPrefix: string = ''
|
||||
export let collapsed: boolean = getPrefixedTreeCollapsed(docId, collapsedPrefix)
|
||||
|
||||
let hovered = false
|
||||
async function onMenuClick (ev: MouseEvent): Promise<void> {
|
||||
if (getMoreActions === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
showPopup(Menu, { actions: await getMoreActions(ev), ctx: docId }, ev.target as HTMLElement, () => {
|
||||
hovered = false
|
||||
})
|
||||
hovered = true
|
||||
}
|
||||
|
||||
$: if (docId !== undefined) collapsed = getPrefixedTreeCollapsed(docId, collapsedPrefix)
|
||||
$: setPrefixedTreeCollapsed(docId, collapsedPrefix, collapsed)
|
||||
$: hasLeftCollapser = parent && !folder
|
||||
$: hasActions = getMoreActions !== undefined
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="antiNav-element tree"
|
||||
class:selected
|
||||
class:hovered
|
||||
class:parent
|
||||
class:collapsed
|
||||
style:padding-left={`${level > 0 ? level * 1.25 + 0.125 : 0.75}rem`}
|
||||
on:click
|
||||
>
|
||||
<div
|
||||
class="an-element__tool arrow"
|
||||
class:empty={!hasLeftCollapser}
|
||||
on:click|preventDefault|stopPropagation={() => {
|
||||
if (hasLeftCollapser) collapsed = !collapsed
|
||||
}}
|
||||
>
|
||||
{#if hasLeftCollapser}<IconChevronDown size={'small'} filled />{/if}
|
||||
</div>
|
||||
|
||||
{#if icon}
|
||||
<div class="an-element__icon" class:folder={level === 0}>
|
||||
<Icon {icon} {iconProps} size={'small'} />
|
||||
</div>
|
||||
{/if}
|
||||
<span class="an-element__label" use:tooltip={{ label: getEmbeddedLabel(title) }}>
|
||||
{title}
|
||||
</span>
|
||||
{#if parent && folder}
|
||||
<div
|
||||
class="an-element__tool arrow hidden"
|
||||
on:click|preventDefault|stopPropagation={() => {
|
||||
if (parent) collapsed = !collapsed
|
||||
}}
|
||||
>
|
||||
<IconChevronDown size={'small'} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="an-element__grow" />
|
||||
|
||||
{#if hasActions}
|
||||
<div class="an-element__tool" class:pressed={hovered} on:click|preventDefault|stopPropagation={onMenuClick}>
|
||||
<IconMoreH size={'small'} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if parent && !collapsed}
|
||||
<div class="antiNav-element__dropbox"><slot /></div>
|
||||
{/if}
|
@ -16,19 +16,17 @@
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { WithLookup, type Doc, type Ref } from '@hcengineering/core'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { getPlatformColorForTextDef, themeStore } from '@hcengineering/ui'
|
||||
import { getPlatformColorForTextDef, themeStore, getTreeCollapsed } from '@hcengineering/ui'
|
||||
import documents, {
|
||||
type DocumentMeta,
|
||||
type DocumentSpace,
|
||||
type Project,
|
||||
type ProjectMeta
|
||||
} from '@hcengineering/controlled-documents'
|
||||
import { TreeNode } from '@hcengineering/view-resources'
|
||||
|
||||
import FolderCollapsed from '../icons/FolderCollapsed.svelte'
|
||||
import FolderExpanded from '../icons/FolderExpanded.svelte'
|
||||
import DocHierarchyLevel from './DocHierarchyLevel.svelte'
|
||||
import DocHierarchyTreeElement from './DocHierarchyTreeElement.svelte'
|
||||
import { getPrefixedTreeCollapsed, setPrefixedTreeCollapsed, getProjectDocsHierarchy } from '../../utils'
|
||||
import { getProjectDocsHierarchy } from '../../utils'
|
||||
|
||||
export let space: DocumentSpace
|
||||
export let project: Ref<Project> | undefined
|
||||
@ -36,9 +34,9 @@
|
||||
export let collapsedPrefix: string = ''
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let collapsed: boolean = getPrefixedTreeCollapsed(space._id, collapsedPrefix)
|
||||
$: setPrefixedTreeCollapsed(space._id, collapsedPrefix, collapsed)
|
||||
$: folderIcon = collapsed ? FolderCollapsed : FolderExpanded
|
||||
// let collapsed: boolean = getPrefixedTreeCollapsed(space._id, collapsedPrefix)
|
||||
// $: setPrefixedTreeCollapsed(space._id, collapsedPrefix, collapsed)
|
||||
// $: folderIcon = collapsed ? FolderCollapsed : FolderExpanded
|
||||
|
||||
let rootDocs: Array<WithLookup<ProjectMeta>> = []
|
||||
let childrenByParent: Record<Ref<DocumentMeta>, Array<WithLookup<ProjectMeta>>> = {}
|
||||
@ -56,29 +54,21 @@
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="root">
|
||||
<DocHierarchyTreeElement
|
||||
bind:collapsed
|
||||
docId={space?._id}
|
||||
icon={folderIcon}
|
||||
iconProps={{
|
||||
fill: getPlatformColorForTextDef(space.name, $themeStore.dark).icon
|
||||
}}
|
||||
title={space.name}
|
||||
folder
|
||||
selected={selected === undefined}
|
||||
parent={rootDocs.length > 0}
|
||||
{collapsedPrefix}
|
||||
on:click={() => {
|
||||
dispatch('selected', space)
|
||||
}}
|
||||
>
|
||||
<DocHierarchyLevel projectMeta={rootDocs} {childrenByParent} {selected} {collapsedPrefix} on:selected />
|
||||
</DocHierarchyTreeElement>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
:global(.root .antiNav-element) {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<TreeNode
|
||||
_id={space?._id}
|
||||
folderIcon
|
||||
iconProps={{
|
||||
fill: getPlatformColorForTextDef(space.name, $themeStore.dark).icon
|
||||
}}
|
||||
title={space.name}
|
||||
highlighted={selected !== undefined}
|
||||
selected={selected === undefined}
|
||||
empty={rootDocs.length === 0}
|
||||
{collapsedPrefix}
|
||||
type={'nested-selectable'}
|
||||
on:click={() => {
|
||||
dispatch('selected', space)
|
||||
}}
|
||||
>
|
||||
<DocHierarchyLevel projectMeta={rootDocs} {childrenByParent} {selected} {collapsedPrefix} on:selected />
|
||||
</TreeNode>
|
||||
|
@ -16,32 +16,22 @@
|
||||
import { WithLookup, type Doc, type Ref, type Space } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import {
|
||||
type Action,
|
||||
getTreeCollapsed,
|
||||
setTreeCollapsed,
|
||||
getPlatformColorForTextDef,
|
||||
themeStore,
|
||||
navigate,
|
||||
IconEdit,
|
||||
Label
|
||||
} from '@hcengineering/ui'
|
||||
import { getActions as getContributedActions } from '@hcengineering/view-resources'
|
||||
import { type Action, getPlatformColorForTextDef, themeStore, navigate, IconEdit, Label } from '@hcengineering/ui'
|
||||
import { getActions as getContributedActions, TreeNode, TreeItem } from '@hcengineering/view-resources'
|
||||
import { ActionGroup } from '@hcengineering/view'
|
||||
import {
|
||||
type ControlledDocument,
|
||||
type DocumentMeta,
|
||||
type DocumentSpace,
|
||||
type DocumentSpaceType,
|
||||
type Project,
|
||||
type ProjectDocument,
|
||||
type ProjectMeta
|
||||
type ProjectMeta,
|
||||
getDocumentName
|
||||
} from '@hcengineering/controlled-documents'
|
||||
|
||||
import FolderCollapsed from '../icons/FolderCollapsed.svelte'
|
||||
import FolderExpanded from '../icons/FolderExpanded.svelte'
|
||||
import ProjectSelector from '../project/ProjectSelector.svelte'
|
||||
import DocHierarchyLevel from './DocHierarchyLevel.svelte'
|
||||
import DocHierarchyRootElement from './DocHierarchyRootElement.svelte'
|
||||
import { getDocumentIdFromFragment, getProjectDocumentLink } from '../../navigation'
|
||||
import {
|
||||
getCurrentProject,
|
||||
@ -55,12 +45,16 @@
|
||||
import documents from '../../plugin'
|
||||
|
||||
export let space: DocumentSpace
|
||||
export let currentSpace: Ref<Space> | undefined
|
||||
export let currentFragment: string | undefined
|
||||
export let getActions: (space: Space) => Promise<Action[]> = async () => []
|
||||
export let deselect: boolean = false
|
||||
export let forciblyСollapsed: boolean = false
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let spaceType: DocumentSpaceType | undefined
|
||||
let pressed: boolean = false
|
||||
|
||||
const spaceTypeQuery = createQuery()
|
||||
$: spaceTypeQuery.query(documents.class.DocumentSpaceType, { _id: space.type }, (result) => {
|
||||
@ -69,10 +63,6 @@
|
||||
|
||||
$: selected = getDocumentIdFromFragment(currentFragment ?? '')
|
||||
|
||||
let collapsed: boolean = getTreeCollapsed(space._id)
|
||||
$: setTreeCollapsed(space._id, collapsed)
|
||||
$: folderIcon = collapsed ? FolderCollapsed : FolderExpanded
|
||||
|
||||
let project: Ref<Project> = documents.ids.NoProject
|
||||
$: void selectProject(space)
|
||||
|
||||
@ -96,6 +86,35 @@
|
||||
}
|
||||
)
|
||||
|
||||
let selectedControlledDoc: ControlledDocument | undefined = undefined
|
||||
|
||||
$: if (selected !== undefined) {
|
||||
void client
|
||||
.findOne(
|
||||
documents.class.ProjectDocument,
|
||||
{ _id: selected },
|
||||
{
|
||||
lookup: {
|
||||
document: documents.class.ControlledDocument
|
||||
}
|
||||
}
|
||||
)
|
||||
.then((result) => {
|
||||
if (result !== undefined) {
|
||||
selectedControlledDoc = result.$lookup?.document as ControlledDocument
|
||||
} else {
|
||||
// There's some issue with resolving which needs to be fixed later
|
||||
void client
|
||||
.findOne(documents.class.ControlledDocument, { _id: selected as unknown as Ref<ControlledDocument> })
|
||||
.then((result) => {
|
||||
selectedControlledDoc = result
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
selectedControlledDoc = undefined
|
||||
}
|
||||
|
||||
async function selectProject (space: DocumentSpace): Promise<void> {
|
||||
project = getCurrentProject(space._id) ?? (await getLatestProjectId(space._id)) ?? documents.ids.NoProject
|
||||
}
|
||||
@ -161,17 +180,21 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<DocHierarchyRootElement
|
||||
bind:collapsed
|
||||
<TreeNode
|
||||
_id={space?._id}
|
||||
icon={folderIcon}
|
||||
folderIcon
|
||||
iconProps={{
|
||||
fill: getPlatformColorForTextDef(space.name, $themeStore.dark).icon
|
||||
}}
|
||||
title={space.name}
|
||||
getMoreActions={() => getSpaceActions(space)}
|
||||
highlighted={space._id === currentSpace && currentFragment !== undefined && !deselect}
|
||||
visible={(space._id === currentSpace && currentFragment !== undefined && !deselect) || forciblyСollapsed}
|
||||
showMenu={pressed}
|
||||
{forciblyСollapsed}
|
||||
actions={() => getSpaceActions(space)}
|
||||
type={'nested'}
|
||||
>
|
||||
<div slot="extra">
|
||||
<svelte:fragment slot="extra">
|
||||
{#if spaceType?.projects === true}
|
||||
<ProjectSelector
|
||||
value={project}
|
||||
@ -180,13 +203,14 @@
|
||||
kind={'ghost'}
|
||||
size={'x-small'}
|
||||
showDropdownIcon
|
||||
bind:pressed
|
||||
on:change={(evt) => {
|
||||
project = evt.detail
|
||||
setCurrentProject(space._id, project)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
{#if rootDocs.length > 0}
|
||||
<DocHierarchyLevel
|
||||
@ -203,7 +227,26 @@
|
||||
<Label label={documents.string.NoDocuments} />
|
||||
</div>
|
||||
{/if}
|
||||
</DocHierarchyRootElement>
|
||||
|
||||
<svelte:fragment slot="visible">
|
||||
{#if (selected || forciblyСollapsed) && selectedControlledDoc}
|
||||
{@const doc = selectedControlledDoc}
|
||||
<TreeItem
|
||||
_id={doc._id}
|
||||
icon={documents.icon.Document}
|
||||
iconProps={{
|
||||
fill: 'currentColor'
|
||||
}}
|
||||
title={getDocumentName(doc)}
|
||||
actions={() => getDocumentActions(doc)}
|
||||
selected
|
||||
isFold
|
||||
empty
|
||||
forciblyСollapsed
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</TreeNode>
|
||||
|
||||
<style lang="scss">
|
||||
.pseudo-element {
|
||||
|
@ -26,7 +26,8 @@
|
||||
|
||||
export let value: Ref<Project> | undefined
|
||||
export let space: Ref<Space>
|
||||
export let disabled = false
|
||||
export let disabled: boolean = false
|
||||
export let pressed: boolean = false
|
||||
export let focusIndex = -1
|
||||
export let maxWidth = ''
|
||||
export let kind: ButtonKind = 'link'
|
||||
@ -48,11 +49,13 @@
|
||||
function showVersionsPopup (): void {
|
||||
if (disabled) return
|
||||
|
||||
pressed = true
|
||||
showPopup(ProjectSelectorPopup, { space, showReadonly, selected: value }, container, (result) => {
|
||||
if (result !== undefined) {
|
||||
value = result
|
||||
dispatch('change', result)
|
||||
}
|
||||
pressed = false
|
||||
})
|
||||
}
|
||||
|
||||
@ -68,6 +71,7 @@
|
||||
{size}
|
||||
{justify}
|
||||
{disabled}
|
||||
stopPropagation
|
||||
on:click={showVersionsPopup}
|
||||
showTooltip={{
|
||||
label: project !== undefined ? getEmbeddedLabel(project.name) : documents.string.Project
|
||||
|
@ -33,13 +33,7 @@ import presentation, { copyDocumentContent, getClient } from '@hcengineering/pre
|
||||
import contact, { type Employee, type PersonAccount } from '@hcengineering/contact'
|
||||
import request, { RequestStatus } from '@hcengineering/request'
|
||||
import textEditor, { isEmptyMarkup } from '@hcengineering/text-editor'
|
||||
import {
|
||||
getEventPositionElement,
|
||||
showPopup,
|
||||
getTreeCollapsed,
|
||||
setTreeCollapsed,
|
||||
type Location
|
||||
} from '@hcengineering/ui'
|
||||
import { getEventPositionElement, showPopup, type Location } from '@hcengineering/ui'
|
||||
import { type KeyFilter } from '@hcengineering/view'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import documents, {
|
||||
@ -679,22 +673,6 @@ export async function canDeleteDocumentCategory (doc?: Doc | Doc[]): Promise<boo
|
||||
return await checkPermission(client, documents.permission.DeleteDocumentCategory, (doc as DocumentCategory).space)
|
||||
}
|
||||
|
||||
function getPrefixedTreeId (_id: string, prefix: string): string {
|
||||
return `${prefix}_###_${_id}`
|
||||
}
|
||||
|
||||
export function getPrefixedTreeCollapsed (_id: string, prefix: string): boolean {
|
||||
if (_id === undefined || _id === 'undefined') return false
|
||||
|
||||
return getTreeCollapsed(prefix === '' ? _id : getPrefixedTreeId(_id, prefix))
|
||||
}
|
||||
|
||||
export function setPrefixedTreeCollapsed (_id: string, prefix: string, collapsed: boolean): void {
|
||||
if (_id === undefined || _id === 'undefined') return
|
||||
|
||||
setTreeCollapsed(prefix === '' ? _id : getPrefixedTreeId(_id, prefix), collapsed)
|
||||
}
|
||||
|
||||
function getCurrentProjectId (space: Ref<DocumentSpace>): string {
|
||||
return `${space}_###_project`
|
||||
}
|
||||
|
@ -32,7 +32,7 @@
|
||||
export let documentById: Map<Ref<Document>, Document>
|
||||
|
||||
export let selected: Ref<Document> | undefined
|
||||
export let level = 1
|
||||
export let level: number = 0
|
||||
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
@ -97,10 +97,12 @@
|
||||
}}
|
||||
title={doc.name}
|
||||
selected={selected === doc._id}
|
||||
parent={desc.length > 0}
|
||||
isFold
|
||||
{level}
|
||||
empty={desc.length === 0}
|
||||
actions={getActions(doc)}
|
||||
moreActions={() => getMoreActions(doc)}
|
||||
{level}
|
||||
shouldTooltip
|
||||
on:click={() => {
|
||||
handleDocumentSelected(doc._id)
|
||||
}}
|
||||
|
@ -17,19 +17,7 @@
|
||||
import { Document } from '@hcengineering/document'
|
||||
import type { Asset, IntlString } from '@hcengineering/platform'
|
||||
import type { Action, AnySvelteComponent } from '@hcengineering/ui'
|
||||
import {
|
||||
ActionIcon,
|
||||
Icon,
|
||||
IconChevronDown,
|
||||
IconMoreH,
|
||||
Label,
|
||||
Menu,
|
||||
navigate,
|
||||
showPopup,
|
||||
getTreeCollapsed,
|
||||
setTreeCollapsed
|
||||
} from '@hcengineering/ui'
|
||||
|
||||
import { IconMoreH, Menu, navigate, showPopup, NavItem, ButtonIcon } from '@hcengineering/ui'
|
||||
import { getDocumentLink } from '../../utils'
|
||||
|
||||
export let doc: Document
|
||||
@ -37,14 +25,16 @@
|
||||
export let iconProps: Record<string, any> | undefined = undefined
|
||||
export let label: IntlString | undefined = undefined
|
||||
export let title: string | undefined = undefined
|
||||
export let parent: boolean = false
|
||||
export let collapsed: boolean = getTreeCollapsed(doc._id)
|
||||
export let selected: boolean = false
|
||||
export let isFold: boolean = false
|
||||
export let empty: boolean = false
|
||||
export let shouldTooltip: boolean = false
|
||||
export let level: number = 0
|
||||
export let actions: Action[] = []
|
||||
export let moreActions: (originalEvent?: MouseEvent) => Promise<Action[]> | undefined = async () => []
|
||||
export let forciblyСollapsed: boolean = false
|
||||
|
||||
let hovered = false
|
||||
let hovered: boolean = false
|
||||
async function onMenuClick (ev: MouseEvent): Promise<void> {
|
||||
showPopup(Menu, { actions: await moreActions(ev), ctx: doc._id }, ev.target as HTMLElement, () => {
|
||||
hovered = false
|
||||
@ -58,59 +48,41 @@
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
$: collapsed = getTreeCollapsed(doc._id)
|
||||
$: setTreeCollapsed(doc._id, collapsed)
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="antiNav-element tree"
|
||||
class:selected
|
||||
class:hovered
|
||||
class:parent
|
||||
class:collapsed
|
||||
style:padding-left={`${level > 0 ? level * 1.25 + 0.125 : 0.75}rem`}
|
||||
<NavItem
|
||||
_id={doc._id}
|
||||
{icon}
|
||||
{iconProps}
|
||||
{label}
|
||||
{title}
|
||||
{isFold}
|
||||
{level}
|
||||
{empty}
|
||||
{selected}
|
||||
showMenu={hovered}
|
||||
{shouldTooltip}
|
||||
{forciblyСollapsed}
|
||||
on:click={() => {
|
||||
selectDocument()
|
||||
if (selected) {
|
||||
collapsed = !collapsed
|
||||
}
|
||||
dispatch('click')
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class="an-element__tool arrow"
|
||||
class:empty={!parent}
|
||||
on:click|preventDefault|stopPropagation={() => {
|
||||
if (parent) collapsed = !collapsed
|
||||
}}
|
||||
>
|
||||
{#if parent}<IconChevronDown size={'small'} filled />{/if}
|
||||
</div>
|
||||
|
||||
{#if icon}
|
||||
<div class="an-element__icon" class:folder={level === 0}>
|
||||
<Icon {icon} {iconProps} size={'small'} />
|
||||
</div>
|
||||
{/if}
|
||||
<span class="an-element__label">
|
||||
{#if label}<Label {label} />{:else}{title}{/if}
|
||||
</span>
|
||||
<div class="an-element__grow" />
|
||||
|
||||
{#each actions as action}
|
||||
{#if action.icon}
|
||||
<div class="an-element__tool">
|
||||
<ActionIcon label={action.label} icon={action.icon} size={'small'} action={(evt) => action.action({}, evt)} />
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
<div class="an-element__tool" class:pressed={hovered} on:click|preventDefault|stopPropagation={onMenuClick}>
|
||||
<IconMoreH size={'small'} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if parent && !collapsed}
|
||||
<div class="antiNav-element__dropbox"><slot /></div>
|
||||
{/if}
|
||||
<svelte:fragment slot="actions">
|
||||
{#each actions as action}
|
||||
{#if action.icon}
|
||||
<ButtonIcon
|
||||
icon={action.icon}
|
||||
kind={'tertiary'}
|
||||
size={'extra-small'}
|
||||
tooltip={{ label: action.label, direction: 'top' }}
|
||||
on:click={(evt) => action.action({}, evt)}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
<ButtonIcon icon={IconMoreH} kind={'tertiary'} size={'extra-small'} pressed={hovered} on:click={onMenuClick} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="dropbox">
|
||||
<slot />
|
||||
</svelte:fragment>
|
||||
</NavItem>
|
||||
|
@ -13,25 +13,27 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Ref, SortingOrder, Space } from '@hcengineering/core'
|
||||
import { Ref, SortingOrder, Space, generateId } from '@hcengineering/core'
|
||||
import { Document, Teamspace } from '@hcengineering/document'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
IconWithEmoji,
|
||||
getTreeCollapsed,
|
||||
setTreeCollapsed,
|
||||
IconEdit,
|
||||
getPlatformColorDef,
|
||||
getPlatformColorForTextDef,
|
||||
themeStore,
|
||||
Action
|
||||
Action,
|
||||
IconAdd
|
||||
} from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { TreeNode } from '@hcengineering/view-resources'
|
||||
import { TreeNode, openDoc, getActions as getContributedActions } from '@hcengineering/view-resources'
|
||||
import { SpacesNavModel } from '@hcengineering/workbench'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
|
||||
import document from '../../plugin'
|
||||
import { getDocumentIdFromFragment } from '../../utils'
|
||||
import { getDocumentIdFromFragment, createEmptyDocument } from '../../utils'
|
||||
import DocHierarchy from './DocHierarchy.svelte'
|
||||
import DocTreeElement from './DocTreeElement.svelte'
|
||||
|
||||
export let space: Teamspace
|
||||
export let model: SpacesNavModel
|
||||
@ -40,9 +42,9 @@
|
||||
export let currentFragment: string | undefined
|
||||
export let getActions: (space: Space) => Promise<Action[]> = async () => []
|
||||
export let deselect: boolean = false
|
||||
export let forciblyСollapsed: boolean = false
|
||||
|
||||
let collapsed: boolean = getTreeCollapsed(space._id)
|
||||
$: setTreeCollapsed(space._id, collapsed)
|
||||
const client = getClient()
|
||||
|
||||
let documents: Ref<Document>[] = []
|
||||
let documentById: Map<Ref<Document>, Document> = new Map<Ref<Document>, Document>()
|
||||
@ -54,6 +56,8 @@
|
||||
|
||||
let selected: Ref<Document> | undefined
|
||||
$: selected = getDocumentIdFromFragment(currentFragment ?? '')
|
||||
let visibleItem: Document | undefined
|
||||
$: visibleItem = selected !== undefined ? documentById.get(selected) : undefined
|
||||
|
||||
// TODO expand tree until the selected document ?
|
||||
|
||||
@ -84,10 +88,43 @@
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
function getDocActions (doc: Document): Action[] {
|
||||
return [
|
||||
{
|
||||
icon: IconAdd,
|
||||
label: document.string.CreateDocument,
|
||||
action: async (ctx: any, evt: Event) => {
|
||||
const id: Ref<Document> = generateId()
|
||||
await createEmptyDocument(client, id, doc.space, doc._id, {})
|
||||
|
||||
const object = await client.findOne(document.class.Document, { _id: id })
|
||||
if (object !== undefined) {
|
||||
openDoc(client.getHierarchy(), object)
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async function getMoreActions (obj: Document): Promise<Action[]> {
|
||||
const result: Action[] = []
|
||||
const extraActions = await getContributedActions(client, obj)
|
||||
for (const act of extraActions) {
|
||||
result.push({
|
||||
icon: act.icon ?? IconEdit,
|
||||
label: act.label,
|
||||
action: async (ctx: any, evt: Event) => {
|
||||
const impl = await getResource(act.action)
|
||||
await impl(obj, evt, act.actionProps)
|
||||
}
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
</script>
|
||||
|
||||
<TreeNode
|
||||
{collapsed}
|
||||
_id={space?._id}
|
||||
icon={space?.icon === view.ids.IconWithEmoji ? IconWithEmoji : space?.icon ?? model.icon}
|
||||
iconProps={space?.icon === view.ids.IconWithEmoji
|
||||
@ -99,10 +136,34 @@
|
||||
: getPlatformColorForTextDef(space.name, $themeStore.dark).icon
|
||||
}}
|
||||
title={space.name}
|
||||
folder
|
||||
parent={descendants.size > 0}
|
||||
type={'nested'}
|
||||
highlighted={currentSpace === space._id}
|
||||
visible={currentSpace === space._id || forciblyСollapsed}
|
||||
actions={() => getActions(space)}
|
||||
on:click={() => (collapsed = !collapsed)}
|
||||
{forciblyСollapsed}
|
||||
>
|
||||
<DocHierarchy {documents} {descendants} {documentById} {selected} />
|
||||
|
||||
<svelte:fragment slot="visible">
|
||||
{#if (selected || forciblyСollapsed) && visibleItem}
|
||||
{@const item = visibleItem}
|
||||
<DocTreeElement
|
||||
doc={item}
|
||||
icon={item.icon === view.ids.IconWithEmoji ? IconWithEmoji : item.icon ?? document.icon.Document}
|
||||
iconProps={item.icon === view.ids.IconWithEmoji
|
||||
? { icon: visibleItem.color }
|
||||
: {
|
||||
fill: item.color !== undefined ? getPlatformColorDef(item.color, $themeStore.dark).icon : 'currentColor'
|
||||
}}
|
||||
title={item.name}
|
||||
selected
|
||||
isFold
|
||||
empty
|
||||
shouldTooltip
|
||||
actions={getDocActions(item)}
|
||||
moreActions={() => getMoreActions(item)}
|
||||
forciblyСollapsed
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</TreeNode>
|
||||
|
@ -15,19 +15,24 @@
|
||||
<script lang="ts">
|
||||
import { Doc, Ref, SortingOrder, Space } from '@hcengineering/core'
|
||||
import { Drive, Folder } from '@hcengineering/drive'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { Action, navigate } from '@hcengineering/ui'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Action, navigate, IconEdit } from '@hcengineering/ui'
|
||||
import { TreeNode, TreeItem, getActions as getContributedActions } from '@hcengineering/view-resources'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
|
||||
import drive from '../plugin'
|
||||
import { getDriveLink, getFolderIdFromFragment, getFolderLink } from '../navigation'
|
||||
import FolderTreeLevel from './FolderTreeLevel.svelte'
|
||||
import FolderTreeElement from './FolderTreeElement.svelte'
|
||||
|
||||
export let space: Drive
|
||||
export let currentSpace: Ref<Space> | undefined
|
||||
export let currentSpecial: string | undefined
|
||||
export let currentFragment: string | undefined
|
||||
export let getActions: (space: Space) => Promise<Action[]> = async () => []
|
||||
export let forciblyСollapsed: boolean = false
|
||||
export let deselect: boolean = false
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let folders: Ref<Folder>[] = []
|
||||
let folderById: Map<Ref<Folder>, Folder> = new Map<Ref<Folder>, Folder>()
|
||||
@ -38,7 +43,9 @@
|
||||
}
|
||||
|
||||
let selected: Ref<Doc> | undefined
|
||||
let visibleItem: Folder | undefined
|
||||
$: selected = getFolderIdFromFragment(currentFragment ?? '')
|
||||
$: visibleItem = selected !== undefined ? folderById.get(selected as Ref<Folder>) : undefined
|
||||
|
||||
const query = createQuery()
|
||||
query.query(
|
||||
@ -75,16 +82,37 @@
|
||||
function handleFolderSelected (_id: Ref<Folder>): void {
|
||||
navigate(getFolderLink(_id))
|
||||
}
|
||||
|
||||
async function getFolderActions (obj: Folder): Promise<Action[]> {
|
||||
const result: Action[] = []
|
||||
const extraActions = await getContributedActions(client, obj)
|
||||
for (const act of extraActions) {
|
||||
result.push({
|
||||
icon: act.icon ?? IconEdit,
|
||||
label: act.label,
|
||||
action: async (ctx: any, evt: Event) => {
|
||||
const impl = await getResource(act.action)
|
||||
await impl(obj, evt, act.actionProps)
|
||||
}
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if space}
|
||||
<FolderTreeElement
|
||||
<TreeNode
|
||||
_id={space._id}
|
||||
icon={drive.icon.Drive}
|
||||
title={space.name}
|
||||
selected={currentSpace === space._id && selected === space._id}
|
||||
parent={descendants.size > 0}
|
||||
highlighted={currentSpace === space._id && !deselect}
|
||||
selected={currentSpace === space._id && selected === drive.ids.Root && currentFragment !== undefined && !deselect}
|
||||
visible={(currentSpace === space._id && !deselect && descendants.size !== 0 && selected !== space._id) ||
|
||||
(forciblyСollapsed && currentFragment !== undefined)}
|
||||
type={'nested-selectable'}
|
||||
empty={descendants.size === 0 || (forciblyСollapsed && selected === space._id && !deselect)}
|
||||
actions={() => getActions(space)}
|
||||
{forciblyСollapsed}
|
||||
on:click={() => {
|
||||
handleDriveSelected(space._id)
|
||||
}}
|
||||
@ -98,5 +126,23 @@
|
||||
handleFolderSelected(ev.detail)
|
||||
}}
|
||||
/>
|
||||
</FolderTreeElement>
|
||||
|
||||
<svelte:fragment slot="visible">
|
||||
{#if (selected || forciblyСollapsed) && visibleItem}
|
||||
{@const folder = visibleItem}
|
||||
<TreeItem
|
||||
_id={folder._id}
|
||||
folderIcon
|
||||
iconProps={{ fill: 'var(--global-accent-IconColor)' }}
|
||||
title={folder.name}
|
||||
selected
|
||||
isFold
|
||||
empty
|
||||
actions={async () => await getFolderActions(folder)}
|
||||
shouldTooltip
|
||||
forciblyСollapsed
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</TreeNode>
|
||||
{/if}
|
||||
|
@ -40,7 +40,7 @@
|
||||
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
|
||||
{#if shouldShowAvatar}
|
||||
<div class="icon">
|
||||
<Icon icon={FolderIcon} size={'small'} fill="#5195D7" />
|
||||
<Icon icon={FolderIcon} size={'small'} fill="var(--global-accent-IconColor)" />
|
||||
</div>
|
||||
{/if}
|
||||
<span class="label nowrap" class:no-underline={noUnderline || disabled} class:fs-bold={accent}>
|
||||
|
@ -1,101 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import type { Doc, Ref } from '@hcengineering/core'
|
||||
import { type Asset, type IntlString, getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { type Action, type AnySvelteComponent, tooltip } from '@hcengineering/ui'
|
||||
import {
|
||||
Icon,
|
||||
IconChevronDown,
|
||||
IconMoreH,
|
||||
Label,
|
||||
Menu,
|
||||
getTreeCollapsed,
|
||||
setTreeCollapsed,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
|
||||
export let _id: Ref<Doc>
|
||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||
export let iconProps: Record<string, any> | undefined = undefined
|
||||
export let label: IntlString | undefined = undefined
|
||||
export let title: string | undefined = undefined
|
||||
export let parent: boolean = false
|
||||
export let collapsed: boolean = getTreeCollapsed(_id)
|
||||
export let selected: boolean = false
|
||||
export let level: number = 0
|
||||
export let actions: (originalEvent?: MouseEvent) => Promise<Action[]> = async () => []
|
||||
|
||||
let hovered = false
|
||||
async function onMenuClick (ev: MouseEvent): Promise<void> {
|
||||
showPopup(Menu, { actions: await actions(ev), ctx: _id }, ev.target as HTMLElement, () => {
|
||||
hovered = false
|
||||
})
|
||||
hovered = true
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
$: collapsed = getTreeCollapsed(_id)
|
||||
$: setTreeCollapsed(_id, collapsed)
|
||||
$: folder = level === 0
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="antiNav-element tree"
|
||||
class:selected
|
||||
class:hovered
|
||||
class:parent
|
||||
class:collapsed
|
||||
style:padding-left={`${level > 0 ? level * 1.25 + 0.125 : 0.75}rem`}
|
||||
on:click={() => {
|
||||
if (selected) {
|
||||
collapsed = !collapsed
|
||||
}
|
||||
dispatch('click')
|
||||
}}
|
||||
>
|
||||
{#if !folder}
|
||||
<div
|
||||
class="an-element__tool arrow"
|
||||
class:empty={!parent}
|
||||
on:click|preventDefault|stopPropagation={() => {
|
||||
if (parent) collapsed = !collapsed
|
||||
}}
|
||||
>
|
||||
{#if parent}<IconChevronDown size={'small'} filled />{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if icon}
|
||||
<div class="an-element__icon" class:folder>
|
||||
<Icon {icon} {iconProps} size={'small'} />
|
||||
</div>
|
||||
{/if}
|
||||
<span class="an-element__label" use:tooltip={{ label: label ?? getEmbeddedLabel(title ?? '') }}>
|
||||
{#if label}<Label {label} />{:else}{title}{/if}
|
||||
</span>
|
||||
<div class="an-element__grow" />
|
||||
|
||||
<div class="an-element__tool" class:pressed={hovered} on:click|preventDefault|stopPropagation={onMenuClick}>
|
||||
<IconMoreH size={'small'} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if parent && !collapsed}
|
||||
<div class="antiNav-element__dropbox"><slot /></div>
|
||||
{/if}
|
@ -19,17 +19,15 @@
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Action, IconEdit } from '@hcengineering/ui'
|
||||
import { getActions as getContributedActions } from '@hcengineering/view-resources'
|
||||
|
||||
import FolderTreeElement from './FolderTreeElement.svelte'
|
||||
import FolderIcon from './icons/Folder.svelte'
|
||||
import { getActions as getContributedActions, TreeItem } from '@hcengineering/view-resources'
|
||||
|
||||
export let folders: Ref<Folder>[]
|
||||
export let folderById: Map<Ref<Folder>, Folder>
|
||||
export let descendants: Map<Ref<Folder>, Folder[]>
|
||||
|
||||
export let selected: Ref<Doc> | undefined
|
||||
export let level = 1
|
||||
export let level: number = 0
|
||||
export let once: boolean = false
|
||||
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
@ -66,24 +64,25 @@
|
||||
{@const desc = _descendants.get(doc._id) ?? []}
|
||||
|
||||
{#if doc}
|
||||
<FolderTreeElement
|
||||
<TreeItem
|
||||
_id={doc._id}
|
||||
icon={FolderIcon}
|
||||
iconProps={{
|
||||
fill: '#5195D7'
|
||||
}}
|
||||
folderIcon
|
||||
title={doc.name}
|
||||
selected={selected === doc._id}
|
||||
parent={desc.length > 0}
|
||||
isFold
|
||||
empty={desc.length === 0}
|
||||
actions={async () => await getActions(doc)}
|
||||
{level}
|
||||
shouldTooltip
|
||||
on:click={() => {
|
||||
handleSelected(doc._id)
|
||||
}}
|
||||
>
|
||||
{#if desc.length}
|
||||
<svelte:self folders={desc} {descendants} {folderById} {selected} level={level + 1} on:selected />
|
||||
{/if}
|
||||
</FolderTreeElement>
|
||||
<svelte:fragment slot="dropbox">
|
||||
{#if desc.length > 0 && !once}
|
||||
<svelte:self folders={desc} {descendants} {folderById} {selected} level={level + 1} on:selected />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</TreeItem>
|
||||
{/if}
|
||||
{/each}
|
||||
|
@ -26,7 +26,7 @@
|
||||
export let descendants: Map<Ref<Department>, Department[]>
|
||||
export let departmentById: Map<Ref<Department>, Department>
|
||||
export let selected: Ref<Department> | undefined
|
||||
export let level = 1
|
||||
export let level: number = 0
|
||||
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
@ -68,16 +68,19 @@
|
||||
icon={hr.icon.Department}
|
||||
title={department.name}
|
||||
selected={selected === department._id}
|
||||
parent={desc.length > 0}
|
||||
isFold
|
||||
empty={desc.length === 0}
|
||||
actions={() => getActions(department)}
|
||||
{level}
|
||||
on:click={() => {
|
||||
handleDepartmentSelected(department._id)
|
||||
}}
|
||||
>
|
||||
{#if desc.length}
|
||||
<svelte:self departments={desc} {descendants} {departmentById} {selected} level={level + 1} on:selected />
|
||||
{/if}
|
||||
<svelte:fragment slot="dropbox">
|
||||
{#if desc.length}
|
||||
<svelte:self departments={desc} {descendants} {departmentById} {selected} level={level + 1} on:selected />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</TreeElement>
|
||||
{/if}
|
||||
{/each}
|
||||
|
@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { Department } from '@hcengineering/hr'
|
||||
import { Scroller, Separator, EditBox } from '@hcengineering/ui'
|
||||
import { Scroller, Separator } from '@hcengineering/ui'
|
||||
import { TreeNode } from '@hcengineering/view-resources'
|
||||
import { NavFooter, NavHeader } from '@hcengineering/workbench-resources'
|
||||
|
||||
@ -33,14 +33,18 @@
|
||||
</script>
|
||||
|
||||
<div class="antiPanel-navigator {appsDirection === 'horizontal' ? 'portrait' : 'landscape'}">
|
||||
<div class="antiPanel-wrap__content">
|
||||
<div class="antiPanel-wrap__content hulyNavPanel-container">
|
||||
<NavHeader label={hr.string.HRApplication} />
|
||||
|
||||
<Scroller shrink>
|
||||
<TreeNode _id={'tree-hr'} label={hr.string.Departments} node>
|
||||
<TreeNode
|
||||
_id={'tree-hr'}
|
||||
label={hr.string.Departments}
|
||||
highlighted={department !== undefined}
|
||||
isFold={department !== undefined}
|
||||
>
|
||||
<DepartmentsHierarchy {departments} {descendants} {departmentById} selected={department} on:selected />
|
||||
</TreeNode>
|
||||
<div class="antiNav-space" />
|
||||
</Scroller>
|
||||
|
||||
<NavFooter />
|
||||
|
@ -23,7 +23,7 @@
|
||||
export let classes: Ref<Class<Doc>>[] = ['contact:class:Contact' as Ref<Class<Doc>>]
|
||||
export let _class: Ref<Class<Doc>> | undefined
|
||||
export let ofClass: Ref<Class<Doc>> | undefined
|
||||
export let level: number = 1
|
||||
export let level: number = 0
|
||||
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
@ -56,6 +56,7 @@
|
||||
{@const clazz = client.getHierarchy().getClass(cl)}
|
||||
{@const desc = getDescendants(cl)}
|
||||
<NavItem
|
||||
_id={clazz._id}
|
||||
label={clazz.label}
|
||||
isFold
|
||||
empty
|
||||
|
@ -119,7 +119,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<Scroller>
|
||||
<NavGroup label={setting.string.Classes} selected={_class !== undefined} categoryName={'classes'} second>
|
||||
<NavGroup
|
||||
label={setting.string.Classes}
|
||||
highlighted={_class !== undefined}
|
||||
categoryName={'classes'}
|
||||
noDivider
|
||||
isFold
|
||||
>
|
||||
<ClassHierarchy
|
||||
{classes}
|
||||
{_class}
|
||||
|
@ -135,9 +135,10 @@
|
||||
{#each categories as _category}
|
||||
{#if _category.extraComponents?.navigation && (_category.expandable ?? _category._id === setting.ids.Setting)}
|
||||
<NavGroup
|
||||
_id={_category._id}
|
||||
label={_category.label}
|
||||
categoryName={_category.name}
|
||||
selected={_category.name === categoryId}
|
||||
highlighted={_category.name === categoryId}
|
||||
tools={_category.extraComponents?.tools}
|
||||
>
|
||||
<Component
|
||||
|
@ -14,7 +14,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Button, IconAdd, showPopup } from '@hcengineering/ui'
|
||||
import { ButtonIcon, IconAdd, showPopup } from '@hcengineering/ui'
|
||||
import CreateSpaceType from './CreateSpaceType.svelte'
|
||||
import { isOwnerOrMaintainer } from '@hcengineering/core'
|
||||
|
||||
@ -24,5 +24,5 @@
|
||||
</script>
|
||||
|
||||
{#if isOwnerOrMaintainer()}
|
||||
<Button id="new-space-type" icon={IconAdd} kind="link" size="small" on:click={handleAdd} />
|
||||
<ButtonIcon id={'new-space-type'} icon={IconAdd} kind={'tertiary'} size={'extra-small'} on:click={handleAdd} />
|
||||
{/if}
|
||||
|
@ -179,13 +179,18 @@
|
||||
|
||||
<div class="flex-col overflow-y-auto">
|
||||
{#each spaces as space (space._id)}
|
||||
<TreeNode label={getEmbeddedLabel(space.name)} actions={async () => await getSpaceActions(space)} parent>
|
||||
{#each getTemplates(templates, space._id) as t (t._id)}
|
||||
{@const getTemps = getTemplates(templates, space._id)}
|
||||
<TreeNode
|
||||
label={getEmbeddedLabel(space.name)}
|
||||
actions={async () => await getSpaceActions(space)}
|
||||
isFold
|
||||
empty={getTemps.length === 0}
|
||||
>
|
||||
{#each getTemps as t (t._id)}
|
||||
<TreeItem
|
||||
_id={space._id}
|
||||
title={t.title}
|
||||
actions={async () => await getActions(t)}
|
||||
indent
|
||||
on:click={() => {
|
||||
selected = t._id
|
||||
title = t.title
|
||||
|
@ -5,6 +5,7 @@
|
||||
"Agenda": "Agenda",
|
||||
"Me": "Me",
|
||||
"Team": "Team",
|
||||
"TeamPlanner": "Team Planner",
|
||||
"Today": "Today",
|
||||
"TodayColon": "Today:",
|
||||
"Tomorrow": "Tomorrow",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"Agenda": "Agenda",
|
||||
"Me": "Yo",
|
||||
"Team": "Equipo",
|
||||
"TeamPlanner": "Planificador de equipos",
|
||||
"Today": "Hoy",
|
||||
"TodayColon": "Hoy:",
|
||||
"Tomorrow": "Mañana",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"Agenda": "Agenda",
|
||||
"Me": "Eu",
|
||||
"Team": "Equipa",
|
||||
"TeamPlanner": "Planificador de equipas",
|
||||
"Today": "Hoje",
|
||||
"TodayColon": "Hoje:",
|
||||
"Tomorrow": "Amanhã",
|
||||
|
@ -5,6 +5,7 @@
|
||||
"Agenda": "Agenda",
|
||||
"Me": "Me",
|
||||
"Team": "Команда",
|
||||
"TeamPlanner": "Планировщик команды",
|
||||
"Today": "Сегодня",
|
||||
"TodayColon": "Сегодня:",
|
||||
"Tomorrow": "Завтра",
|
||||
|
@ -63,6 +63,7 @@
|
||||
"@hcengineering/time": "^0.6.0",
|
||||
"fast-equals": "^5.0.1",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/activity-resources": "^0.6.1"
|
||||
"@hcengineering/activity-resources": "^0.6.1",
|
||||
"@hcengineering/workbench-resources": "^0.6.1"
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
import { Ref, getCurrentAccount } from '@hcengineering/core'
|
||||
import { Asset, IntlString } from '@hcengineering/platform'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { NavFooter } from '@hcengineering/workbench-resources'
|
||||
import tagsPlugin, { TagElement as TagElementType } from '@hcengineering/tags'
|
||||
import ui, {
|
||||
Label,
|
||||
@ -12,8 +13,7 @@
|
||||
Scroller,
|
||||
Month,
|
||||
getPlatformColorDef,
|
||||
themeStore,
|
||||
areDatesEqual
|
||||
themeStore
|
||||
} from '@hcengineering/ui'
|
||||
import { ToDosMode } from '..'
|
||||
import time from '../plugin'
|
||||
@ -126,8 +126,9 @@
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
<div class="min-h-3 flex-no-shrink" />
|
||||
|
||||
<div class="hulyAccordionItem-container border pb-2">
|
||||
<div class="hulyAccordionItem-container border" class:noBorder={tags.length === 0}>
|
||||
<Month
|
||||
currentDate={mode === 'date' ? currentDate : null}
|
||||
on:update={(event) => {
|
||||
@ -162,7 +163,15 @@
|
||||
</div>
|
||||
|
||||
{#if tags.length > 0}
|
||||
<NavGroup label={tagsPlugin.string.Tags} selected={mode === 'tag'} categoryName={'tags'} second>
|
||||
<NavGroup
|
||||
_id={'planner-tags'}
|
||||
label={tagsPlugin.string.Tags}
|
||||
highlighted={mode === 'tag'}
|
||||
categoryName={'tags'}
|
||||
noDivider
|
||||
isFold
|
||||
visible={tag !== undefined}
|
||||
>
|
||||
{#each tags as _tag}
|
||||
{@const color = getPlatformColorDef(_tag.color ?? 0, $themeStore.dark)}
|
||||
<NavItem
|
||||
@ -178,9 +187,19 @@
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
<svelte:fragment slot="visible" let:isOpen>
|
||||
{#if !isOpen}
|
||||
{@const visibleTag = tags.find((t) => t._id === tag)}
|
||||
{#if visibleTag}
|
||||
{@const color = getPlatformColorDef(visibleTag.color ?? 0, $themeStore.dark)}
|
||||
<NavItem color={color.color} title={visibleTag.title} selected type={'type-tag'} />
|
||||
{/if}
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</NavGroup>
|
||||
{/if}
|
||||
</Scroller>
|
||||
<NavFooter />
|
||||
</div>
|
||||
<Separator
|
||||
name={'time'}
|
||||
@ -230,4 +249,7 @@
|
||||
top: 0.1875rem;
|
||||
right: 0.1875rem;
|
||||
}
|
||||
.noBorder {
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
</style>
|
||||
|
@ -16,8 +16,9 @@
|
||||
import { Ref, getCurrentAccount } from '@hcengineering/core'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import task, { Project } from '@hcengineering/task'
|
||||
import { Label, Separator } from '@hcengineering/ui'
|
||||
import { Label, Separator, Scroller, NavItem } from '@hcengineering/ui'
|
||||
import { ObjectPresenter, TreeNode } from '@hcengineering/view-resources'
|
||||
import { NavFooter } from '@hcengineering/workbench-resources'
|
||||
import time from '../../plugin'
|
||||
|
||||
export let navFloat: boolean = false
|
||||
@ -37,30 +38,55 @@
|
||||
projects = result
|
||||
}
|
||||
)
|
||||
$: selectedItem = projects.find((pr) => pr._id === selected)
|
||||
</script>
|
||||
|
||||
<div class="antiPanel-navigator {appsDirection === 'horizontal' ? 'portrait' : 'landscape'}">
|
||||
<div class="antiPanel-wrap__content">
|
||||
<div class="antiNav-header overflow-label">
|
||||
<Label label={time.string.Team} />
|
||||
<Label label={time.string.Planner} />
|
||||
<div class="antiPanel-wrap__content hulyNavPanel-container">
|
||||
<div class="hulyNavPanel-header">
|
||||
<span class="overflow-label"><Label label={time.string.TeamPlanner} /></span>
|
||||
</div>
|
||||
<TreeNode _id={'projects-planning'} label={time.string.Team} node>
|
||||
{#each projects as _project}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="antiNav-element parent"
|
||||
class:selected={selected === _project._id}
|
||||
on:click={() => {
|
||||
selected = _project._id
|
||||
localStorage.setItem('team_last_mode', selected)
|
||||
}}
|
||||
>
|
||||
<ObjectPresenter objectId={_project._id} _class={_project._class} value={_project} />
|
||||
</div>
|
||||
{/each}
|
||||
</TreeNode>
|
||||
<div class="antiNav-divider line" />
|
||||
|
||||
<Scroller shrink>
|
||||
<TreeNode
|
||||
_id={'projects-planning'}
|
||||
label={time.string.Team}
|
||||
isFold
|
||||
empty={projects.length === 0}
|
||||
highlighted={selected !== undefined}
|
||||
visible={selected !== undefined}
|
||||
>
|
||||
{#each projects as _project}
|
||||
<NavItem
|
||||
selected={selected === _project._id}
|
||||
on:click={() => {
|
||||
selected = _project._id
|
||||
localStorage.setItem('team_last_mode', selected)
|
||||
}}
|
||||
>
|
||||
<ObjectPresenter
|
||||
objectId={_project._id}
|
||||
_class={_project._class}
|
||||
value={_project}
|
||||
colorInherit={selected === _project._id}
|
||||
/>
|
||||
</NavItem>
|
||||
{/each}
|
||||
<svelte:fragment slot="visible">
|
||||
{#if selected && selectedItem}
|
||||
<NavItem selected>
|
||||
<ObjectPresenter
|
||||
objectId={selectedItem._id}
|
||||
_class={selectedItem._class}
|
||||
value={selectedItem}
|
||||
colorInherit
|
||||
/>
|
||||
</NavItem>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</TreeNode>
|
||||
</Scroller>
|
||||
<NavFooter />
|
||||
</div>
|
||||
<Separator
|
||||
name={'time'}
|
||||
|
@ -62,6 +62,7 @@ export default mergeIds(timeId, time, {
|
||||
AddTitle: '' as IntlString,
|
||||
MyWork: '' as IntlString,
|
||||
WorkSchedule: '' as IntlString,
|
||||
SummaryDuration: '' as IntlString
|
||||
SummaryDuration: '' as IntlString,
|
||||
TeamPlanner: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -21,21 +21,19 @@
|
||||
Label,
|
||||
getPlatformColorDef,
|
||||
getPlatformColorForTextDef,
|
||||
showPopup,
|
||||
themeStore
|
||||
} from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { canEditSpace } from '@hcengineering/view-resources'
|
||||
import { CreateProject } from '../..'
|
||||
import tracker from '../../plugin'
|
||||
|
||||
export let value: Project | undefined
|
||||
export let inline: boolean = false
|
||||
export let accent: boolean = false
|
||||
export let colorInherit: boolean = false
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
<div class="flex-presenter cursor-default" class:inline-presenter={inline}>
|
||||
<div class="flex-presenter cursor-default" class:inline-presenter={inline} class:colorInherit>
|
||||
<div class="icon" class:emoji={value.icon === view.ids.IconWithEmoji}>
|
||||
<Icon
|
||||
icon={value.icon === view.ids.IconWithEmoji ? IconWithEmoji : value.icon ?? tracker.icon.Home}
|
||||
|
@ -20,8 +20,6 @@
|
||||
IconWithEmoji,
|
||||
getPlatformColorDef,
|
||||
getPlatformColorForTextDef,
|
||||
getTreeCollapsed,
|
||||
setTreeCollapsed,
|
||||
themeStore,
|
||||
type Action
|
||||
} from '@hcengineering/ui'
|
||||
@ -36,9 +34,7 @@
|
||||
export let currentSpecial: string | undefined
|
||||
export let getActions: (space: Project) => Promise<Action[]> = async () => []
|
||||
export let deselect: boolean = false
|
||||
|
||||
let collapsed: boolean = getTreeCollapsed(space._id)
|
||||
$: setTreeCollapsed(space._id, collapsed)
|
||||
export let forciblyСollapsed: boolean = false
|
||||
|
||||
let specials: SpecialNavModel[] = []
|
||||
|
||||
@ -60,11 +56,13 @@
|
||||
}
|
||||
|
||||
$: updateSpecials(model, space)
|
||||
$: visible =
|
||||
(!deselect && currentSpace !== undefined && currentSpecial !== undefined && space._id === currentSpace) ||
|
||||
forciblyСollapsed
|
||||
</script>
|
||||
|
||||
{#if specials}
|
||||
<TreeNode
|
||||
{collapsed}
|
||||
_id={space?._id}
|
||||
icon={space?.icon === view.ids.IconWithEmoji ? IconWithEmoji : space?.icon ?? model.icon}
|
||||
iconProps={space?.icon === view.ids.IconWithEmoji
|
||||
@ -76,9 +74,11 @@
|
||||
: getPlatformColorForTextDef(space.name, $themeStore.dark).icon
|
||||
}}
|
||||
title={space.name}
|
||||
folder
|
||||
type={'nested'}
|
||||
highlighted={space._id === currentSpace}
|
||||
{visible}
|
||||
actions={() => getActions(space)}
|
||||
on:click={() => (collapsed = !collapsed)}
|
||||
{forciblyСollapsed}
|
||||
>
|
||||
{#each specials as special}
|
||||
<NavLink space={space._id} special={special.id}>
|
||||
@ -90,5 +90,16 @@
|
||||
/>
|
||||
</NavLink>
|
||||
{/each}
|
||||
|
||||
<svelte:fragment slot="visible">
|
||||
{#if visible}
|
||||
{@const item = specials.find((sp) => sp.id === currentSpecial && currentSpace === space._id)}
|
||||
{#if item}
|
||||
<NavLink space={space._id} special={item.id}>
|
||||
<SpecialElement indent label={item.label} icon={item.icon} selected forciblyСollapsed />
|
||||
</NavLink>
|
||||
{/if}
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</TreeNode>
|
||||
{/if}
|
||||
|
@ -25,6 +25,7 @@
|
||||
export let props: Record<string, any> = {}
|
||||
export let inline: boolean = false
|
||||
export let accent: boolean = false
|
||||
export let colorInherit: boolean = false
|
||||
export let shouldShowAvatar: boolean = true
|
||||
export let noUnderline: boolean = false
|
||||
export let disabled: boolean = false
|
||||
@ -75,6 +76,7 @@
|
||||
value={doc}
|
||||
{inline}
|
||||
{accent}
|
||||
{colorInherit}
|
||||
{shouldShowAvatar}
|
||||
{shouldShowName}
|
||||
{noUnderline}
|
||||
|
@ -19,36 +19,42 @@
|
||||
import type { Action, AnySvelteComponent, IconSize } from '@hcengineering/ui'
|
||||
import {
|
||||
ActionIcon,
|
||||
Icon,
|
||||
IconChevronDown,
|
||||
IconMoreH,
|
||||
Label,
|
||||
Menu,
|
||||
showPopup,
|
||||
getTreeCollapsed,
|
||||
setTreeCollapsed
|
||||
NavItem,
|
||||
NavGroup,
|
||||
ButtonIcon
|
||||
} from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let _id: Ref<Doc> | string | undefined = undefined
|
||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||
export let folderIcon: boolean = false
|
||||
export let iconProps: Record<string, any> | undefined = undefined
|
||||
export let iconSize: IconSize = 'small'
|
||||
export let label: IntlString | undefined = undefined
|
||||
export let title: string | undefined = undefined
|
||||
export let notifications = 0
|
||||
export let parent: boolean = false
|
||||
export let node: boolean = false
|
||||
export let type: 'default' | 'nested' | 'nested-selectable' = 'default'
|
||||
export let indent: boolean = false
|
||||
export let folder: boolean = false
|
||||
export let isFold: boolean = false
|
||||
export let empty: boolean = false
|
||||
export let visible: boolean = false
|
||||
export let level: number = 0
|
||||
export let collapsed: boolean = getTreeCollapsed(_id)
|
||||
export let collapsedPrefix: string = ''
|
||||
export let collapsed: boolean = getTreeCollapsed(_id, collapsedPrefix)
|
||||
export let highlighted: boolean = false
|
||||
export let selected: boolean = false
|
||||
export let bold: boolean = false
|
||||
export let shouldTooltip: boolean = false
|
||||
export let showMenu: boolean = false
|
||||
export let showNotify: boolean = false
|
||||
export let forciblyСollapsed: boolean = false
|
||||
export let actions: (originalEvent?: MouseEvent) => Promise<Action[]> = async () => []
|
||||
|
||||
let hovered = false
|
||||
let pressed: boolean = false
|
||||
let inlineActions: Action[] = []
|
||||
let popupMenuActions: Action[] = []
|
||||
|
||||
@ -59,96 +65,132 @@
|
||||
|
||||
async function onMenuClick (ev: MouseEvent): Promise<void> {
|
||||
// Read actual popup actions on open as visibility might have been changed
|
||||
pressed = true
|
||||
popupMenuActions = await actions().then((res) => res.filter((action) => action.inline !== true))
|
||||
showPopup(Menu, { actions: popupMenuActions, ctx: _id }, ev.target as HTMLElement, () => {
|
||||
hovered = false
|
||||
pressed = false
|
||||
})
|
||||
hovered = true
|
||||
}
|
||||
|
||||
async function onInlineClick (ev: MouseEvent, action: Action): Promise<void> {
|
||||
action.action([], ev)
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
$: if (_id) collapsed = getTreeCollapsed(_id)
|
||||
$: setTreeCollapsed(_id, collapsed)
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="antiNav-element relative"
|
||||
class:selected
|
||||
class:hovered
|
||||
class:parent
|
||||
class:collapsed
|
||||
style:padding-left={`${level > 0 ? level * 1.25 + 0.125 : indent ? 2.5 : 0.75}rem`}
|
||||
on:click={() => {
|
||||
collapsed = !collapsed
|
||||
dispatch('click')
|
||||
}}
|
||||
>
|
||||
{#if showNotify}
|
||||
<div class="notify" />
|
||||
{/if}
|
||||
{#if icon && !node}
|
||||
<div class="an-element__icon" class:folder>
|
||||
<Icon {icon} {iconProps} size={iconSize} />
|
||||
</div>
|
||||
{/if}
|
||||
<span class="an-element__label" class:title={node} class:bold>
|
||||
{#if label}<Label {label} />{:else}{title}{/if}
|
||||
</span>
|
||||
|
||||
{#if parent}
|
||||
<div class="an-element__tool arrow hidden">
|
||||
<IconChevronDown size={'small'} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="an-element__grow" />
|
||||
|
||||
{#if inlineActions.length > 0}
|
||||
{#each inlineActions as action}
|
||||
<div class="an-element__tool" on:click|preventDefault|stopPropagation={(ev) => onInlineClick(ev, action)}>
|
||||
<Icon icon={action.icon ?? ActionIcon} size={'small'} />
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if popupMenuActions.length === 1 && popupMenuActions[0].icon}
|
||||
<div id={_id} class="an-element__tool">
|
||||
<ActionIcon
|
||||
label={popupMenuActions[0].label}
|
||||
icon={popupMenuActions[0].icon}
|
||||
size={'small'}
|
||||
action={async (ev) => {
|
||||
void popupMenuActions[0].action(_id, ev)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else if popupMenuActions.length > 0}
|
||||
<div class="an-element__tool" class:pressed={hovered} on:click|preventDefault|stopPropagation={onMenuClick}>
|
||||
<IconMoreH size={'small'} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if notifications > 0 && collapsed}
|
||||
<div class="an-element__counter">{notifications}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if parent && !collapsed}
|
||||
<div class="antiNav-element__dropbox"><slot /></div>
|
||||
{#if parent}
|
||||
<NavGroup
|
||||
{_id}
|
||||
categoryName={_id ?? 'nav'}
|
||||
{icon}
|
||||
{folderIcon}
|
||||
{iconProps}
|
||||
{iconSize}
|
||||
{label}
|
||||
{title}
|
||||
{highlighted}
|
||||
{selected}
|
||||
isOpen={!collapsed}
|
||||
{collapsedPrefix}
|
||||
{type}
|
||||
{isFold}
|
||||
{empty}
|
||||
{visible}
|
||||
{forciblyСollapsed}
|
||||
{shouldTooltip}
|
||||
showMenu={showMenu || pressed}
|
||||
on:click
|
||||
on:toggle={(ev) => {
|
||||
if (ev.detail !== undefined) collapsed = !ev.detail
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="extra"><slot name="extra" /></svelte:fragment>
|
||||
<svelte:fragment slot="tools">
|
||||
{#if inlineActions.length > 0}
|
||||
{#each inlineActions as action}
|
||||
<ButtonIcon
|
||||
icon={action.icon ?? ActionIcon}
|
||||
size={'extra-small'}
|
||||
kind={'tertiary'}
|
||||
on:click={(ev) => onInlineClick(ev, action)}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if popupMenuActions.length === 1 && popupMenuActions[0].icon}
|
||||
<ButtonIcon
|
||||
id={_id}
|
||||
icon={popupMenuActions[0].icon}
|
||||
size={'extra-small'}
|
||||
kind={'tertiary'}
|
||||
tooltip={{ label: popupMenuActions[0].label, direction: 'top' }}
|
||||
on:click={async (ev) => {
|
||||
void popupMenuActions[0].action(_id, ev)
|
||||
}}
|
||||
/>
|
||||
{:else if popupMenuActions.length > 0}
|
||||
<ButtonIcon icon={IconMoreH} size={'extra-small'} kind={'tertiary'} {pressed} on:click={onMenuClick} />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="visible"><slot name="visible" /></svelte:fragment>
|
||||
<slot />
|
||||
</NavGroup>
|
||||
{:else}
|
||||
<NavItem
|
||||
{_id}
|
||||
{label}
|
||||
{title}
|
||||
{icon}
|
||||
{folderIcon}
|
||||
{iconProps}
|
||||
{iconSize}
|
||||
{selected}
|
||||
{bold}
|
||||
{indent}
|
||||
isOpen={!collapsed}
|
||||
{collapsedPrefix}
|
||||
{isFold}
|
||||
{empty}
|
||||
{visible}
|
||||
{forciblyСollapsed}
|
||||
{level}
|
||||
{shouldTooltip}
|
||||
showMenu={showMenu || pressed}
|
||||
on:click
|
||||
>
|
||||
<slot />
|
||||
<svelte:fragment slot="extra"><slot name="extra" /></svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
{#if $$slots.actions}<slot name="actions" />{/if}
|
||||
{#if inlineActions.length > 0}
|
||||
{#each inlineActions as action}
|
||||
<ButtonIcon
|
||||
icon={action.icon ?? ActionIcon}
|
||||
size={'extra-small'}
|
||||
kind={'tertiary'}
|
||||
on:click={(ev) => onInlineClick(ev, action)}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if popupMenuActions.length === 1 && popupMenuActions[0].icon}
|
||||
<ButtonIcon
|
||||
id={_id}
|
||||
icon={popupMenuActions[0].icon}
|
||||
size={'extra-small'}
|
||||
kind={'tertiary'}
|
||||
tooltip={{ label: popupMenuActions[0].label, direction: 'top' }}
|
||||
on:click={async (ev) => {
|
||||
void popupMenuActions[0].action(_id, ev)
|
||||
}}
|
||||
/>
|
||||
{:else if popupMenuActions.length > 0}
|
||||
<ButtonIcon icon={IconMoreH} size={'extra-small'} kind={'tertiary'} {pressed} on:click={onMenuClick} />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="notify">
|
||||
{#if $$slots.notify}<slot name="notify" />{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="dropbox">
|
||||
{#if isFold}<slot name="dropbox" />{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="visible"><slot name="visible" /></svelte:fragment>
|
||||
</NavItem>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.notify {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
left: 1.5rem;
|
||||
height: 0.5rem;
|
||||
width: 0.5rem;
|
||||
background-color: var(--theme-inbox-notify);
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
|
@ -19,31 +19,50 @@
|
||||
import TreeElement from './TreeElement.svelte'
|
||||
import { AnySvelteComponent } from '@hcengineering/ui'
|
||||
|
||||
export let _id: Ref<Doc>
|
||||
export let _id: Ref<Doc> | undefined = undefined
|
||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||
export let folderIcon: boolean = false
|
||||
export let iconProps: Record<string, any> | undefined = undefined
|
||||
export let iconSize: IconSize = 'small'
|
||||
export let title: string
|
||||
export let notifications = 0
|
||||
export let actions: (originalEvent?: MouseEvent) => Promise<Action[]> = async () => []
|
||||
export let selected: boolean = false
|
||||
export let showMenu: boolean = false
|
||||
export let empty: boolean = false
|
||||
export let bold: boolean = false
|
||||
export let indent: boolean = false
|
||||
export let isFold: boolean = false
|
||||
export let level: number = 0
|
||||
export let shouldTooltip: boolean = false
|
||||
export let showNotify: boolean = false
|
||||
export let forciblyСollapsed: boolean = false
|
||||
export let collapsedPrefix: string = ''
|
||||
</script>
|
||||
|
||||
<TreeElement
|
||||
{_id}
|
||||
{icon}
|
||||
{folderIcon}
|
||||
{iconSize}
|
||||
{iconProps}
|
||||
{title}
|
||||
{notifications}
|
||||
{selected}
|
||||
{actions}
|
||||
{iconProps}
|
||||
{bold}
|
||||
{indent}
|
||||
{showNotify}
|
||||
collapsed
|
||||
{shouldTooltip}
|
||||
{isFold}
|
||||
{empty}
|
||||
{collapsedPrefix}
|
||||
{level}
|
||||
{showMenu}
|
||||
{forciblyСollapsed}
|
||||
on:click
|
||||
/>
|
||||
>
|
||||
<svelte:fragment slot="extra"><slot name="extra" /></svelte:fragment>
|
||||
<svelte:fragment slot="dropbox"><slot name="dropbox" /></svelte:fragment>
|
||||
<svelte:fragment slot="visible"><slot name="visible" /></svelte:fragment>
|
||||
</TreeElement>
|
||||
|
@ -22,13 +22,22 @@
|
||||
export let title: string | undefined = undefined
|
||||
export let label: IntlString | undefined = undefined
|
||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||
export let folderIcon: boolean = false
|
||||
export let iconProps: Record<string, any> | undefined = undefined
|
||||
export let actions: () => Promise<Action[]> = async () => []
|
||||
export let notifications: number = 0
|
||||
export let parent: boolean = true
|
||||
export let node: boolean = false
|
||||
export let folder: boolean = false
|
||||
export let type: 'default' | 'nested' | 'nested-selectable' = 'default'
|
||||
export let isFold: boolean = false
|
||||
export let empty: boolean = false
|
||||
export let visible: boolean = false
|
||||
export let collapsed: boolean = false
|
||||
export let collapsedPrefix: string = ''
|
||||
export let highlighted: boolean = false
|
||||
export let selected: boolean = false
|
||||
export let showMenu: boolean = false
|
||||
export let shouldTooltip: boolean = false
|
||||
export let forciblyСollapsed: boolean = false
|
||||
</script>
|
||||
|
||||
<TreeElement
|
||||
@ -37,13 +46,24 @@
|
||||
{label}
|
||||
{iconProps}
|
||||
{icon}
|
||||
{folderIcon}
|
||||
{notifications}
|
||||
{collapsed}
|
||||
bind:collapsed
|
||||
{collapsedPrefix}
|
||||
{actions}
|
||||
{node}
|
||||
{folder}
|
||||
{type}
|
||||
{isFold}
|
||||
{empty}
|
||||
{visible}
|
||||
{parent}
|
||||
{highlighted}
|
||||
{selected}
|
||||
{shouldTooltip}
|
||||
{showMenu}
|
||||
{forciblyСollapsed}
|
||||
on:click
|
||||
>
|
||||
<slot />
|
||||
<svelte:fragment slot="extra"><slot name="extra" /></svelte:fragment>
|
||||
<svelte:fragment slot="visible"><slot name="visible" /></svelte:fragment>
|
||||
</TreeElement>
|
||||
|
@ -17,8 +17,10 @@
|
||||
import { Label } from '@hcengineering/ui'
|
||||
|
||||
export let label: IntlString
|
||||
export let withButton: boolean = false
|
||||
</script>
|
||||
|
||||
<div class="antiNav-header overflow-label">
|
||||
<Label {label} />
|
||||
<div class="hulyNavPanel-header" class:withButton>
|
||||
<span class="overflow-label"><Label {label} /></span>
|
||||
<slot />
|
||||
</div>
|
||||
|
@ -17,12 +17,11 @@
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import preference, { SpacePreference } from '@hcengineering/preference'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Scroller } from '@hcengineering/ui'
|
||||
import { Scroller, NavItem } from '@hcengineering/ui'
|
||||
import { NavLink } from '@hcengineering/view-resources'
|
||||
import type { Application, NavigatorModel, SpecialNavModel } from '@hcengineering/workbench'
|
||||
import { getSpecialSpaceClass } from '../utils'
|
||||
import SpacesNav from './navigator/SpacesNav.svelte'
|
||||
import SpecialElement from './navigator/SpecialElement.svelte'
|
||||
import StarredNav from './navigator/StarredNav.svelte'
|
||||
import TreeSeparator from './navigator/TreeSeparator.svelte'
|
||||
import SavedView from './SavedView.svelte'
|
||||
@ -150,18 +149,20 @@
|
||||
{/if}
|
||||
{#await checkIsDisabled(special) then disabled}
|
||||
<NavLink space={special.id} {disabled}>
|
||||
<SpecialElement
|
||||
<NavItem
|
||||
label={special.label}
|
||||
icon={special.icon}
|
||||
selected={menuSelection ? false : special.id === currentSpecial}
|
||||
selected={menuSelection
|
||||
? false
|
||||
: special.id === currentSpecial && (currentFragment === undefined || currentFragment === '')}
|
||||
{disabled}
|
||||
/>
|
||||
</NavLink>
|
||||
{/await}
|
||||
{/each}
|
||||
{/if}
|
||||
<div class="min-h-3 flex-no-shrink" />
|
||||
|
||||
{#if specials.length > 0 && (starred.length > 0 || savedMenu)}<TreeSeparator line />{/if}
|
||||
<SavedView
|
||||
{currentApplication}
|
||||
on:shown={(res) => (savedMenu = res.detail)}
|
||||
@ -181,7 +182,6 @@
|
||||
{/if}
|
||||
|
||||
{#each model.spaces as m, i (m.label)}
|
||||
{#if (i === 0 && (specials.length > 0 || starred.length > 0 || savedMenu)) || i !== 0}<TreeSeparator line />{/if}
|
||||
<SpacesNav
|
||||
spaces={shownSpaces.filter((it) => hierarchy.isDerived(it._class, m.spaceClass))}
|
||||
{currentSpace}
|
||||
@ -190,9 +190,8 @@
|
||||
on:open
|
||||
{currentSpecial}
|
||||
{currentFragment}
|
||||
deselect={menuSelection}
|
||||
deselect={menuSelection || starred.some((s) => s._id === currentSpace)}
|
||||
/>
|
||||
{/each}
|
||||
<div class="antiNav-space" />
|
||||
</Scroller>
|
||||
{/if}
|
||||
|
@ -264,14 +264,18 @@
|
||||
return []
|
||||
}
|
||||
}
|
||||
$: visibleFilter = myFilteredViews.find((fv) => fv._id === selectedId)
|
||||
</script>
|
||||
|
||||
{#if shown}
|
||||
<TreeNode
|
||||
_id={'tree-saved'}
|
||||
label={view.string.FilteredViews}
|
||||
node
|
||||
highlighted={selectedId !== undefined}
|
||||
actions={async () => await getActions(availableFilteredViews)}
|
||||
isFold
|
||||
empty={myFilteredViews.length === 0}
|
||||
visible={selectedId !== undefined}
|
||||
>
|
||||
{#each myFilteredViews as fv}
|
||||
<TreeItem
|
||||
@ -284,5 +288,12 @@
|
||||
actions={async (ov) => await viewAction(fv, ov)}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<svelte:fragment slot="visible">
|
||||
{#if visibleFilter}
|
||||
{@const item = visibleFilter}
|
||||
<TreeItem _id={item._id} title={item.name} selected actions={async (ov) => await viewAction(item, ov)} />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</TreeNode>
|
||||
{/if}
|
||||
|
@ -260,7 +260,13 @@
|
||||
loc.path[4] = (currentSpecial as string) ?? resolved.defaultLocation.path[4]
|
||||
} else {
|
||||
loc.path[3] = resolvedSpace
|
||||
loc.path[4] = resolvedSpecial ?? currentSpecial ?? resolved.defaultLocation.path[4]
|
||||
if (resolvedSpecial) {
|
||||
loc.path[4] = resolvedSpecial
|
||||
} else if (currentSpace && currentSpecial) {
|
||||
loc.path[4] = currentSpecial
|
||||
} else {
|
||||
loc.path[4] = resolved.defaultLocation.path[4]
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let index = 0; index < loc.path.length; index++) {
|
||||
@ -728,7 +734,7 @@
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
{#if navFloat}<div class="cover shown" on:click={() => (visibleNav = false)} />{/if}
|
||||
<div class="antiPanel-navigator no-print {appsDirection === 'horizontal' ? 'portrait' : 'landscape'}">
|
||||
<div class="antiPanel-wrap__content">
|
||||
<div class="antiPanel-wrap__content hulyNavPanel-container">
|
||||
{#if currentApplication}
|
||||
<NavHeader label={currentApplication.label} />
|
||||
{#if currentApplication.navHeaderComponent}
|
||||
|
@ -15,7 +15,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Doc, Ref, Space } from '@hcengineering/core'
|
||||
import core, { AccountRole, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
|
||||
import { AccountRole, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
|
||||
import { IntlString, getResource } from '@hcengineering/platform'
|
||||
import preference from '@hcengineering/preference'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
@ -23,28 +23,20 @@
|
||||
Action,
|
||||
AnyComponent,
|
||||
IconAdd,
|
||||
IconEdit,
|
||||
IconSearch,
|
||||
getCurrentResolvedLocation,
|
||||
navigate,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import {
|
||||
NavLink,
|
||||
TreeItem,
|
||||
TreeNode,
|
||||
getActions as getContributedActions,
|
||||
getSpacePresenter,
|
||||
classIcon
|
||||
} from '@hcengineering/view-resources'
|
||||
import { TreeNode } from '@hcengineering/view-resources'
|
||||
import { SpacesNavModel } from '@hcengineering/workbench'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
|
||||
import { DocNotifyContext, InboxNotification } from '@hcengineering/notification'
|
||||
|
||||
import plugin from '../../plugin'
|
||||
import { getSpaceName } from '../../utils'
|
||||
import TreeSeparator from './TreeSeparator.svelte'
|
||||
import SpacesNavItem from './SpacesNavItem.svelte'
|
||||
|
||||
export let model: SpacesNavModel
|
||||
export let currentSpace: Ref<Space> | undefined
|
||||
@ -89,24 +81,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function getActions (space: Space): Promise<Action[]> {
|
||||
const result = [starSpace]
|
||||
|
||||
const extraActions = await getContributedActions(client, space, core.class.Space)
|
||||
for (const act of extraActions) {
|
||||
result.push({
|
||||
icon: act.icon ?? IconEdit,
|
||||
label: act.label,
|
||||
inline: act.inline,
|
||||
action: async (ctx: any, evt: Event) => {
|
||||
const impl = await getResource(act.action)
|
||||
await impl(space, evt, act.actionProps)
|
||||
}
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const inboxClient = InboxNotificationsClientImpl.getClient()
|
||||
const notifyContextByDocStore = inboxClient.contextByDoc
|
||||
const inboxNotificationsByContextStore = inboxClient.inboxNotificationsByContext
|
||||
@ -164,38 +138,51 @@
|
||||
} else {
|
||||
filteredSpaces = spaces
|
||||
}
|
||||
$: visibleSpace = filteredSpaces.find((fs) => fs._id === currentSpace)
|
||||
$: empty = filteredSpaces.length === 0 || filteredSpaces === undefined
|
||||
$: visible =
|
||||
visibleSpace !== undefined &&
|
||||
(currentSpecial !== undefined || currentFragment !== undefined || currentFragment !== '') &&
|
||||
!deselect &&
|
||||
!empty
|
||||
</script>
|
||||
|
||||
<TreeNode _id={'tree-' + model.id} label={model.label} node actions={async () => getParentActions()}>
|
||||
<TreeNode
|
||||
_id={'tree-' + model.id}
|
||||
label={model.label}
|
||||
actions={async () => getParentActions()}
|
||||
highlighted={visible}
|
||||
isFold={!empty}
|
||||
{empty}
|
||||
{visible}
|
||||
>
|
||||
{#each filteredSpaces as space, i (space._id)}
|
||||
{#await getSpacePresenter(client, space._class) then presenter}
|
||||
{#if separate && model.specials && i !== 0}<TreeSeparator line />{/if}
|
||||
{#if model.specials && presenter}
|
||||
<svelte:component
|
||||
this={presenter}
|
||||
{space}
|
||||
{model}
|
||||
{currentSpace}
|
||||
{currentSpecial}
|
||||
{currentFragment}
|
||||
{getActions}
|
||||
{deselect}
|
||||
/>
|
||||
{:else}
|
||||
<NavLink space={space._id}>
|
||||
{#await getSpaceName(client, space) then name}
|
||||
<TreeItem
|
||||
_id={space._id}
|
||||
title={name}
|
||||
icon={classIcon(client, space._class)}
|
||||
selected={deselect ? false : currentSpace === space._id}
|
||||
actions={async () => await getActions(space)}
|
||||
bold={isChanged(space, $notifyContextByDocStore, $inboxNotificationsByContextStore)}
|
||||
indent
|
||||
/>
|
||||
{/await}
|
||||
</NavLink>
|
||||
{/if}
|
||||
{/await}
|
||||
{#if separate && model.specials && i !== 0}<TreeSeparator line />{/if}
|
||||
<SpacesNavItem
|
||||
{model}
|
||||
{space}
|
||||
{currentSpace}
|
||||
{currentSpecial}
|
||||
{currentFragment}
|
||||
{deselect}
|
||||
isChanged={isChanged(space, $notifyContextByDocStore, $inboxNotificationsByContextStore)}
|
||||
spaceActions={[starSpace]}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<svelte:fragment slot="visible">
|
||||
{#if visibleSpace}
|
||||
<SpacesNavItem
|
||||
{model}
|
||||
space={visibleSpace}
|
||||
{currentSpace}
|
||||
{currentSpecial}
|
||||
{currentFragment}
|
||||
{deselect}
|
||||
isChanged={isChanged(visibleSpace, $notifyContextByDocStore, $inboxNotificationsByContextStore)}
|
||||
spaceActions={[starSpace]}
|
||||
forciblyСollapsed
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</TreeNode>
|
||||
|
@ -0,0 +1,93 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Ref, Space } from '@hcengineering/core'
|
||||
import core from '@hcengineering/core'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Action, IconEdit } from '@hcengineering/ui'
|
||||
import {
|
||||
NavLink,
|
||||
TreeItem,
|
||||
getActions as getContributedActions,
|
||||
getSpacePresenter,
|
||||
classIcon
|
||||
} from '@hcengineering/view-resources'
|
||||
import { SpacesNavModel } from '@hcengineering/workbench'
|
||||
import { getSpaceName } from '../../utils'
|
||||
|
||||
export let model: SpacesNavModel
|
||||
export let currentSpace: Ref<Space> | undefined
|
||||
export let space: Space
|
||||
export let currentSpecial: string | undefined
|
||||
export let currentFragment: string | undefined
|
||||
export let deselect: boolean = false
|
||||
export let isChanged: boolean = false
|
||||
export let spaceActions: Action[] | undefined
|
||||
export let forciblyСollapsed: boolean = false
|
||||
|
||||
const client = getClient()
|
||||
|
||||
async function getActions (space: Space): Promise<Action[]> {
|
||||
const result = [...(spaceActions ?? [])]
|
||||
|
||||
const extraActions = await getContributedActions(client, space, core.class.Space)
|
||||
for (const act of extraActions) {
|
||||
result.push({
|
||||
icon: act.icon ?? IconEdit,
|
||||
label: act.label,
|
||||
inline: act.inline,
|
||||
action: async (ctx: any, evt: Event) => {
|
||||
const impl = await getResource(act.action)
|
||||
await impl(space, evt, act.actionProps)
|
||||
}
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await getSpacePresenter(client, space._class) then presenter}
|
||||
{#if model.specials && presenter}
|
||||
<svelte:component
|
||||
this={presenter}
|
||||
{space}
|
||||
{model}
|
||||
{currentSpace}
|
||||
{currentSpecial}
|
||||
{currentFragment}
|
||||
{getActions}
|
||||
{deselect}
|
||||
{forciblyСollapsed}
|
||||
selected={deselect ? false : currentSpace === space._id}
|
||||
/>
|
||||
{:else}
|
||||
<NavLink space={space._id}>
|
||||
{#await getSpaceName(client, space) then name}
|
||||
<TreeItem
|
||||
_id={space._id}
|
||||
title={name}
|
||||
icon={classIcon(client, space._class)}
|
||||
selected={deselect ? false : currentSpace === space._id}
|
||||
actions={async () => await getActions(space)}
|
||||
bold={isChanged}
|
||||
{forciblyСollapsed}
|
||||
indent
|
||||
/>
|
||||
{/await}
|
||||
</NavLink>
|
||||
{/if}
|
||||
{/await}
|
@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import type { Asset, IntlString } from '@hcengineering/platform'
|
||||
import type { Action } from '@hcengineering/ui'
|
||||
import { ActionIcon, Icon, Label } from '@hcengineering/ui'
|
||||
import { ButtonIcon, NavItem } from '@hcengineering/ui'
|
||||
|
||||
export let icon: Asset | undefined = undefined
|
||||
export let iconProps: Record<string, any> | undefined = undefined
|
||||
@ -25,35 +25,33 @@
|
||||
export let selected: boolean = false
|
||||
export let disabled: boolean = false
|
||||
export let indent: boolean = false
|
||||
export let forciblyСollapsed: boolean = false
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="antiNav-element" class:selected class:disabled class:indent>
|
||||
<div class="an-element__icon">
|
||||
{#if icon}
|
||||
<Icon {icon} size={'small'} {iconProps} />
|
||||
{/if}
|
||||
</div>
|
||||
<span class="an-element__label">
|
||||
{#if label}<Label {label} />{/if}
|
||||
</span>
|
||||
<div class="an-element__grow" />
|
||||
{#each actions as action}
|
||||
{#if action.icon}
|
||||
<div class="an-element__tool">
|
||||
<ActionIcon
|
||||
label={action.label}
|
||||
<NavItem
|
||||
{icon}
|
||||
{iconProps}
|
||||
iconSize={'small'}
|
||||
{label}
|
||||
count={notifications > 0 ? notifications : null}
|
||||
{selected}
|
||||
{disabled}
|
||||
{indent}
|
||||
{forciblyСollapsed}
|
||||
>
|
||||
<svelte:fragment slot="actions">
|
||||
{#each actions as action}
|
||||
{#if action.icon}
|
||||
<ButtonIcon
|
||||
icon={action.icon}
|
||||
size={'small'}
|
||||
action={async (evt) => {
|
||||
kind={'tertiary'}
|
||||
size={'extra-small'}
|
||||
tooltip={{ label: action.label }}
|
||||
on:click={async (evt) => {
|
||||
await action.action({}, evt)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if notifications > 0}
|
||||
<div class="an-element__counter">{notifications}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</svelte:fragment>
|
||||
</NavItem>
|
||||
|
@ -14,24 +14,16 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Class, Doc, Ref, Space } from '@hcengineering/core'
|
||||
import core from '@hcengineering/core'
|
||||
import { DocNotifyContext, InboxNotification } from '@hcengineering/notification'
|
||||
import { InboxNotificationsClientImpl } from '@hcengineering/notification-resources'
|
||||
import { IntlString, getResource } from '@hcengineering/platform'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import preference from '@hcengineering/preference'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Action, IconEdit } from '@hcengineering/ui'
|
||||
import { Action } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import {
|
||||
NavLink,
|
||||
TreeItem,
|
||||
TreeNode,
|
||||
getActions as getContributedActions,
|
||||
getSpacePresenter,
|
||||
classIcon
|
||||
} from '@hcengineering/view-resources'
|
||||
import { TreeNode } from '@hcengineering/view-resources'
|
||||
import { SpacesNavModel } from '@hcengineering/workbench'
|
||||
import { getSpaceName } from '../../utils'
|
||||
import StarredNavItem from './StarredNavItem.svelte'
|
||||
|
||||
export let label: IntlString
|
||||
export let spaces: Space[]
|
||||
@ -43,17 +35,6 @@
|
||||
|
||||
const client = getClient()
|
||||
|
||||
const unStarSpace: Action = {
|
||||
label: preference.string.Unstar,
|
||||
icon: preference.icon.Star,
|
||||
action: async (_id: Ref<Doc>): Promise<void> => {
|
||||
const current = await client.findOne(preference.class.SpacePreference, { attachedTo: _id as Ref<Space> })
|
||||
if (current !== undefined) {
|
||||
await client.remove(current)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const unStarAll: Action = {
|
||||
label: preference.string.DeleteStarred,
|
||||
icon: view.icon.Delete,
|
||||
@ -69,23 +50,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function getActions (space: Space): Promise<Action[]> {
|
||||
const result = [unStarSpace]
|
||||
|
||||
const extraActions = await getContributedActions(client, space, core.class.Space)
|
||||
for (const act of extraActions) {
|
||||
result.push({
|
||||
icon: act.icon ?? IconEdit,
|
||||
label: act.label,
|
||||
action: async (ctx: any, evt: Event) => {
|
||||
const impl = await getResource(act.action)
|
||||
await impl(space, evt, act.actionProps)
|
||||
}
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function getSpaceModel (space: Ref<Class<Space>>): SpacesNavModel | undefined {
|
||||
const hierarchy = client.getHierarchy()
|
||||
const ancestors = [space, ...[...hierarchy.getAncestors(space)].reverse()]
|
||||
@ -109,37 +73,44 @@
|
||||
if (notifyContext === undefined) return false
|
||||
return !notifyContext.hidden && !!inboxNotificationsByContext.get(notifyContext._id)?.length
|
||||
}
|
||||
$: visibleSpace = spaces.find((space) => currentSpace === space._id)
|
||||
</script>
|
||||
|
||||
<TreeNode _id={'tree-stared'} {label} node actions={async () => [unStarAll]}>
|
||||
<TreeNode
|
||||
_id={'tree-stared'}
|
||||
{label}
|
||||
actions={async () => [unStarAll]}
|
||||
highlighted={spaces.some((s) => s._id === currentSpace) && !deselect}
|
||||
isFold
|
||||
empty={spaces.length === 0}
|
||||
visible={visibleSpace !== undefined && !deselect}
|
||||
>
|
||||
{#each spaces as space (space._id)}
|
||||
{@const model = getSpaceModel(space._class)}
|
||||
{#await getSpacePresenter(client, space._class) then presenter}
|
||||
{#if presenter && model}
|
||||
<svelte:component
|
||||
this={presenter}
|
||||
{space}
|
||||
{model}
|
||||
{currentSpace}
|
||||
{currentSpecial}
|
||||
{currentFragment}
|
||||
{getActions}
|
||||
{deselect}
|
||||
/>
|
||||
{:else}
|
||||
{#await getSpaceName(client, space) then name}
|
||||
<NavLink space={space._id}>
|
||||
<TreeItem
|
||||
_id={space._id}
|
||||
title={name}
|
||||
icon={classIcon(client, space._class)}
|
||||
selected={currentSpace === space._id}
|
||||
actions={async () => await getActions(space)}
|
||||
bold={isChanged(space, $notifyContextByDocStore, $inboxNotificationsByContextStore)}
|
||||
/>
|
||||
</NavLink>
|
||||
{/await}
|
||||
{/if}
|
||||
{/await}
|
||||
<StarredNavItem
|
||||
{space}
|
||||
{model}
|
||||
{currentSpace}
|
||||
{currentSpecial}
|
||||
{currentFragment}
|
||||
{deselect}
|
||||
isChanged={isChanged(space, $notifyContextByDocStore, $inboxNotificationsByContextStore)}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<svelte:fragment slot="visible">
|
||||
{#if visibleSpace}
|
||||
{@const model = getSpaceModel(visibleSpace._class)}
|
||||
<StarredNavItem
|
||||
space={visibleSpace}
|
||||
{model}
|
||||
{currentSpace}
|
||||
{currentSpecial}
|
||||
{currentFragment}
|
||||
{deselect}
|
||||
isChanged={isChanged(visibleSpace, $notifyContextByDocStore, $inboxNotificationsByContextStore)}
|
||||
forciblyСollapsed
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</TreeNode>
|
||||
|
@ -0,0 +1,101 @@
|
||||
<!--
|
||||
// Copyright © 2022, 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Doc, Ref, Space } from '@hcengineering/core'
|
||||
import core from '@hcengineering/core'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import preference from '@hcengineering/preference'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Action, IconEdit } from '@hcengineering/ui'
|
||||
import {
|
||||
NavLink,
|
||||
TreeItem,
|
||||
getActions as getContributedActions,
|
||||
getSpacePresenter,
|
||||
classIcon
|
||||
} from '@hcengineering/view-resources'
|
||||
import { SpacesNavModel } from '@hcengineering/workbench'
|
||||
import { getSpaceName } from '../../utils'
|
||||
|
||||
export let space: Space
|
||||
export let model: SpacesNavModel | undefined
|
||||
export let currentSpace: Ref<Space> | undefined
|
||||
export let currentSpecial: string | undefined
|
||||
export let currentFragment: string | undefined
|
||||
export let deselect: boolean = false
|
||||
export let isChanged: boolean = false
|
||||
export let forciblyСollapsed: boolean = false
|
||||
|
||||
const client = getClient()
|
||||
|
||||
const unStarSpace: Action = {
|
||||
label: preference.string.Unstar,
|
||||
icon: preference.icon.Star,
|
||||
action: async (_id: Ref<Doc>): Promise<void> => {
|
||||
const current = await client.findOne(preference.class.SpacePreference, { attachedTo: _id as Ref<Space> })
|
||||
if (current !== undefined) {
|
||||
await client.remove(current)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getActions (space: Space): Promise<Action[]> {
|
||||
const result = [unStarSpace]
|
||||
|
||||
const extraActions = await getContributedActions(client, space, core.class.Space)
|
||||
for (const act of extraActions) {
|
||||
result.push({
|
||||
icon: act.icon ?? IconEdit,
|
||||
label: act.label,
|
||||
action: async (ctx: any, evt: Event) => {
|
||||
const impl = await getResource(act.action)
|
||||
await impl(space, evt, act.actionProps)
|
||||
}
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await getSpacePresenter(client, space._class) then presenter}
|
||||
{#if presenter && model}
|
||||
<svelte:component
|
||||
this={presenter}
|
||||
{space}
|
||||
{model}
|
||||
{currentSpace}
|
||||
{currentSpecial}
|
||||
{currentFragment}
|
||||
{getActions}
|
||||
{deselect}
|
||||
{forciblyСollapsed}
|
||||
type={'nested'}
|
||||
/>
|
||||
{:else}
|
||||
{#await getSpaceName(client, space) then name}
|
||||
<NavLink space={space._id}>
|
||||
<TreeItem
|
||||
_id={space._id}
|
||||
title={name}
|
||||
icon={classIcon(client, space._class)}
|
||||
selected={currentSpace === space._id}
|
||||
actions={async () => await getActions(space)}
|
||||
bold={isChanged}
|
||||
{forciblyСollapsed}
|
||||
/>
|
||||
</NavLink>
|
||||
{/await}
|
||||
{/if}
|
||||
{/await}
|
@ -20,9 +20,11 @@ export class ContractPage {
|
||||
|
||||
readonly appContact = (): Locator => this.page.locator('[id="app-contact\\:string\\:Contacts"]')
|
||||
readonly employeeNavElement = (Employee: string): Locator =>
|
||||
this.page.locator(`.antiNav-element:has-text("${Employee}")`)
|
||||
this.page.locator(`.hulyNavItem-container:has-text("${Employee}")`)
|
||||
|
||||
readonly employeeButton = (Employee: string): Locator =>
|
||||
this.page.locator(`button:not(.hulyNavItem-container):has-text("${Employee}")`)
|
||||
|
||||
readonly employeeButton = (Employee: string): Locator => this.page.locator(`button:has-text("${Employee}")`)
|
||||
readonly firstNameInput = (): Locator => this.page.locator('[placeholder="First name"]')
|
||||
readonly lastNameInput = (): Locator => this.page.locator('[placeholder="Last name"]')
|
||||
readonly emailInput = (): Locator => this.page.locator('[placeholder="Email"]')
|
||||
@ -54,8 +56,8 @@ export class ContractPage {
|
||||
readonly personName = (person: string): Locator => this.page.locator(`text=${person}`)
|
||||
readonly personTable = (): Locator => this.page.locator('.antiTable-body__row')
|
||||
readonly personMarina = (): Locator => this.page.getByRole('link', { name: 'MM M. Marina' })
|
||||
readonly comapnyTab = (): Locator => this.page.locator('.antiNav-element:has-text("Company")')
|
||||
readonly addCompany = (): Locator => this.page.locator('button:has-text("Company")')
|
||||
readonly comapnyTab = (): Locator => this.page.locator('.hulyNavItem-container:has-text("Company")')
|
||||
readonly addCompany = (): Locator => this.page.locator('button.antiButton:has-text("Company")')
|
||||
readonly companyName = (): Locator => this.page.locator('[placeholder="Company name"]')
|
||||
readonly companyCreateButton = (): Locator => this.page.locator('button:has-text("Create")')
|
||||
readonly companyByName = (company: string): Locator => this.page.locator(`text=${company}`)
|
||||
|
@ -20,8 +20,10 @@ export class DocumentsPage extends CommonPage {
|
||||
readonly buttonCreateDocument = (): Locator =>
|
||||
this.page.locator('div[data-float="navigator"] button[id="new-document"]')
|
||||
|
||||
readonly divTeamspacesParent = (): Locator => this.page.locator('div#tree-teamspaces').locator('xpath=..')
|
||||
readonly buttonCreateTeamspace = (): Locator => this.page.locator('div#tree-teamspaces > button')
|
||||
readonly divTeamspacesParent = (): Locator =>
|
||||
this.page.locator('div#navGroup-tree-teamspaces').locator('xpath=../button[1]')
|
||||
|
||||
readonly buttonCreateTeamspace = (): Locator => this.page.locator('button#tree-teamspaces')
|
||||
readonly formNewTeamspace = (): Locator => this.page.locator('form[id="document:string:NewTeamspace"]')
|
||||
readonly formEditTeamspace = (): Locator => this.page.locator('form[id="document:string:EditTeamspace"]')
|
||||
readonly inputModalNewTeamspaceTitle = (): Locator =>
|
||||
@ -62,25 +64,31 @@ export class DocumentsPage extends CommonPage {
|
||||
|
||||
async openTeamspace (name: string): Promise<void> {
|
||||
const classes = await this.page
|
||||
.locator('div.antiNav-element span[class*="label"]', { hasText: name })
|
||||
.locator('xpath=..')
|
||||
.locator('button.hulyNavGroup-header span[class*="label"]', { hasText: name })
|
||||
.locator('xpath=../..')
|
||||
.getAttribute('class')
|
||||
if (classes != null && classes.includes('collapsed')) {
|
||||
await this.page.locator('div.antiNav-element span[class*="label"]', { hasText: name }).click()
|
||||
if (classes != null && !classes.includes('isOpen')) {
|
||||
await this.page.getByRole('button', { name }).click()
|
||||
}
|
||||
}
|
||||
|
||||
async checkTeamspaceExist (name: string): Promise<void> {
|
||||
await expect(this.page.locator('div[class*="dropbox"] span[class*="label"]', { hasText: name })).toHaveCount(1)
|
||||
await expect(
|
||||
this.page.locator('div[class*="hulyNavGroup-content"] span[class*="label"]', { hasText: name })
|
||||
).toHaveCount(1)
|
||||
}
|
||||
|
||||
async checkTeamspaceNotExist (name: string): Promise<void> {
|
||||
await expect(this.page.locator('div[class*="dropbox"] span[class*="label"]', { hasText: name })).toHaveCount(0)
|
||||
await expect(
|
||||
this.page.locator('button[class*="hulyNavGroup-header"] span[class*="label"]', { hasText: name })
|
||||
).toHaveCount(0)
|
||||
}
|
||||
|
||||
async moreActionTeamspace (name: string, action: string): Promise<void> {
|
||||
await this.page.locator('div[class*="dropbox"] > div > span[class*="label"]', { hasText: name }).hover()
|
||||
await this.page.locator(`xpath=//span[text()="${name}"]/../div[last()]`).click()
|
||||
await this.page.locator('button.hulyNavGroup-header span[class*="label"]', { hasText: name }).hover()
|
||||
await this.page
|
||||
.locator(`xpath=//span[text()="${name}"]/../../div[@class="hulyNavGroup-header__tools"]/button[last()]`)
|
||||
.click()
|
||||
await this.selectFromDropdown(this.page, action)
|
||||
}
|
||||
|
||||
@ -89,14 +97,14 @@ export class DocumentsPage extends CommonPage {
|
||||
}
|
||||
|
||||
async openDocument (name: string): Promise<void> {
|
||||
await this.page.locator('div.tree > span[class*="label"]', { hasText: name }).click()
|
||||
await this.page.locator('button.hulyNavItem-container > span[class*="label"]', { hasText: name }).click()
|
||||
}
|
||||
|
||||
async openDocumentForTeamspace (spaceName: string, documentName: string): Promise<void> {
|
||||
await this.page
|
||||
.locator('div.parent > span[class*="label"]', { hasText: spaceName })
|
||||
.locator('xpath=../following-sibling::div[1]')
|
||||
.locator('div.tree > span[class*="label"]', { hasText: documentName })
|
||||
.locator('button.hulyNavGroup-header span[class*="label"]', { hasText: spaceName })
|
||||
.locator('xpath=../../following-sibling::div[1]')
|
||||
.locator('button.hulyNavItem-container span[class*="label"]', { hasText: documentName })
|
||||
.click()
|
||||
}
|
||||
|
||||
@ -120,10 +128,11 @@ export class DocumentsPage extends CommonPage {
|
||||
}
|
||||
|
||||
async moreActionsOnDocument (documentName: string, action: string): Promise<void> {
|
||||
await this.page.locator('button.hulyNavItem-container span[class*="label"]', { hasText: documentName }).hover()
|
||||
await this.page
|
||||
.locator('div.tree > span[class*="label"]', { hasText: documentName })
|
||||
.locator('button.hulyNavItem-container > span[class*="label"]', { hasText: documentName })
|
||||
.locator('xpath=..')
|
||||
.locator('div[class*="tool"]:nth-child(6)')
|
||||
.locator('div.hulyNavItem-actions > button:last-child')
|
||||
.click()
|
||||
await this.selectFromDropdown(this.page, action)
|
||||
}
|
||||
|
@ -12,7 +12,9 @@ export class ApplicationsPage extends CommonRecruitingPage {
|
||||
}
|
||||
|
||||
readonly pageHeader = (): Locator => this.page.locator('span[class*="header"]', { hasText: 'Applications' })
|
||||
readonly buttonCreateApplication = (): Locator => this.page.locator('button > span', { hasText: 'Application' })
|
||||
readonly buttonCreateApplication = (): Locator =>
|
||||
this.page.locator('button.antiButton > span', { hasText: 'Application' })
|
||||
|
||||
readonly buttonTalentSelector = (): Locator => this.page.locator('div[id="vacancy.talant.selector"]')
|
||||
readonly buttonSpaceSelector = (): Locator => this.page.locator('div[id="space.selector"]')
|
||||
readonly buttonAssignedRecruiter = (): Locator =>
|
||||
|
@ -10,7 +10,7 @@ export class RecruitingPage {
|
||||
recruitApplication = (): Locator => this.page.locator('[id="app-recruit\\:string\\:RecruitApplication"]')
|
||||
talentsNavElement = (): Locator => this.page.locator('text=Talents')
|
||||
reviews = (): Locator => this.page.locator('text=Reviews')
|
||||
reviewButton = (): Locator => this.page.locator('button:has-text("Review")')
|
||||
reviewButton = (): Locator => this.page.getByRole('button', { name: 'Review', exact: true })
|
||||
|
||||
frontendEngineerOption = (): Locator => this.page.locator('td:has-text("Frontend Engineer")')
|
||||
searchOrRunCommandInput = (): Locator => this.page.locator('[placeholder="Search\\ or\\ run\\ a\\ command\\.\\.\\."]')
|
||||
@ -35,7 +35,7 @@ export class RecruitingPage {
|
||||
|
||||
generalChatLink = (): Locator => this.page.locator('text=general')
|
||||
contactsButton = (): Locator => this.page.locator('[id="app-contact\\:string\\:Contacts"]')
|
||||
employeeSection = (): Locator => this.page.locator('.antiNav-element:has-text("Employee")')
|
||||
employeeSection = (): Locator => this.page.getByRole('button', { name: 'Employee' })
|
||||
johnAppleseed = (): Locator => this.page.locator('text=Appleseed John')
|
||||
|
||||
async clickRecruitApplication (): Promise<void> {
|
||||
|
@ -48,7 +48,9 @@ export class TalentsPage extends CommonRecruitingPage {
|
||||
talentsLink = (): Locator => this.page.locator('text=Talents')
|
||||
firstNameInput = (): Locator => this.page.locator('[placeholder="First name"]')
|
||||
lastNameInput = (): Locator => this.page.locator('[placeholder="Last name"]')
|
||||
skillsButton = (): Locator => this.page.locator('button:has-text("Skills")')
|
||||
skillsButton = (): Locator =>
|
||||
this.page.locator('[id="recruit\\:string\\:CreateTalent"]').getByRole('button', { name: 'Skills' })
|
||||
|
||||
addSkillButton = (): Locator => this.page.locator('.header > button:nth-child(3)')
|
||||
skillTitleInput = (): Locator => this.page.getByPlaceholder('Please type title')
|
||||
createSkillInput = (): Locator => this.page.getByPlaceholder('Please type skill title')
|
||||
@ -58,7 +60,7 @@ export class TalentsPage extends CommonRecruitingPage {
|
||||
createCandidateButton = (): Locator => this.page.locator('button:has-text("Create")')
|
||||
openOtherSkills = (): Locator => this.page.getByText('Other')
|
||||
skillsLink = (): Locator => this.page.locator('text=Skills')
|
||||
newSkillButton = (): Locator => this.page.locator('button:has-text("Skill")')
|
||||
newSkillButton = (): Locator => this.page.getByRole('button', { name: 'Skill', exact: true })
|
||||
emailContact = (): Locator =>
|
||||
this.page.locator('div[class^="popupPanel-body__header"] button[id="gmail:string:Email"]')
|
||||
|
||||
|
@ -51,7 +51,7 @@ export class VacanciesPage extends CommonRecruitingPage {
|
||||
readonly vacancyRow = (vacancyId: string): Locator =>
|
||||
this.page.locator(`tr:has-text("${vacancyId}") > td:nth-child(3) >> .sm-tool-icon`)
|
||||
|
||||
readonly applicationButton = (): Locator => this.page.locator('button:has-text("Application")')
|
||||
readonly applicationButton = (): Locator => this.page.getByRole('button', { name: 'Application', exact: true })
|
||||
readonly talentSelector = (): Locator =>
|
||||
this.page.locator('form[id="recruit:string:CreateApplication"] [id="vacancy.talant.selector"]')
|
||||
|
||||
|
@ -81,7 +81,7 @@ export class CommonTrackerPage extends CalendarPage {
|
||||
|
||||
trackerApplicationButton = (): Locator => this.page.locator('[id="app-tracker\\:string\\:TrackerApplication"]')
|
||||
componentsLink = (): Locator => this.page.locator('text=Components')
|
||||
createComponentButton = (): Locator => this.page.locator('button:has-text("Component")')
|
||||
createComponentButton = (): Locator => this.page.getByRole('button', { name: 'Component', exact: true })
|
||||
componentNameInput = (): Locator => this.page.locator('[placeholder="Component\\ name"]')
|
||||
createComponentConfirmButton = (): Locator => this.page.locator('button:has-text("Create component")')
|
||||
newIssueButton = (): Locator => this.page.locator('button:has-text("New issue")')
|
||||
|
@ -3,7 +3,7 @@ import { CommonTrackerPage } from './common-tracker-page'
|
||||
import { NewIssue } from './types'
|
||||
|
||||
export class TemplatePage extends CommonTrackerPage {
|
||||
buttonNewTemplate = (): Locator => this.page.locator('button > span', { hasText: 'Template' })
|
||||
buttonNewTemplate = (): Locator => this.page.getByRole('button', { name: 'Template', exact: true })
|
||||
inputIssueTitle = (): Locator => this.page.locator('form[id$="NewProcess"] input[type="text"]')
|
||||
inputIssueDescription = (): Locator => this.page.locator('form[id$="NewProcess"] div.tiptap')
|
||||
buttonPopupCreateNewTemplatePriority = (): Locator =>
|
||||
|
@ -9,24 +9,23 @@ export class TrackerNavigationMenuPage extends CommonPage {
|
||||
this.page = page
|
||||
}
|
||||
|
||||
buttonCreateProject = (): Locator => this.page.locator('div#tree-projects').locator('xpath=..')
|
||||
buttonProjectsParent = (): Locator => this.page.locator('div.parent > span')
|
||||
buttonCreateProject = (): Locator => this.page.locator('div#navGroup-tree-projects').locator('xpath=../button[1]')
|
||||
buttonProjectsParent = (): Locator => this.page.locator('button.hulyNavGroup-header span')
|
||||
templateLinkForProject = (projectName: string): Locator =>
|
||||
this.page.locator(`a[href$="templates"][href*="${projectName}"]`)
|
||||
|
||||
issuesLinkForProject = (projectName: string): Locator =>
|
||||
this.page.locator(
|
||||
`xpath=//div[contains(@class, "parent")]/span[text()="${projectName}"]/../following-sibling::div[1]/a[contains(@href, "issues")]`,
|
||||
{ hasText: 'Issues' }
|
||||
)
|
||||
this.page
|
||||
.getByRole('button', { name: projectName, exact: true })
|
||||
.locator('xpath=following-sibling::div[1]/a[contains(@href, "issues")]', { hasText: 'Issues' })
|
||||
|
||||
milestonesLinkForProject = (projectName: string): Locator =>
|
||||
this.page.locator(`div[class*="antiNav-element"] a[href$="milestones"][href*="${projectName}"]> div > span`, {
|
||||
this.page.locator(`a[href$="milestones"][href*="${projectName}"] > button[class*="hulyNavItem-container"] > span`, {
|
||||
hasText: 'Milestones'
|
||||
})
|
||||
|
||||
componentsLinkForProject = (projectName: string): Locator =>
|
||||
this.page.locator(`div[class*="antiNav-element"] a[href$="components"][href*="${projectName}"]> div > span`, {
|
||||
this.page.locator(`a[href$="components"][href*="${projectName}"] > button[class*="hulyNavItem-container"] > span`, {
|
||||
hasText: 'Components'
|
||||
})
|
||||
|
||||
@ -39,7 +38,7 @@ export class TrackerNavigationMenuPage extends CommonPage {
|
||||
milestone = (): Locator => this.page.getByRole('link', { name: 'Milestones' })
|
||||
templates = (): Locator => this.page.getByRole('link', { name: 'Templates' })
|
||||
|
||||
createProjectButton = (): Locator => this.buttonCreateProject().locator('button.small')
|
||||
createProjectButton = (): Locator => this.buttonCreateProject().locator('#tree-projects')
|
||||
|
||||
async pressCreateProjectButton (): Promise<void> {
|
||||
await this.buttonCreateProject().hover()
|
||||
@ -70,8 +69,8 @@ export class TrackerNavigationMenuPage extends CommonPage {
|
||||
await this.buttonProjectsParent().filter({ hasText: projectName }).hover()
|
||||
await this.buttonProjectsParent()
|
||||
.filter({ hasText: projectName })
|
||||
.locator('xpath=..')
|
||||
.locator('div[class*="tool"]:not([class*="arrow"])')
|
||||
.locator('xpath=../..')
|
||||
.locator('div[class*="tools"] button')
|
||||
.click()
|
||||
await this.selectFromDropdown(this.page, action)
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ export async function createComponent (page: Page, componentName: string): Promi
|
||||
await expect(page).toHaveURL(
|
||||
`${PlatformURI}/workbench/sanity-ws/tracker/tracker%3Aproject%3ADefaultProject/components`
|
||||
)
|
||||
await page.click('button:has-text("Component")')
|
||||
await page.getByRole('button', { name: 'Component', exact: true }).click()
|
||||
await page.click('[placeholder="Component\\ name"]')
|
||||
await page.fill('[placeholder="Component\\ name"]', componentName)
|
||||
await page.click('button:has-text("Create component")')
|
||||
@ -124,7 +124,7 @@ export async function createMilestone (page: Page, milestoneName: string): Promi
|
||||
await expect(page).toHaveURL(
|
||||
`${PlatformURI}/workbench/sanity-ws/tracker/tracker%3Aproject%3ADefaultProject/milestones`
|
||||
)
|
||||
await page.click('button:has-text("Milestone")')
|
||||
await page.getByRole('button', { name: 'Milestone', exact: true }).click()
|
||||
await page.click('[placeholder="Milestone\\ name"]')
|
||||
await page.fill('[placeholder="Milestone\\ name"]', milestoneName)
|
||||
await page.click('button:has-text("Create")')
|
||||
|
Loading…
Reference in New Issue
Block a user