Redesign card nav header (#10107)

* Redesign card nav header

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* Add comments

Signed-off-by: Artem Savchenko <armisav@gmail.com>

---------

Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
Artyom Savchenko
2025-10-17 12:44:33 +07:00
committed by GitHub
parent 3e63a7a5df
commit 35237b1ceb
7 changed files with 105 additions and 138 deletions
+4 -2
View File
@@ -442,7 +442,9 @@ export function createModel (builder: Builder): void {
componentProps: {
_class: card.class.CardSpace,
icon: view.icon.List,
label: core.string.Spaces
label: core.string.Spaces,
createLabel: card.string.CreateSpace,
createComponent: card.component.CreateSpace
},
position: 'top'
}
@@ -468,7 +470,7 @@ export function createModel (builder: Builder): void {
}
]
},
navHeaderComponent: card.component.NewCardHeader
navHeaderActions: card.component.CardHeaderButton
},
card.app.Card
)
@@ -0,0 +1,78 @@
<!--
// 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 { AccountRole, getCurrentAccount, hasAccountRole, Ref } from '@hcengineering/core'
import { ButtonIcon, getCurrentLocation, IconAdd, location, Menu, navigate, showPopup } from '@hcengineering/ui'
import { MasterTag } from '@hcengineering/card'
import card from '../../plugin'
import CreateSpace from './CreateSpace.svelte'
import CreateCardPopup from '../CreateCardPopup.svelte'
const me = getCurrentAccount()
let pressed: boolean = false
$: _class = $location.path[4] as Ref<MasterTag>
async function navigateToCard (cardId: string): Promise<void> {
const loc = getCurrentLocation()
loc.path[3] = cardId
loc.path.length = 4
navigate(loc)
}
async function handleCreateCard (): Promise<void> {
showPopup(CreateCardPopup, { type: _class }, 'center', async (result) => {
if (result != null && result !== '') {
await navigateToCard(result)
}
})
}
async function newTeamspace (): Promise<void> {
showPopup(CreateSpace, {}, 'top')
}
const globalActions = hasAccountRole(me, AccountRole.User)
? [
{
label: card.string.CreateCard,
icon: IconAdd,
action: handleCreateCard
},
{
label: card.string.CreateSpace,
icon: IconAdd,
action: newTeamspace
}
]
: [
{
label: card.string.CreateCard,
icon: IconAdd,
action: handleCreateCard
}
]
function addButtonClicked (ev: MouseEvent): void {
pressed = true
showPopup(Menu, { actions: globalActions }, ev.target as HTMLElement, () => {
pressed = false
})
}
</script>
<ButtonIcon icon={IconAdd} hasMenu {pressed} kind={'primary'} size={'small'} on:click={addButtonClicked} />
@@ -1,130 +0,0 @@
<!--
// 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 { AccountRole, getCurrentAccount, hasAccountRole, Ref, Space } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import {
Button,
ButtonWithDropdown,
getCurrentLocation,
IconAdd,
IconDropdown,
Loading,
location,
navigate,
SelectPopupValueType,
showPopup
} from '@hcengineering/ui'
import { MasterTag } from '@hcengineering/card'
import card from '../../plugin'
import CreateSpace from './CreateSpace.svelte'
import { createCard } from '../../utils'
export let currentSpace: Ref<Space> | undefined
const query = createQuery()
const me = getCurrentAccount()
let loading = true
let hasSpace = false
query.query(
card.class.CardSpace,
{ archived: false, members: me.uuid },
(res) => {
hasSpace = res.length > 0
loading = false
},
{ limit: 1, projection: { _id: 1 } }
)
$: _class = $location.path[4] as Ref<MasterTag>
async function handleCreateCard (): Promise<void> {
if (_class === undefined || currentSpace === undefined) return
const _id = await createCard(_class, currentSpace)
const loc = getCurrentLocation()
loc.path[3] = _id
loc.path.length = 4
navigate(loc)
}
async function newTeamspace (): Promise<void> {
showPopup(CreateSpace, {}, 'top')
}
async function dropdownItemSelected (res?: SelectPopupValueType['id']): Promise<void> {
if (res === 'card') {
await handleCreateCard()
} else if (res === 'space') {
await newTeamspace()
}
}
const dropdownItems = hasAccountRole(me, AccountRole.User)
? [
{ id: 'card', label: card.string.CreateCard },
{ id: 'space', label: card.string.CreateSpace }
]
: [{ id: 'card', label: card.string.CreateCard }]
</script>
{#if loading}
<Loading shrink />
{:else if hasAccountRole(getCurrentAccount(), AccountRole.User) || hasSpace}
<div class="antiNav-subheader">
{#if hasAccountRole(getCurrentAccount(), AccountRole.User)}
{#if hasSpace && currentSpace !== undefined && _class !== undefined}
<ButtonWithDropdown
icon={IconAdd}
justify={'left'}
kind={'primary'}
label={card.string.CreateCard}
on:click={handleCreateCard}
mainButtonId={'new-document'}
dropdownIcon={IconDropdown}
{dropdownItems}
on:dropdown-selected={(ev) => {
void dropdownItemSelected(ev.detail)
}}
/>
{:else}
<Button
id={'new-teamspace'}
icon={IconAdd}
label={card.string.CreateSpace}
justify={'left'}
width={'100%'}
kind={'primary'}
gap={'large'}
on:click={newTeamspace}
/>
{/if}
{:else if hasSpace}
<Button
id={'new-document'}
icon={IconAdd}
label={card.string.CreateCard}
justify={'left'}
width={'100%'}
kind={'primary'}
gap={'large'}
disabled={currentSpace === undefined || _class === undefined}
on:click={handleCreateCard}
/>
{/if}
</div>
{/if}
+5 -3
View File
@@ -51,13 +51,14 @@ import CardRefPresenter from './components/CardRefPresenter.svelte'
import ChangeType from './components/ChangeType.svelte'
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'
import CardWidget from './components/CardWidget.svelte'
import CreateSpace from './components/navigator/CreateSpace.svelte'
import CardHeaderButton from './components/navigator/CardHeaderButton.svelte'
// Card Sections
import AttachmentsCardSection from './components/sections/AttachmentsSection.svelte'
@@ -110,7 +111,6 @@ export default async (): Promise<Resources> => ({
ChangeType,
CreateCardButton,
CardArrayEditor,
NewCardHeader,
SpacePresenter,
TypesNavigator,
LabelsPresenter,
@@ -122,7 +122,9 @@ export default async (): Promise<Resources> => ({
CardTagColored,
CardTagsColored,
CardIcon,
CardFeedView
CardFeedView,
CreateSpace,
CardHeaderButton
},
sectionComponent: {
AttachmentsSection: AttachmentsCardSection,
+3 -2
View File
@@ -44,14 +44,15 @@ export default mergeIds(cardId, card, {
CardRefPresenter: '' as AnyComponent,
ChangeType: '' as AnyComponent,
CreateCardButton: '' as AnyComponent,
NewCardHeader: '' as AnyComponent,
CreateSpace: '' as AnyComponent,
SpacePresenter: '' as AnyComponent,
TypesNavigator: '' as AnyComponent,
RolesSection: '' as AnyComponent,
EditRole: '' as AnyComponent,
CardWidget: '' as AnyComponent,
CardWidgetTab: '' as AnyComponent,
CreateCard: '' as AnyComponent
CreateCard: '' as AnyComponent,
CardHeaderButton: '' as AnyComponent
},
function: {
CardFactory: '' as Resource<(props?: Record<string, any>) => Promise<Ref<Doc> | undefined>>
@@ -978,7 +978,18 @@
>
<div class="antiPanel-wrap__content hulyNavPanel-container">
{#if currentApplication}
<NavHeader label={currentApplication.label} />
<NavHeader label={currentApplication.label}>
{#if currentApplication.navHeaderActions != null}
<Component
is={currentApplication.navHeaderActions}
props={{
currentSpace,
currentSpecial,
currentFragment
}}
/>
{/if}
</NavHeader>
{#if currentApplication.navHeaderComponent}
<Component
is={currentApplication.navHeaderComponent}
+3
View File
@@ -50,7 +50,10 @@ export interface Application extends Doc {
// Component will be displayed in case navigator model is not defined, or nothing is selected in navigator model
component?: AnyComponent
// Component to display below the navigator header
navHeaderComponent?: AnyComponent
// Component to display in the actions area of the navigator header
navHeaderActions?: AnyComponent
accessLevel?: AccountRole
navFooterComponent?: AnyComponent
}