Skip to content

Commit

Permalink
Headless Live Preview (statamic#5109)
Browse files Browse the repository at this point in the history
Co-authored-by: StyleCI Bot <[email protected]>
  • Loading branch information
jasonvarga and StyleCIBot authored Feb 25, 2022
1 parent 6e0ab40 commit 1b51f28
Show file tree
Hide file tree
Showing 57 changed files with 1,801 additions and 142 deletions.
2 changes: 2 additions & 0 deletions resources/js/components/entries/BaseCreateForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
:can-manage-publish-state="canManagePublishState"
:create-another-url="createAnotherUrl"
:listing-url="listingUrl"
:preview-targets="previewTargets"
@saved="saved"
></entry-publish-form>

Expand All @@ -44,6 +45,7 @@ export default {
'canManagePublishState',
'createAnotherUrl',
'listingUrl',
'previewTargets',
],
methods: {
Expand Down
6 changes: 4 additions & 2 deletions resources/js/components/entries/PublishForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@
:name="publishContainer"
:url="livePreviewUrl"
:previewing="isPreviewing"
:targets="previewTargets"
:values="values"
:blueprint="fieldset.handle"
:amp="amp"
@opened-via-keyboard="openLivePreview"
@closed="closeLivePreview"
>
Expand Down Expand Up @@ -303,6 +303,7 @@ export default {
createAnotherUrl: String,
listingUrl: String,
collectionHasRoutes: Boolean,
previewTargets: Array,
},
data() {
Expand Down Expand Up @@ -342,7 +343,8 @@ export default {
saveKeyBinding: null,
quickSaveKeyBinding: null,
quickSave: false
quickSave: false,
previewTarget: this.previewTargets[0],
}
},
Expand Down
79 changes: 42 additions & 37 deletions resources/js/components/live-preview/LivePreview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
<div v-show="headerVisible" class="live-preview-header">
<div class="text-base text-grey-70 font-medium mr-2">{{ __('Live Preview') }}</div>
<div class="flex items-center">
<label v-if="amp" class="mr-2"><input type="checkbox" v-model="previewAmp" /> AMP</label>
<button v-if="canPopOut && !poppedOut" class="btn" @click="popout">{{ __('Pop out') }}</button>
<button v-if="poppedOut" class="btn" @click="closePopout">{{ __('Pop in') }}</button>
<select-input :options="deviceSelectOptions" v-model="previewDevice" v-show="!poppedOut" class="ml-2" />
<select-input :options="targetSelectOptions" v-model="target" class="ml-2" v-if="targets.length > 1" />

<component
v-for="(component, handle) in inputs"
Expand Down Expand Up @@ -62,9 +62,7 @@
</transition>

<transition name="live-preview-contents-slide">
<div v-show="panesVisible" ref="contents" class="live-preview-contents items-center justify-center overflow-auto" :class="{ 'pointer-events-none': editorResizing }">
<iframe ref="iframe" frameborder="0" :class="previewDevice ? 'device' : 'responsive'" :style="{ width: previewDeviceWidth, height: previewDeviceHeight }" />
</div>
<div v-show="panesVisible" ref="contents" class="live-preview-contents items-center justify-center overflow-auto" :class="{ 'pointer-events-none': editorResizing }" />
</transition>

</div>
Expand All @@ -79,12 +77,17 @@
<script>
import Provider from './Provider.vue';
import Resizer from './Resizer.vue';
import UpdatesIframe from './UpdatesIframe';
let source;
const widthLocalStorageKey = 'statamic.live-preview.editor-width';
export default {
mixins: [
UpdatesIframe
],
components: {
Provider,
Resizer
Expand All @@ -93,10 +96,10 @@ export default {
props: {
url: String,
previewing: Boolean,
targets: Array,
values: Object,
name: String,
blueprint: String,
amp: Boolean
},
data() {
Expand All @@ -107,7 +110,6 @@ export default {
editorResizing: false,
editorCollapsed: false,
previewDevice: null,
previewAmp: false,
provides: {
storeName: this.name
},
Expand All @@ -118,20 +120,27 @@ export default {
loading: true,
extras: {},
keybinding: null,
token: null,
target: 0,
}
},
computed: {
payload() {
return {
amp: this.previewAmp,
blueprint: this.blueprint,
preview: this.values,
extras: this.extras
}
},
targetSelectOptions() {
return Object.values(_.mapObject(this.targets, (target, key) => {
return { value: key, label: __(target.label) };
}));
},
deviceSelectOptions() {
let options = Object.values(_.mapObject(this.$config.get('livePreview.devices'), (dimensions, device) => {
return { value: device, label: __(device) };
Expand Down Expand Up @@ -162,6 +171,16 @@ export default {
canPopOut() {
return typeof BroadcastChannel === 'function';
},
tokenizedUrl() {
let url = this.url;
url += (url.includes('?') ? '&' : '?') + `target=${this.target}`;
if (this.token) url += `&token=${this.token}`;
return url;
}
},
Expand All @@ -180,6 +199,10 @@ export default {
handler(payload) {
if (this.previewing) this.update();
}
},
target() {
this.update();
}
},
Expand All @@ -203,35 +226,28 @@ export default {
methods: {
update: _.debounce(function () {
if (this.poppedOut) {
this.sendPayloadToPoppedOutWindow();
return;
}
if (source) source.cancel();
source = this.$axios.CancelToken.source();
this.loading = true;
this.$axios.post(this.url, this.payload, { cancelToken: source.token }).then(response => {
this.updateIframeContents(response.data);
this.$axios.post(this.tokenizedUrl, this.payload, { cancelToken: source.token }).then(response => {
this.token = response.data.token;
const url = response.data.url;
this.poppedOut
? this.channel.postMessage({ event: 'updated', url })
: this.updateIframeContents(url);
this.loading = false;
}).catch(e => {
if (this.$axios.isCancel(e)) return;
throw e;
});
}, 150),
updateIframeContents(contents) {
const iframe = this.$refs.iframe;
const scrollX = $(iframe.contentWindow.document).scrollLeft();
const scrollY = $(iframe.contentWindow.document).scrollTop();
contents += '<script type="text/javascript">window.scrollTo('+scrollX+', '+scrollY+');\x3c/script>';
iframe.contentWindow.document.open();
iframe.contentWindow.document.write(contents);
iframe.contentWindow.document.close();
this.loading = false;
setIframeAttributes(iframe) {
iframe.setAttribute('frameborder', '0');
iframe.setAttribute('class', this.previewDevice ? 'device' : 'responsive');
if (this.previewDevice) iframe.setAttribute('style', `width: ${this.previewDeviceWidth}; height: ${this.previewDeviceHeight}`);
},
close() {
Expand Down Expand Up @@ -282,21 +298,14 @@ export default {
switch (e.data.event) {
case 'popout.opened':
this.listenForPopoutClose();
this.sendPayloadToPoppedOutWindow();
this.updateIframeContents('');
this.update();
break;
case 'popout.closed':
this.poppedOut = false;
this.update();
case 'popout.pong':
this.popoutResponded = true;
break;
case 'popout.loading':
this.loading = true;
break;
case 'popout.loaded':
this.loading = false;
break;
default:
break;
}
Expand All @@ -312,10 +321,6 @@ export default {
if (this.poppedOut) this.popoutWindow.close();
},
sendPayloadToPoppedOutWindow() {
this.channel.postMessage({ event: 'updated', url: this.url, payload: this.payload })
},
listenForPopoutClose() {
this.channel.postMessage({ event: 'ping' });
setTimeout(() => {
Expand Down
47 changes: 10 additions & 37 deletions resources/js/components/live-preview/Popout.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
<template>

<div class="live-preview-contents min-h-screen">
<iframe ref="iframe" frameborder="0" class="min-h-screen" />
</div>
<div class="live-preview-contents min-h-screen" ref="contents" />

</template>

<script>
let source;
import UpdatesIframe from './UpdatesIframe';
export default {
mixins: [
UpdatesIframe
],
data() {
return {
payload: null,
channel: null
}
},
Expand All @@ -24,9 +25,7 @@ export default {
this.channel.onmessage = e => {
switch (e.data.event) {
case 'updated':
this.url = e.data.url;
this.payload = e.data.payload;
this.update();
this.updateIframeContents(e.data.url);
break;
case 'ping':
this.channel.postMessage({ event: 'popout.pong' });
Expand All @@ -40,35 +39,9 @@ export default {
},
methods: {
update: _.debounce(function () {
if (source) source.cancel();
source = this.$axios.CancelToken.source();
this.channel.postMessage({ event: 'popout.loading' });
this.$axios.post(this.url, this.payload, { cancelToken: source.token }).then(response => {
this.updateIframeContents(response.data);
}).catch(e => {
if (this.$axios.isCancel(e)) return;
throw e;
});
}, 150),
updateIframeContents(contents) {
const iframe = this.$refs.iframe;
const scrollX = $(iframe.contentWindow.document).scrollLeft();
const scrollY = $(iframe.contentWindow.document).scrollTop();
contents += '<script type="text/javascript">window.scrollTo('+scrollX+', '+scrollY+');\x3c/script>';
iframe.contentWindow.document.open();
iframe.contentWindow.document.write(contents);
iframe.contentWindow.document.close();
this.channel.postMessage({ event: 'popout.loaded' })
},
setIframeAttributes(iframe) {
iframe.setAttribute('class', 'min-h-screen');
}
}
}
</script>
21 changes: 21 additions & 0 deletions resources/js/components/live-preview/UpdatesIframe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export default {
methods: {
updateIframeContents(url) {
const iframe = document.createElement('iframe');
iframe.setAttribute('frameborder', '0');
iframe.setAttribute('src', url);
this.setIframeAttributes(iframe);

const container = this.$refs.contents;
container.firstChild
? container.replaceChild(iframe, container.firstChild)
: container.appendChild(iframe);

// todo: maintain scroll position
},

setIframeAttributes(iframe) {
//
}
}
}
2 changes: 2 additions & 0 deletions resources/js/components/terms/BaseCreateForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
:initial-origin-values="{}"
:create-another-url="createAnotherUrl"
:listing-url="listingUrl"
:preview-targets="previewTargets"
@saved="saved"
></term-publish-form>

Expand All @@ -37,6 +38,7 @@ export default {
'localizations',
'createAnotherUrl',
'listingUrl',
'previewTargets',
],
methods: {
Expand Down
6 changes: 4 additions & 2 deletions resources/js/components/terms/PublishForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@
:name="publishContainer"
:url="livePreviewUrl"
:previewing="isPreviewing"
:targets="previewTargets"
:values="values"
:blueprint="fieldset.handle"
:amp="amp"
@opened-via-keyboard="openLivePreview"
@closed="closeLivePreview"
>
Expand Down Expand Up @@ -279,6 +279,7 @@ export default {
preloadedAssets: Array,
createAnotherUrl: String,
listingUrl: String,
previewTargets: Array,
},
data() {
Expand Down Expand Up @@ -312,7 +313,8 @@ export default {
preferencesPrefix: `taxonomies.${this.taxonomyHandle}`,
saveKeyBinding: null,
quickSaveKeyBinding: null,
quickSave: false
quickSave: false,
previewTarget: this.previewTargets[0],
}
},
Expand Down
Loading

0 comments on commit 1b51f28

Please sign in to comment.