Updated navigator layout (#5619)

Signed-off-by: Alexander Platov <alexander.platov@hardcoreeng.com>
This commit is contained in:
Alexander Platov
2024-06-05 12:50:13 +07:00
committed by GitHub
parent 07d35f0827
commit a437926f35
79 changed files with 1582 additions and 1012 deletions
@@ -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}