Skip to content
This repository was archived by the owner on Sep 6, 2021. It is now read-only.

Commit 1a950d0

Browse files
committed
Merge pull request #7384 from oslego/shapes-editor
Add CSS Shapes Editor extension to Brackets
2 parents 575bf97 + 57efa99 commit 1a950d0

13 files changed

Lines changed: 12555 additions & 0 deletions

File tree

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
/*
2+
* Copyright (c) 2013 Adobe Systems Incorporated.
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a
5+
* copy of this software and associated documentation files (the "Software"),
6+
* to deal in the Software without restriction, including without limitation
7+
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
8+
* and/or sell copies of the Software, and to permit persons to whom the
9+
* Software is furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19+
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20+
* DEALINGS IN THE SOFTWARE.
21+
*/
22+
23+
/*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */
24+
/*global define, $, brackets, window, _reset: false, _reconnect: false, update: false */
25+
26+
define(function (require, exports, module) {
27+
"use strict";
28+
29+
var Inspector = brackets.getModule("LiveDevelopment/Inspector/Inspector"),
30+
_ = brackets.getModule("thirdparty/lodash");
31+
32+
var LiveEditorRemoteDriver = require("text!LiveEditorRemoteDriver.js"),
33+
34+
/** @type {string} namspace in the inspected page where live editor methods live */
35+
_namespace = "window._LD_CSS_EDITOR",
36+
37+
/** @type {Object} snapshot of remote model from live editor in the inspected page (live preivew) */
38+
_model = {},
39+
40+
/** @type {boolean} true if live editor instance was set up */
41+
_hasEditor = false,
42+
43+
/** @type {number} milliseconds interval after which to sync the remote model with the local _model snapshot */
44+
_syncFrequency = 100,
45+
46+
/** @type {Interval} result of setInterval() */
47+
_syncInterval,
48+
49+
/** @type {number} number of attempts to reconnect after an error */
50+
_retryCount = 5,
51+
52+
/** @type {Object} misc storage; used in reconnect scenario */
53+
_cache = {};
54+
55+
/**
56+
* @private
57+
* Evaluate the given expression in the context of the pave in LivePreview.
58+
* Returns a promise.
59+
* Fails the promise if the inspector is not connected.
60+
* Fails the promise if an error was raised in the LivePreview.
61+
*
62+
* @param {!string} expression JavaScript code to be evaluated
63+
* @return {$.Promise}
64+
*/
65+
function _call(expression) {
66+
var deferred = $.Deferred();
67+
68+
if (!expression || typeof expression !== "string") {
69+
throw new TypeError("Invalid input. Expected string JS expression, got: " + expression);
70+
}
71+
72+
if (Inspector.connected() !== true) {
73+
return deferred.reject();
74+
}
75+
76+
Inspector.Runtime.evaluate(expression, function (resp) {
77+
if (!resp || resp.wasThrown) {
78+
console.error(resp.result);
79+
deferred.reject(resp.result);
80+
} else {
81+
deferred.resolve(resp.result);
82+
}
83+
});
84+
85+
return deferred.promise();
86+
}
87+
88+
/**
89+
* @private
90+
* Inject remote live editor driver and any specified editor providers.
91+
* The remote live editor driver mirrors most of the local live editor driver API
92+
* to provide an interface to the in-browser live editor.
93+
* @param {Array.<string>=} providers String sources of editors to be available in the browser; optional
94+
*/
95+
function init(providers) {
96+
var scripts = [].concat(LiveEditorRemoteDriver, providers || []);
97+
98+
// cache dependencies for reuse when a re-init is required (ex: after a page refresh)
99+
_cache.dependencies = scripts;
100+
101+
$(exports).triggerHandler("init");
102+
103+
return _call(scripts.join(";"));
104+
}
105+
106+
/**
107+
* @private
108+
* Send instructions to remove the live editor from the page in LivePreview.
109+
* @return {$.Promise}
110+
*/
111+
function remove() {
112+
if (_hasEditor === false) {
113+
var deferred = $.Deferred();
114+
return deferred.reject();
115+
}
116+
117+
_reset();
118+
var expr = _namespace + ".remove()";
119+
return _call(expr);
120+
}
121+
122+
/**
123+
* @private
124+
* Handle the succesful promise of getting the model from the browser.
125+
*
126+
* Dispatches these events:
127+
* update.model -- when the model received differs from the local snapshot
128+
*
129+
* @throws {TypeError} if the promise result is not a string.
130+
* @param {!string} response JSON stringified object with CSS property, value
131+
*/
132+
function _whenGetRemoteModel(response) {
133+
if (!response || !response.value || typeof response.value !== "string") {
134+
throw new TypeError("Invalid result from remote driver .getModel(). Expected JSON string, got:" + response.value);
135+
}
136+
137+
var data = JSON.parse(response.value),
138+
hasChanged = false,
139+
key;
140+
141+
// sync the local model snapshot with the remote model
142+
_.forEach(data, function (value, key) {
143+
if (!_model[key] || !_.isEqual(_model[key], value)) {
144+
_model[key] = value;
145+
hasChanged = true;
146+
}
147+
});
148+
149+
// notify Brackets so it can update the code editor
150+
if (hasChanged || data.forceUpdate) {
151+
$(exports).triggerHandler("update.model", [_model, data.forceUpdate]);
152+
}
153+
}
154+
155+
/**
156+
* @private
157+
* Handle failed promises for eval() calls to the inspected page.
158+
* Promises can fail if the user manually refreshes the page or navigates
159+
* because the injected editor files will be lost.
160+
*
161+
* @param {$.Promise=} result promise result
162+
*/
163+
function _whenRemoteCallFailed(result) {
164+
if (result) {
165+
return _reconnect();
166+
} else {
167+
_cache.model = undefined;
168+
return remove();
169+
}
170+
}
171+
172+
/**
173+
* @private
174+
* Stop polling for the remote model
175+
*/
176+
function _stopSyncLoop() {
177+
window.clearInterval(_syncInterval);
178+
}
179+
180+
/**
181+
* @private
182+
* Reset flags and clear snapshot of remote model
183+
*/
184+
function _reset() {
185+
_stopSyncLoop();
186+
_hasEditor = false;
187+
_model = {};
188+
}
189+
190+
/**
191+
* @private
192+
* Attempt to get the model from the page in LivePreview.
193+
*/
194+
function _onSyncTick() {
195+
var expr = _namespace + ".getModel()";
196+
_call(expr).then(_whenGetRemoteModel).fail(_whenRemoteCallFailed);
197+
}
198+
199+
/**
200+
* @private
201+
* Poll for the remote model
202+
*/
203+
function _startSyncLoop() {
204+
_syncInterval = window.setInterval(_onSyncTick, _syncFrequency);
205+
}
206+
207+
/**
208+
* Send instructions to setup a live editor in the page in LivePreview
209+
* using the selector, css property and css value in the given model.
210+
*
211+
* If an editor for the current model already exists, then update it.
212+
* The model here is an instance of Model, not an object literal, like the local _model.
213+
*
214+
* @param {!Model} model Instance of Model with attributes from code editor
215+
* @return {$.Promise}
216+
*/
217+
function setup(model) {
218+
219+
_cache.model = _cache.model || model;
220+
221+
var attr = {
222+
selector: model.get("selector"),
223+
value: model.get("value"),
224+
property: model.get("property")
225+
};
226+
227+
if (_hasEditor) {
228+
// If we are asked to re-setup the same editor, update the existing one
229+
if (attr.selector === _model.selector && attr.property === _model.property) {
230+
return update(model);
231+
}
232+
}
233+
234+
var expr = _namespace + ".setup(" + JSON.stringify(attr) + ")";
235+
236+
return _call(expr)
237+
.then(_startSyncLoop)
238+
.then(function () { _hasEditor = true; })
239+
.fail(_whenRemoteCallFailed);
240+
}
241+
242+
/**
243+
* Send instructions to update the existing live editor in
244+
* the page in LivePreview with the state of the given model.
245+
*
246+
* The model here is an instance of Model, not an object literal, like _model.
247+
*
248+
* @throws {TypeError} if the input model is falsy.
249+
* @param {!Model} model Instance of Model obj with attributes from code editor.
250+
* @return {$.Promise}
251+
*/
252+
function update(model) {
253+
if (!model) {
254+
throw new TypeError("Invalid update() input. Expected {Model} instance, got: " + model);
255+
}
256+
257+
if (_hasEditor === false) {
258+
return setup(model);
259+
}
260+
261+
_cache.model = model;
262+
263+
var attr = {
264+
selector: model.get("selector"),
265+
value: model.get("value"),
266+
property: model.get("property")
267+
};
268+
269+
// Asking to update a different element / property? Setup a new editor
270+
if (attr.selector !== _model.selector || attr.property !== _model.property) {
271+
return remove().then(function () { return setup(model); });
272+
}
273+
274+
var expr = _namespace + ".update(" + JSON.stringify(attr) + ")";
275+
return _call(expr).fail(_whenRemoteCallFailed);
276+
}
277+
278+
/**
279+
* @private
280+
* When a user refreshes the live preview window, the injected live editor
281+
* and its dependecies get lost.
282+
*
283+
* This method attempts to re-inject them. It tries
284+
* a number of times before giving up.
285+
*
286+
* After a successful reconnect, it sets up the editor in the last cached state.
287+
*
288+
* @return {$.Promise}
289+
*/
290+
function _reconnect() {
291+
var deferred = $.Deferred();
292+
293+
function onPostInit() {
294+
_reset();
295+
setup(_cache.model);
296+
_retryCount = 5;
297+
}
298+
299+
if (_retryCount === 0) {
300+
return deferred.reject();
301+
}
302+
303+
_retryCount--;
304+
305+
return init(_cache.dependencies).then(onPostInit);
306+
}
307+
308+
exports.init = init;
309+
exports.setup = setup;
310+
exports.update = update;
311+
exports.remove = remove;
312+
});

0 commit comments

Comments
 (0)