diff --git a/javascript/apps/taiga/src/app/modules/project/story-detail/components/story-detail-header/story-detail-header.component.css b/javascript/apps/taiga/src/app/modules/project/story-detail/components/story-detail-header/story-detail-header.component.css new file mode 100644 index 000000000..1891c0229 --- /dev/null +++ b/javascript/apps/taiga/src/app/modules/project/story-detail/components/story-detail-header/story-detail-header.component.css @@ -0,0 +1,112 @@ +/* +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"); + +/* Rotate arrows in RTL */ +:host-context([dir="rtl"]) { + & .story-navigation-arrow { + transform: rotate(180deg); + } +} + +.view-options-list { + @mixin option-list; +} + +.story-options-list { + @mixin option-list; +} + +.story-header { + display: flex; + justify-content: space-between; + margin-block: var(--spacing-8); + margin-inline: var(--spacing-8); + + & .start { + align-items: center; + display: flex; + margin-inline-start: var(--spacing-8); + } + + & .end { + column-gap: var(--spacing-8); + display: flex; + + &.no-close { + grid-template-columns: 32px 32px 40px 32px; + } + } + + & .change-view { + margin-inline-start: var(--spacing-8); + position: relative; + + &::before { + block-size: var(--spacing-24); + border-inline-start: solid 1px var(--color-gray20); + content: ""; + inset-block: 0; + inset-inline-start: calc(var(--spacing-8) * -1); + margin-block: auto; + position: absolute; + } + } +} + +.workflow { + @mixin font-inline; + + color: var(--color-secondary); + font-weight: var(--font-weight-medium); + position: relative; + + &:hover { + text-decoration: underline; + } +} + +.chevron-right-icon { + block-size: var(--spacing-8); + color: var(--color-gray60); + inline-size: var(--spacing-8); + margin-block: 0; + margin-inline: var(--spacing-4); +} + +.story-ref { + @mixin font-inline; + + color: var(--color-gray80); + font-weight: var(--font-weight-medium); + margin-inline-end: var(--spacing-4); + + &::ng-deep .hash { + color: var(--color-secondary50); + margin-inline-start: var(--spacing-4); + } +} + +.copy-link { + &::ng-deep tui-svg { + block-size: var(--spacing-12); + inline-size: var(--spacing-12); + } + + &.copied { + &::ng-deep [tuiWrapper] { + background-color: var(--color-ok10); + } + + & tui-svg { + color: var(--color-ok); + } + } +} diff --git a/javascript/apps/taiga/src/app/modules/project/story-detail/components/story-detail-header/story-detail-header.component.html b/javascript/apps/taiga/src/app/modules/project/story-detail/components/story-detail-header/story-detail-header.component.html new file mode 100644 index 000000000..d8a2155a6 --- /dev/null +++ b/javascript/apps/taiga/src/app/modules/project/story-detail/components/story-detail-header/story-detail-header.component.html @@ -0,0 +1,198 @@ + + + + +
+ +
+ + + + + + + + + + +
+ + + + + + + + + + +
+
+
diff --git a/javascript/apps/taiga/src/app/modules/project/story-detail/components/story-detail-header/story-detail-header.component.ts b/javascript/apps/taiga/src/app/modules/project/story-detail/components/story-detail-header/story-detail-header.component.ts new file mode 100644 index 000000000..3b92a3c53 --- /dev/null +++ b/javascript/apps/taiga/src/app/modules/project/story-detail/components/story-detail-header/story-detail-header.component.ts @@ -0,0 +1,208 @@ +/** + * 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 { Clipboard } from '@angular/cdk/clipboard'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Output, +} from '@angular/core'; +import { Store } from '@ngrx/store'; +import { RxState } from '@rx-angular/state'; +import { Project, StoryDetail, StoryView } from '@taiga/data'; +import { selectCurrentProject } from '~/app/modules/project/data-access/+state/selectors/project.selectors'; +import { + selectStory, + selectStoryView, +} from '~/app/modules/project/story-detail/data-access/+state/selectors/story-detail.selectors'; +import { filterNil } from '~/app/shared/utils/operators'; +import { + TuiButtonModule, + TuiSvgModule, + TuiHostedDropdownModule, + TuiDataListModule, +} from '@taiga-ui/core'; +import { Location, CommonModule } from '@angular/common'; +import { TranslocoDirective } from '@ngneat/transloco'; +import { TooltipDirective } from '@taiga/ui/tooltip'; +import { Router } from '@angular/router'; +import { StoryDetailActions } from '~/app/modules/project/story-detail/data-access/+state/actions/story-detail.actions'; +import { HasPermissionDirective } from '~/app/shared/directives/has-permissions/has-permission.directive'; + +export interface StoryDetailHeaderState { + project: Project; + story: StoryDetail; + selectedStoryView: StoryView; +} + +@Component({ + selector: 'tg-story-detail-header', + templateUrl: './story-detail-header.component.html', + styleUrls: ['./story-detail-header.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [RxState], + standalone: true, + imports: [ + CommonModule, + TranslocoDirective, + TuiButtonModule, + TuiSvgModule, + TuiHostedDropdownModule, + TuiDataListModule, + TooltipDirective, + HasPermissionDirective, + ], +}) +export class StoryDetailHeaderComponent { + @Output() + public closeStory = new EventEmitter(); + + @Output() + public showDeleteStoryConfirm = new EventEmitter(); + + public model$ = this.state.select(); + public linkCopied = false; + public hintShown = false; + public resetCopyLinkTimeout?: ReturnType; + public showCopyLinkHintTimeout?: ReturnType; + public dropdownState = false; + public storyOptionsState = false; + public storyViewOptions: { id: StoryView; translation: string }[] = [ + { + id: 'modal-view', + translation: 'story.modal_view', + }, + { + id: 'side-view', + translation: 'story.side_panel_view', + }, + { + id: 'full-view', + translation: 'story.full_width_view', + }, + ]; + + constructor( + private cd: ChangeDetectorRef, + public state: RxState, + private clipboard: Clipboard, + private store: Store, + private router: Router, + private location: Location + ) { + this.state.connect( + 'project', + this.store.select(selectCurrentProject).pipe(filterNil()) + ); + this.state.connect( + 'story', + this.store.select(selectStory).pipe(filterNil()) + ); + this.state.connect('selectedStoryView', this.store.select(selectStoryView)); + } + + public trackByIndex(index: number) { + return index; + } + + public get getCurrentViewTranslation() { + const index = this.storyViewOptions.findIndex( + (it) => it.id === this.state.get('selectedStoryView') + ); + + if (index !== -1) { + return this.storyViewOptions[index].translation; + } + + return ''; + } + + public selectStoryView(id: StoryView) { + this.dropdownState = false; + this.store.dispatch( + StoryDetailActions.updateStoryViewMode({ + storyView: id, + previousStoryView: this.state.get('selectedStoryView'), + }) + ); + + // reset state to prevent focus on navigation arrows + this.location.replaceState(this.location.path(), undefined, {}); + } + + public displayHint() { + this.showCopyLinkHintTimeout = setTimeout(() => { + this.hintShown = true; + this.cd.detectChanges(); + }, 200); + } + + public resetCopyLink(type: 'fast' | 'slow') { + if (this.showCopyLinkHintTimeout) { + clearTimeout(this.showCopyLinkHintTimeout); + } + + if (this.linkCopied) { + const time = type === 'fast' ? 200 : 4000; + this.resetCopyLinkTimeout = setTimeout(() => { + this.hintShown = false; + this.linkCopied = false; + this.cd.detectChanges(); + }, time); + } else { + this.hintShown = false; + } + } + + public getStoryLink() { + this.clipboard.copy(window.location.href); + + this.linkCopied = true; + } + + public navigateToNextStory(ref: number) { + void this.router.navigate( + [ + 'project', + this.state.get('project').id, + this.state.get('project').slug, + 'stories', + ref, + ], + { + state: { + nextStoryNavigation: true, + }, + } + ); + } + + public navigateToPreviousStory(ref: number) { + void this.router.navigate( + [ + 'project', + this.state.get('project').id, + this.state.get('project').slug, + 'stories', + ref, + ], + { + state: { + previousStoryNavigation: true, + }, + } + ); + } + + public deleteStoryConfirmModal() { + this.storyOptionsState = false; + this.showDeleteStoryConfirm.next(); + } +} 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 711403d27..d4f1848eb 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 @@ -16,89 +16,12 @@ Copyright (c) 2023-present Kaleidos INC inline-size: 100%; } -/* Rotate arrows in RTL */ -:host-context([dir="rtl"]) { - & .story-navigation-arrow { - transform: rotate(180deg); - } -} - -.workflow { - @mixin font-inline; - - color: var(--color-secondary); - font-weight: var(--font-weight-medium); - position: relative; - - &:hover { - text-decoration: underline; - } -} - -.chevron-right-icon { - block-size: var(--spacing-8); - color: var(--color-gray60); - inline-size: var(--spacing-8); - margin-block: 0; - margin-inline: var(--spacing-4); -} - .story { block-size: 100%; display: flex; flex-direction: column; } -.story-ref { - @mixin font-inline; - - color: var(--color-gray80); - font-weight: var(--font-weight-medium); - margin-inline-end: var(--spacing-4); - - &::ng-deep .hash { - color: var(--color-secondary50); - margin-inline-start: var(--spacing-4); - } -} - -.story-header { - display: flex; - justify-content: space-between; - margin-block: var(--spacing-8); - margin-inline: var(--spacing-8); - - & .start { - align-items: center; - display: flex; - margin-inline-start: var(--spacing-8); - } - - & .end { - column-gap: var(--spacing-8); - display: flex; - - &.no-close { - grid-template-columns: 32px 32px 40px 32px; - } - } - - & .change-view { - margin-inline-start: var(--spacing-8); - position: relative; - - &::before { - block-size: var(--spacing-24); - border-inline-start: solid 1px var(--color-gray20); - content: ""; - inset-block: 0; - inset-inline-start: calc(var(--spacing-8) * -1); - margin-block: auto; - position: absolute; - } - } -} - .scrollbar-content { display: flex; } @@ -178,43 +101,6 @@ Copyright (c) 2023-present Kaleidos INC } } -.copy-link { - &::ng-deep tui-svg { - block-size: var(--spacing-12); - inline-size: var(--spacing-12); - } - - &.copied { - &::ng-deep [tuiWrapper] { - background-color: var(--color-ok10); - } - - & tui-svg { - color: var(--color-ok); - } - } -} - -.view-options-list { - @mixin option-list; -} - -.story-options-list { - @mixin option-list; -} - -.story-option { - justify-content: flex-start; -} - -.story-option-icon { - @mixin option-icon; -} - -.story-option-name { - @mixin option-name; -} - .delete-story-confirm-title { @mixin font-heading-3; 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 01a205090..5db79a4ef 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 @@ -14,197 +14,11 @@
-
- -
- - - - - - - - - - -
- - - - - - - - - - -
+
; - public showCopyLinkHintTimeout?: ReturnType; public form: FormGroup | null = null; public model$ = this.state.select(); public project$ = this.store.select(selectCurrentProject); - public get getCurrentViewTranslation() { - const index = this.storyViewOptions.findIndex( - (it) => it.id === this.state.get('selectedStoryView') - ); - - if (index !== -1) { - return this.storyViewOptions[index].translation; - } - - return ''; - } - constructor( private cd: ChangeDetectorRef, private store: Store, - private clipboard: Clipboard, private location: Location, private permissionService: PermissionsService, private wsService: WsService, @@ -463,87 +421,6 @@ export class StoryDetailComponent { } } - public trackByIndex(index: number) { - return index; - } - - public selectStoryView(id: StoryView) { - this.dropdownState = false; - this.store.dispatch( - StoryDetailActions.updateStoryViewMode({ - storyView: id, - previousStoryView: this.state.get('selectedStoryView'), - }) - ); - - // reset state to prevent focus on navigation arrows - this.location.replaceState(this.location.path(), undefined, {}); - } - - public displayHint() { - this.showCopyLinkHintTimeout = setTimeout(() => { - this.hintShown = true; - this.cd.detectChanges(); - }, 200); - } - - public getStoryLink() { - this.clipboard.copy(window.location.href); - - this.linkCopied = true; - } - - public resetCopyLink(type: 'fast' | 'slow') { - if (this.showCopyLinkHintTimeout) { - clearTimeout(this.showCopyLinkHintTimeout); - } - - if (this.linkCopied) { - const time = type === 'fast' ? 200 : 4000; - this.resetCopyLinkTimeout = setTimeout(() => { - this.hintShown = false; - this.linkCopied = false; - this.cd.detectChanges(); - }, time); - } else { - this.hintShown = false; - } - } - - public navigateToNextStory(ref: number) { - void this.router.navigate( - [ - 'project', - this.state.get('project').id, - this.state.get('project').slug, - 'stories', - ref, - ], - { - state: { - nextStoryNavigation: true, - }, - } - ); - } - - public navigateToPreviousStory(ref: number) { - void this.router.navigate( - [ - 'project', - this.state.get('project').id, - this.state.get('project').slug, - 'stories', - ref, - ], - { - state: { - previousStoryNavigation: true, - }, - } - ); - } - public closeStory() { const ref = this.state.get('story').ref; @@ -585,8 +462,7 @@ export class StoryDetailComponent { this.showDeleteStoryConfirm = false; } - public deleteStoryConfirmModal() { - this.storyOptionsState = false; + public showDeleteConfirm() { this.showDeleteStoryConfirm = true; }