fix(web): the ticket's header fits, with «Más» for the less frequent actions
The reminder and what is new are pi-notes; the poll editor's buttons align. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -69,12 +69,101 @@
|
||||
// Follow and edit, together, level with the title's first line.
|
||||
.ts-issue__actions {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
/* «Más»: the ticket's less frequent actions, in a menu. */
|
||||
/* Laid out as pi-icon-button lays out its own (its styles are scoped). */
|
||||
.ts-more {
|
||||
display: flex !important;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
|
||||
mat-icon {
|
||||
display: flex;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.ts-menu {
|
||||
display: grid;
|
||||
width: min(22rem, calc(100vw - 2rem));
|
||||
padding: 0.375rem;
|
||||
border: 1px solid var(--ts-line);
|
||||
border-radius: 0.75rem;
|
||||
background: var(--ts-surface);
|
||||
box-shadow: 0 12px 32px rgb(0 0 0 / 14%);
|
||||
}
|
||||
|
||||
/* @pi/ui styles every <button> globally, hence the !important. */
|
||||
.ts-menu__item {
|
||||
display: grid !important;
|
||||
grid-template-columns: 1.25rem minmax(0, 1fr) auto;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-width: 0 !important;
|
||||
height: auto !important;
|
||||
padding: 0.5rem 0.625rem !important;
|
||||
border: 0 !important;
|
||||
border-radius: 0.5rem !important;
|
||||
background: transparent !important;
|
||||
color: var(--ts-ink) !important;
|
||||
font: inherit;
|
||||
font-size: 0.875rem;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&.cdk-menu-item-focused {
|
||||
background: var(--ts-sunken) !important;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--ts-brand);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
pi-icon {
|
||||
color: var(--ts-ink-soft);
|
||||
}
|
||||
}
|
||||
|
||||
.ts-menu__text {
|
||||
display: grid;
|
||||
gap: 0.125rem;
|
||||
font-weight: 600;
|
||||
|
||||
small {
|
||||
color: var(--ts-muted);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.ts-menu__check {
|
||||
color: var(--ts-brand);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* A note above the ticket (my reminder, what is new). */
|
||||
.ts-banner {
|
||||
flex-wrap: wrap;
|
||||
row-gap: 0.5rem;
|
||||
}
|
||||
|
||||
.ts-banner__actions {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
// Workflow: one button per allowed destination, in that status' colour.
|
||||
.ts-transitions {
|
||||
display: flex;
|
||||
@@ -373,26 +462,6 @@ a.ts-key {
|
||||
}
|
||||
}
|
||||
|
||||
/* What others did since my last visit, above the ticket. */
|
||||
.ts-news {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.25rem 0.625rem;
|
||||
margin: 0;
|
||||
padding: 0.5rem 0.875rem;
|
||||
border-radius: 0.625rem;
|
||||
background: color-mix(in srgb, var(--ts-brand) 9%, transparent);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.ts-news__dot {
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
border-radius: 50%;
|
||||
background: var(--ts-brand);
|
||||
}
|
||||
|
||||
.ts-comment.is-pointed {
|
||||
animation: ts-pointed 2.4s ease-out;
|
||||
}
|
||||
@@ -827,25 +896,6 @@ a.ts-key {
|
||||
background: var(--ts-c-green, #2e9e6a);
|
||||
}
|
||||
|
||||
/* My own reminder on the ticket. */
|
||||
.ts-reminder {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.25rem 0.625rem;
|
||||
margin: 0;
|
||||
padding: 0.5rem 0.875rem;
|
||||
border-radius: 0.625rem;
|
||||
background: color-mix(in srgb, var(--ts-c-amber) 14%, transparent);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.ts-reminder__actions {
|
||||
display: inline-flex;
|
||||
gap: 0.25rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.ts-reminder__choices {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
|
||||
@@ -16,6 +16,14 @@ import { toObservable, toSignal } from '@angular/core/rxjs-interop';
|
||||
import { form, FormField, required } from '@angular/forms/signals';
|
||||
import { catchError, distinctUntilChanged, filter, of, switchMap } from 'rxjs';
|
||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
import {
|
||||
CdkMenu,
|
||||
CdkMenuItem,
|
||||
CdkMenuItemCheckbox,
|
||||
CdkMenuTrigger,
|
||||
} from '@angular/cdk/menu';
|
||||
import { ConnectedPosition } from '@angular/cdk/overlay';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import {
|
||||
PiActionButtonComponent,
|
||||
PiCheckboxComponent,
|
||||
@@ -25,6 +33,7 @@ import {
|
||||
PiIconComponent,
|
||||
PiLabelDirective,
|
||||
PiNoteComponent,
|
||||
PiNoteActionContentDirective,
|
||||
PiSegmentedControlComponent,
|
||||
PiProgressBarComponent,
|
||||
PiSearchableSelectFieldComponent,
|
||||
@@ -108,6 +117,11 @@ const MAX_FILE_BYTES = 25 * 1024 * 1024;
|
||||
RichEditorComponent,
|
||||
NgTemplateOutlet,
|
||||
RouterLink,
|
||||
CdkMenu,
|
||||
CdkMenuItem,
|
||||
CdkMenuItemCheckbox,
|
||||
CdkMenuTrigger,
|
||||
MatIcon,
|
||||
FormField,
|
||||
AttachmentItem,
|
||||
MediaRecorderComponent,
|
||||
@@ -119,6 +133,7 @@ const MAX_FILE_BYTES = 25 * 1024 * 1024;
|
||||
PiIconButtonComponent,
|
||||
PiLabelDirective,
|
||||
PiNoteComponent,
|
||||
PiNoteActionContentDirective,
|
||||
PiSegmentedControlComponent,
|
||||
PiProgressBarComponent,
|
||||
PiSearchableSelectFieldComponent,
|
||||
@@ -223,79 +238,6 @@ const MAX_FILE_BYTES = 25 * 1024 * 1024;
|
||||
(execute)="facade.setWatching(!facade.watching())"
|
||||
/>
|
||||
}
|
||||
@if (project.canContribute()) {
|
||||
<pi-icon-button
|
||||
icon="eye"
|
||||
color="grey"
|
||||
appearance="outline"
|
||||
text="Pedir revisión"
|
||||
title="Pedir a alguien del equipo que lo revise"
|
||||
(execute)="openReviewRequest()"
|
||||
/>
|
||||
}
|
||||
@if (project.myRole() && inMyDay() !== null) {
|
||||
<pi-icon-button
|
||||
icon="sun"
|
||||
color="grey"
|
||||
appearance="outline"
|
||||
[text]="inMyDay() ? 'En mi día' : 'Mi día'"
|
||||
[attr.aria-pressed]="inMyDay()"
|
||||
[attr.title]="
|
||||
inMyDay()
|
||||
? 'Quitar de lo que quiero sacar hoy'
|
||||
: 'Añadir a lo que quiero sacar hoy (en el inicio)'
|
||||
"
|
||||
(execute)="toggleMyDay(issue)"
|
||||
/>
|
||||
}
|
||||
@if (project.myRole()) {
|
||||
<pi-icon-button
|
||||
icon="bell"
|
||||
color="grey"
|
||||
appearance="outline"
|
||||
[text]="issue.myReminder ? 'Recordatorio' : 'Recordármelo'"
|
||||
[attr.aria-pressed]="!!issue.myReminder"
|
||||
title="Avisarme de este ticket cuando yo diga"
|
||||
(execute)="openReminder()"
|
||||
/>
|
||||
}
|
||||
@if (
|
||||
project.myRole() &&
|
||||
(facade.children().length || issue.followingTree)
|
||||
) {
|
||||
<pi-icon-button
|
||||
icon="kanban"
|
||||
color="grey"
|
||||
appearance="outline"
|
||||
[text]="
|
||||
issue.followingTree
|
||||
? 'Siguiendo todo · ' + facade.children().length
|
||||
: 'Seguir todo'
|
||||
"
|
||||
[attr.aria-pressed]="issue.followingTree"
|
||||
[attr.title]="
|
||||
issue.followingTree
|
||||
? 'Dejar de seguir lo que cuelga de este ticket'
|
||||
: 'Avisarme de este ticket y de todo lo que cuelga de él, también lo que se añada'
|
||||
"
|
||||
(execute)="facade.setFollowingTree(!issue.followingTree)"
|
||||
/>
|
||||
}
|
||||
@if (project.myRole()) {
|
||||
<pi-icon-button
|
||||
icon="pin"
|
||||
color="grey"
|
||||
appearance="outline"
|
||||
[text]="issue.pinned ? 'Fijado' : 'Fijar'"
|
||||
[attr.aria-pressed]="issue.pinned"
|
||||
[attr.title]="
|
||||
issue.pinned
|
||||
? 'Quitar de mis fijados'
|
||||
: 'Tenerlo a mano en el inicio y al buscar'
|
||||
"
|
||||
(execute)="facade.setPinned(!issue.pinned)"
|
||||
/>
|
||||
}
|
||||
@if (project.canContribute()) {
|
||||
<pi-icon-button
|
||||
icon="edit"
|
||||
@@ -305,64 +247,210 @@ const MAX_FILE_BYTES = 25 * 1024 * 1024;
|
||||
title="Editar (tecla E)"
|
||||
(execute)="openEdit()"
|
||||
/>
|
||||
<pi-icon-button
|
||||
icon="copy"
|
||||
color="grey"
|
||||
appearance="outline"
|
||||
text="Duplicar"
|
||||
title="Crear otro ticket con lo mismo (sin estado, persona ni comentarios)"
|
||||
(execute)="duplicate()"
|
||||
/>
|
||||
}
|
||||
@if (project.myRole()) {
|
||||
<!-- @pi/ui's look on a native button: the menu needs it. -->
|
||||
<button
|
||||
type="button"
|
||||
class="ts-more has-text pi-button pi-button__appearance--outline pi-button__icon pi-button__medium pi-color__grey pi-color__mode--dark"
|
||||
[cdkMenuTriggerFor]="moreMenu"
|
||||
[cdkMenuPosition]="morePositions"
|
||||
>
|
||||
<span>Más</span>
|
||||
<mat-icon svgIcon="chevron-down" aria-hidden="true" />
|
||||
</button>
|
||||
}
|
||||
<ng-template #moreMenu>
|
||||
<div class="ts-menu" cdkMenu>
|
||||
@if (project.canContribute()) {
|
||||
<button
|
||||
type="button"
|
||||
class="ts-menu__item"
|
||||
cdkMenuItem
|
||||
(cdkMenuItemTriggered)="openReviewRequest()"
|
||||
>
|
||||
<pi-icon icon="eye" size="small" />
|
||||
<span class="ts-menu__text"
|
||||
>Pedir revisión<small
|
||||
>A alguien del equipo, que lo verá en «Mi
|
||||
trabajo»</small
|
||||
></span
|
||||
>
|
||||
</button>
|
||||
}
|
||||
@if (inMyDay() !== null) {
|
||||
<button
|
||||
type="button"
|
||||
class="ts-menu__item"
|
||||
cdkMenuItemCheckbox
|
||||
[cdkMenuItemChecked]="!!inMyDay()"
|
||||
(cdkMenuItemTriggered)="toggleMyDay(issue)"
|
||||
>
|
||||
<pi-icon icon="sun" size="small" />
|
||||
<span class="ts-menu__text"
|
||||
>{{ inMyDay() ? 'En mi día' : 'Añadir a mi día'
|
||||
}}<small
|
||||
>Lo que quiero sacar hoy, en el inicio</small
|
||||
></span
|
||||
>
|
||||
@if (inMyDay()) {
|
||||
<span class="ts-menu__check" aria-hidden="true"
|
||||
>✓</span
|
||||
>
|
||||
}
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
type="button"
|
||||
class="ts-menu__item"
|
||||
cdkMenuItem
|
||||
(cdkMenuItemTriggered)="openReminder()"
|
||||
>
|
||||
<pi-icon icon="bell" size="small" />
|
||||
<span class="ts-menu__text"
|
||||
>{{
|
||||
issue.myReminder
|
||||
? 'Cambiar el recordatorio'
|
||||
: 'Recordármelo'
|
||||
}}<small
|
||||
>Un aviso solo para mí, cuando yo diga</small
|
||||
></span
|
||||
>
|
||||
</button>
|
||||
@if (facade.children().length || issue.followingTree) {
|
||||
<button
|
||||
type="button"
|
||||
class="ts-menu__item"
|
||||
cdkMenuItemCheckbox
|
||||
[cdkMenuItemChecked]="issue.followingTree"
|
||||
(cdkMenuItemTriggered)="
|
||||
facade.setFollowingTree(!issue.followingTree)
|
||||
"
|
||||
>
|
||||
<pi-icon icon="kanban" size="small" />
|
||||
<span class="ts-menu__text"
|
||||
>{{
|
||||
issue.followingTree
|
||||
? 'Siguiendo todo · ' + facade.children().length
|
||||
: 'Seguir todo'
|
||||
}}<small
|
||||
>Este ticket y lo que cuelga de él, también lo
|
||||
nuevo</small
|
||||
></span
|
||||
>
|
||||
@if (issue.followingTree) {
|
||||
<span class="ts-menu__check" aria-hidden="true"
|
||||
>✓</span
|
||||
>
|
||||
}
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
type="button"
|
||||
class="ts-menu__item"
|
||||
cdkMenuItemCheckbox
|
||||
[cdkMenuItemChecked]="issue.pinned"
|
||||
(cdkMenuItemTriggered)="facade.setPinned(!issue.pinned)"
|
||||
>
|
||||
<pi-icon icon="pin" size="small" />
|
||||
<span class="ts-menu__text"
|
||||
>{{ issue.pinned ? 'Fijado' : 'Fijar'
|
||||
}}<small>A mano en el inicio y al buscar</small></span
|
||||
>
|
||||
@if (issue.pinned) {
|
||||
<span class="ts-menu__check" aria-hidden="true">✓</span>
|
||||
}
|
||||
</button>
|
||||
@if (project.canContribute()) {
|
||||
<button
|
||||
type="button"
|
||||
class="ts-menu__item"
|
||||
cdkMenuItem
|
||||
(cdkMenuItemTriggered)="duplicate()"
|
||||
>
|
||||
<pi-icon icon="copy" size="small" />
|
||||
<span class="ts-menu__text"
|
||||
>Duplicar<small
|
||||
>Otro ticket con lo mismo, sin estado ni
|
||||
comentarios</small
|
||||
></span
|
||||
>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@if (issue.myReminder; as reminder) {
|
||||
<p class="ts-reminder" role="status">
|
||||
<span aria-hidden="true">⏰</span>
|
||||
Te lo recordaré {{ when(reminder.at) }}
|
||||
@if (reminder.note) {
|
||||
· «{{ reminder.note }}»
|
||||
}
|
||||
<span class="ts-reminder__actions">
|
||||
<pi-action-button
|
||||
size="x-small"
|
||||
color="grey"
|
||||
appearance="outline"
|
||||
text="Cambiar"
|
||||
(execute)="openReminder()"
|
||||
/>
|
||||
<pi-action-button
|
||||
size="x-small"
|
||||
color="grey"
|
||||
appearance="ghost"
|
||||
text="Quitar"
|
||||
(execute)="facade.setReminder(null)"
|
||||
/>
|
||||
</span>
|
||||
</p>
|
||||
<pi-note
|
||||
class="ts-banner"
|
||||
gap="xs"
|
||||
status="info"
|
||||
appearance="fill"
|
||||
type="status"
|
||||
justify="space-between"
|
||||
alignment="center"
|
||||
[title]="'Te lo recordaré ' + when(reminder.at)"
|
||||
[text]="reminder.note ? '«' + reminder.note + '»' : ''"
|
||||
>
|
||||
<pi-note-action-content>
|
||||
<span class="ts-banner__actions">
|
||||
<pi-icon-button
|
||||
icon="edit"
|
||||
color="grey"
|
||||
appearance="outline"
|
||||
size="small"
|
||||
text="Cambiar"
|
||||
(execute)="openReminder()"
|
||||
/>
|
||||
<pi-icon-button
|
||||
icon="trash"
|
||||
color="grey"
|
||||
appearance="outline"
|
||||
size="small"
|
||||
text="Quitar"
|
||||
(execute)="facade.setReminder(null)"
|
||||
/>
|
||||
</span>
|
||||
</pi-note-action-content>
|
||||
</pi-note>
|
||||
}
|
||||
|
||||
@if (news(); as news) {
|
||||
<p class="ts-news" role="status">
|
||||
<span class="ts-news__dot" aria-hidden="true"></span>
|
||||
<span
|
||||
>Desde tu última visita, {{ ago(news.since) }}:
|
||||
<strong>{{ news.what }}</strong> de {{ news.who }}.</span
|
||||
>
|
||||
<pi-note
|
||||
class="ts-banner"
|
||||
gap="xs"
|
||||
status="info"
|
||||
appearance="fill"
|
||||
type="status"
|
||||
justify="space-between"
|
||||
alignment="center"
|
||||
[title]="
|
||||
'Desde tu última visita (' +
|
||||
ago(news.since) +
|
||||
'): ' +
|
||||
news.what +
|
||||
' de ' +
|
||||
news.who
|
||||
"
|
||||
text=""
|
||||
>
|
||||
@if (news.firstComment; as first) {
|
||||
<span class="ts-reminder__actions">
|
||||
<pi-action-button
|
||||
size="x-small"
|
||||
color="grey"
|
||||
appearance="outline"
|
||||
text="Ver lo nuevo"
|
||||
(execute)="goToNews(first)"
|
||||
/>
|
||||
</span>
|
||||
<pi-note-action-content>
|
||||
<span class="ts-banner__actions">
|
||||
<pi-action-button
|
||||
size="small"
|
||||
color="grey"
|
||||
appearance="outline"
|
||||
text="Ver lo nuevo"
|
||||
(execute)="goToNews(first)"
|
||||
/>
|
||||
</span>
|
||||
</pi-note-action-content>
|
||||
}
|
||||
</p>
|
||||
</pi-note>
|
||||
}
|
||||
|
||||
@if (project.canContribute() && issue.allowedTransitions.length) {
|
||||
@@ -1311,11 +1399,11 @@ const MAX_FILE_BYTES = 25 * 1024 * 1024;
|
||||
(execute)="pollOptions.set([...pollOptions(), ''])"
|
||||
/>
|
||||
}
|
||||
<pi-action-button
|
||||
size="small"
|
||||
<pi-icon-button
|
||||
icon="trash"
|
||||
color="grey"
|
||||
appearance="ghost"
|
||||
[hasPadding]="false"
|
||||
appearance="outline"
|
||||
size="small"
|
||||
text="Quitar la encuesta"
|
||||
(execute)="pollOn.set(false)"
|
||||
/>
|
||||
@@ -2226,6 +2314,23 @@ export class IssuePage {
|
||||
protected readonly inMyDay = signal<boolean | null>(null);
|
||||
/** Changes with the ticket, not with each reload of it. */
|
||||
private readonly openIssueId = computed(() => this.facade.issue()?.id);
|
||||
/** «Más» opens under its button, aligned to its right edge. */
|
||||
protected readonly morePositions: ConnectedPosition[] = [
|
||||
{
|
||||
originX: 'end',
|
||||
originY: 'bottom',
|
||||
overlayX: 'end',
|
||||
overlayY: 'top',
|
||||
offsetY: 6,
|
||||
},
|
||||
{
|
||||
originX: 'end',
|
||||
originY: 'top',
|
||||
overlayX: 'end',
|
||||
overlayY: 'bottom',
|
||||
offsetY: -6,
|
||||
},
|
||||
];
|
||||
|
||||
/* «Lo nuevo desde tu última visita»: what others did since I last came. */
|
||||
private readonly visit = signal<{
|
||||
|
||||
Reference in New Issue
Block a user