UNPKG

ares-ide

Version:

A browser-based code editor and UI designer for Enyo 2 projects

342 lines (325 loc) 11.3 kB
/*global ProjectKindsModel, ares, enyo, setTimeout */ enyo.kind({ name: "Designer", published: { designerFrameReady: false, height: null, width: null, currentFileName: "", autoZoom: false, }, events: { onSelect: "", onSelected: "", onCreateItem: "", onMoveItem: "", onSyncDropTargetHighlighting: "", onReloadComplete: "", onResizeItem: "", onError: "", onReturnPositionValue: "", onForceCloseDesigner: "", onScaleChange: "" }, components: [ {name: "client", tag: "iframe", classes: "ares-designer-frame-client"}, {name: "communicator", kind: "RPCCommunicator", onMessage: "receiveMessage"} ], baseSource: "designerFrame.html", projectSource: null, selection: null, reloadNeeded: false, scale: 1, reloading: false, debug: false, create: function() { ares.setupTraceLogger(this); this.inherited(arguments); }, rendered: function() { this.inherited(arguments); this.$.communicator.setRemote(this.$.client.hasNode().contentWindow); }, heightChanged: function() { this.$.client.applyStyle("height", this.getHeight()+"px"); this.resizeClient(); this.repositionClient(); }, widthChanged: function() { this.$.client.applyStyle("width", this.getWidth()+"px"); this.resizeClient(); this.repositionClient(); this.applyZoom(); }, zoom: function(inScale) { this.scale = (inScale >= 0) ? Math.max(inScale / 100, 0.2) : 1; enyo.dom.transformValue(this.$.client, "scale", this.scale); this.$.client.resized(); this.repositionClient(); }, zoomFromWidth: function(){ var scale = (this.width > 0) ? Math.round((this.getBounds().width * 100)/this.width) : 100; this.zoom(scale); return scale; }, applyZoom: function(){ if(this.autoZoom){ var scale = this.zoomFromWidth(); this.doScaleChange({scale : scale}); } }, resizeHandler: function(inSender, inEvent){ this.applyZoom(); return true; }, repositionClient: function() { var height = this.getHeight(), width = this.getWidth(), scaledHeight = height * this.scale, scaledWidth = width * this.scale, y = -1*(height - scaledHeight)/2, x = -1*(width - scaledWidth)/2; this.$.client.addStyles("top: " + y + "px; left: " + x + "px"); }, updateSourcePending: [], /** * * @param {Ares.Model.Project} inSource is a backbone object defined in WorkspaceData.js * @param {Function} next */ updateSource: function(inSource, next) { ares.assertCb(next); if (this.updateSourceCallback) { this.log("updateSource called while previous updateSourceCallback is still pending "); this.updateSourcePending.push([inSource, next]); return ; } var serviceConfig = inSource.getService().config; this.setDesignerFrameReady(false); this.projectSource = inSource; this.projectPath = serviceConfig.origin + serviceConfig.pathname + "/file"; var iframeUrl = this.projectSource.getProjectUrl() + "/" + this.baseSource + "?overlay=designer"; this.trace("Setting designerFrame url: ", iframeUrl); this.$.client.hasNode().src = iframeUrl; // next callback is run once a "ready" message is received from designerFrame this.updateSourceCallback = next; }, reload: function() { this.reloading = true; this.updateSource(this.projectSource, function(){} ); }, //* Send message via communicator sendMessage: function(inMessage) { this.trace("Op: ", inMessage.op, inMessage); this.$.communicator.sendMessage(inMessage); }, //* Respond to message from communicator receiveMessage: function(inSender, inEvent) { var msg = inEvent.message; var deimos = this.owner; var cbData; this.trace("Op: ", msg.op, msg); if(!msg || !msg.op) { enyo.warn("Deimos designer received invalid message data:", msg); return; } // designerFrame is initialized and ready to do work. if(msg.op === "state" && msg.val === "initialized") { this.sendDesignerFrameContainerData(); // designerFrame received container data } else if(msg.op === "state" && msg.val === "ready") { this.setDesignerFrameReady(true); if(this.reloading) { this.doReloadComplete(); this.reloading = false; } // call back *once* the function passed to updateSource if (this.updateSourceCallback) { this.trace("update source done."); this.updateSourceCallback(); this.updateSourceCallback = null; // FIXME a real state machine is needd here if (this.updateSourcePending.length) { this.log("resuming delayed update source"); cbData = this.updateSourcePending.shift() ; this.updateSource(cbData[0], cbData[1]); } } // Loaded event sent from designerFrame and awaiting aresOptions. } else if(msg.op === "state" && msg.val === "loaded") { this.designerFrameLoaded(); // The current kind was successfully rendered in the iframe } else if(msg.op === "rendered") { this.updateKind(msg); // Select event sent from here was completed successfully. Set _this.selection_. } else if(msg.op === "selected") { this.selection = enyo.json.codify.from(msg.val); this.doSelected({component: this.selection}); // New select event triggered in designerFrame. Set _this.selection_ and bubble. } else if(msg.op === "select") { this.selection = enyo.json.codify.from(msg.val); this.doSelect({component: this.selection}); // Highlight drop target to minic what's happening in designerFrame } else if(msg.op === "syncDropTargetHighlighting") { this.doSyncDropTargetHighlighting({component: enyo.json.codify.from(msg.val)}); // New component dropped in designerFrame } else if(msg.op === "createItem") { this.doCreateItem(msg.val); // Existing component dropped in designerFrame } else if(msg.op === "moveItem") { this.doMoveItem(msg.val); } else if (msg.op === "reloadNeeded") { this.reloadNeeded = true; } else if(msg.op === "error") { if (( ! msg.val.hasOwnProperty('popup')) || msg.val.popup === true) { if ( msg.val.triggeredByOp === 'render' && this.renderCallback ) { this.log("dropping renderCallback after error ", msg.val.msg); this.renderCallback = null; } if (msg.val.requestReload === true) { msg.val.callback = deimos.closeDesigner.bind(deimos); msg.val.action = "Switching back to code editor"; } this.doError(msg.val); } else { // TODO: We should store the error into a kind of rotating error log - ENYO-2462 } // Existing component resized } else if(msg.op === "resize") { this.doResizeItem(msg.val); // Returning requested position value } else if(msg.op === "returnPositionValue") { this.doReturnPositionValue(msg.val); // Default case } else { enyo.warn("Deimos designer received unknown message op:", msg); } }, //* Pass _isContainer_ info down to designerFrame sendDesignerFrameContainerData: function() { this.sendMessage({op: "containerData", val: ProjectKindsModel.getFlattenedContainerInfo()}); }, /** * Tell designerFrame to render a kind * @param {String} fileName * @param {Object} theKind to render * @param {String} inSelectId * @param {Function} next */ renderKind: function(fileName, theKind, inSelectId,next) { ares.assertCb(next); this.trace("reloadNeeded", this.reloadNeeded); if (this.reloadNeeded) { this.reloadNeeded = false; // trigger a complete reload of designerFrame this.reload(); setTimeout(next(new Error('reload started')), 0); return; } if(!this.getDesignerFrameReady()) { // frame is still being reloaded. setTimeout(next(new Error('on-going reload')), 0); return; } if (this.renderCallback) { // a rendering is on-going this.log("dropped rendering: another one is on-going"); setTimeout(next(new Error('on-going rendering')), 0); return; } this.currentFileName = fileName; this.renderCallback = next ; var components = [theKind]; this.sendMessage({ op: "render", filename: fileName, val: { name: theKind.name, components: enyo.json.codify.to(theKind.components), componentKinds: enyo.json.codify.to(components), selectId: inSelectId } }); }, select: function(inControl) { this.sendMessage({op: "select", val: inControl}); }, highlightDropTarget: function(inControl) { this.sendMessage({op: "highlight", val: inControl}); }, unHighlightDropTargets: function() { this.sendMessage({op: "unhighlight"}); }, //* Property was modified in Inspector, update designerFrame. modifyProperty: function(inProperty, inValue) { this.sendMessage({op: "modify", filename: this.currentFileName, val: {property: inProperty, value: inValue}}); }, //* Send message to Deimos with new kind/components from designerFrame updateKind: function(msg) { var deimos = this.owner; // need to check if the rendered was done for the current file if (msg.filename === this.currentFileName) { deimos.buildComponentView(msg); deimos.updateCodeInEditor(msg.filename); // based on new Component view // updateKind may be called by other op than 'render' if (this.renderCallback && msg.triggeredByOp === 'render') { this.renderCallback() ; this.renderCallback = null; } } else { this.log("dropped rendered message for stale file ",msg.filename); if (this.renderCallback && msg.triggeredByOp === 'render') { // other cleanup may be needed... this.renderCallback = null; } } }, //* Initialize the designerFrame depending on aresOptions designerFrameLoaded: function() { // FIXME: ENYO-3433 : options are hard-coded with // defaultKindOptions that are currently known. the whole/real // set must be determined indeed. this.sendMessage({op: "initializeOptions", options: ProjectKindsModel.get("defaultKindOptions")}); }, //* Clean up the designerFrame before closing designer cleanUp: function() { this.sendMessage({op: "cleanUp"}); }, /** * Pass inCode down to the designerFrame (to avoid needing to reload the iFrame) * @param {String} projectName * @param {String} filename * @param {String} inCode */ syncFile: function(projectName, filename, inCode) { // check if the file is indeed part of the current project if (projectName === this.projectSource.getName() ){ if( /\.css$/i.test(filename) ) { // Sync the CSS in inCode with the designerFrame (to avoid needing to reload the iFrame) this.sendMessage({op: "cssUpdate", val: {filename: this.projectPath + filename, code: inCode}}); } else if( /\.js$/i.test(filename) ) { this.sendMessage({op: "codeUpdate", val: inCode}); } } else { this.warn("syncFile aborted: project mismatch. Expected " + this.projectSource.getName() + ' got '+ projectName + ' with file ' + filename); } }, resizeClient: function() { this.sendMessage({op: "resize"}); }, //* Prerender simulated drop in designerFrame prerenderDrop: function(inTargetId, inBeforeId) { this.sendMessage({op: "prerenderDrop", val: {targetId: inTargetId, beforeId: inBeforeId}}); }, //* Request auto-generated position value from designerFrame requestPositionValue: function(inProp) { this.sendMessage({op: "requestPositionValue", val: inProp}); }, sendSerializerOptions: function(serializerOptions) { this.sendMessage({op: "serializerOptions", val: serializerOptions}); }, sendDragType: function(type) { this.sendMessage({op: "dragStart", val: type}); } });