diff --git a/src/components/EntryEditor/EntryEditor.js b/src/components/EntryEditor/EntryEditor.js
index 1c04fba8..ecf523ed 100644
--- a/src/components/EntryEditor/EntryEditor.js
+++ b/src/components/EntryEditor/EntryEditor.js
@@ -1,63 +1,44 @@
-import React, { Component, PropTypes } from 'react';
+import React, { PropTypes } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
+import { ScrollSync, ScrollSyncPane } from '../ScrollSync';
import ControlPane from '../ControlPanel/ControlPane';
import PreviewPane from '../PreviewPane/PreviewPane';
import styles from './EntryEditor.css';
-export default class EntryEditor extends Component {
-
- state = {
- scrollTop: 0,
- scrollHeight: 0,
- offsetHeight: 0,
- }
-
- handleControlPaneScroll = evt => {
- const { scrollTop, scrollHeight, offsetHeight } = evt.target;
- this.setState({
- scrollTop,
- scrollHeight,
- offsetHeight,
- });
- }
-
- render() {
- const { collection, entry, getMedia, onChange, onAddMedia, onRemoveMedia, onPersist } = this.props;
- const { scrollTop, scrollHeight, offsetHeight } = this.state;
-
- return (
-
+export default function EntryEditor(
+ {
+ collection, entry, getMedia, onChange, onAddMedia, onRemoveMedia, onPersist
+ }) {
+ return (
+
+
-
- Save
-
+
+
+ Save
- );
- }
+
+ );
}
EntryEditor.propTypes = {
diff --git a/src/components/PreviewPane/PreviewPane.js b/src/components/PreviewPane/PreviewPane.js
index e97348bb..8134d0a8 100644
--- a/src/components/PreviewPane/PreviewPane.js
+++ b/src/components/PreviewPane/PreviewPane.js
@@ -1,6 +1,7 @@
import React, { PropTypes } from 'react';
-import { render } from 'react-dom';
+import ReactDOM from 'react-dom';
import ImmutablePropTypes from 'react-immutable-proptypes';
+import { ScrollSyncPane } from '../ScrollSync';
import registry from '../../lib/registry';
import { resolveWidget } from '../Widgets';
import Preview from './Preview';
@@ -8,16 +9,8 @@ import styles from './PreviewPane.css';
export default class PreviewPane extends React.Component {
- componentDidUpdate(prevProps) {
- // Update scroll position of the iframe
- const { scrollTop, scrollHeight, offsetHeight, ...rest } = this.props;
- const frameHeight = this.iframeBody.scrollHeight - offsetHeight;
- this.iframeBody.scrollTop = frameHeight * scrollTop / (scrollHeight - offsetHeight);
-
- // We don't want to re-render on scroll
- if (prevProps.collection !== this.props.collection || prevProps.entry !== this.props.entry) {
- this.renderPreview(rest);
- }
+ componentDidUpdate() {
+ this.renderPreview();
}
widgetFor = name => {
@@ -30,15 +23,21 @@ export default class PreviewPane extends React.Component {
field,
getMedia,
});
- }
+ };
- renderPreview(props) {
- const component = registry.getPreviewTemplate(props.collection.get('name')) || Preview;
+ renderPreview() {
+ const component = registry.getPreviewTemplate(this.props.collection.get('name')) || Preview;
const previewProps = {
- ...props,
+ ...this.props,
widgetFor: this.widgetFor
};
- render(React.createElement(component, previewProps), this.previewEl);
+ // We need to use this API in order to pass context to the iframe
+ ReactDOM.unstable_renderSubtreeIntoContainer(
+ this,
+
+ {React.createElement(component, previewProps)}
+
+ , this.previewEl);
}
handleIframeRef = ref => {
@@ -52,9 +51,9 @@ export default class PreviewPane extends React.Component {
this.previewEl = document.createElement('div');
this.iframeBody = ref.contentDocument.body;
this.iframeBody.appendChild(this.previewEl);
- this.renderPreview(this.props);
+ this.renderPreview();
}
- }
+ };
render() {
const { collection } = this.props;
diff --git a/src/components/ScrollSync/ScrollSync.js b/src/components/ScrollSync/ScrollSync.js
new file mode 100644
index 00000000..35ce2fc4
--- /dev/null
+++ b/src/components/ScrollSync/ScrollSync.js
@@ -0,0 +1,81 @@
+import React, { Component, PropTypes } from 'react';
+import { without } from 'lodash';
+
+export default class ScrollSync extends Component {
+
+ static propTypes = {
+ children: PropTypes.element.isRequired,
+ };
+
+ static childContextTypes = {
+ registerPane: PropTypes.func,
+ unregisterPane: PropTypes.func,
+ };
+
+ panes = [];
+
+ getChildContext() {
+ return {
+ registerPane: this.registerPane,
+ unregisterPane: this.unregisterPane,
+ };
+ }
+
+ registerPane = node => {
+ if (!this.findPane(node)) {
+ this.addEvents(node);
+ this.panes.push(node);
+ }
+ };
+
+ unregisterPane = node => {
+ if (this.findPane(node)) {
+ this.removeEvents(node);
+ this.panes = without(this.panes, node);
+ }
+ };
+
+ addEvents = node => {
+ node.onscroll = this.handlePaneScroll.bind(this, node);
+ // node.addEventListener('scroll', this.handlePaneScroll, false)
+ };
+
+ removeEvents = node => {
+ node.onscroll = null;
+ // node.removeEventListener('scroll', this.handlePaneScroll, false)
+ };
+
+ findPane = node => {
+ return this.panes.find(p => p === node);
+ };
+
+ handlePaneScroll = node => {
+ // const node = evt.target
+ window.requestAnimationFrame(() => {
+ this.syncScrollPositions(node);
+ });
+ };
+
+ syncScrollPositions = scrolledPane => {
+ const { scrollTop, scrollHeight, clientHeight } = scrolledPane;
+ this.panes.forEach(pane => {
+ /* For all panes beside the currently scrolling one */
+ if (scrolledPane !== pane) {
+ /* Remove event listeners from the node that we'll manipulate */
+ this.removeEvents(pane);
+ /* Calculate the actual pane height */
+ const paneHeight = pane.scrollHeight - clientHeight;
+ /* Adjust the scrollTop position of it accordingly */
+ pane.scrollTop = paneHeight * scrollTop / (scrollHeight - clientHeight);
+ /* Re-attach event listeners after we're done scrolling */
+ window.requestAnimationFrame(() => {
+ this.addEvents(pane);
+ });
+ }
+ });
+ };
+
+ render() {
+ return React.Children.only(this.props.children);
+ }
+}
diff --git a/src/components/ScrollSync/ScrollSyncPane.js b/src/components/ScrollSync/ScrollSyncPane.js
new file mode 100644
index 00000000..840edf59
--- /dev/null
+++ b/src/components/ScrollSync/ScrollSyncPane.js
@@ -0,0 +1,28 @@
+import { Component, PropTypes } from 'react';
+import ReactDOM from 'react-dom';
+
+export default class ScrollSyncPane extends Component {
+
+ static propTypes = {
+ children: PropTypes.node.isRequired,
+ attachTo: PropTypes.any
+ };
+
+ static contextTypes = {
+ registerPane: PropTypes.func.isRequired,
+ unregisterPane: PropTypes.func.isRequired,
+ };
+
+ componentDidMount() {
+ this.node = this.props.attachTo || ReactDOM.findDOMNode(this);
+ this.context.registerPane(this.node);
+ }
+
+ componentWillUnmount() {
+ this.context.unregisterPane(this.node);
+ }
+
+ render() {
+ return this.props.children;
+ }
+}
diff --git a/src/components/ScrollSync/index.js b/src/components/ScrollSync/index.js
new file mode 100644
index 00000000..5a50c651
--- /dev/null
+++ b/src/components/ScrollSync/index.js
@@ -0,0 +1,2 @@
+export { default as ScrollSync } from './ScrollSync';
+export { default as ScrollSyncPane } from './ScrollSyncPane';
diff --git a/src/components/stories/ScrollSync.js b/src/components/stories/ScrollSync.js
new file mode 100644
index 00000000..53a19b94
--- /dev/null
+++ b/src/components/stories/ScrollSync.js
@@ -0,0 +1,55 @@
+import React from 'react';
+import ScrollSync from '../ScrollSync/ScrollSync';
+import ScrollSyncPane from '../ScrollSync/ScrollSyncPane';
+import { storiesOf } from '@kadira/storybook';
+
+const paneStyle = {
+ border: '1px solid green',
+ overflow: 'auto',
+};
+
+storiesOf('ScrollSync', module)
+ .add('Default', () => (
+
+
+
+
+
+ Left Pane Content
+
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab aperiam doloribus
+ dolorum est eum eveniet exercitationem iste labore minus, neque nobis odit officiis
+ omnis possimus quasi rerum sed soluta veritatis.
+
+
+
+
+
+
+
+
+ Right Pane Content
+
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab aperiam doloribus
+ dolorum est eum eveniet exercitationem iste labore minus, neque nobis odit officiis
+ omnis possimus quasi rerum sed soluta veritatis.
+
+
+
+
+
+
+
+
+ Third Pane Content
+
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab aperiam doloribus
+ dolorum est eum eveniet exercitationem iste labore minus, neque nobis odit officiis
+ omnis possimus quasi rerum sed soluta veritatis.
+
+
+
+
+
+
+ ));
diff --git a/src/components/stories/index.js b/src/components/stories/index.js
index 30acb086..1e73d155 100644
--- a/src/components/stories/index.js
+++ b/src/components/stories/index.js
@@ -3,3 +3,4 @@ import './Icon';
import './Toast';
import './FindBar';
import './MarkupItReactRenderer';
+import './ScrollSync';