Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions models/card/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,15 +447,24 @@ export function createModel (builder: Builder): void {
position: 'top'
}
],
spaces: [
spaces: [],
groups: [
{
id: 'spaces',
label: core.string.Spaces,
spaceClass: card.class.CardSpace,
addSpaceLabel: core.string.Space,
icon: card.icon.Space,
// intentionally left empty in order to make space presenter working
specials: []
id: 'types',
label: card.string.MasterTags,
groupByClass: card.class.MasterTag,
icon: card.icon.MasterTags,
component: card.component.TypesNavigator,
specials: [
{
id: 'type',
label: card.string.Cards,
component: card.component.Main,
componentProps: {
defaultViewletDescriptor: card.viewlet.CardFeedDescriptor
}
}
]
}
]
},
Expand Down Expand Up @@ -658,7 +667,7 @@ export function createModel (builder: Builder): void {
)

builder.mixin(card.class.Card, core.class.Class, view.mixin.ClassFilters, {
filters: [],
filters: ['space'],
ignoreKeys: ['parent']
})

Expand Down
13 changes: 12 additions & 1 deletion plugins/card-resources/src/components/CardFeedView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
}
}
)
$: cardSpace = space ?? getSpaceFromFilter(resultQuery)

$: hasNextPage = total > cards.length

Expand All @@ -76,6 +77,16 @@
}
}

function getSpaceFromFilter (_query: DocumentQuery<Card>): Ref<CardSpace> | undefined {
if (_query.space != null) {
if (typeof _query.space === 'object' && '$in' in _query.space && Array.isArray(_query.space.$in)) {
return _query.space.$in[0] as Ref<CardSpace>
}
return _query.space as Ref<CardSpace>
}
return undefined
}

function getFormatDateId (timestamp: number): string {
const now = new Date()
const date = new Date(timestamp)
Expand Down Expand Up @@ -110,7 +121,7 @@

<Scroller bind:divScroll {onScroll} padding="2rem 4rem">
<div class="home">
<NewCardForm type={_class !== card.class.Card ? _class : undefined} {space} />
<NewCardForm type={_class !== card.class.Card ? _class : undefined} space={cardSpace} />
<div class="body flex-gap-2">
{#each cards as card, index}
{@const previousCard = cards[index - 1]}
Expand Down
9 changes: 3 additions & 6 deletions plugins/card-resources/src/components/Main.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,20 @@
-->
<script lang="ts">
import { MasterTag } from '@hcengineering/card'
import { Class, Doc, Ref, Space } from '@hcengineering/core'
import { Class, Doc, Ref } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { createQuery } from '@hcengineering/presentation'
import { location } from '@hcengineering/ui'
import { SpecialView } from '@hcengineering/workbench-resources'
import { onDestroy } from 'svelte'
import card from '../plugin'

export let currentSpace: Ref<Space>

let _class: Ref<Class<Doc>> | undefined

onDestroy(
location.subscribe((loc) => {
_class = loc.path[4]
const isTypeSpecified = loc.path[3] === 'type'
_class = isTypeSpecified ? loc.path[4] : card.class.Card
})
)

Expand All @@ -51,8 +50,6 @@
{#if clazz !== undefined && label !== undefined}
<SpecialView
_class={clazz._id}
baseQuery={{ space: currentSpace }}
space={currentSpace}
defaultViewletDescriptor={card.viewlet.CardFeedDescriptor}
{label}
icon={card.icon.Card}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,23 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { MasterTag } from '@hcengineering/card'
import { Class, Doc, Ref, Space } from '@hcengineering/core'
import { IconWithEmoji, getClient } from '@hcengineering/presentation'
import { NavItem, getCurrentLocation, navigate } from '@hcengineering/ui'
import card from '../../plugin'
import view from '@hcengineering/view'
import card from '../../plugin'

export let space: Ref<Space>
export let space: Ref<Space> | undefined
export let classes: MasterTag[] = []
export let allClasses: MasterTag[] = []
export let _class: Ref<Class<Doc>> | undefined
export let level: number = 0
export let currentSpace: Ref<Space> | undefined

const client = getClient()
const dispatch = createEventDispatcher()
let descendants = new Map<Ref<Class<Doc>>, MasterTag[]>()

function getDescendants (_class: Ref<MasterTag>): MasterTag[] {
Expand Down Expand Up @@ -72,7 +74,11 @@
{level}
selected={clazz._id === _class && currentSpace === space}
on:click={() => {
select(clazz._id, space)
if (space !== undefined) {
select(clazz._id, space)
} else {
dispatch('select', clazz._id)
}
}}
>
<svelte:fragment slot="dropbox">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<!--
// Copyright © 2025 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 { Class, Doc, Ref } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import { getCurrentLocation, navigate, location as locationStore } from '@hcengineering/ui'
import { MasterTag } from '@hcengineering/card'
import { TreeNode } from '@hcengineering/view-resources'
import { GroupsNavModel } from '@hcengineering/workbench'
import TagHierarchy from './TagHierarchy.svelte'
import card from '../../plugin'

export let model: GroupsNavModel

let classes: MasterTag[] = []
let _class: Ref<Class<Doc>> | undefined

const query = createQuery()
query.query(card.class.MasterTag, {}, (res) => {
classes = res.filter((it) => it.removed !== true).sort((a, b) => a.label.localeCompare(b.label))
})

function getRootClasses (_classes: MasterTag[]): MasterTag[] {
return _classes.filter((it) => it.extends === card.class.Card)
}

function buildTypePath (currentPath: any[], type: Ref<MasterTag>): any[] {
return [...currentPath.slice(0, 3), 'type', type]
}

function selectType (type: Ref<MasterTag>): void {
const loc = getCurrentLocation()
loc.path = buildTypePath(loc.path, type)
navigate(loc)
}

$: _class = $locationStore.path[4] as Ref<Class<Doc>>
$: selectedClass = classes.find((it) => it._id === _class)
$: rootClasses = getRootClasses(classes)
$: empty = rootClasses === undefined || rootClasses.length === 0
</script>

<div class="flex-col w-full">
<TreeNode
_id={'tree-' + model.id}
label={model.label}
highlighted={selectedClass !== undefined}
isFold={!empty}
{empty}
>
<TagHierarchy
classes={rootClasses}
allClasses={classes}
{_class}
space={undefined}
currentSpace={undefined}
on:select={(e) => {
selectType(e.detail)
}}
/>
</TreeNode>
</div>
2 changes: 2 additions & 0 deletions plugins/card-resources/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import CreateCardButton from './components/CreateCardButton.svelte'
import CardArrayEditor from './components/CardArrayEditor.svelte'
import NewCardHeader from './components/navigator/NewCardHeader.svelte'
import SpacePresenter from './components/navigator/SpacePresenter.svelte'
import TypesNavigator from './components/navigator/TypesNavigator.svelte'
import LabelsPresenter from './components/LabelsPresenter.svelte'
import RolesSection from './components/settings/RolesSection.svelte'
import EditRole from './components/settings/EditRole.svelte'
Expand Down Expand Up @@ -111,6 +112,7 @@ export default async (): Promise<Resources> => ({
CardArrayEditor,
NewCardHeader,
SpacePresenter,
TypesNavigator,
LabelsPresenter,
RolesSection,
EditRole,
Expand Down
1 change: 1 addition & 0 deletions plugins/card-resources/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export default mergeIds(cardId, card, {
CreateCardButton: '' as AnyComponent,
NewCardHeader: '' as AnyComponent,
SpacePresenter: '' as AnyComponent,
TypesNavigator: '' as AnyComponent,
RolesSection: '' as AnyComponent,
EditRole: '' as AnyComponent,
CardWidget: '' as AnyComponent,
Expand Down
3 changes: 2 additions & 1 deletion plugins/card-resources/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ export async function resolveLocation (loc: Location): Promise<ResolvedLocation
}

const id = loc.path[3]
if (loc.path[4] === undefined && id !== undefined && id !== 'browser') {
const specialItems = ['browser', 'type', 'all']
if (loc.path[4] === undefined && id !== undefined && !specialItems.includes(id)) {
return await generateLocation(loc, id)
}
}
Expand Down
11 changes: 10 additions & 1 deletion plugins/workbench-resources/src/components/Navigator.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import { getResource } from '@hcengineering/platform'
import preference, { SpacePreference } from '@hcengineering/preference'
import { createQuery, getClient, isAdminUser } from '@hcengineering/presentation'
import { Scroller, NavItem } from '@hcengineering/ui'
import { Scroller, NavItem, Component } from '@hcengineering/ui'
import { NavLink } from '@hcengineering/view-resources'
import type { Application, NavigatorModel, SpecialNavModel } from '@hcengineering/workbench'
import { getSpecialSpaceClass } from '../utils'
Expand Down Expand Up @@ -198,5 +198,14 @@
deselect={menuSelection || starred.some((s) => s._id === currentSpace)}
/>
{/each}

{#if model.groups && model.groups.length > 0}
<div class="min-h-3 flex-no-shrink" />
{#each model.groups as group (group.id)}
{#if group.component}
<Component is={group.component} props={{ model: group }} />
{/if}
{/each}
{/if}
</Scroller>
{/if}
6 changes: 6 additions & 0 deletions plugins/workbench-resources/src/components/Workbench.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,12 @@
return sp
}
}
for (const g of navigatorModel?.groups ?? []) {
const sp = g.specials?.find((x) => x.id === id)
if (sp !== undefined) {
return sp
}
}
}

let cover: HTMLElement
Expand Down
16 changes: 16 additions & 0 deletions plugins/workbench/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export interface ApplicationNavModel extends Doc {

spaces?: SpacesNavModel[]
specials?: SpecialNavModel[]
groups?: GroupsNavModel[]
}

/** @public */
Expand All @@ -136,10 +137,25 @@ export interface SpacesNavModel {
visibleIf?: Resource<(space: Space) => Promise<boolean>>
}

/** @public */
export interface GroupsNavModel {
id: string // Id could be used for extending of navigation model
label?: IntlString
groupByClass: Ref<Class<Doc>> // Any class to group by (MasterTag, Project, etc.)
icon?: Asset
component?: AnyComponent // Component to render the group navigation (e.g., TypesNavigator)

// Child special items.
specials?: SpecialNavModel[]

visibleIf?: Resource<(docs: Doc[]) => Promise<boolean>>
}

/** @public */
export interface NavigatorModel {
spaces: SpacesNavModel[]
specials?: SpecialNavModel[]
groups?: GroupsNavModel[]
}

/** @public */
Expand Down
Loading