Change layout of notification row to new design

pull/9594/head
Henriette Darge 3 years ago
parent 4724c6113c
commit a9c37a947f
  1. 1
      config/locales/js-en.yml
  2. 2
      frontend/src/app/features/in-app-notifications/center/in-app-notification-center.component.html
  3. 152
      frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.html
  4. 118
      frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.sass
  5. 6
      frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.ts
  6. 2
      frontend/src/app/features/in-app-notifications/entry/status/in-app-notification-status.component.sass

@ -576,6 +576,7 @@ en:
all: 'All'
center:
mark_all_read: 'Mark all as read'
text_update_date: "%{date} by"
total_count_warning: "Showing the %{newest_count} most recent notifications. %{more_count} more are not displayed."
settings:
default_all_projects: 'Default for all projects'

@ -2,7 +2,7 @@
<div class="op-ian-center--content">
<ng-container *ngIf="(hasNotifications$ | async) as notifications; else noResults">
<cdk-virtual-scroll-viewport
itemSize="80"
itemSize="90"
class="op-ian-center--viewport"
>
<op-in-app-notification-entry

@ -1,108 +1,108 @@
<div
class="op-ian-item"
[class.op-ian-item_expanded]="notification.expanded"
[class.op-ian-item_read]="notification.readIAN === true"
attr.data-qa-selector="op-ian-notification-item-{{notification.id}}"
[attr.data-qa-ian-read]="notification.readIAN === true || undefined"
class="op-ian-item"
[class.op-ian-item_expanded]="notification.expanded"
[class.op-ian-item_read]="notification.readIAN === true"
attr.data-qa-selector="op-ian-notification-item-{{notification.id}}"
[attr.data-qa-ian-read]="notification.readIAN === true || undefined"
>
<button
type="button"
class="op-ian-item--row"
[class.op-ian-item--row_unexpandable]="unexpandable"
(click)="showDetails()"
type="button"
class="op-ian-item--row"
[class.op-ian-item--row_unexpandable]="unexpandable"
(click)="showDetails()"
>
<div
*ngIf="project"
class="op-ian-item--project"
>
<a
<ng-container *ngIf="workPackage$ && (workPackage$ | async) as workPackage; else workPackageLoading">
<div
class="op-ian-item--top-line"
>
<div class="op-ian-item--status">
<op-in-app-notification-status [status]="workPackage.status"></op-in-app-notification-status>
</div>
<a
class="op-ian-item--work-package-id-link"
[attr.title]="workPackage.subject"
uiSref="work-packages.show"
[uiParams]="{workPackageId: workPackage.id}"
[textContent]="'#' + workPackage.id"
(click)="resourceLinkClicked.emit(workPackage)"
>
</a>
<div
*ngIf="project"
class="op-ian-item--project"
>
- <a
class="op-ian-item--project-link" [href]="project.showUrl"
[textContent]="project.title"
(click)="projectClicked($event)"
target="_blank"
></a>
</div>
></a> -
</div>
<ng-container *ngIf="workPackage$ && (workPackage$ | async) as workPackage; else workPackageLoading">
<span
class="op-ian-item--title"
ngPreserveWhitespaces
>
<span [ngClass]="'__hl_inline_type_' + workPackage.type.id" [textContent]="workPackage.type.name">
</span>
<a
[attr.title]="workPackage.subject"
uiSref="work-packages.show"
[uiParams]="{workPackageId: workPackage.id}"
[textContent]="'#' + workPackage.id"
(click)="resourceLinkClicked.emit(workPackage)"
>
</a>
<div
class="op-ian-item--reason-wrapper"
>
<span
[textContent]="workPackage.subject"
class="op-ian-item--work-package-subject"
[class.-read]="notification.readIAN"
class="op-ian-item--reason"
*ngFor="let item of translatedReasons | keyvalue; let first = first; let last = last"
>
{{ item.key }}<ng-container *ngIf="!last && first !== last">, </ng-container>
</span>
</span>
</div>
<ng-container *ngIf="!workPackage$">
<span
class="op-ian-item--title"
[textContent]="notification.subject"
class="op-ian-item--reason-count"
[textContent]="aggregatedNotifications.length"
></span>
</ng-container>
<op-principal-list
class="op-ian-item--actors"
[principals]="actors"
></op-principal-list>
<div class="op-ian-item--status hidden-for-mobile">
<op-in-app-notification-status [status]="workPackage.status"></op-in-app-notification-status>
<div class="op-ian-item--buttons">
<i
*ngIf="!notification.readIAN"
class="op-ian-item--button icon-yes"
(click)="markAsRead($event, aggregatedNotifications)"
>
</i>
</div>
</div>
<div
class="op-ian-item--reason-wrapper"
class="op-ian-item--middle-line"
>
<span
class="op-ian-item--reason"
*ngFor="let item of translatedReasons | keyvalue; let first = first; let last = last"
>
<span
*ngIf="item.value > 1"
class="op-ian-item--reason-count"
[textContent]="item.value"
></span>
{{ item.key }}<ng-container *ngIf="!last && first !== last">, </ng-container>
[ngClass]="'op-ian-item--work-package-type __hl_inline_type_' + workPackage.type.id"
[textContent]="workPackage.type.name">
</span>
</div>
<div class="op-ian-item--buttons">
<button
*ngIf="!notification.readIAN"
type="button"
class="op-ian-item--button button -tiny"
[disabled]="loading$ | async"
(click)="markAsRead($event, aggregatedNotifications)"
<span
[textContent]="workPackage.subject"
class="op-ian-item--work-package-subject"
[class.-read]="notification.readIAN"
>
<i class="button--icon icon-yes"></i>
</button>
</span>
</div>
<div
class="op-ian-item--date"
[title]="fixedTime"
[textContent]="relativeTime$ | async"
></div>
class="op-ian-item--bottom-line"
>
<div
class="op-ian-item--date"
[title]="fixedTime"
[textContent]="relativeTime$ | async"
></div>
<op-principal-list
class="op-ian-item--actors"
[principals]="actors"
></op-principal-list>
</div>
</ng-container>
<ng-template #workPackageLoading>
<span
*ngIf="workPackage$"
class="op-ian-item--title"
[textContent]="text.loading"
></span>
<span
*ngIf="workPackage$"
class="op-ian-item--middle-line"
[textContent]="text.loading"
></span>
</ng-template>
</button>
</div>

@ -7,19 +7,13 @@ $subject-font-size: 14px
.op-ian-item
background: $ian-bg-color
margin: 0 0 10px 0
padding: 5px 10px
padding: 10px 20px 10px 10px
font-size: 0.9rem
border: 1px solid var(--content-default-border-color)
border-radius: 2px
// This needs to be set in the itemSize of
// the virtual scroller
height: 80px
&_expanded
height: auto
display: grid
grid-template-rows: auto 1fr
grid-gap: 0.5rem
height: 90px
&:hover
background: $ian-bg-hover-color
@ -27,106 +21,76 @@ $subject-font-size: 14px
&--row
@include unset-button-styles
width: 100%
height: 100%
display: grid
grid-template-columns: 50px 1fr 200px 15% 15%
grid-template-rows: auto $subject-font-size 1fr
grid-template-areas: "project project project project project" "title title status status date" "actors reason reason reason reason"
grid-gap: 0.5rem
grid-template-rows: auto $subject-font-size auto
grid-template-areas: "header" "body" "footer"
grid-gap: 10px
align-items: center
@media screen and (max-width: 679px)
grid-template-columns: 50px 1fr 15% 15%
grid-template-areas: "project project date date" "title title title title" "actors reason reason reason"
&_unexpandable
cursor: default
&--top-line
grid-area: header
display: grid
grid-template-columns: max-content max-content minmax(0, max-content) max-content auto minmax(20px, max-content)
grid-template-areas: "status wpIdLink project reason count buttons"
grid-column-gap: 5px
font-size: 14px
align-items: center
&--indicator
width: 10px
height: 10px
margin-right: 0.5rem
display: block
background: var(--primary-color)
border-radius: 50%
&--middle-line
grid-area: body
display: grid
grid-template-columns: auto 1fr
grid-column-gap: 5px
&_read
background: #AFAFAF
&--bottom-line
grid-area: footer
display: grid
grid-template-columns: auto 1fr
grid-column-gap: 5px
align-items: center
&--work-package-subject
@include text-shortener
font-weight: bold
&.-read
font-weight: normal
&--work-package-id-link
grid-area: wpIdLink
&--project
grid-area: project
font-size: 0.8rem
color: var(--gray-dark)
@include text-shortener
&--project-link
&--project-link,
&--work-package-id-link
color: var(--gray-dark)
// Set line-height to show underline
// which is otherwise swallowed by overflow
line-height: 1.1
&--reason,
&--date
color: #7f7f7f
font-weight: bold
&--reason-count
color: white
grid-area: count
@include indicator-bubble
background: #7f7f7f
&--title
grid-area: title
font-size: $subject-font-size
@include text-shortener
justify-self: flex-end
&--reason
grid-area: reason
@include text-shortener
color: var(--gray-dark)
max-width: 100%
&--date
grid-area: date
@include text-shortener(false)
white-space: nowrap
max-width: 100%
text-align: right
@media screen and (max-width: 679px)
justify-self: end
&:not(#{&}_read):hover &--date,
&--buttons:focus-within + &--date
opacity: 0
@media screen and (max-width: 679px)
opacity: 1
&--actors
grid-area: actors
@include text-shortener
color: var(--gray-dark)
max-width: 100%
&--status
grid-area: status
&--buttons
grid-area: date
text-align: right
align-items: end
@media screen and (max-width: 679px)
grid-area: buttons
grid-area: buttons
&--button
margin: 0
opacity: 0
@media screen and (max-width: 679px)
display: none
&:not(#{&}_read):hover &--button,
&--button:focus
opacity: 1
&:hover
color: var(--content-link-hover-active-color)

@ -75,6 +75,8 @@ export class InAppNotificationEntryComponent implements OnInit {
text = {
loading: this.I18n.t('js.ajax.loading'),
placeholder: this.I18n.t('js.placeholders.default'),
updated_by_at: (age:string):string => this.I18n.t('js.notifications.center.text_update_date',
{ date: age }),
};
constructor(
@ -121,7 +123,9 @@ export class InAppNotificationEntryComponent implements OnInit {
this.fixedTime = this.timezoneService.formattedDatetime(this.notification.createdAt);
this.relativeTime$ = timer(0, 10000)
.pipe(
map(() => this.timezoneService.formattedRelativeDateTime(this.notification.createdAt)),
map(() => this.text.updated_by_at(
this.timezoneService.formattedRelativeDateTime(this.notification.createdAt),
)),
distinctUntilChanged(),
);
}

@ -2,12 +2,12 @@
.op-ian-wp-status
@include text-shortener(false)
padding: 5px
margin-bottom: 0
border: none
border-radius: 2px
color: white
max-width: 100%
font-size: 12px
&--text
margin: 0 5px

Loading…
Cancel
Save