initial
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
@import "@wordpress/base-styles/colors.native"; // stylelint-disable-line scss/at-import-partial-extension
|
||||
|
||||
.vrts-test-run-alerts {
|
||||
|
||||
&__heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #f0f0f1;
|
||||
padding: 0;
|
||||
height: 62px;
|
||||
overflow: hidden;
|
||||
transition: box-shadow 0.2s;
|
||||
|
||||
&[data-is-sticky="true"] {
|
||||
box-shadow: 0 1px 0 0 $gray-10;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
> * {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
@media (min-width: 851px) {
|
||||
padding: 0 20px;
|
||||
position: sticky;
|
||||
top: -0.1px;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
&-link {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
min-height: 30px;
|
||||
transition: color 0.2s;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
left: -15px;
|
||||
right: -15px;
|
||||
bottom: -5px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $blue-50;
|
||||
}
|
||||
|
||||
&--button {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__list {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
padding: 0 10px 10px 10px;
|
||||
margin: 0 -10px;
|
||||
overflow-x: scroll;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
@media (min-width: 851px) {
|
||||
flex-direction: column;
|
||||
overflow: visible;
|
||||
padding: 0 20px;
|
||||
margin: 0 0 20px 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__card {
|
||||
box-sizing: border-box;
|
||||
background: #fff;
|
||||
border: 1px solid $gray-10;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 0 auto;
|
||||
transition: all 0.1s;
|
||||
box-shadow: inset 0 0 0 1px transparent;
|
||||
width: 60%;
|
||||
|
||||
@media (min-width: 480px) {
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
@media (min-width: 600px) {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
@media (min-width: 851px) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&:has([data-vrts-state="read"]) {
|
||||
background: #f6f7f9;
|
||||
}
|
||||
|
||||
&:has(:hover),
|
||||
&:has([data-vrts-current="true"]) {
|
||||
border-color: var(--vrts-admin-theme-color);
|
||||
box-shadow: inset 0 0 0 1px var(--vrts-admin-theme-color);
|
||||
}
|
||||
|
||||
&:has([data-vrts-current="false"]:focus-visible) {
|
||||
border-color: $gray-20;
|
||||
box-shadow: inset 0 0 0 1px $gray-20;
|
||||
}
|
||||
|
||||
&-link {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
padding: 10px 10px 0 10px;
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-figure {
|
||||
position: relative;
|
||||
background: #f6f7f7;
|
||||
border: 1px solid $gray-5;
|
||||
aspect-ratio: 1 / 1.15;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-flag {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
visibility: hidden;
|
||||
|
||||
[data-vrts-false-positive="true"] & {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
&-image {
|
||||
position: absolute;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
&-title-inner,
|
||||
&-path {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3125rem;
|
||||
color: $gray-70;
|
||||
font-weight: 700;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 4px;
|
||||
text-decoration: none;
|
||||
|
||||
[data-vrts-state="read"] & {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
[data-vrts-state="unread"] &::after {
|
||||
content: "";
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--vrts-admin-theme-color);
|
||||
border-radius: 50%;
|
||||
flex: 0 0 auto;
|
||||
margin-top: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
&-path {
|
||||
padding: 10px;
|
||||
margin-top: -10px;
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid $gray-20;
|
||||
outline-offset: -8px;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
use Vrts\Models\Alert;
|
||||
use Vrts\Core\Utilities\Image_Helpers;
|
||||
use Vrts\Core\Utilities\Url_Helpers;
|
||||
|
||||
$unread_alerts = Alert::get_unread_count_by_test_run_ids( $data['run']->id );
|
||||
$unread_count = $unread_alerts[0]->count ?? 0;
|
||||
$unred_runs_count = Alert::get_total_items_grouped_by_test_run();
|
||||
$current_alert_id = isset( $data['alert']->id ) ? $data['alert']->id : 0;
|
||||
|
||||
?>
|
||||
<vrts-test-run-alerts
|
||||
class="vrts-test-run-alerts"
|
||||
data-vrts-current-alert="<?php echo esc_attr( $current_alert_id ? $current_alert_id : 'false' ); ?>"
|
||||
data-vrts-unread-runs="<?php echo esc_attr( $unred_runs_count ); ?>">
|
||||
<div class="vrts-test-run-alerts__heading">
|
||||
<a href="<?php echo esc_url( Url_Helpers::get_page_url( 'runs' ) ); ?>" class="vrts-test-run-alerts__heading-link">
|
||||
<?php vrts()->icon( 'chevron-left' ); ?>
|
||||
<?php esc_html_e( 'All Runs', 'visual-regression-tests' ); ?>
|
||||
</a>
|
||||
<?php if ( $data['alerts'] ) : ?>
|
||||
<button data-vrts-loading="false" data-vrts-action-state="<?php echo esc_attr( $unread_count > 0 ? 'primary' : 'secondary' ); ?>" data-vrts-test-run-id="<?php echo esc_attr( $data['run']->id ); ?>" data-vrts-test-run-action="read-status" class="vrts-test-run-alerts__heading-link vrts-test-run-alerts__heading-link--button vrts-action-button">
|
||||
<span class="vrts-action-button__icons">
|
||||
<span class="vrts-action-button__icon" data-vrts-action-state-secondary><?php vrts()->icon( 'email-unread' ); ?></span>
|
||||
<span class="vrts-action-button__icon" data-vrts-action-state-primary><?php vrts()->icon( 'email-read' ); ?></span>
|
||||
<span class="vrts-action-button__spinner"><?php vrts()->icon( 'spinner' ); ?></span>
|
||||
</span>
|
||||
<span class="vrts-action-button__info" data-vrts-action-state-primary><?php esc_html_e( 'Mark all as read', 'visual-regression-tests' ); ?></span>
|
||||
<span class="vrts-action-button__info" data-vrts-action-state-secondary><?php esc_html_e( 'Mark all as unread', 'visual-regression-tests' ); ?></span>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php if ( $data['alerts'] ) : ?>
|
||||
<div class="vrts-test-run-alerts__list">
|
||||
<?php
|
||||
foreach ( $data['alerts'] as $alert ) :
|
||||
$test = null;
|
||||
foreach ( $data['tests'] as $some_test ) {
|
||||
if ( $some_test['post_id'] === $alert->post_id ) {
|
||||
$test = $some_test;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$alert_link = add_query_arg( [
|
||||
'run_id' => $data['run']->id,
|
||||
'alert_id' => $alert->id,
|
||||
], Url_Helpers::get_page_url( 'runs' ) );
|
||||
|
||||
$alert_permalink = '';
|
||||
$alert_post_title = '';
|
||||
if ( $test ) {
|
||||
$alert_permalink = $test['permalink'];
|
||||
$alert_post_title = $test['post_title'];
|
||||
}
|
||||
if ( ! $alert_permalink ) {
|
||||
$alert_permalink = get_permalink( $alert->post_id );
|
||||
}
|
||||
if ( ! $alert_post_title ) {
|
||||
$alert_post_title = get_the_title( $alert->post_id ) ?: 'N/A';
|
||||
}
|
||||
$alert_relative_permalink = Url_Helpers::make_relative( $alert_permalink );
|
||||
|
||||
?>
|
||||
<div class="vrts-test-run-alerts__card">
|
||||
<a
|
||||
id="vrts-alert-<?php echo esc_attr( $alert->id ); ?>"
|
||||
href="<?php echo esc_url( $alert_link ); ?>"
|
||||
class="vrts-test-run-alerts__card-link"
|
||||
data-vrts-alert="<?php echo esc_attr( $alert->id ); ?>"
|
||||
data-vrts-current="<?php echo esc_attr( $current_alert_id === $alert->id ? 'true' : 'false' ); ?>"
|
||||
data-vrts-state="<?php echo esc_attr( intval( $alert->alert_state ) === 0 ? 'unread' : 'read' ); ?>"
|
||||
data-vrts-false-positive="<?php echo esc_attr( $alert->is_false_positive ? 'true' : 'false' ); ?>">
|
||||
<figure class="vrts-test-run-alerts__card-figure">
|
||||
<img class="vrts-test-run-alerts__card-image" src="<?php echo esc_url( Image_Helpers::get_screenshot_url( $alert, 'comparison', 'preview' ) ); ?>" alt="<?php esc_attr_e( 'Difference', 'visual-regression-tests' ); ?>">
|
||||
<span class="vrts-test-run-alerts__card-flag"><?php vrts()->icon( 'flag' ); ?></span>
|
||||
</figure>
|
||||
<span class="vrts-test-run-alerts__card-title">
|
||||
<span class="vrts-test-run-alerts__card-title-inner"><?php echo esc_html( $alert_post_title ); ?></span>
|
||||
</span>
|
||||
</a>
|
||||
<a href="<?php echo esc_url( get_permalink( $alert->post_id ) ); ?>" target="_blank" class="vrts-test-run-alerts__card-path"><?php echo esc_html( $alert_relative_permalink ); ?></a>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php vrts()->component( 'test-run-receipt', $data ); ?>
|
||||
<script>
|
||||
const urlParams = new URLSearchParams( window.location.search );
|
||||
const currentAlertId = urlParams.get( 'alert_id' );
|
||||
|
||||
if ( currentAlertId ) {
|
||||
const $sidebar = document.querySelector(
|
||||
'.vrts-test-run-page__sidebar'
|
||||
);
|
||||
|
||||
let $alert = document.getElementById(
|
||||
`vrts-alert-${ currentAlertId }`
|
||||
);
|
||||
|
||||
let offsetTop = 0;
|
||||
|
||||
while ( $alert && $alert !== $sidebar ) {
|
||||
offsetTop += $alert.offsetTop;
|
||||
$alert = $alert.offsetParent;
|
||||
}
|
||||
|
||||
if ( $alert ) {
|
||||
$sidebar.scrollTo( {
|
||||
left: 0,
|
||||
top: offsetTop - 82,
|
||||
} );
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</vrts-test-run-alerts>
|
||||
@@ -0,0 +1,297 @@
|
||||
class VrtsTestRunAlerts extends window.HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.resolveElements();
|
||||
this.bindFunctions();
|
||||
this.bindEvents();
|
||||
this.unreadAlerts = new Set();
|
||||
this.initialUnreadAlerts = 0;
|
||||
this.unreadRuns = parseInt(
|
||||
this.getAttribute( 'data-vrts-unread-runs' ),
|
||||
10
|
||||
);
|
||||
}
|
||||
|
||||
resolveElements() {
|
||||
this.$heading = this.querySelector( '.vrts-test-run-alerts__heading' );
|
||||
this.$alerts = this.querySelectorAll( '[data-vrts-alert]' );
|
||||
this.$actionButtons = this.querySelectorAll(
|
||||
'[data-vrts-test-run-action]'
|
||||
);
|
||||
|
||||
this.$runsMenuItems = [
|
||||
document.querySelector(
|
||||
'.vrts-admin-header [href*="page=vrts-runs"]'
|
||||
),
|
||||
document.querySelector(
|
||||
'#adminmenu .menu-top[href*="page=vrts"] .wp-menu-name'
|
||||
),
|
||||
document.querySelector(
|
||||
'#adminmenu .wp-submenu a[href*="page=vrts-runs"]'
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
bindFunctions() {
|
||||
this.handleAlertClick = this.handleAlertClick.bind( this );
|
||||
this.handleActionClick = this.handleActionClick.bind( this );
|
||||
this.updateRunsCount = this.updateRunsCount.bind( this );
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.$alerts?.forEach( ( item ) => {
|
||||
item.addEventListener( 'click', this.handleAlertClick );
|
||||
} );
|
||||
this.$actionButtons?.forEach( ( item ) => {
|
||||
item.addEventListener( 'click', this.handleActionClick );
|
||||
} );
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.checkHeadingSticky();
|
||||
this.checkReadStatusChange();
|
||||
|
||||
this.$alerts.forEach( ( item ) => {
|
||||
const isUnread =
|
||||
item.getAttribute( 'data-vrts-state' ) === 'unread';
|
||||
if ( isUnread ) {
|
||||
this.unreadAlerts.add( item.getAttribute( 'data-vrts-alert' ) );
|
||||
}
|
||||
} );
|
||||
|
||||
this.initialUnreadAlerts = this.unreadAlerts.size;
|
||||
}
|
||||
|
||||
checkHeadingSticky() {
|
||||
const checkIsSticky = ( entries ) => {
|
||||
const isSticky = ! entries[ 0 ].isIntersecting;
|
||||
this.$heading.setAttribute( 'data-is-sticky', isSticky );
|
||||
};
|
||||
|
||||
const observer = new window.IntersectionObserver( checkIsSticky, {
|
||||
root: document,
|
||||
threshold: [ 1 ],
|
||||
} );
|
||||
|
||||
observer.observe( this.$heading );
|
||||
}
|
||||
|
||||
checkReadStatusChange() {
|
||||
const observer = new window.MutationObserver( ( mutations ) => {
|
||||
mutations.forEach( ( mutation ) => {
|
||||
if (
|
||||
mutation.type === 'attributes' &&
|
||||
mutation.attributeName === 'data-vrts-state'
|
||||
) {
|
||||
const id =
|
||||
mutation.target.getAttribute( 'data-vrts-alert' );
|
||||
const state =
|
||||
mutation.target.getAttribute( 'data-vrts-state' );
|
||||
|
||||
if ( id === 'receipt' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 'unread' === state ) {
|
||||
this.unreadAlerts.add( id );
|
||||
} else {
|
||||
this.unreadAlerts.delete( id );
|
||||
}
|
||||
|
||||
this.querySelector(
|
||||
'[data-vrts-test-run-action="read-status"]'
|
||||
).setAttribute(
|
||||
'data-vrts-action-state',
|
||||
this.unreadAlerts.size > 0 ? 'primary' : 'secondary'
|
||||
);
|
||||
|
||||
this.updateRunsCount( this.unreadAlerts.size );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
this.$alerts.forEach( ( item ) => {
|
||||
observer.observe( item, {
|
||||
attributes: true,
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
updateRunsCount( unreadAlerts ) {
|
||||
let unreadRuns = this.unreadRuns;
|
||||
|
||||
if ( unreadAlerts > 0 && this.initialUnreadAlerts === 0 ) {
|
||||
unreadRuns += 1;
|
||||
} else if ( unreadAlerts === 0 && this.initialUnreadAlerts > 0 ) {
|
||||
unreadRuns -= 1;
|
||||
}
|
||||
|
||||
this.$runsMenuItems.forEach( ( item ) => {
|
||||
if ( item ) {
|
||||
item.querySelector( '.update-plugins' )?.remove();
|
||||
item.textContent = item.textContent.replace( /\u00A0/g, '' );
|
||||
|
||||
if ( unreadRuns > 0 ) {
|
||||
const $count = document.createElement( 'span' );
|
||||
const nbsp = document.createTextNode( '\u00A0' );
|
||||
$count.classList.add( 'update-plugins' );
|
||||
$count.textContent = unreadRuns;
|
||||
|
||||
item.appendChild( nbsp );
|
||||
item.appendChild( $count );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
handleAlertClick( e ) {
|
||||
e.preventDefault();
|
||||
const $el = e.currentTarget;
|
||||
const isCurrent = $el.getAttribute( 'data-vrts-current' ) === 'true';
|
||||
|
||||
if ( isCurrent ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const href = $el.getAttribute( 'href' );
|
||||
const $content =
|
||||
document.querySelector( 'vrts-comparisons' ) ||
|
||||
document.querySelector( 'vrts-test-run-success' );
|
||||
const $pagination = document.querySelector(
|
||||
'vrts-test-run-pagination'
|
||||
);
|
||||
|
||||
this.$alerts.forEach( ( item ) => {
|
||||
item.setAttribute( 'data-vrts-current', 'false' );
|
||||
} );
|
||||
|
||||
$el.setAttribute( 'data-vrts-current', 'true' );
|
||||
|
||||
const timeout = setTimeout( () => {
|
||||
$content.setAttribute( 'data-vrts-loading', 'true' );
|
||||
}, 200 );
|
||||
|
||||
fetch( href )
|
||||
.then( ( response ) => {
|
||||
return response.text();
|
||||
} )
|
||||
.then( ( data ) => {
|
||||
const parser = new window.DOMParser();
|
||||
const $html = parser.parseFromString( data, 'text/html' );
|
||||
|
||||
const $newContent =
|
||||
$html.querySelector( 'vrts-comparisons' ) ||
|
||||
$html.querySelector( 'vrts-test-run-success' );
|
||||
const $newPagination = $html.querySelector(
|
||||
'vrts-test-run-pagination'
|
||||
);
|
||||
|
||||
window.history.replaceState( {}, '', href );
|
||||
|
||||
this.scrollTo( $content.offsetTop - 62 );
|
||||
|
||||
if ( $newContent ) {
|
||||
$content.replaceWith( $newContent );
|
||||
}
|
||||
|
||||
if ( $newPagination ) {
|
||||
$pagination.replaceWith( $newPagination );
|
||||
}
|
||||
|
||||
clearTimeout( timeout );
|
||||
} );
|
||||
}
|
||||
|
||||
handleActionClick( e ) {
|
||||
const $el = e.currentTarget;
|
||||
const isLoading = $el.getAttribute( 'data-vrts-loading' ) === 'true';
|
||||
const state = $el.getAttribute( 'data-vrts-action-state' );
|
||||
const isPrimary = state === 'primary';
|
||||
|
||||
if ( isLoading ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const action = $el.getAttribute( 'data-vrts-test-run-action' );
|
||||
const id = $el.getAttribute( 'data-vrts-test-run-id' );
|
||||
|
||||
this.handleAction( action, $el, id, isPrimary );
|
||||
}
|
||||
|
||||
handleAction( action, $el, id, shouldSetAction ) {
|
||||
const restEndpoint = `${ window.vrts_admin_vars.rest_url }/test-runs/${ id }/${ action }`;
|
||||
const method = shouldSetAction ? 'POST' : 'DELETE';
|
||||
|
||||
let loadingElapsedTime = 0;
|
||||
let interval = null;
|
||||
|
||||
const timeout = setTimeout( () => {
|
||||
$el.setAttribute( 'data-vrts-loading', 'true' );
|
||||
const loadingStartTime = window.Date.now();
|
||||
interval = setInterval( () => {
|
||||
loadingElapsedTime = window.Date.now() - loadingStartTime;
|
||||
}, 50 );
|
||||
}, 200 );
|
||||
|
||||
fetch( restEndpoint, {
|
||||
method,
|
||||
headers: {
|
||||
'X-WP-Nonce': window.vrts_admin_vars.rest_nonce,
|
||||
},
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
return response.json();
|
||||
} )
|
||||
.then( () => {
|
||||
const loadingTimeoutTime =
|
||||
loadingElapsedTime > 0
|
||||
? Math.abs( loadingElapsedTime - 400 )
|
||||
: 0;
|
||||
|
||||
setTimeout( () => {
|
||||
$el.setAttribute( 'data-vrts-loading', 'false' );
|
||||
$el.setAttribute(
|
||||
'data-vrts-action-state',
|
||||
shouldSetAction ? 'secondary' : 'primary'
|
||||
);
|
||||
|
||||
const $alerts =
|
||||
document.querySelectorAll( '[data-vrts-alert]' );
|
||||
|
||||
$alerts.forEach( ( item ) => {
|
||||
item.setAttribute(
|
||||
'data-vrts-state',
|
||||
shouldSetAction ? 'read' : 'unread'
|
||||
);
|
||||
} );
|
||||
}, loadingTimeoutTime );
|
||||
|
||||
clearTimeout( timeout );
|
||||
clearInterval( interval );
|
||||
} );
|
||||
}
|
||||
|
||||
scrollTo( offset ) {
|
||||
const $el =
|
||||
document.fullscreenElement ||
|
||||
document.webkitFullscreenElement ||
|
||||
document.msFullscreenElement ||
|
||||
window;
|
||||
|
||||
$el.scrollTo( {
|
||||
top: offset,
|
||||
behavior: 'smooth',
|
||||
} );
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.$alerts?.forEach( ( item ) => {
|
||||
item.removeEventListener( 'click', this.handleAlertClick );
|
||||
} );
|
||||
this.$actionButtons?.forEach( ( item ) => {
|
||||
item.removeEventListener( 'click', this.handleActionClick );
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define( 'vrts-test-run-alerts', VrtsTestRunAlerts );
|
||||
Reference in New Issue
Block a user