Mailspring/src/react-remote/react-remote-parent.js

405 lines
13 KiB
JavaScript

/*
This code stopped working when we moved from React `0.13.2` to `0.14.7`.
It would most likely still work, but some of the internal modules have moved.
var ipcRenderer = require("electron").ipcRenderer;
var React = require('react');
var ReactDOM = require('react-dom');
var _ = require('underscore');
var LinkedValueUtils = require('react/lib/LinkedValueUtils');
var ReactDOMComponent = require('react/lib/ReactDOMComponent');
var methods = Object.keys(ReactDOMComponent.BackendIDOperations);
var invocationTargets = [];
var lastSelectionData = {}
var sources = {
CSSPropertyOperations: require('react/lib/CSSPropertyOperations'),
DOMPropertyOperations: require('react/lib/DOMPropertyOperations'),
DOMChildrenOperations: require('react/lib/DOMChildrenOperations'),
ReactDOMIDOperations: require('react/lib/ReactDOMIDOperations'),
ReactDOMSelect: require('react/lib/ReactDOMSelect')
}
var Custom = {
sendSelectCurrentValue: function() {
var reactid = this.getDOMNode().dataset.reactid;
var target = invocationTargetForReactId(reactid);
if (target) {
var value = LinkedValueUtils.getValue(this);
target.send({
parent: 'Custom',
sel: 'setSelectCurrentValue',
arguments: [reactid, LinkedValueUtils.getValue(this)]
});
}
}
};
var invocationTargetForReactId = function(id) {
for (var ii = 0; ii < invocationTargets.length; ii++) {
var target = invocationTargets[ii];
if (id.substr(0, target.reactid.length) == target.reactid) {
return target;
}
if (target.reactid == 'not-yet-rendered') {
var node = document.querySelector("[data-reactid='"+id+"']");
while (node = node.parentNode) {
if (node == target.container) {
return target;
}
}
}
}
return null;
};
var observeMethod = function(parent, sel, callback) {
var owner = sources[parent];
if (!owner[sel]) {
owner = owner.prototype;
}
var oldImpl = owner[sel];
owner[sel] = function() {
oldImpl.apply(this, arguments);
if (invocationTargets.length == 0)
return;
callback.apply(this, arguments);
}
};
var observeMethodAndBroadcast = function(parent, sel) {
observeMethod(parent, sel, function() {
var id = null;
var target = null;
var firstArgType = null;
var args = [];
for (var ii = 0; ii < arguments.length; ii ++) {
args.push(arguments[ii]);
}
if (arguments[0] instanceof Node) {
args[0] = args[0].dataset.reactid;
target = invocationTargetForReactId(args[0]);
firstArgType = "node";
} else if (typeof(args[0]) === 'string') {
target = invocationTargetForReactId(args[0]);
firstArgType = "id";
} else if (args[0] instanceof Array) {
for (var ii = 0; ii < args[0].length; ii ++) {
args[0][ii].parentNode = args[0][ii].parentNode.dataset.reactid;
}
target = invocationTargetForReactId(args[0][0].parentNode);
firstArgType = "array";
}
if (target) {
target.send({
parent: parent,
sel: sel,
arguments: args,
firstArgType: firstArgType
});
target.sendSizingInformation();
}
});
};
setTimeout(function(){
observeMethodAndBroadcast('CSSPropertyOperations', 'setValueForStyles');
observeMethodAndBroadcast('DOMChildrenOperations', 'updateTextContent');
observeMethodAndBroadcast('DOMChildrenOperations', 'dangerouslyReplaceNodeWithMarkup');
observeMethodAndBroadcast('DOMPropertyOperations', 'deleteValueForProperty');
observeMethodAndBroadcast('DOMPropertyOperations', 'setValueForProperty');
observeMethodAndBroadcast('ReactDOMIDOperations', 'updateInnerHTMLByID');
observeMethodAndBroadcast('DOMChildrenOperations', 'processUpdates');
observeMethod('ReactDOMSelect', 'componentDidUpdate', Custom.sendSelectCurrentValue);
observeMethod('ReactDOMSelect', 'componentDidMount', Custom.sendSelectCurrentValue);
}, 10);
ipcRenderer.on('from-react-remote-window', function(event, json) {
var container = null;
for (var ii = 0; ii < invocationTargets.length; ii ++) {
if (invocationTargets[ii].windowId == json.windowId) {
container = invocationTargets[ii].container;
}
}
if (!container) {
console.error("Received message from child window "+json.windowId+" which is not recognized.");
return;
}
if (json.event) {
var rep = json.event;
if (rep.targetReactId) {
rep.target = document.querySelector(["[data-reactid='"+rep.targetReactId+"']"]);
}
if (rep.target && rep.target.contentEditable=="true") {
rep.target.innerHTML = rep.targetValue;
}
else if (rep.target && (rep.targetValue !== undefined)) {
rep.target.value = rep.targetValue;
}
if (rep.target && (rep.targetChecked !== undefined)) {
rep.target.checked = rep.targetChecked;
}
var EventClass = {
"MouseEvent": MouseEvent,
"KeyboardEvent": KeyboardEvent,
"FocusEvent": FocusEvent
}[rep.eventClass] || Event;
var e = new EventClass(rep.eventType, rep);
if (rep.target) {
rep.target.dispatchEvent(e);
} else {
container.dispatchEvent(e);
}
}
});
exp = require('./selection-listeners.js');
restoreSelection = exp.restoreSelection;
getSelectionData = exp.getSelectionData;
selectionChange = function() {
selectionData = getSelectionData();
if (_.isEqual(selectionData, lastSelectionData)) { return; }
lastSelectionData = _.clone(selectionData)
for (var i = 0; i < invocationTargets.length; i++) {
var target = invocationTargets[i];
target.send({selectionData: selectionData})
}
}
// document.addEventListener("selectionchange", selectionChange);
ipcRenderer.on('from-react-remote-window-selection', function(event, selectionData){
document.removeEventListener("selectionchange", selectionChange)
restoreSelection(selectionData)
document.addEventListener("selectionchange", selectionChange);
});
var parentListenersAttached = false;
var reactRemoteContainer = document.createElement('div');
reactRemoteContainer.style.left = '-10000px';
reactRemoteContainer.style.top = '40px';
reactRemoteContainer.style.backgroundColor = 'white';
reactRemoteContainer.style.position = 'absolute';
reactRemoteContainer.style.zIndex = 10000;
reactRemoteContainer.style.border = '5px solid orange';
document.body.appendChild(reactRemoteContainer);
var reactRemoteContainerTitle = document.createElement('div');
reactRemoteContainerTitle.style.color = 'white';
reactRemoteContainerTitle.style.backgroundColor = 'orange';
reactRemoteContainerTitle.innerText = 'React Remote Container';
reactRemoteContainer.appendChild(reactRemoteContainerTitle);
var toggleContainerVisible = function() {
if (reactRemoteContainer.style.left === '-10000px') {
reactRemoteContainer.style.left = 0;
} else {
reactRemoteContainer.style.left = '-10000px';
}
};
var openWindowForComponent = function(Component, options) {
// If a tag is specified, see if we can find an existing window to bring to foreground
if (options.tag) {
for (var ii = 0; ii < invocationTargets.length; ii++) {
if (invocationTargets[ii].tag === options.tag) {
invocationTargets[ii].window.focus();
return;
}
}
}
var remote = require('electron').remote;
var url = require('url');
var BrowserWindow = remote.require('browser-window');
// Read rendered styles out of the page
var styles = document.querySelectorAll("style");
var thinStyles = "";
for (var ii = 0; ii < styles.length; ii++) {
var styleNode = styles[ii];
if (!styleNode.sourcePath) {
continue;
}
if ((styleNode.sourcePath.indexOf('index') > 0) || (options.stylesheetRegex && options.stylesheetRegex.test(styleNode.sourcePath))) {
thinStyles = thinStyles + styleNode.innerText;
}
}
// Create a browser window
var thinWindowUrl = url.format({
protocol: 'file',
pathname: NylasEnv.getLoadSettings().resourcePath+"/static/react-remote-child.html",
slashes: true
});
var thinWindow = new BrowserWindow({
title: options.title || "",
frame: process.platform !== 'darwin',
width: options.width || 800,
height: options.height || 600,
resizable: options.resizable,
show: false
});
thinWindow.loadURL(thinWindowUrl);
if (process.platform !== 'darwin') {
thinWindow.setMenu(null);
}
// Add a container to our local document to hold the root component of the window
var container = document.createElement('div');
container.id = 'react-remote-window-container-'+thinWindow.id;
if (options.width) {
container.style.width = options.width+'px';
} else {
container.style.height = 'auto';
}
if (options.height) {
container.style.height = options.height+'px';
} else {
container.style.height = 'auto';
}
reactRemoteContainer.appendChild(container);
var cleanup = function() {
if (container == null) {
return;
}
for (var ii = 0; ii < invocationTargets.length; ii++) {
if (invocationTargets[ii].container === container) {
invocationTargets[ii].windowReady = false
invocationTargets[ii].window = null
invocationTargets.splice(ii, 1);
break;
}
}
ReactDOM.render(React.createElement('div'), container, function() {
reactRemoteContainer.removeChild(container);
});
container = null;
thinWindow = null;
};
var sendWaiting = [];
var sendSizingInformation = function() {
if (!options.autosize) {
return;
}
if (!thinWindow) {
return;
}
// Weirdly, this returns an array of [width, height] and not a hash
var size = thinWindow.getContentSize();
var changed = false;
var containerSize = container.getBoundingClientRect();
var containerWidth = Math.ceil(containerSize.width);
var containerHeight = Math.ceil(containerSize.height);
if (containerHeight == 0) {
containerHeight = 100;
debugger;
}
if (containerWidth == 0) {
containerWidth = 400;
debugger;
}
if ((!options.height) && (size[1] != containerHeight)) {
size[1] = containerHeight;
changed = true;
}
if ((!options.width) && (size[0] != containerWidth)) {
size[0] = containerWidth;
changed = true;
}
if (changed) {
thinWindow.setContentSize(size[0], size[1]);
}
};
// Create a "Target" object that we'll use to store information about the
// remote window, it's reactId, etc.
var target = {
container: container,
containerReady: false,
window: thinWindow,
windowReady: false,
windowId: thinWindow.id,
tag: options.tag,
reactid: 'not-yet-rendered',
send: function(args) {
if (target.containerReady && target.windowReady) {
thinWindow.webContents.send('to-react-remote-window', args);
} else {
sendWaiting.push(args);
}
},
sendSizingInformation: _.debounce(function() {
if (target.containerReady && target.windowReady) {
sendSizingInformation();
}
}, 20),
sendHTMLIfReady: function() {
if (target.containerReady && target.windowReady) {
sendSizingInformation();
thinWindow.webContents.send('to-react-remote-window', {
html: container.innerHTML,
style: thinStyles,
waiting: sendWaiting
});
}
}
};
invocationTargets.push(target);
// Finally, render the react component into our local container and open
// the browser window. When both of these things finish, we send the html
// css, and any observed method invocations that occurred during the first
// React cycle (componentDidMount).
ReactDOM.render(React.createElement(Component, options.props), container, function() {
target.reactid = container.firstChild.dataset.reactid,
target.containerReady = true;
target.sendHTMLIfReady();
});
thinWindow.on('closed', cleanup);
thinWindow.webContents.on('crashed', cleanup);
thinWindow.webContents.on('did-finish-load', function () {
target.windowReady = true;
target.sendHTMLIfReady();
});
// The first time a remote window is opened, add event listeners to our
// own window so that we close dependent windows when we're closed.
if (parentListenersAttached == false) {
remote.getCurrentWindow().on('close', function() {
for (var ii = 0; ii < invocationTargets.length; ii++) {
invocationTargets[ii].window.close();
}
invocationTargets = [];
})
parentListenersAttached = true;
}
};
module.exports = {
openWindowForComponent: openWindowForComponent,
toggleContainerVisible: toggleContainerVisible
};
*/