diff --git a/javascript/apps/taiga/src/app/modules/project/feature-kanban/data-access/+state/reducers/kanban.reducer.ts b/javascript/apps/taiga/src/app/modules/project/feature-kanban/data-access/+state/reducers/kanban.reducer.ts index fdeb1a3c9..86c905ac7 100644 --- a/javascript/apps/taiga/src/app/modules/project/feature-kanban/data-access/+state/reducers/kanban.reducer.ts +++ b/javascript/apps/taiga/src/app/modules/project/feature-kanban/data-access/+state/reducers/kanban.reducer.ts @@ -10,15 +10,18 @@ import { createFeature, createSelector, on } from '@ngrx/store'; import { Status, Story, Workflow } from '@taiga/data'; import { DropCandidate } from '@taiga/ui/drag/drag.model'; import { - projectEventActions, projectApiActions, + projectEventActions, } from '~/app/modules/project/data-access/+state/actions/project.actions'; import { KanbanStory, KanbanStoryA11y, PartialStory, } from '~/app/modules/project/feature-kanban/kanban.model'; -import { StoryDetailActions } from '~/app/modules/project/story-detail/data-access/+state/actions/story-detail.actions'; +import { + StoryDetailActions, + StoryDetailApiActions, +} from '~/app/modules/project/story-detail/data-access/+state/actions/story-detail.actions'; import { moveItemArray } from '~/app/shared/utils/move-item-array'; import { pick } from '~/app/shared/utils/pick'; import { createImmerReducer } from '~/app/shared/utils/store'; @@ -35,7 +38,6 @@ import { replaceStory, setIntialPosition, } from './kanban.reducer.helpers'; -import { StoryDetailApiActions } from '~/app/modules/project/story-detail/data-access/+state/actions/story-detail.actions'; export interface KanbanState { loadingWorkflow: boolean; @@ -632,6 +634,18 @@ export const reducer = createImmerReducer( return state; } ), + on( + StoryDetailApiActions.updateStoryWorkflowSuccess, + (state, { story }): KanbanState => { + if (state.currentWorkflowSlug !== story.workflow.slug) { + state = removeStory(state, (it) => it.ref === story.ref); + } else { + state = addStory(state, story, undefined, 'top', story.status); + } + + return state; + } + ), on( KanbanActions.deleteStory, StoryDetailActions.deleteStory, diff --git a/javascript/apps/taiga/src/app/modules/project/feature-view-setter/project-feature-view-setter.component.ts b/javascript/apps/taiga/src/app/modules/project/feature-view-setter/project-feature-view-setter.component.ts index 4712c1f35..93505a748 100644 --- a/javascript/apps/taiga/src/app/modules/project/feature-view-setter/project-feature-view-setter.component.ts +++ b/javascript/apps/taiga/src/app/modules/project/feature-view-setter/project-feature-view-setter.component.ts @@ -18,13 +18,13 @@ import { } from '@angular/core'; import { Store } from '@ngrx/store'; import { + combineLatest, distinctUntilChanged, filter, map, + of, pairwise, startWith, - combineLatest, - of, } from 'rxjs'; import { RouteHistoryService } from '~/app/shared/route-history/route-history.service'; import { StoryDetailActions } from '../story-detail/data-access/+state/actions/story-detail.actions'; @@ -33,18 +33,18 @@ import { selectStoryView, } from '../story-detail/data-access/+state/selectors/story-detail.selectors'; -import { ProjectFeatureStoryWrapperFullViewModule } from '../feature-story-wrapper-full-view/project-feature-story-wrapper-full-view.module'; -import { filterNil } from '~/app/shared/utils/operators'; +import { CommonModule } from '@angular/common'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { ActivatedRoute, ActivatedRouteSnapshot, Router, } from '@angular/router'; import { RxState } from '@rx-angular/state'; -import { CommonModule } from '@angular/common'; -import { StoryDetail, StoryView, Project, Story, Workflow } from '@taiga/data'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { selectCurrentWorkflowSlug } from '~/app/modules/project/feature-kanban/data-access/+state/selectors/kanban.selectors'; +import { Project, Story, StoryDetail, StoryView, Workflow } from '@taiga/data'; +import { filterNil } from '~/app/shared/utils/operators'; +import { selectCurrentWorkflowSlug } from '../feature-kanban/data-access/+state/selectors/kanban.selectors'; +import { ProjectFeatureStoryWrapperFullViewModule } from '../feature-story-wrapper-full-view/project-feature-story-wrapper-full-view.module'; interface ProjectFeatureViewSetterComponentState { storyView: StoryView; diff --git a/javascript/apps/taiga/src/app/modules/project/story-detail/components/story-detail-workflow/story-detail-workflow.component.css b/javascript/apps/taiga/src/app/modules/project/story-detail/components/story-detail-workflow/story-detail-workflow.component.css new file mode 100644 index 000000000..b006abd53 --- /dev/null +++ b/javascript/apps/taiga/src/app/modules/project/story-detail/components/story-detail-workflow/story-detail-workflow.component.css @@ -0,0 +1,56 @@ +/* +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Copyright (c) 2023-present Kaleidos INC +*/ + +@import url("tools/typography.css"); +@import url("shared/option-list.css"); + +:host { + display: block; +} + +.workflow-selector-wrapper { + inline-size: 245px; +} + +.workflow-list-header { + @mixin font-small; + + color: var(--color-gray100); + padding-block: var(--spacing-4) var(--spacing-8); + padding-inline: var(--spacing-4); + text-transform: uppercase; +} + +.separator { + @mixin separator; + + margin-inline: 0; +} + +.workflow-btn { + @mixin font-inline; + + align-items: center; + color: var(--color-gray90); + display: flex; + gap: var(--spacing-8); + justify-content: flex-start; +} + +.workflow-icon { + color: var(--color-gray40); +} + +.workflow-name { + @mixin ellipsis; + + &:hover { + background: var(--color-gray20); + color: var(--color-secondary80); + } +} diff --git a/javascript/apps/taiga/src/app/modules/project/story-detail/components/story-detail-workflow/story-detail-workflow.component.html b/javascript/apps/taiga/src/app/modules/project/story-detail/components/story-detail-workflow/story-detail-workflow.component.html new file mode 100644 index 000000000..e340990f0 --- /dev/null +++ b/javascript/apps/taiga/src/app/modules/project/story-detail/components/story-detail-workflow/story-detail-workflow.component.html @@ -0,0 +1,62 @@ + + + + + + + + +
+ +
+ {{ t('kanban.move_to_workflow') }} +
+
+ + + +
+
+
+
+
diff --git a/javascript/apps/taiga/src/app/modules/project/story-detail/components/story-detail-workflow/story-detail-workflow.component.ts b/javascript/apps/taiga/src/app/modules/project/story-detail/components/story-detail-workflow/story-detail-workflow.component.ts new file mode 100644 index 000000000..46c27d769 --- /dev/null +++ b/javascript/apps/taiga/src/app/modules/project/story-detail/components/story-detail-workflow/story-detail-workflow.component.ts @@ -0,0 +1,81 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2023-present Kaleidos INC + */ + +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + OnChanges, + Output, + ViewChild, +} from '@angular/core'; + +import { CommonModule } from '@angular/common'; +import { TranslocoDirective } from '@ngneat/transloco'; +import { + TuiButtonModule, + TuiDataListModule, + TuiHostedDropdownComponent, + TuiHostedDropdownModule, + TuiSvgModule, +} from '@taiga-ui/core'; +import { StoryDetail, Workflow } from '@taiga/data'; +import { BreadcrumbComponent } from '@taiga/ui/breadcrumb/breadcrumb.component'; + +@Component({ + selector: 'tg-story-detail-workflow', + templateUrl: './story-detail-workflow.component.html', + styleUrls: ['./story-detail-workflow.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + CommonModule, + TranslocoDirective, + BreadcrumbComponent, + TuiButtonModule, + TuiHostedDropdownModule, + TuiDataListModule, + TuiSvgModule, + ], +}) +export class StoryDetailWorkflowComponent implements OnChanges { + @Input({ required: true }) + public story!: StoryDetail; + + @Input({ required: true }) + public workflows!: Workflow[]; + + @Input() + public canEdit = false; + + @ViewChild(TuiHostedDropdownComponent) + public component?: TuiHostedDropdownComponent; + + @Output() + public toWorkflow = new EventEmitter(); + + public openWorkflowList = false; + public filteredWorkflows: Workflow[] = []; + + public ngOnChanges(): void { + this.filteredWorkflows = this.workflows.filter( + (workflow) => workflow.slug !== this.story.workflow.slug + ); + } + + public trackByWorkflowId(_index: number, workflow: Workflow) { + return workflow.id; + } + + public moveToWorkflow(workflow: Workflow) { + this.openWorkflowList = false; + this.component?.nativeFocusableElement?.focus(); + this.toWorkflow.emit(workflow); + } +} diff --git a/javascript/apps/taiga/src/app/modules/project/story-detail/data-access/+state/actions/story-detail.actions.ts b/javascript/apps/taiga/src/app/modules/project/story-detail/data-access/+state/actions/story-detail.actions.ts index 10664e6c8..e65c78d7c 100644 --- a/javascript/apps/taiga/src/app/modules/project/story-detail/data-access/+state/actions/story-detail.actions.ts +++ b/javascript/apps/taiga/src/app/modules/project/story-detail/data-access/+state/actions/story-detail.actions.ts @@ -34,6 +34,10 @@ export const StoryDetailActions = createActionGroup({ story: StoryUpdate; projectId: Project['id']; }>(), + 'Update story workflow': props<{ + story: StoryUpdate; + projectId: Project['id']; + }>(), 'Delete story': props<{ ref: Story['ref']; project: Project; @@ -104,6 +108,7 @@ export const StoryDetailApiActions = createActionGroup({ 'Fetch Story Success': props<{ story: StoryDetail }>(), 'Fetch Workflow Success': props<{ workflow: Workflow }>(), 'Update Story Success': props<{ story: StoryDetail }>(), + 'Update Story Workflow Success': props<{ story: StoryDetail }>(), 'Delete Story Success': props<{ project: Project; ref: Story['ref']; diff --git a/javascript/apps/taiga/src/app/modules/project/story-detail/data-access/+state/effects/story-detail.effects.ts b/javascript/apps/taiga/src/app/modules/project/story-detail/data-access/+state/effects/story-detail.effects.ts index cbf413c7a..b0490d042 100644 --- a/javascript/apps/taiga/src/app/modules/project/story-detail/data-access/+state/effects/story-detail.effects.ts +++ b/javascript/apps/taiga/src/app/modules/project/story-detail/data-access/+state/effects/story-detail.effects.ts @@ -54,7 +54,10 @@ export class StoryDetailEffects { public loadWorkflow$ = createEffect(() => { return this.actions$.pipe( - ofType(StoryDetailApiActions.fetchStorySuccess), + ofType( + StoryDetailApiActions.fetchStorySuccess, + StoryDetailApiActions.updateStoryWorkflowSuccess + ), concatLatestFrom(() => [ this.store.select(selectWorkflow), this.store.select(selectCurrentProject).pipe(filterNil()), @@ -157,6 +160,46 @@ export class StoryDetailEffects { ); }); + public updateStoryWorkflow$ = createEffect(() => { + return this.actions$.pipe( + ofType(StoryDetailActions.updateStoryWorkflow), + pessimisticUpdate({ + run: (action) => { + return this.projectApiService + .updateStory(action.projectId, action.story) + .pipe( + map((story) => + StoryDetailApiActions.updateStoryWorkflowSuccess({ story }) + ) + ); + }, + onError: (action, httpResponse: HttpErrorResponse) => { + if (httpResponse.status === 400) { + this.appService.toastNotification({ + message: 'move.error', + paramsMessage: { workflow: action.story.workflow }, + status: TuiNotification.Error, + scope: 'story', + autoClose: true, + closeOnNavigation: false, + }); + } else { + this.appService.errorManagement(httpResponse, { + any: { + type: 'toast', + options: { + label: 'errors.save_changes', + message: 'errors.please_refresh', + status: TuiNotification.Error, + }, + }, + }); + } + }, + }) + ); + }); + public deleteStory$ = createEffect(() => { return this.actions$.pipe( ofType(StoryDetailActions.deleteStory), diff --git a/javascript/apps/taiga/src/app/modules/project/story-detail/data-access/+state/reducers/story-detail.reducer.ts b/javascript/apps/taiga/src/app/modules/project/story-detail/data-access/+state/reducers/story-detail.reducer.ts index 78e209436..75099e5bd 100644 --- a/javascript/apps/taiga/src/app/modules/project/story-detail/data-access/+state/reducers/story-detail.reducer.ts +++ b/javascript/apps/taiga/src/app/modules/project/story-detail/data-access/+state/reducers/story-detail.reducer.ts @@ -158,12 +158,14 @@ export const reducer = createImmerReducer( ), on( StoryDetailApiActions.updateStorySuccess, + StoryDetailApiActions.updateStoryWorkflowSuccess, (state, { story }): StoryDetailState => { state.story = story; return state; } ), + on(StoryDetailActions.updateStory, (state, { story }): StoryDetailState => { if (state.story) { if (story.title) { diff --git a/javascript/apps/taiga/src/app/modules/project/story-detail/story-detail.component.css b/javascript/apps/taiga/src/app/modules/project/story-detail/story-detail.component.css index ed07386e7..5dec1d9f3 100644 --- a/javascript/apps/taiga/src/app/modules/project/story-detail/story-detail.component.css +++ b/javascript/apps/taiga/src/app/modules/project/story-detail/story-detail.component.css @@ -136,9 +136,11 @@ Copyright (c) 2023-present Kaleidos INC --no-user-avatar-size: 24px; } -.story-breadcrumb-modal, +tg-story-detail-workflow, tg-story-detail-status, -tg-story-detail-assign { +tg-story-detail-assign, +tg-attachments, +tg-story-detail-description { margin-block-end: var(--spacing-16); } @@ -160,11 +162,6 @@ tg-story-detail-assign { } /* stylelint-enable selector-max-compound-selectors, selector-max-type */ -tg-attachments, -tg-story-detail-description { - margin-block-end: var(--spacing-16); -} - .attachments-side-view { --undo-inline-size: 100%; } diff --git a/javascript/apps/taiga/src/app/modules/project/story-detail/story-detail.component.html b/javascript/apps/taiga/src/app/modules/project/story-detail/story-detail.component.html index 453a44e23..563d291c8 100644 --- a/javascript/apps/taiga/src/app/modules/project/story-detail/story-detail.component.html +++ b/javascript/apps/taiga/src/app/modules/project/story-detail/story-detail.component.html @@ -118,20 +118,20 @@ diff --git a/javascript/apps/taiga/src/app/modules/project/story-detail/story-detail.component.ts b/javascript/apps/taiga/src/app/modules/project/story-detail/story-detail.component.ts index 576437de9..718697cc7 100644 --- a/javascript/apps/taiga/src/app/modules/project/story-detail/story-detail.component.ts +++ b/javascript/apps/taiga/src/app/modules/project/story-detail/story-detail.component.ts @@ -6,7 +6,7 @@ * Copyright (c) 2023-present Kaleidos INC */ -import { Location, DatePipe, CommonModule } from '@angular/common'; +import { CommonModule, DatePipe, Location } from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, @@ -49,7 +49,6 @@ import { import { map, merge, pairwise, startWith, take } from 'rxjs'; import { TuiScrollbarModule } from '@taiga-ui/core/components/scrollbar'; -import { BreadcrumbComponent } from '@taiga/ui/breadcrumb/breadcrumb.component'; import { InputsModule } from '@taiga/ui/inputs'; import { ModalComponent } from '@taiga/ui/modal/components'; import { v4 } from 'uuid'; @@ -58,14 +57,27 @@ import { selectCurrentProject } from '~/app/modules/project/data-access/+state/s import { AppService } from '~/app/services/app.service'; import { PermissionsService } from '~/app/services/permissions.service'; import { WsService } from '~/app/services/ws'; +import { AttachmentsComponent } from '~/app/shared/attachments/attachments.component'; import { CommentsComponent, OrderComments, } from '~/app/shared/comments/comments.component'; +import { CommentsAutoScrollDirective } from '~/app/shared/comments/directives/comments-auto-scroll.directive'; +import { DiscardChangesModalComponent } from '~/app/shared/discard-changes-modal/discard-changes-modal.component'; import { EditorImageUploadService } from '~/app/shared/editor/editor-image-upload.service'; +import { NouserAvatarComponent } from '~/app/shared/nouser-avatar/nouser-avatar.component'; import { PermissionUpdateNotificationService } from '~/app/shared/permission-update-notification/permission-update-notification.service'; +import { DateDistancePipe } from '~/app/shared/pipes/date-distance/date-distance.pipe'; import { ResizedDirective } from '~/app/shared/resize/resize.directive'; +import { UserAvatarComponent } from '~/app/shared/user-avatar/user-avatar.component'; import { filterNil } from '~/app/shared/utils/operators'; +import { selectCurrentWorkflowSlug } from '../feature-kanban/data-access/+state/selectors/kanban.selectors'; +import { StoryDetailAssignComponent } from './components/story-detail-assign/story-detail-assign.component'; +import { StoryDetailDescriptionComponent } from './components/story-detail-description/story-detail-description.component'; +import { StoryDetailHeaderComponent } from './components/story-detail-header/story-detail-header.component'; +import { StoryDetailStatusComponent } from './components/story-detail-status/story-detail-status.component'; +import { StoryDetailTitleComponent } from './components/story-detail-title/story-detail-title.component'; +import { StoryDetailWorkflowComponent } from './components/story-detail-workflow/story-detail-workflow.component'; import { StoryDetailActions, StoryDetailApiActions, @@ -77,19 +89,8 @@ import { selectStoryView, selectWorkflow, } from './data-access/+state/selectors/story-detail.selectors'; -import { StoryDetaiImageUploadService } from './story-detail-image-upload.service'; -import { StoryDetailHeaderComponent } from './components/story-detail-header/story-detail-header.component'; -import { StoryDetailDescriptionComponent } from './components/story-detail-description/story-detail-description.component'; -import { StoryDetailAssignComponent } from './components/story-detail-assign/story-detail-assign.component'; -import { StoryDetailStatusComponent } from './components/story-detail-status/story-detail-status.component'; -import { StoryDetailTitleComponent } from './components/story-detail-title/story-detail-title.component'; import { StoryCommentsPaginationDirective } from './directives/story-comments-pagination.directive'; -import { AttachmentsComponent } from '~/app/shared/attachments/attachments.component'; -import { CommentsAutoScrollDirective } from '~/app/shared/comments/directives/comments-auto-scroll.directive'; -import { DiscardChangesModalComponent } from '~/app/shared/discard-changes-modal/discard-changes-modal.component'; -import { NouserAvatarComponent } from '~/app/shared/nouser-avatar/nouser-avatar.component'; -import { DateDistancePipe } from '~/app/shared/pipes/date-distance/date-distance.pipe'; -import { UserAvatarComponent } from '~/app/shared/user-avatar/user-avatar.component'; +import { StoryDetaiImageUploadService } from './story-detail-image-upload.service'; export interface StoryDetailState { project: Project; @@ -113,6 +114,7 @@ export interface StoryDetailState { user: User; attachments: Attachment[]; loadingAttachments: LoadingAttachment[]; + workflowSlug: Workflow['slug']; } export interface StoryDetailForm { @@ -166,7 +168,7 @@ export interface StoryDetailForm { DiscardChangesModalComponent, DatePipe, DateDistancePipe, - BreadcrumbComponent, + StoryDetailWorkflowComponent, ], }) export class StoryDetailComponent { @@ -292,6 +294,11 @@ export class StoryDetailComponent { this.store.select(storyDetailFeature.selectLoadingAttachments) ); + this.state.connect( + 'workflowSlug', + this.store.select(selectCurrentWorkflowSlug) + ); + this.state .select('story') .pipe(filterNil(), take(1)) @@ -427,10 +434,11 @@ export class StoryDetailComponent { const ref = this.state.get('story').ref; this.store.dispatch(StoryDetailActions.leaveStoryDetail()); + this.store; void this.router.navigateByUrl( `project/${this.state.get('project').id}/${ this.state.get('project').slug - }/kanban/${this.state.get('story').workflow.slug}` + }/kanban/${this.state.get('workflowSlug') || 'main'}` ); if (ref) { requestAnimationFrame(() => { @@ -477,6 +485,18 @@ export class StoryDetailComponent { ); } + public moveStoryToWorkflow(workflow: Workflow) { + const story = this.getStoryUpdate(); + story.workflow = workflow.slug; + + this.store.dispatch( + StoryDetailActions.updateStoryWorkflow({ + projectId: this.state.get('project').id, + story, + }) + ); + } + public fieldFocus(focus: boolean) { this.state.set({ fieldFocus: focus }); } diff --git a/javascript/apps/taiga/src/app/styles/taiga-ui/tui-data-list.css b/javascript/apps/taiga/src/app/styles/taiga-ui/tui-data-list.css index 839f6b13e..bd8bcd105 100644 --- a/javascript/apps/taiga/src/app/styles/taiga-ui/tui-data-list.css +++ b/javascript/apps/taiga/src/app/styles/taiga-ui/tui-data-list.css @@ -14,7 +14,6 @@ Copyright (c) 2023-present Kaleidos INC tui-data-list { --tui-data-list-padding: var(--spacing-4); - border: 1px solid var(--color-gray40); border-radius: 3px; &:focus { diff --git a/javascript/apps/taiga/src/assets/i18n/kanban/en-US.json b/javascript/apps/taiga/src/assets/i18n/kanban/en-US.json index c38bb7c6a..a80f4282d 100644 --- a/javascript/apps/taiga/src/assets/i18n/kanban/en-US.json +++ b/javascript/apps/taiga/src/assets/i18n/kanban/en-US.json @@ -38,6 +38,7 @@ "create_status_explanation": "Once the admins of this project set up the kanban, you can start creating stories", "delete_workflow": "Delete workflow", "edit_workflow_name": "Edit workflow name", + "move_to_workflow": "Move story to another workflow:", "delete_status_modal": { "title": "Delete {{ statusName }} status?", "what_to_do_stories": "What do you want to do with the stories in this status?", diff --git a/javascript/apps/taiga/src/assets/i18n/story/en-US.json b/javascript/apps/taiga/src/assets/i18n/story/en-US.json index d01afbf49..9b149c91e 100644 --- a/javascript/apps/taiga/src/assets/i18n/story/en-US.json +++ b/javascript/apps/taiga/src/assets/i18n/story/en-US.json @@ -23,6 +23,9 @@ "empty_description": "Add a description to your story", "empty_description_view_permissions": "This story doesn't have a description yet.", "edit_description": "Edit description", + "move": { + "error": "The workflow {{workflow}} does not have any status. Create one and try again." + }, "delete": { "delete_story": "Delete Story", "confirm": "Are you sure you want to delete this story?", @@ -32,4 +35,4 @@ "confirm_delete": "Story #{{ref}} has been deleted", "confirm_delete_event": "{{username}} has deleted the story #{{ref}}" } -} +} \ No newline at end of file diff --git a/javascript/apps/taiga/src/assets/icons/change.svg b/javascript/apps/taiga/src/assets/icons/change.svg new file mode 100644 index 000000000..5a5b7937a --- /dev/null +++ b/javascript/apps/taiga/src/assets/icons/change.svg @@ -0,0 +1,5 @@ + + +Change + + diff --git a/javascript/libs/data/src/lib/story.model.ts b/javascript/libs/data/src/lib/story.model.ts index b72144f92..2243d7a09 100644 --- a/javascript/libs/data/src/lib/story.model.ts +++ b/javascript/libs/data/src/lib/story.model.ts @@ -44,6 +44,7 @@ export interface StoryUpdate { status?: Story['status']['id']; title?: Story['title']; description?: Story['description']; + workflow?: Workflow['slug']; } export interface createdBy { diff --git a/javascript/libs/ui/src/lib/breadcrumb/breadcrumb.component.html b/javascript/libs/ui/src/lib/breadcrumb/breadcrumb.component.html index 8d4c08576..5954ac33b 100644 --- a/javascript/libs/ui/src/lib/breadcrumb/breadcrumb.component.html +++ b/javascript/libs/ui/src/lib/breadcrumb/breadcrumb.component.html @@ -39,5 +39,6 @@ *ngIf="!(hideLastCrumb && i === crumbs.length - 2) && !isLast" src="chevron-right"> +