mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 11:01:54 +03:00
[UBER-155] Replace "Back" button with "Breadcrumbs" (#3239)
Signed-off-by: Sergei Ogorelkov <sergei.ogorelkov@icloud.com>
This commit is contained in:
parent
6773c0d5bb
commit
e7ba92c764
@ -109,7 +109,7 @@
|
||||
>
|
||||
<svelte:fragment slot="navigator">
|
||||
{#if $$slots.navigator}
|
||||
<div class="buttons-group xsmall-gap mx-2">
|
||||
<div class="flex-row-center flex-gap-1-5 mx-2">
|
||||
<slot name="navigator" />
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -0,0 +1,67 @@
|
||||
<!--
|
||||
// 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 { Component, ScrollerBar, getPlatformColor } from '@hcengineering/ui'
|
||||
import BreadcrumbsElement from './BreadcrumbsElement.svelte'
|
||||
import { NavLink } from '../..'
|
||||
import { BreadcrumbsModel } from './types'
|
||||
import { hasComponent } from './utils'
|
||||
|
||||
export let models: readonly BreadcrumbsModel[]
|
||||
export let gap: 'none' | 'small' | 'big' = 'small'
|
||||
|
||||
let scroller: HTMLElement
|
||||
|
||||
function getPosition (position: number): 'start' | 'end' | 'middle' {
|
||||
if (position === 0) {
|
||||
return 'start'
|
||||
}
|
||||
|
||||
if (position === models.length - 1) {
|
||||
return 'end'
|
||||
}
|
||||
|
||||
return 'middle'
|
||||
}
|
||||
</script>
|
||||
|
||||
<ScrollerBar {gap} bind:scroller>
|
||||
{#each models as model, i}
|
||||
{@const { color } = model}
|
||||
{#if hasComponent(model)}
|
||||
{@const { component, props } = model}
|
||||
<BreadcrumbsElement
|
||||
position={getPosition(i)}
|
||||
color={color !== undefined ? getPlatformColor(color) : 'var(--accent-bg-color)'}
|
||||
>
|
||||
{#if typeof component === 'string'}
|
||||
<Component is={component} {props} />
|
||||
{:else}
|
||||
<svelte:component this={component} {...props} />
|
||||
{/if}
|
||||
</BreadcrumbsElement>
|
||||
{:else}
|
||||
{@const { title, href, onClick } = model}
|
||||
<NavLink {href} noUnderline {onClick}>
|
||||
<BreadcrumbsElement
|
||||
label={title}
|
||||
{title}
|
||||
position={getPosition(i)}
|
||||
color={color !== undefined ? getPlatformColor(color) : 'var(--accent-bg-color)'}
|
||||
/>
|
||||
</NavLink>
|
||||
{/if}
|
||||
{/each}
|
||||
</ScrollerBar>
|
@ -1,6 +1,5 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 Hardcore Engineering Inc.
|
||||
// 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
|
||||
@ -14,37 +13,38 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { afterUpdate } from 'svelte'
|
||||
import type { StatesBarPosition } from '../..'
|
||||
import { resizeObserver } from '@hcengineering/ui'
|
||||
|
||||
export let label: string
|
||||
export let position: StatesBarPosition = undefined
|
||||
export let selected: boolean = false
|
||||
export let color: string = 'var(--body-color)'
|
||||
export let label: string | undefined = undefined
|
||||
export let title: string | undefined = undefined
|
||||
export let position: 'start' | 'middle' | 'end' | undefined = undefined
|
||||
export let selected = false
|
||||
export let color = 'var(--body-color)'
|
||||
|
||||
let lenght: number = 0
|
||||
let text: HTMLElement
|
||||
let divBar: HTMLElement
|
||||
let svgBack: SVGElement
|
||||
|
||||
afterUpdate(() => {
|
||||
if (text) lenght = text.clientWidth + 32 > 300 ? 300 : text.clientWidth + 32
|
||||
})
|
||||
let lenght = 0
|
||||
</script>
|
||||
|
||||
<div class="hidden-text text-md font-medium" bind:this={text}>{label}</div>
|
||||
<div
|
||||
class="hidden-text text-md font-medium pointer-events-none content-pointer-events-none"
|
||||
use:resizeObserver={(element) => (lenght = element.clientWidth + 32 > 300 ? 300 : element.clientWidth + 32)}
|
||||
>
|
||||
{#if $$slots.default}
|
||||
<slot />
|
||||
{:else}
|
||||
{label}
|
||||
{/if}
|
||||
</div>
|
||||
{#if lenght > 0}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
bind:this={divBar}
|
||||
class="asb-bar"
|
||||
class:selected
|
||||
class:cursor-pointer={!selected}
|
||||
class:cursor-default={selected}
|
||||
on:click|stopPropagation
|
||||
class:cursor-pointer={!selected && !$$slots.default}
|
||||
class:cursor-default={selected || $$slots.default}
|
||||
{title}
|
||||
on:click
|
||||
>
|
||||
<svg
|
||||
bind:this={svgBack}
|
||||
class="asb-bar__back"
|
||||
viewBox="0 0 {lenght} 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -53,6 +53,7 @@
|
||||
{#if position === 'start'}
|
||||
<path
|
||||
class="asb-bar__{selected ? 'selected' : 'element'}"
|
||||
class:asb-bar__disabled={$$slots.default}
|
||||
style={selected ? `fill: ${color};` : ''}
|
||||
d="M0,5.3C0,2.4,2.3,0,5.2,0h1.3h{lenght -
|
||||
13}h1.2c0.5,0,1,0.3,1.2,0.9l4,10.7c0.1,0.3,0.1,0.7,0,0.9l-4,10.7c-0.2,0.5-0.7,0.9-1.2,0.9 l-1.2,0h-{lenght -
|
||||
@ -61,6 +62,7 @@
|
||||
{:else if position === 'middle'}
|
||||
<path
|
||||
class="asb-bar__{selected ? 'selected' : 'element'}"
|
||||
class:asb-bar__disabled={$$slots.default}
|
||||
style={selected ? `fill: ${color};` : ''}
|
||||
d="M4,11.5L0.1,0.9C-0.1,0.5,0.2,0,0.6,0h5.8h{lenght -
|
||||
13}h1.2c0.5,0,1,0.3,1.2,0.9l4,10.7c0.1,0.3,0.1,0.7,0,0.9l-4,10.7 c-0.2,0.5-0.7,0.9-1.2,0.9h-1.2h-{lenght -
|
||||
@ -69,6 +71,7 @@
|
||||
{:else if position === 'end'}
|
||||
<path
|
||||
class="asb-bar__{selected ? 'selected' : 'element'}"
|
||||
class:asb-bar__disabled={$$slots.default}
|
||||
style={selected ? `fill: ${color};` : ''}
|
||||
d="M4.1,11.5l-4-10.6C-0.1,0.5,0.2,0,0.7,0h{lenght - 7}C{lenght -
|
||||
3},0,{lenght},2.4,{lenght},5.3v13.3c0,2.9-2.4,5.3-5.3,5.3h-{lenght}H0.6c-0.5,0-0.8-0.5-0.6-0.9L4,12.5C4.1,12.2,4.1,11.8,4,11.5z"
|
||||
@ -76,11 +79,20 @@
|
||||
{:else}
|
||||
<path
|
||||
class="asb-bar__{selected ? 'selected' : 'element'}"
|
||||
class:asb-bar__disabled={$$slots.default}
|
||||
style={selected ? `fill: ${color};` : ''}
|
||||
d="M0,5.3C0,2.4,2.3,0,5.2,0h1.3h{lenght}h1.3C49.7,0,52,2.4,52,5.3v13.3c0,2.9-2.3,5.3-5.2,5.3h-1.3h-{lenght}H5.2 C2.3,24,0,21.6,0,18.7V5.3z"
|
||||
/>
|
||||
{/if}
|
||||
</svg>
|
||||
<div class="asb-label__container" class:selected><div class="overflow-label">{label}</div></div>
|
||||
<div class="asb-label__container" class:selected class:disabled={!$$slots.default}>
|
||||
<div class="overflow-label">
|
||||
{#if $$slots.default}
|
||||
<slot />
|
||||
{:else}
|
||||
{label}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
30
packages/presentation/src/components/breadcrumbs/types.ts
Normal file
30
packages/presentation/src/components/breadcrumbs/types.ts
Normal file
@ -0,0 +1,30 @@
|
||||
// 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.
|
||||
|
||||
import { AnyComponent, AnySvelteComponent } from '@hcengineering/ui'
|
||||
|
||||
interface BreadcrumbsProps {
|
||||
readonly color?: number | undefined
|
||||
}
|
||||
|
||||
type TextBreadcrumbsProps = { title: string } & (
|
||||
| { readonly href: string, readonly onClick?: undefined }
|
||||
| { readonly href?: undefined, readonly onClick: (event: MouseEvent) => void }
|
||||
)
|
||||
|
||||
export interface ComponentBreadcrumbsProps {
|
||||
readonly component: AnyComponent | AnySvelteComponent
|
||||
readonly props: Record<string, any>
|
||||
}
|
||||
|
||||
export type BreadcrumbsModel = BreadcrumbsProps & (TextBreadcrumbsProps | ComponentBreadcrumbsProps)
|
18
packages/presentation/src/components/breadcrumbs/utils.ts
Normal file
18
packages/presentation/src/components/breadcrumbs/utils.ts
Normal file
@ -0,0 +1,18 @@
|
||||
// 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.
|
||||
|
||||
import { BreadcrumbsModel, ComponentBreadcrumbsProps } from './types'
|
||||
|
||||
export function hasComponent (model: BreadcrumbsModel): model is ComponentBreadcrumbsProps {
|
||||
return 'component' in model
|
||||
}
|
@ -39,6 +39,8 @@ export { default as IndexedDocumentCompare } from './components/IndexedDocumentC
|
||||
export { default as DraggableList } from './components/DraggableList.svelte'
|
||||
export { default as NavLink } from './components/NavLink.svelte'
|
||||
export { default as IconForward } from './components/icons/Forward.svelte'
|
||||
export { default as Breadcrumbs } from './components/breadcrumbs/Breadcrumbs.svelte'
|
||||
export { default as BreadcrumbsElement } from './components/breadcrumbs/BreadcrumbsElement.svelte'
|
||||
export { default } from './plugin'
|
||||
export * from './types'
|
||||
export * from './utils'
|
||||
|
@ -2,6 +2,8 @@ import { Client, Doc, RelatedDocument } from '@hcengineering/core'
|
||||
import { Asset, IntlString, Resource } from '@hcengineering/platform'
|
||||
import { AnyComponent, AnySvelteComponent } from '@hcengineering/ui'
|
||||
|
||||
export * from './components/breadcrumbs/types'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
@ -288,7 +288,6 @@ input.search {
|
||||
color: var(--theme-content-color);
|
||||
|
||||
overflow: hidden;
|
||||
visibility: visible;
|
||||
user-select: none;
|
||||
|
||||
&:not(.nowrap) {
|
||||
|
@ -341,6 +341,7 @@
|
||||
&:hover { fill: var(--theme-button-hovered); }
|
||||
}
|
||||
&__selected { fill: var(--theme-button-pressed); }
|
||||
&__disabled { pointer-events: none; }
|
||||
|
||||
.asb-label__container {
|
||||
position: absolute;
|
||||
@ -356,7 +357,8 @@
|
||||
font-weight: 500;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--theme-dark-color);
|
||||
pointer-events: none;
|
||||
|
||||
&.disabled { pointer-events: none; }
|
||||
|
||||
&.selected {
|
||||
color: var(--theme-caption-color);
|
||||
|
@ -32,7 +32,13 @@
|
||||
Label,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import { ContextMenu, DocAttributeBar, invokeAction, UpDownNavigator } from '@hcengineering/view-resources'
|
||||
import {
|
||||
ContextMenu,
|
||||
DocAttributeBar,
|
||||
invokeAction,
|
||||
ParentsNavigator,
|
||||
UpDownNavigator
|
||||
} from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import board from '../plugin'
|
||||
import { getCardActions } from '../utils/CardActionUtils'
|
||||
@ -116,6 +122,7 @@
|
||||
>
|
||||
<svelte:fragment slot="navigator">
|
||||
<UpDownNavigator element={object} />
|
||||
<ParentsNavigator element={object} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="header">
|
||||
<div class="flex fs-title flex-gap-1">
|
||||
|
@ -119,7 +119,6 @@
|
||||
color: var(--theme-caption-color);
|
||||
|
||||
overflow: hidden;
|
||||
visibility: visible;
|
||||
display: -webkit-box;
|
||||
/* autoprefixer: ignore next */
|
||||
-webkit-box-orient: vertical;
|
||||
|
@ -47,7 +47,7 @@
|
||||
SelectPopup,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import { ClassAttributeBar, ContextMenu, UpDownNavigator } from '@hcengineering/view-resources'
|
||||
import { ClassAttributeBar, ContextMenu, ParentsNavigator, UpDownNavigator } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
|
||||
import document from '../plugin'
|
||||
import DocumentEditor from './DocumentEditor.svelte'
|
||||
@ -385,6 +385,7 @@
|
||||
>
|
||||
<svelte:fragment slot="navigator">
|
||||
<UpDownNavigator element={documentObject} />
|
||||
<ParentsNavigator element={documentObject} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="header">
|
||||
<span class="fs-title flex-row-center flex-shrink gap-1-5">
|
||||
|
@ -15,12 +15,11 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { statusStore } from '@hcengineering/presentation'
|
||||
import { BreadcrumbsElement, statusStore } from '@hcengineering/presentation'
|
||||
import task, { SpaceWithStates, State } from '@hcengineering/task'
|
||||
import { getColorNumberByText, getPlatformColor, ScrollerBar } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import type { StatesBarPosition } from '../..'
|
||||
import StatesBarElement from './StatesBarElement.svelte'
|
||||
|
||||
export let space: Ref<SpaceWithStates>
|
||||
export let state: Ref<State> | undefined = undefined
|
||||
@ -57,12 +56,13 @@
|
||||
|
||||
<ScrollerBar {gap} bind:scroller={divScroll}>
|
||||
{#each states as item, i (item._id)}
|
||||
<StatesBarElement
|
||||
<BreadcrumbsElement
|
||||
label={item.name}
|
||||
position={getPosition(i)}
|
||||
selected={item._id === state}
|
||||
color={getPlatformColor(item.color ?? getColorNumberByText(item.name))}
|
||||
on:click={(ev) => {
|
||||
ev.stopPropagation()
|
||||
if (item._id !== state) selectItem(ev, item)
|
||||
}}
|
||||
/>
|
||||
|
@ -34,14 +34,11 @@
|
||||
const spaceQuery = createQuery()
|
||||
let currentProject: Project | undefined = value?.$lookup?.space
|
||||
|
||||
$: if (projects === undefined) {
|
||||
if (value && value?.$lookup?.space === undefined) {
|
||||
spaceQuery.query(tracker.class.Project, { _id: value.space }, (res) => ([currentProject] = res))
|
||||
} else {
|
||||
spaceQuery.unsubscribe()
|
||||
}
|
||||
$: if (value?.$lookup?.space === undefined && !projects?.has(value.space)) {
|
||||
spaceQuery.query(tracker.class.Project, { _id: value.space }, (res) => ([currentProject] = res))
|
||||
} else {
|
||||
currentProject = projects.get(value.space)
|
||||
currentProject = value?.$lookup?.space ?? projects?.get(value.space)
|
||||
spaceQuery.unsubscribe()
|
||||
}
|
||||
|
||||
$: title = currentProject ? `${currentProject.identifier}-${value?.number}` : `${value?.number}`
|
||||
|
@ -20,7 +20,7 @@
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import setting, { settingId } from '@hcengineering/setting'
|
||||
import type { Issue, Project } from '@hcengineering/tracker'
|
||||
import { Issue, Project } from '@hcengineering/tracker'
|
||||
import {
|
||||
Button,
|
||||
EditBox,
|
||||
@ -34,7 +34,14 @@
|
||||
navigate,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import { ActionContext, ContextMenu, DocNavLink, UpDownNavigator, contextStore } from '@hcengineering/view-resources'
|
||||
import {
|
||||
ActionContext,
|
||||
ContextMenu,
|
||||
DocNavLink,
|
||||
ParentsNavigator,
|
||||
UpDownNavigator,
|
||||
contextStore
|
||||
} from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import { generateIssueShortLink, getIssueId } from '../../../issues'
|
||||
import tracker from '../../../plugin'
|
||||
@ -158,6 +165,7 @@
|
||||
<svelte:fragment slot="navigator">
|
||||
{#if !embedded}
|
||||
<UpDownNavigator element={issue} />
|
||||
<ParentsNavigator element={issue} />
|
||||
{/if}
|
||||
|
||||
<span class="ml-4 fs-title select-text-i">
|
||||
|
@ -16,7 +16,7 @@
|
||||
import core, { Class, Doc, Ref, SortingOrder, TxCUD, WithLookup } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import type { Scrum, ScrumRecord } from '@hcengineering/tracker'
|
||||
import { UpDownNavigator } from '@hcengineering/view-resources'
|
||||
import { ParentsNavigator, UpDownNavigator } from '@hcengineering/view-resources'
|
||||
import { Panel } from '@hcengineering/panel'
|
||||
import { Button, closePanel, TabItem, TabList } from '@hcengineering/ui'
|
||||
import tracker from '../../plugin'
|
||||
@ -99,6 +99,7 @@
|
||||
<Panel object={scrumRecord} isUtils={isRecording} isHeader={false} on:close>
|
||||
<svelte:fragment slot="navigator">
|
||||
<UpDownNavigator element={scrumRecord} />
|
||||
<ParentsNavigator element={scrumRecord} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">
|
||||
<span class="fs-title select-text-i">
|
||||
|
@ -32,7 +32,7 @@
|
||||
navigate,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import { ContextMenu, UpDownNavigator } from '@hcengineering/view-resources'
|
||||
import { ContextMenu, ParentsNavigator, UpDownNavigator } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
|
||||
import tracker from '../../plugin'
|
||||
|
||||
@ -158,6 +158,7 @@
|
||||
<svelte:fragment slot="navigator">
|
||||
{#if !embedded}
|
||||
<UpDownNavigator element={template} />
|
||||
<ParentsNavigator element={template} />
|
||||
{/if}
|
||||
|
||||
<div class="ml-2">
|
||||
|
@ -32,7 +32,7 @@
|
||||
import { AnyComponent, Button, Component, IconMixin, IconMoreH, showPopup } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import { ContextMenu } from '..'
|
||||
import { ContextMenu, ParentsNavigator } from '..'
|
||||
import { categorizeFields, getCollectionCounter, getFiltredKeys } from '../utils'
|
||||
import ActionContext from './ActionContext.svelte'
|
||||
import DocAttributeBar from './DocAttributeBar.svelte'
|
||||
@ -302,6 +302,7 @@
|
||||
<svelte:fragment slot="navigator">
|
||||
{#if !embedded}
|
||||
<UpDownNavigator element={object} />
|
||||
<ParentsNavigator element={object} />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
|
@ -0,0 +1,76 @@
|
||||
<!--
|
||||
// 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 { AttachedDoc, Doc } from '@hcengineering/core'
|
||||
import { Breadcrumbs, BreadcrumbsModel, getClient } from '@hcengineering/presentation'
|
||||
import { getObjectPresenter, isAttachedDoc } from '../utils'
|
||||
import { AttributeModel } from '@hcengineering/view'
|
||||
|
||||
export let element: Doc | AttachedDoc
|
||||
|
||||
const client = getClient()
|
||||
|
||||
async function getParents (doc: AttachedDoc): Promise<readonly Doc[]> {
|
||||
const parents: Doc[] = []
|
||||
|
||||
let currentDoc: Doc | undefined = doc
|
||||
|
||||
while (currentDoc && isAttachedDoc(currentDoc)) {
|
||||
const parent: Doc | undefined = await client.findOne(currentDoc.attachedToClass, { _id: currentDoc.attachedTo })
|
||||
|
||||
if (parent) {
|
||||
currentDoc = parent
|
||||
parents.push(parent)
|
||||
} else {
|
||||
currentDoc = undefined
|
||||
}
|
||||
}
|
||||
|
||||
return parents.reverse()
|
||||
}
|
||||
|
||||
async function getBreadcrumbsModels (doc: typeof element): Promise<readonly BreadcrumbsModel[]> {
|
||||
if (!isAttachedDoc(doc)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const parents = await getParents(doc)
|
||||
if (parents.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
const models: BreadcrumbsModel[] = []
|
||||
for (const parent of parents) {
|
||||
const attributeModel: AttributeModel | undefined = await getObjectPresenter(client, parent._class, { key: '' })
|
||||
|
||||
if (attributeModel) {
|
||||
const breadcrumbsModel: BreadcrumbsModel = {
|
||||
component: attributeModel.presenter,
|
||||
props: { inline: true, ...(attributeModel.props ?? {}), value: parent }
|
||||
}
|
||||
|
||||
models.push(breadcrumbsModel)
|
||||
}
|
||||
}
|
||||
|
||||
return models
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await getBreadcrumbsModels(element) then models}
|
||||
{#if models.length > 0}
|
||||
<Breadcrumbs {models} gap="none" />
|
||||
{/if}
|
||||
{/await}
|
@ -123,6 +123,7 @@ export { default as StatusRefPresenter } from './components/status/StatusRefPres
|
||||
export { default as TableBrowser } from './components/TableBrowser.svelte'
|
||||
export { default as ValueSelector } from './components/ValueSelector.svelte'
|
||||
export { default as FilterRemovedNotification } from './components/filter/FilterRemovedNotification.svelte'
|
||||
export { default as ParentsNavigator } from './components/ParentsNavigator.svelte'
|
||||
export * from './context'
|
||||
export * from './filter'
|
||||
export * from './selection'
|
||||
|
@ -949,3 +949,7 @@ export async function statusSort (
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export function isAttachedDoc (doc: Doc | AttachedDoc): doc is AttachedDoc {
|
||||
return 'attachedTo' in doc
|
||||
}
|
||||
|
@ -25,6 +25,6 @@ test.describe('recruit tests', () => {
|
||||
await page.click('[id="contact:string:AddMember"]')
|
||||
await page.click('button:has-text("Chen Rosamund")')
|
||||
await page.click('text=Chen Rosamund less than a minute ago >> span')
|
||||
await page.click(`:nth-match(:text("${orgId}"), 2)`)
|
||||
await page.click(`.card a:has-text("${orgId}")`)
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user