Skip to content

Commit

Permalink
feat: Communication with preview iframe. (#255)
Browse files Browse the repository at this point in the history
Allow for the editor to communicate with the preview to enable advanced functionality.
Use list index indicators to show visible partials in the preview.
Partial hovering from preview highlights the partial field.
  • Loading branch information
Zoramite committed Dec 8, 2021
1 parent ba77b4b commit 38c7e52
Show file tree
Hide file tree
Showing 13 changed files with 892 additions and 271 deletions.
28 changes: 14 additions & 14 deletions package.json
Expand Up @@ -66,51 +66,51 @@
"@types/marked": "^4.0.1",
"@types/mime-types": "^2.1.1",
"@types/minimatch": "^3.0.5",
"@types/node": "^16.11.9",
"@types/node": "^16.11.12",
"@types/nunjucks": "^3.2.0",
"@types/prosemirror-commands": "^1.0.4",
"@types/prosemirror-model": "^1.13.2",
"@types/prosemirror-state": "^1.2.8",
"@types/quill": "^2.0.9",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"@typescript-eslint/eslint-plugin": "^5.6.0",
"@typescript-eslint/parser": "^5.6.0",
"ava": "^3.14.0",
"codecov": "^3.8.3",
"concurrently": "^6.4.0",
"eslint": "^8.2.0",
"eslint": "^8.4.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-sort-class-members": "^1.14.0",
"eslint-plugin-sort-class-members": "^1.14.1",
"express": "^4.17.1",
"gts": "^3.1.0",
"nodemon": "^2.0.15",
"nunjucks": "^3.2.3",
"nyc": "^15.1.0",
"prettier": "^2.4.1",
"puppeteer": "^11.0.0",
"prettier": "^2.5.1",
"puppeteer": "^12.0.1",
"ts-loader": "^9.2.6",
"ts-node": "^10.4.0",
"typedoc": "^0.22.9",
"typedoc": "^0.22.10",
"typescript": "^4.5.2",
"webpack": "^5.64.1",
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.5.0",
"webpack-dev-server": "^4.6.0",
"webpack-merge": "^5.8.0"
},
"dependencies": {
"@blinkk/selective-edit": "^3.2.4",
"@blinkk/selective-edit": "^3.3.0",
"@toast-ui/editor": "^3.1.1",
"bent": "^7.3.12",
"codemirror": "^5.63.3",
"codemirror": "^5.64.0",
"javascript-time-ago": "^2.3.10",
"js-yaml": "^4.1.0",
"lodash.clonedeep": "^4.5.0",
"lodash.merge": "^4.6.2",
"marked": "^4.0.4",
"marked": "^4.0.6",
"minimatch": "^3.0.4",
"path-browserify": "^1.0.1",
"quill": "^1.3.7",
"sass": "^1.43.4",
"sass": "^1.44.0",
"stackdriver-errors-js": "^0.12.0"
}
}
2 changes: 1 addition & 1 deletion src/sass/_scheme.sass
Expand Up @@ -80,7 +80,7 @@
--color-primary-light: #48a999
--color-on-primary-light: #ffffff

--color-secondary: ##9e9e9e
--color-secondary: #9e9e9e
--color-on-secondary: #000000

--color-tertiary: #2C2C2C
Expand Down
90 changes: 90 additions & 0 deletions src/sass/example/preview.sass
@@ -0,0 +1,90 @@
@import '../config'
@import '../scheme'

body
background-color: rgba(200, 200, 200, .3)
background-image: linear-gradient(rgba(255, 255, 255, .35) 1px, transparent 1px), linear-gradient(90deg, rgba(255, 255, 255, .35) 1px, transparent 1px)
background-position: 0 0
background-size: 10px 10px, 10px 10px
padding: 0
margin: 0

h1, h2, h3, h4, h5, h6
margin: .5rem 0

.container
display: flex
flex-flow: column
height: 100vh

.description
margin: $le-space-medium

.details
font-size: .8em
margin: $le-space-medium

.partials
display: flex
flex-flow: column
margin: $le-space-medium 0

.partial
// background-color: var(--color-primary)
// background-size: 10px 10px
// background-image: repeating-linear-gradient(45deg, #e4e3e4 0, #e4e3e4 1px, #fff 0, #fff 50%)
border: $le-border-size-medium solid var(--color-primary)
// color: var(--color-on-primary)
height: 45vh
margin: .5em 0
padding: .5em

&:hover
border-color: rgba(0, 0, 0, .5)

&--hovered
outline: $le-border-size-xlarge dotted var(--color-secondary)

.partial__edit
font-size: $le-font-size-small

.connection
flex-grow: 1
display: flex
flex-flow: column

.preview
flex-grow: 1
flex-shrink: 1
height: 0
overflow-x: auto

.communication__log
background: var(--color-secondary)
bottom: 0
color: var(--color-on-secondary)
left: 0
max-height: 20vh
min-height: 20px
padding: 1em
right: 0

.communication__log__items
column-gap: $le-space-small
display: grid
font-family: var(--font-family-mono)
font-size: $le-font-size-small
grid-template-columns: minmax(40px, 1fr) 1fr minmax(50px, 2fr)
max-height: 10vh
overflow-y: auto
row-gap: $le-space-small

.communication__log__item__source
font-weight: 700
flex-basis: 10%

.communication__log__item__event
grid-column-start: 2

.communication__log__item__details
grid-column-start: 3
33 changes: 33 additions & 0 deletions src/sass/selective/field/_generic_partials.sass
Expand Up @@ -14,6 +14,39 @@
&:focus-within
border-color: var(--color-tertiary-dark)

> .selective__list__fields__header > .selective__field__actions,
> .selective__field__actions
.selective__list__item__index
aspect-ratio: 1
border: $le-border-size-medium solid var(--color-tertiary)
border-radius: 50%
display: inline-block
text-align: center
width: $le-space-large

.selective__list__item:focus-within &
border-color: var(--color-tertiary-dark)

> .selective__list__fields__header > .selective__field__actions
.selective__list__item__index
border-color: var(--color-on-tertiary)

.selective__list__item--visible &
border-color: var(--color-secondary)

.selective__list__item:focus-within &
border-color: var(--color-tertiary-dark)


&--visible
> .selective__list__fields__header > .selective__field__actions,
> .selective__field__actions
.selective__list__item__index
border-color: var(--color-secondary)

&--hovered
outline: $le-border-size-large solid var(--color-secondary)

> .selective__list__fields__header > .selective__list__item__preview,
> .selective__list__item__preview
.selective__field__partials__preview
Expand Down
31 changes: 26 additions & 5 deletions src/sass/selective/field/_list.sass
Expand Up @@ -24,16 +24,40 @@
display: flex
flex-flow: row

> .selective__list__fields__header .selective__field__actions,
> .selective__field__actions
column-gap: $le-space-small

&--pre
margin-left: $le-space-small
margin-right: $le-space-small

&--post
margin-left: $le-space-small
margin-right: $le-space-small

.selective__list__item__drag
cursor: grab

.material-icons
padding-left: 0
padding-right: 0

.selective__field__action
+clickable

.material-icons
padding: $le-space-small

> .selective__list__item__index
aspect-ratio: 1
border: $le-border-size-medium solid var(--color-tertiary)
border-radius: 50%
display: inline-block
padding: $le-space-small
text-align: center
width: $le-space-large

> .selective__field
flex-grow: 1
margin: $le-space-small 0
Expand All @@ -42,7 +66,7 @@
> .selective__list__item__preview
flex-grow: 1
overflow: hidden
padding: $le-space-small 0
padding: $le-space-medium 0
text-overflow: ellipsis
white-space: nowrap

Expand All @@ -67,6 +91,7 @@
&--pre
.material-icons
padding-left: 0
padding-right: 0

&--empty
padding: $le-space-medium 0
Expand All @@ -88,7 +113,6 @@
display: flex
flex-flow: row
font-weight: var(--font-weight-bold)
padding: $le-space-small
position: sticky
top: 0
z-index: 1
Expand All @@ -100,9 +124,6 @@
> .selective__list__fields
padding: $le-space-medium

&--no-drag
padding-left: $le-space-medium

&.selective__sortable--hover
*
pointer-events: none
Expand Down
6 changes: 6 additions & 0 deletions src/ts/editor/editor.ts
Expand Up @@ -8,6 +8,7 @@ import {AppUi} from './ui/app';
import {EVENT_SAVE} from './events';
import {GrowProjectType} from '../projectType/grow/growProjectType';
import {LiveEditorLabels} from './template';
import {PreviewCommunicator} from './preview';
import {ProjectTypeComponent} from '../projectType/projectType';
import cloneDeep from 'lodash.clonedeep';

Expand Down Expand Up @@ -65,13 +66,18 @@ export class LiveEditor {
config: LiveEditorConfig;
isPendingRender?: boolean;
isRendering?: boolean;
/**
* Used to handle communiction between the editor and the preview iframe.
*/
preview: PreviewCommunicator;
state: EditorState;
storage: DataStorage;

constructor(config: LiveEditorConfig, container: HTMLElement) {
this.config = config;
this.storage = new LocalDataStorage();
this.state = this.config.state;
this.preview = new PreviewCommunicator();

// Bind the editor to the global selective config.
if (this.config.selectiveConfig.global) {
Expand Down
67 changes: 67 additions & 0 deletions src/ts/editor/preview.ts
@@ -1,4 +1,7 @@
import {EditorPreviewSettings, WorkspaceData} from './api';

import {Base} from '@blinkk/selective-edit/dist/mixins';
import {ListenersMixin} from '../mixin/listeners';
import {interpolate} from '../utility/stringLiteral';
import {shortenWorkspaceName} from './workspace';

Expand Down Expand Up @@ -60,3 +63,67 @@ export function interpolatePreviewConfigUrl(
// Default to the `preview.json` file.
return `${params.baseUrl}preview.json`;
}

export interface PreviewEvent {
event: string;
details?: any;
}

export interface PreviewConnectEvent extends PreviewEvent {
event: 'connect';
}

export type PreviewCommunication = PreviewConnectEvent | PreviewEvent;

/**
* Enable communication between the preview iframe and the editor.
*/
export class PreviewCommunicator extends ListenersMixin(Base) {
iframe?: HTMLIFrameElement;
queue: PreviewCommunication[] = [];

constructor() {
super();

window.addEventListener('message', event => {
if (!event.data.event) {
return;
}

// Check for a connect event, link the iframe to the communicator.
if (event.data.event === 'connect') {
const iframe = document.querySelector(
'.le__part__preview__frame iframe'
);
if (iframe) {
this.connect(iframe as HTMLIFrameElement);
}
}

this.triggerListener(event.data.event, event.data.details || {});
});
}

connect(iframe: HTMLIFrameElement) {
this.iframe = iframe;

// Send connect message back to iframe as a confirmation.
this.send({event: 'connect'});

// Send queued events.
for (const event of this.queue) {
this.send(event);
}
this.queue = [];
}

send(event: PreviewCommunication) {
if (!this.iframe) {
this.queue.push(event);
return;
}

// Currently ignores when no iframe. Change to queue messages?
this.iframe?.contentWindow?.postMessage(event, '*');
}
}

0 comments on commit 38c7e52

Please sign in to comment.