diff --git a/assets/scripts/cachedFetch.js b/assets/scripts/cachedFetch.js new file mode 100644 index 0000000..1130f1f --- /dev/null +++ b/assets/scripts/cachedFetch.js @@ -0,0 +1,26 @@ +const cache = new Map(); + +export const cachedFetch = async ( url ) => { + if ( cache.has( url ) ) { + return cache.get( url ); + } + + const response = await fetch( url ); + const data = await response.text(); + + cache.set( url, data ); + + return data; +}; + +export const clearFetchCache = ( url ) => { + if ( url ) { + cache.delete( url ); + } else { + cache.clear(); + } +}; + +export const setFetchCache = ( url, data ) => { + cache.set( url, data ); +}; diff --git a/assets/scripts/ric.js b/assets/scripts/ric.js new file mode 100644 index 0000000..3ec1d31 --- /dev/null +++ b/assets/scripts/ric.js @@ -0,0 +1,24 @@ +export function requestIdleCallback( callback, options = {} ) { + if ( typeof window.requestIdleCallback === 'function' ) { + return window.requestIdleCallback( callback, options ); + } + + const timeout = options.timeout || 50; + const start = Date.now(); + + return setTimeout( () => { + callback( { + didTimeout: false, + timeRemaining: () => + Math.max( 0, timeout - ( Date.now() - start ) ), + } ); + }, 1 ); +} + +export function cancelIdleCallback( id ) { + if ( typeof window.cancelIdleCallback === 'function' ) { + return window.cancelIdleCallback( id ); + } + + clearTimeout( id ); +} diff --git a/components/alert-actions/index.php b/components/alert-actions/index.php index 6bea706..9ace77f 100644 --- a/components/alert-actions/index.php +++ b/components/alert-actions/index.php @@ -3,7 +3,7 @@ use Vrts\Core\Utilities\Url_Helpers; ?> - + diff --git a/components/alert-actions/script.js b/components/alert-actions/script.js index 00aaa9d..a143959 100644 --- a/components/alert-actions/script.js +++ b/components/alert-actions/script.js @@ -1,4 +1,5 @@ import Dropdown from '../../assets/scripts/dropdown'; +import { clearFetchCache, setFetchCache } from 'scripts/cachedFetch'; class VrtsAlertActions extends window.HTMLElement { constructor() { @@ -74,6 +75,8 @@ class VrtsAlertActions extends window.HTMLElement { const $form = e.currentTarget; const formData = new window.FormData( $form ); const postId = formData.get( 'post_id' ); + const $actions = $form.closest( 'vrts-alert-actions' ); + const alertUrl = $actions.getAttribute( 'data-vrts-alert-url' ); this.$spinner.classList.add( 'is-active' ); this.$success.classList.remove( 'is-active' ); @@ -86,11 +89,15 @@ class VrtsAlertActions extends window.HTMLElement { body: new URLSearchParams( formData ), } ) .then( ( response ) => { + if ( alertUrl ) { + clearFetchCache( alertUrl ); + } return response.json(); } ) .then( () => { this.$spinner.classList.remove( 'is-active' ); this.$success.classList.add( 'is-active' ); + setFetchCache( alertUrl, document.body.innerHTML ); } ); } @@ -117,6 +124,8 @@ class VrtsAlertActions extends window.HTMLElement { handleAction( action, $el, id, shouldSetAction ) { const restEndpoint = `${ window.vrts_admin_vars.rest_url }/alerts/${ id }/${ action }`; const method = shouldSetAction ? 'POST' : 'DELETE'; + const $actions = $el.closest( 'vrts-alert-actions' ); + const alertUrl = $actions.getAttribute( 'data-vrts-alert-url' ); let loadingElapsedTime = 0; let interval = null; @@ -136,6 +145,9 @@ class VrtsAlertActions extends window.HTMLElement { }, } ) .then( ( response ) => { + if ( alertUrl ) { + clearFetchCache( alertUrl ); + } return response.json(); } ) .then( () => { @@ -171,6 +183,7 @@ class VrtsAlertActions extends window.HTMLElement { ); } } + setFetchCache( alertUrl, document.body.innerHTML ); }, loadingTimeoutTime ); clearTimeout( timeout ); diff --git a/components/test-run-alerts/script.js b/components/test-run-alerts/script.js index ac6bdd4..6fb6679 100644 --- a/components/test-run-alerts/script.js +++ b/components/test-run-alerts/script.js @@ -1,3 +1,10 @@ +import { + cachedFetch, + clearFetchCache, + setFetchCache, +} from 'scripts/cachedFetch'; +import { requestIdleCallback } from 'scripts/ric'; + class VrtsTestRunAlerts extends window.HTMLElement { constructor() { super(); @@ -36,11 +43,13 @@ class VrtsTestRunAlerts extends window.HTMLElement { this.handleAlertClick = this.handleAlertClick.bind( this ); this.handleActionClick = this.handleActionClick.bind( this ); this.updateRunsCount = this.updateRunsCount.bind( this ); + this.handleAlertHover = this.handleAlertHover.bind( this ); } bindEvents() { this.$alerts?.forEach( ( item ) => { item.addEventListener( 'click', this.handleAlertClick ); + item.addEventListener( 'pointerenter', this.handleAlertHover ); } ); this.$actionButtons?.forEach( ( item ) => { item.addEventListener( 'click', this.handleActionClick ); @@ -144,6 +153,19 @@ class VrtsTestRunAlerts extends window.HTMLElement { } ); } + handleAlertHover( e ) { + const $el = e.currentTarget; + const isCurrent = $el.getAttribute( 'data-vrts-current' ) === 'true'; + + if ( isCurrent ) { + return; + } + + const href = $el.getAttribute( 'href' ); + + requestIdleCallback( () => cachedFetch( href ) ); + } + handleAlertClick( e ) { e.preventDefault(); const $el = e.currentTarget; @@ -171,35 +193,31 @@ class VrtsTestRunAlerts extends window.HTMLElement { $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' ); + cachedFetch( href ).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' - ); + const $newContent = + $html.querySelector( 'vrts-comparisons' ) || + $html.querySelector( 'vrts-test-run-success' ); + const $newPagination = $html.querySelector( + 'vrts-test-run-pagination' + ); - window.history.replaceState( {}, '', href ); + window.history.replaceState( {}, '', href ); - this.scrollTo( $content.offsetTop - 62 ); + this.scrollTo( $content.offsetTop - 62 ); - if ( $newContent ) { - $content.replaceWith( $newContent ); - } + if ( $newContent ) { + $content.replaceWith( $newContent ); + } - if ( $newPagination ) { - $pagination.replaceWith( $newPagination ); - } + if ( $newPagination ) { + $pagination.replaceWith( $newPagination ); + } - clearTimeout( timeout ); - } ); + clearTimeout( timeout ); + } ); } handleActionClick( e ) { @@ -240,6 +258,7 @@ class VrtsTestRunAlerts extends window.HTMLElement { }, } ) .then( ( response ) => { + clearFetchCache(); return response.json(); } ) .then( () => { @@ -264,6 +283,10 @@ class VrtsTestRunAlerts extends window.HTMLElement { shouldSetAction ? 'read' : 'unread' ); } ); + setFetchCache( + window.location.href, + document.body.innerHTML + ); }, loadingTimeoutTime ); clearTimeout( timeout ); @@ -287,6 +310,7 @@ class VrtsTestRunAlerts extends window.HTMLElement { disconnectedCallback() { this.$alerts?.forEach( ( item ) => { item.removeEventListener( 'click', this.handleAlertClick ); + item.removeEventListener( 'pointerenter', this.handleAlertHover ); } ); this.$actionButtons?.forEach( ( item ) => { item.removeEventListener( 'click', this.handleActionClick ); diff --git a/components/test-run-page/script.js b/components/test-run-page/script.js index 035f75d..3f2f2ec 100644 --- a/components/test-run-page/script.js +++ b/components/test-run-page/script.js @@ -1,8 +1,10 @@ +import { setFetchCache } from 'scripts/cachedFetch'; class VrtsTestRunPage extends window.HTMLElement { constructor() { super(); this.bindFunctions(); this.bindEvents(); + setFetchCache( window.location.href, document.body.innerHTML ); this.isScrolling = false; } diff --git a/components/test-run-pagination/script.js b/components/test-run-pagination/script.js index fa9f471..a06ac3f 100644 --- a/components/test-run-pagination/script.js +++ b/components/test-run-pagination/script.js @@ -1,9 +1,13 @@ +import { cachedFetch } from 'scripts/cachedFetch'; +import { requestIdleCallback } from 'scripts/ric'; + class VrtsTestRunPagination extends window.HTMLElement { constructor() { super(); this.resolveElements(); this.bindFunctions(); this.bindEvents(); + requestIdleCallback( () => this.preloadCache() ); } resolveElements() { @@ -73,43 +77,39 @@ class VrtsTestRunPagination extends window.HTMLElement { behavior: 'smooth', } ); - 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 ); - - const loadingTimeoutTime = - loadingElapsedTime > 0 - ? Math.abs( loadingElapsedTime - 400 ) - : 0; - - setTimeout( () => { - if ( $newContent ) { - $content.replaceWith( $newContent ); - } - - if ( $newPagination ) { - this.replaceWith( $newPagination ); - } - }, loadingTimeoutTime ); - - clearTimeout( timeout ); - clearInterval( interval ); - } ); + cachedFetch( href ).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 ); + + const loadingTimeoutTime = + loadingElapsedTime > 0 + ? Math.abs( loadingElapsedTime - 400 ) + : 0; + + setTimeout( () => { + if ( $newContent ) { + $content.replaceWith( $newContent ); + } + + if ( $newPagination ) { + this.replaceWith( $newPagination ); + } + }, loadingTimeoutTime ); + + clearTimeout( timeout ); + clearInterval( interval ); + } ); } handleKeyDown( e ) { @@ -137,6 +137,15 @@ class VrtsTestRunPagination extends window.HTMLElement { } ); } + preloadCache() { + this.$buttons?.forEach( ( item ) => { + const href = item.getAttribute( 'href' ); + if ( href ) { + cachedFetch( href ); + } + } ); + } + disconnectedCallback() { this.$buttons?.forEach( ( item ) => { item.removeEventListener( 'click', this.handleClick );