Skip to content

Commit

Permalink
Introduce a new auto-updating preview panel inside the page editor (w…
Browse files Browse the repository at this point in the history
…agtail#8671)

Co-authored-by: Thibaud Colas <[email protected]>
  • Loading branch information
laymonage and thibaudcolas authored Jul 14, 2022
1 parent 1859e64 commit 5ee5acb
Show file tree
Hide file tree
Showing 33 changed files with 850 additions and 195 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ Changelog
* Add rich text editor empty block highlight by showing their block type (Thibaud Colas)
* Make ModelAdmin InspectView footer actions consistent with other parts of the UI (Thibaud Colas)
* Switch all report views to use Wagtail’s reusable header component (Paarth Agarwal)
* Introduce a new auto-updating preview panel inside the page editor (Sage Abdullah)
* Fix: Typo in `ResumeWorkflowActionFormatter` message (Stefan Hammer)
* Fix: Throw a meaningful error when saving an image to an unrecognised image format (Christian Franke)
* Fix: Remove extra padding for headers with breadcrumbs on mobile viewport (Steven Steinwand)
Expand Down
22 changes: 22 additions & 0 deletions client/scss/components/_preview-error.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.preview-error {
@apply w-bg-white;
position: absolute;
inset-inline-start: 0;
width: 100%;
display: grid;
min-height: 100vh;
align-content: center;
text-align: center;

&__header {
margin-bottom: 0.5rem;
}

&__title {
@apply w-h4 w-text-grey-600;
}

&__details {
@apply w-help-text;
}
}
187 changes: 187 additions & 0 deletions client/scss/components/_preview-panel.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
.preview-panel {
height: 100%;
display: flex;
flex-direction: column;

--preview-width-ratio: min(
1,
var(--preview-panel-width, 450) / var(--preview-device-width, 375)
);
--preview-iframe-width: calc(1px * var(--preview-device-width, 375));

&__area {
height: 100%;
display: flex;
flex-direction: column-reverse;
justify-content: space-between;
// Needed for the warning message when the data is not valid.
overflow: hidden;
}

&__wrapper {
width: calc(var(--preview-iframe-width) * var(--preview-width-ratio));
height: 100%;
margin-inline-start: auto;
margin-inline-end: auto;
}

&__iframe {
transform-origin: 0 0;
width: var(--preview-iframe-width);
height: calc(100% / var(--preview-width-ratio));
transform: scale(var(--preview-width-ratio));
display: block;
}

&__sizes {
@apply w-border-grey-100 w-border-b;
display: flex;
align-items: center;
justify-content: center;
gap: 0.75rem;
padding-bottom: 1rem;
margin-bottom: 1rem;
}

&__size-button {
@apply w-text-grey-400 w-transition hover:w-transform hover:w-scale-110 hover:w-text-primary focus:w-text-primary;

width: 2rem;
height: 2rem;
background: transparent;
box-sizing: border-box;
padding: 0;
border-radius: 5px;
display: grid;
place-items: center;
cursor: pointer;

&:focus-within {
@include focus-outline;
}

.icon {
@include svg-icon(1rem);

&.icon-tablet-alt,
&.icon-desktop {
@include svg-icon(1.25rem);
}

&.icon-link-external {
@include svg-icon(0.9rem);
}
}

input[type='radio'] {
position: absolute;
width: 0;
height: 0;
opacity: 0;
}
}

&__refresh-button.button--icon {
display: flex;
align-items: center;
gap: 0.5rem;
position: absolute;
top: 1.25rem;
inset-inline-end: 1.5rem;

.icon {
@include svg-icon(0.9rem);
}
}

&__spinner {
position: absolute;
top: 1.25rem;
inset-inline-end: 1.5rem;
}

&--mobile &__size-button--mobile,
&--tablet &__size-button--tablet,
&--desktop &__size-button--desktop {
@apply w-bg-primary w-text-white w-transform-none w-border w-border-transparent;
}

&__controls {
@apply w-border-t w-border-transparent w-duration-500 w-ease-in-out;
transition-property: border-color, margin-top, padding-top;
margin-top: 0;
padding-top: 0;

// Show the border only if there's an error,
// but always show it if there are multiple preview modes
.preview-panel--has-errors &:not(&--multiple),
&--multiple {
@apply w-border-grey-100 w-border-t;
padding-top: 1rem;
margin-top: 1rem;
}
}

&__error-banner {
@apply w-text-grey-600 w-duration-1000 w-ease-in-out w-translate-y-20;
transition-property: max-height, transform, visibility;
visibility: hidden;
max-height: 0;
display: flex;
align-items: center;
gap: 1rem;
position: relative;
z-index: -1;

.icon {
@apply w-text-warning-100;
}
}

&--has-errors &__error-banner {
@apply w-translate-y-0;
visibility: visible;
max-height: 6rem;
}

&__error-title {
@apply w-label-2;
color: inherit;
margin-top: 0;
margin-bottom: 0.25rem;
}

&__error-details {
color: inherit;
}

&__modes {
@apply w-bg-white;
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
}

&__mode-label {
padding: 0;
width: auto;
}

&__mode-select {
@apply w-bg-white w-outline-offset-inside;
font-size: 1em;
padding: 0.6rem 0.75rem;
padding-inline-end: 2.5rem;
width: auto;
}

&__modes.choice_field &__mode-select ~ span:after {
@apply w-text-grey-400;
border: 0;
font-size: 1.5em;
line-height: 1.65em;
top: auto;
width: 2.5rem;
}
}
2 changes: 2 additions & 0 deletions client/scss/core.scss
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ These are classes for components.
@import 'components/workflow-timeline';
@import 'components/switch';
@import 'components/bulk_actions';
@import 'components/preview-panel';
@import 'components/preview-error';

@import '../src/components/Sidebar/Sidebar';

Expand Down
13 changes: 6 additions & 7 deletions client/scss/layouts/_page-editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,12 @@
w-right-0
w-top-full
w-w-full
w-h-screen
sm:w-max-w-[420px]
w-h-[calc(100vh-100%)]
sm:w-max-w-[500px]
w-transform
w-translate-x-full
w-px-5
w-py-10
md:w-p-10
w-px-6
w-py-4
w-bg-white
w-box-border
w-transition
Expand All @@ -85,7 +84,7 @@
}

&__close-button {
@apply w-text-primary w-absolute w-left-2 w-top-2 hover:w-text-primary-200 w-bg-white w-p-2.5 w-hidden w-transition;
@apply w-text-primary w-absolute w-left-3 w-top-3 hover:w-text-primary-200 w-bg-white w-p-3 w-hidden w-transition;

.icon {
@apply w-w-4 w-h-4;
Expand All @@ -98,7 +97,7 @@
}

&__panel {
@apply w-hidden w-pl-2.5;
@apply w-hidden w-h-full;
}
}
}
Expand Down
80 changes: 4 additions & 76 deletions client/src/entrypoints/admin/page-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,10 @@ window.initErrorDetection = initErrorDetection;
function initKeyboardShortcuts() {
// eslint-disable-next-line no-undef
Mousetrap.bind(['mod+p'], () => {
$('.action-preview').trigger('click');
const previewToggle = document.querySelector(
'[data-side-panel-toggle="preview"]',
);
if (previewToggle) previewToggle.click();
return false;
});

Expand All @@ -289,81 +292,6 @@ $(() => {
initSlugCleaning();
initErrorDetection();
initKeyboardShortcuts();

//
// Preview
//
// In order to make the preview truly reliable, the preview page needs
// to be perfectly independent from the edit page,
// from the browser perspective. To pass data from the edit page
// to the preview page, we send the form after each change
// and save it inside the user session.

const $previewButton = $('.action-preview');
const $form = $('#page-edit-form');
const previewUrl = $previewButton.data('action');
let autoUpdatePreviewDataTimeout = -1;

function setPreviewData() {
return $.ajax({
url: previewUrl,
method: 'POST',
data: new FormData($form[0]),
processData: false,
contentType: false,
});
}

$previewButton.one('click', () => {
if ($previewButton.data('auto-update')) {
// Form data is changed when field values are changed
// (change event), when HTML elements are added, modified, moved,
// and deleted (DOMSubtreeModified event), and we need to delay
// setPreviewData when typing to avoid useless extra AJAX requests
// (so we postpone setPreviewData when keyup occurs).

// TODO: Replace DOMSubtreeModified with a MutationObserver.
$form
.on('change keyup DOMSubtreeModified', () => {
clearTimeout(autoUpdatePreviewDataTimeout);
autoUpdatePreviewDataTimeout = setTimeout(setPreviewData, 1000);
})
.trigger('change');
}
});

// eslint-disable-next-line func-names
$previewButton.on('click', function (e) {
e.preventDefault();
const $this = $(this);
const $icon = $this.filter('.icon');
const thisPreviewUrl = $this.data('action');
$icon.addClass('icon-spinner').removeClass('icon-view');
const previewWindow = window.open('', thisPreviewUrl);
previewWindow.focus();

setPreviewData()
.done((data) => {
if (data.is_valid) {
previewWindow.document.location = thisPreviewUrl;
} else {
window.focus();
previewWindow.close();

// TODO: Stop sending the form, as it removes file data.
$form.trigger('submit');
}
})
.fail(() => {
// eslint-disable-next-line no-alert
alert('Error while sending preview data.');
window.focus();
previewWindow.close();
})
.always(() => {
$icon.addClass('icon-view').removeClass('icon-spinner');
});
});
});

let updateFooterTextTimeout = -1;
Expand Down
Loading

0 comments on commit 5ee5acb

Please sign in to comment.